FT: Add expiration to PMK-R0 and PMK-R1 cache

IEEE Std 802.11-2016, 12.7.1.7.1 indicates that the lifetime of the
PMK-R0 (and PMK-R1) is bound to the lifetime of PSK or MSK from which
the key was derived. This is currently stored in r0_key_lifetime, but
cache entries are not actually removed.

This commit uses the r0_key_lifetime configuration parameter when
wpa_auth_derive_ptk_ft() is called. This may need to be extended to use
the MSK lifetime, if provided by an external authentication server, with
some future changes. For PSK, there is no such lifetime, but it also
matters less as FT-PSK can be achieved without inter-AP communication.

The expiration timeout is then passed from R0KH to R1KH. The R1KH verifies
that the given timeout for sanity, it may not exceed the locally configured
r1_max_key_lifetime.

Signed-off-by: Michael Braun <michael-dev@fami-braun.de>
This commit is contained in:
Michael Braun 2017-05-18 15:21:50 +02:00 committed by Jouni Malinen
parent 09211c9894
commit 3a3e28320b
6 changed files with 122 additions and 10 deletions

View file

@ -2748,6 +2748,8 @@ static int hostapd_config_fill(struct hostapd_config *conf,
bss->r0_key_lifetime = atoi(pos) * 60;
} else if (os_strcmp(buf, "ft_r0_key_lifetime") == 0) {
bss->r0_key_lifetime = atoi(pos);
} else if (os_strcmp(buf, "r1_max_key_lifetime") == 0) {
bss->r1_max_key_lifetime = atoi(pos);
} else if (os_strcmp(buf, "reassociation_deadline") == 0) {
bss->reassociation_deadline = atoi(pos);
} else if (os_strcmp(buf, "rkh_pos_timeout") == 0) {

View file

@ -1529,6 +1529,12 @@ own_ip_addr=127.0.0.1
# (dot11FTR0KeyLifetime)
#ft_r0_key_lifetime=1209600
# Maximum lifetime for PMK-R1; applied only if not zero
# PMK-R1 is removed at latest after this limit.
# Removing any PMK-R1 for expiry can be disabled by setting this to -1.
# (default: 0)
#r1_max_key_lifetime=0
# PMK-R1 Key Holder identifier (dot11FTR1KeyHolderID)
# 6-octet identifier as a hex string.
# Defaults to BSSID.

View file

@ -361,6 +361,7 @@ struct hostapd_bss_config {
int pmk_r1_push;
int ft_over_ds;
int ft_psk_generate_local;
int r1_max_key_lifetime;
#endif /* CONFIG_IEEE80211R_AP */
char *ctrl_interface; /* directory for UNIX domain sockets */

View file

@ -77,6 +77,7 @@ struct ft_rrb_frame {
#define FT_RRB_PMK_R1 10 /* PMK_LEN */
#define FT_RRB_PAIRWISE 11 /* le16 */
#define FT_RRB_EXPIRES_IN 12 /* le16 seconds */
struct ft_rrb_tlv {
le16 type;
@ -92,6 +93,7 @@ struct ft_rrb_seq {
/* session TLVs:
* required: PMK_R1, PMK_R1_NAME, PAIRWISE
* optional: EXPIRES_IN
*
* pull frame TLVs:
* auth:
@ -191,6 +193,7 @@ struct wpa_auth_config {
int rkh_neg_timeout;
int rkh_pull_timeout; /* ms */
int rkh_pull_retries;
int r1_max_key_lifetime;
u32 reassociation_deadline;
struct ft_remote_r0kh **r0kh_list;
struct ft_remote_r1kh **r1kh_list;

View file

@ -839,7 +839,8 @@ struct wpa_ft_pmk_r0_sa {
u8 pmk_r0_name[WPA_PMK_NAME_LEN];
u8 spa[ETH_ALEN];
int pairwise; /* Pairwise cipher suite, WPA_CIPHER_* */
/* TODO: expiration, identity, radius_class, EAP type, VLAN ID */
os_time_t expiration; /* 0 for no expiration */
/* TODO: identity, radius_class, EAP type */
int pmk_r1_pushed;
};
@ -858,30 +859,70 @@ struct wpa_ft_pmk_cache {
};
static void wpa_ft_expire_pmk_r0(void *eloop_ctx, void *timeout_ctx);
static void wpa_ft_expire_pmk_r1(void *eloop_ctx, void *timeout_ctx);
static void wpa_ft_free_pmk_r0(struct wpa_ft_pmk_r0_sa *r0)
{
if (!r0)
return;
dl_list_del(&r0->list);
eloop_cancel_timeout(wpa_ft_expire_pmk_r0, r0, NULL);
os_memset(r0->pmk_r0, 0, PMK_LEN);
os_free(r0);
}
static void wpa_ft_expire_pmk_r0(void *eloop_ctx, void *timeout_ctx)
{
struct wpa_ft_pmk_r0_sa *r0 = eloop_ctx;
struct os_reltime now;
int expires_in;
os_get_reltime(&now);
if (!r0)
return;
expires_in = r0->expiration - now.sec;
if (expires_in > 0) {
wpa_printf(MSG_ERROR,
"FT: %s() called for non-expired entry %p, delete in %ds",
__func__, r0, expires_in);
eloop_cancel_timeout(wpa_ft_expire_pmk_r0, r0, NULL);
eloop_register_timeout(expires_in + 1, 0,
wpa_ft_expire_pmk_r0, r0, NULL);
return;
}
wpa_ft_free_pmk_r0(r0);
}
static void wpa_ft_free_pmk_r1(struct wpa_ft_pmk_r1_sa *r1)
{
if (!r1)
return;
dl_list_del(&r1->list);
eloop_cancel_timeout(wpa_ft_expire_pmk_r1, r1, NULL);
os_memset(r1->pmk_r1, 0, PMK_LEN);
os_free(r1);
}
static void wpa_ft_expire_pmk_r1(void *eloop_ctx, void *timeout_ctx)
{
struct wpa_ft_pmk_r1_sa *r1 = eloop_ctx;
wpa_ft_free_pmk_r1(r1);
}
struct wpa_ft_pmk_cache * wpa_ft_pmk_cache_init(void)
{
struct wpa_ft_pmk_cache *cache;
@ -915,12 +956,15 @@ void wpa_ft_pmk_cache_deinit(struct wpa_ft_pmk_cache *cache)
static int wpa_ft_store_pmk_r0(struct wpa_authenticator *wpa_auth,
const u8 *spa, const u8 *pmk_r0,
const u8 *pmk_r0_name, int pairwise)
const u8 *pmk_r0_name, int pairwise,
int expires_in)
{
struct wpa_ft_pmk_cache *cache = wpa_auth->ft_pmk_cache;
struct wpa_ft_pmk_r0_sa *r0;
struct os_reltime now;
/* TODO: add expiration and limit on number of entries in cache */
/* TODO: add limit on number of entries in cache */
os_get_reltime(&now);
r0 = os_zalloc(sizeof(*r0));
if (r0 == NULL)
@ -930,8 +974,13 @@ static int wpa_ft_store_pmk_r0(struct wpa_authenticator *wpa_auth,
os_memcpy(r0->pmk_r0_name, pmk_r0_name, WPA_PMK_NAME_LEN);
os_memcpy(r0->spa, spa, ETH_ALEN);
r0->pairwise = pairwise;
if (expires_in > 0)
r0->expiration = now.sec + expires_in;
dl_list_add(&cache->pmk_r0, &r0->list);
if (expires_in > 0)
eloop_register_timeout(expires_in + 1, 0, wpa_ft_expire_pmk_r0,
r0, NULL);
return 0;
}
@ -943,7 +992,9 @@ static int wpa_ft_fetch_pmk_r0(struct wpa_authenticator *wpa_auth,
{
struct wpa_ft_pmk_cache *cache = wpa_auth->ft_pmk_cache;
struct wpa_ft_pmk_r0_sa *r0;
struct os_reltime now;
os_get_reltime(&now);
dl_list_for_each(r0, &cache->pmk_r0, struct wpa_ft_pmk_r0_sa, list) {
if (os_memcmp(r0->spa, spa, ETH_ALEN) == 0 &&
os_memcmp_const(r0->pmk_r0_name, pmk_r0_name,
@ -960,12 +1011,17 @@ static int wpa_ft_fetch_pmk_r0(struct wpa_authenticator *wpa_auth,
static int wpa_ft_store_pmk_r1(struct wpa_authenticator *wpa_auth,
const u8 *spa, const u8 *pmk_r1,
const u8 *pmk_r1_name, int pairwise)
const u8 *pmk_r1_name, int pairwise,
int expires_in)
{
struct wpa_ft_pmk_cache *cache = wpa_auth->ft_pmk_cache;
int max_expires_in = wpa_auth->conf.r1_max_key_lifetime;
struct wpa_ft_pmk_r1_sa *r1;
/* TODO: add expiration and limit on number of entries in cache */
/* TODO: limit on number of entries in cache */
if (max_expires_in && (max_expires_in < expires_in || expires_in == 0))
expires_in = max_expires_in;
r1 = os_zalloc(sizeof(*r1));
if (r1 == NULL)
@ -978,6 +1034,10 @@ static int wpa_ft_store_pmk_r1(struct wpa_authenticator *wpa_auth,
dl_list_add(&cache->pmk_r1, &r1->list);
if (expires_in > 0)
eloop_register_timeout(expires_in + 1, 0, wpa_ft_expire_pmk_r1,
r1, NULL);
return 0;
}
@ -1533,8 +1593,10 @@ static int wpa_ft_pull_pmk_r1(struct wpa_state_machine *sm,
int wpa_ft_store_pmk_fils(struct wpa_state_machine *sm,
const u8 *pmk_r0, const u8 *pmk_r0_name)
{
int expires_in = sm->wpa_auth->conf.r0_key_lifetime;
return wpa_ft_store_pmk_r0(sm->wpa_auth, sm->addr, pmk_r0, pmk_r0_name,
sm->pairwise);
sm->pairwise, expires_in);
}
@ -1551,6 +1613,7 @@ int wpa_auth_derive_ptk_ft(struct wpa_state_machine *sm, const u8 *pmk,
const u8 *ssid = sm->wpa_auth->conf.ssid;
size_t ssid_len = sm->wpa_auth->conf.ssid_len;
int psk_local = sm->wpa_auth->conf.ft_psk_generate_local;
int expires_in = sm->wpa_auth->conf.r0_key_lifetime;
if (sm->xxkey_len == 0) {
wpa_printf(MSG_DEBUG, "FT: XXKey not available for key "
@ -1566,7 +1629,7 @@ int wpa_auth_derive_ptk_ft(struct wpa_state_machine *sm, const u8 *pmk,
wpa_hexdump(MSG_DEBUG, "FT: PMKR0Name", pmk_r0_name, WPA_PMK_NAME_LEN);
if (!psk_local || !wpa_key_mgmt_ft_psk(sm->wpa_key_mgmt))
wpa_ft_store_pmk_r0(sm->wpa_auth, sm->addr, pmk_r0, pmk_r0_name,
sm->pairwise);
sm->pairwise, expires_in);
if (wpa_derive_pmk_r1(pmk_r0, pmk_r0_name, r1kh, sm->addr,
pmk_r1, sm->pmk_r1_name) < 0)
@ -1576,7 +1639,8 @@ int wpa_auth_derive_ptk_ft(struct wpa_state_machine *sm, const u8 *pmk,
WPA_PMK_NAME_LEN);
if (!psk_local || !wpa_key_mgmt_ft_psk(sm->wpa_key_mgmt))
wpa_ft_store_pmk_r1(sm->wpa_auth, sm->addr, pmk_r1,
sm->pmk_r1_name, sm->pairwise);
sm->pmk_r1_name, sm->pairwise,
expires_in);
return wpa_pmk_r1_to_ptk(pmk_r1, sm->SNonce, sm->ANonce, sm->addr,
sm->wpa_auth->addr, sm->pmk_r1_name,
@ -2094,6 +2158,7 @@ static int wpa_ft_local_derive_pmk_r1(struct wpa_authenticator *wpa_auth,
struct wpa_auth_config *conf = &wpa_auth->conf;
const struct wpa_ft_pmk_r0_sa *r0;
u8 pmk_r1_name[WPA_PMK_NAME_LEN];
int expires_in = 0;
if (conf->r0_key_holder_len != r0kh_id_len ||
os_memcmp(conf->r0_key_holder, r0kh_id, conf->r0_key_holder_len) !=
@ -2113,8 +2178,15 @@ static int wpa_ft_local_derive_pmk_r1(struct wpa_authenticator *wpa_auth,
wpa_hexdump_key(MSG_DEBUG, "FT: PMK-R1", out_pmk_r1, PMK_LEN);
wpa_hexdump(MSG_DEBUG, "FT: PMKR1Name", pmk_r1_name, WPA_PMK_NAME_LEN);
if (r0->expiration) {
struct os_reltime now;
os_get_reltime(&now);
expires_in = r0->expiration - now.sec;
}
wpa_ft_store_pmk_r1(wpa_auth, sm->addr, out_pmk_r1, pmk_r1_name,
sm->pairwise);
sm->pairwise, expires_in);
*out_pairwise = sm->pairwise;
return 0;
@ -2678,6 +2750,9 @@ static int wpa_ft_rrb_build_r0(const u8 *key, const size_t key_len,
u8 pmk_r1[PMK_LEN];
u8 pmk_r1_name[WPA_PMK_NAME_LEN];
u8 f_pairwise[sizeof(le16)];
u8 f_expires_in[sizeof(le16)];
int expires_in;
struct os_reltime now;
int ret;
struct tlv_list sess_tlv[] = {
{ .type = FT_RRB_PMK_R1, .len = sizeof(pmk_r1),
@ -2686,6 +2761,8 @@ static int wpa_ft_rrb_build_r0(const u8 *key, const size_t key_len,
.data = pmk_r1_name },
{ .type = FT_RRB_PAIRWISE, .len = sizeof(f_pairwise),
.data = f_pairwise },
{ .type = FT_RRB_EXPIRES_IN, .len = sizeof(f_expires_in),
.data = f_expires_in },
{ .type = FT_RRB_LAST_EMPTY, .len = 0, .data = NULL },
};
@ -2700,6 +2777,15 @@ static int wpa_ft_rrb_build_r0(const u8 *key, const size_t key_len,
wpa_hexdump(MSG_DEBUG, "FT: PMKR1Name", pmk_r1_name, WPA_PMK_NAME_LEN);
WPA_PUT_LE16(f_pairwise, pmk_r0->pairwise);
os_get_reltime(&now);
if (pmk_r0->expiration > now.sec)
expires_in = pmk_r0->expiration - now.sec;
else if (pmk_r0->expiration)
expires_in = 1;
else
expires_in = 0;
WPA_PUT_LE16(f_expires_in, expires_in);
ret = wpa_ft_rrb_build(key, key_len, tlvs, sess_tlv, tlv_auth,
src_addr, type, packet, packet_len);
@ -2876,10 +2962,13 @@ static int wpa_ft_rrb_rx_r1(struct wpa_authenticator *wpa_auth,
int seq_ret;
const u8 *f_r1kh_id, *f_s1kh_id, *f_r0kh_id;
const u8 *f_pmk_r1_name, *f_pairwise, *f_pmk_r1;
const u8 *f_expires_in;
size_t f_r1kh_id_len, f_s1kh_id_len, f_r0kh_id_len;
size_t f_pmk_r1_name_len, f_pairwise_len, f_pmk_r1_len;
size_t f_expires_in_len;
int pairwise;
int ret = -1;
int expires_in;
RRB_GET_AUTH(FT_RRB_R0KH_ID, r0kh_id, msgtype, -1);
wpa_hexdump(MSG_DEBUG, "FT: R0KH-ID", f_r0kh_id, f_r0kh_id_len);
@ -2963,8 +3052,18 @@ static int wpa_ft_rrb_rx_r1(struct wpa_authenticator *wpa_auth,
pairwise = WPA_GET_LE16(f_pairwise);
RRB_GET_OPTIONAL(FT_RRB_EXPIRES_IN, expires_in, msgtype,
sizeof(le16));
if (f_expires_in)
expires_in = WPA_GET_LE16(f_expires_in);
else
expires_in = 0;
wpa_printf(MSG_DEBUG, "FT: PMK-R1 %s - expires_in=%d", msgtype,
expires_in);
if (wpa_ft_store_pmk_r1(wpa_auth, f_s1kh_id, f_pmk_r1, f_pmk_r1_name,
pairwise) < 0)
pairwise, expires_in) < 0)
goto out;
ret = 0;

View file

@ -76,6 +76,7 @@ static void hostapd_wpa_auth_conf(struct hostapd_bss_config *conf,
}
os_memcpy(wconf->r1_key_holder, conf->r1_key_holder, FT_R1KH_ID_LEN);
wconf->r0_key_lifetime = conf->r0_key_lifetime;
wconf->r1_max_key_lifetime = conf->r1_max_key_lifetime;
wconf->reassociation_deadline = conf->reassociation_deadline;
wconf->rkh_pos_timeout = conf->rkh_pos_timeout;
wconf->rkh_neg_timeout = conf->rkh_neg_timeout;