From 3a3e28320b6d68053c6c2b27d4d7f16cc676045a Mon Sep 17 00:00:00 2001 From: Michael Braun Date: Thu, 18 May 2017 15:21:50 +0200 Subject: [PATCH] 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 --- hostapd/config_file.c | 2 + hostapd/hostapd.conf | 6 +++ src/ap/ap_config.h | 1 + src/ap/wpa_auth.h | 3 ++ src/ap/wpa_auth_ft.c | 119 +++++++++++++++++++++++++++++++++++++---- src/ap/wpa_auth_glue.c | 1 + 6 files changed, 122 insertions(+), 10 deletions(-) diff --git a/hostapd/config_file.c b/hostapd/config_file.c index 5f957a0b5..90f30ddcd 100644 --- a/hostapd/config_file.c +++ b/hostapd/config_file.c @@ -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) { diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf index a15d990d1..eaae3fd1d 100644 --- a/hostapd/hostapd.conf +++ b/hostapd/hostapd.conf @@ -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. diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h index 58a63f902..03ab80d43 100644 --- a/src/ap/ap_config.h +++ b/src/ap/ap_config.h @@ -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 */ diff --git a/src/ap/wpa_auth.h b/src/ap/wpa_auth.h index dd106f410..189001fec 100644 --- a/src/ap/wpa_auth.h +++ b/src/ap/wpa_auth.h @@ -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; diff --git a/src/ap/wpa_auth_ft.c b/src/ap/wpa_auth_ft.c index a13ae708b..a8646e19e 100644 --- a/src/ap/wpa_auth_ft.c +++ b/src/ap/wpa_auth_ft.c @@ -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; diff --git a/src/ap/wpa_auth_glue.c b/src/ap/wpa_auth_glue.c index afc10f980..c891a352c 100644 --- a/src/ap/wpa_auth_glue.c +++ b/src/ap/wpa_auth_glue.c @@ -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;