[PATCH v2 10/33] FT: add expiration to PMK-R0 and PMK-R1 cache

Michael Braun michael-dev at fami-braun.de
Sat Sep 24 13:53:51 PDT 2016


IEEE 802.11-2012 says: MSK has a lifetime that limits PMK_R0 and PMK_R1
lifetime. This is currently stored in r0_key_lifetime, but cache entries
are not actually removed.

This patch assumes a default MSK lifetime to be 3600s when
wpa_auth_derive_ptk_ft is called. This matches the default eapol_reauth
timeout and should probably be passed in from eapol state machine with
some future changes. For PSK, things could be handled quite differently,
but currently are not, as PMK-R0/R1 caching can be avoided for FT_PSK with
some other patch in this queue.

The expiration timeout is then passed form R0KH to R1KH. The R1KH verifies
that the given timeout for sanity, it max not exceed the locally configured
r0_key_lifetime.

Signed-off-by: Michael Braun <michael-dev at fami-braun.de>
---
 hostapd/hostapd.conf |   1 +
 src/ap/ap_config.c   |   1 +
 src/ap/wpa_auth.h    |   2 +
 src/ap/wpa_auth_ft.c | 197 ++++++++++++++++++++++++++++++++++++++++-----------
 4 files changed, 161 insertions(+), 40 deletions(-)

diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf
index 8aa4248..811fa58 100644
--- a/hostapd/hostapd.conf
+++ b/hostapd/hostapd.conf
@@ -1286,6 +1286,7 @@ own_ip_addr=127.0.0.1
 # This is configured with nas_identifier (see RADIUS client section above).
 
 # Default lifetime of the PMK-RO in minutes; range 1..65535
+# (default: 60 minutes; 0 = disable timeout)
 # (dot11FTR0KeyLifetime)
 #r0_key_lifetime=10000
 
diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c
index accd9a3..6363da6 100644
--- a/src/ap/ap_config.c
+++ b/src/ap/ap_config.c
@@ -94,6 +94,7 @@ void hostapd_config_defaults_bss(struct hostapd_bss_config *bss)
 	bss->rkh_neg_timeout = 60;
 	bss->rkh_pull_timeout = 1000;
 	bss->rkh_pull_retries = 4;
+	bss->r0_key_lifetime = 60; /* same as eap_reauth_period */
 #endif /* CONFIG_IEEE80211R */
 
 	bss->radius_das_time_window = 300;
diff --git a/src/ap/wpa_auth.h b/src/ap/wpa_auth.h
index 83fc305..d541f8d 100644
--- a/src/ap/wpa_auth.h
+++ b/src/ap/wpa_auth.h
@@ -73,6 +73,7 @@ struct ft_rrb_frame {
 #define FT_RRB_PMK_R1         9 /* PMK_LEN */
 
 #define FT_RRB_PAIRWISE      10 /* le16 */
+#define FT_RRB_EXPIRES_IN    11 /* le16 seconds */
 
 struct ft_rrbv1_tlv {
 	le16 type;
@@ -82,6 +83,7 @@ struct ft_rrbv1_tlv {
 
 /* session TLVs:
  *   required: [PMK_R1, PMK_R1_NAME, PAIRWISE]
+ *   optional: EXPIRES_IN
  *
  * pull frame TLVs:
  *   required: NONCE, R0KH_ID, PMK_R0_NAME, R1KH_ID, S1KH_ID
diff --git a/src/ap/wpa_auth_ft.c b/src/ap/wpa_auth_ft.c
index 5abe412..15bf55e 100644
--- a/src/ap/wpa_auth_ft.c
+++ b/src/ap/wpa_auth_ft.c
@@ -257,6 +257,17 @@ static int wpa_ft_rrb_build(const u8 *kek, size_t kek_len,
 	} \
 } while (0)
 
+#define RRB_GET_OPTIONAL(type, field, txt, checklength) do { \
+	if (wpa_ft_rrb_get_tlv(plain, plain_len, type, \
+				&f_##field##_len, &f_##field) < 0 || \
+	    (checklength > 0 && ((size_t) checklength) != f_##field##_len)) { \
+		wpa_printf(MSG_DEBUG, "FT: Missing " #field " in PMK-R1 " \
+			   "%s from " MACSTR, txt, MAC2STR(src_addr)); \
+		f_##field##_len = 0; \
+		f_##field = NULL; \
+	} \
+} while (0)
+
 
 static int wpa_ft_rrb_send(struct wpa_authenticator *wpa_auth, const u8 *dst,
 			   const u8 *data, size_t data_len)
@@ -382,17 +393,18 @@ int wpa_write_ftie(struct wpa_auth_config *conf, const u8 *r0kh_id,
 
 
 struct wpa_ft_pmk_r0_sa {
-	struct wpa_ft_pmk_r0_sa *next;
+	struct dl_list list;
 	u8 pmk_r0[PMK_LEN];
 	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;
 };
 
 struct wpa_ft_pmk_r1_sa {
-	struct wpa_ft_pmk_r1_sa *next;
+	struct dl_list list;
 	u8 pmk_r1[PMK_LEN];
 	u8 pmk_r1_name[WPA_PMK_NAME_LEN];
 	u8 spa[ETH_ALEN];
@@ -401,15 +413,83 @@ struct wpa_ft_pmk_r1_sa {
 };
 
 struct wpa_ft_pmk_cache {
-	struct wpa_ft_pmk_r0_sa *pmk_r0;
-	struct wpa_ft_pmk_r1_sa *pmk_r1;
+	struct dl_list pmk_r0;
+	struct dl_list pmk_r1;
 };
 
+
+static void wpa_ft_free_pmk_r0(struct wpa_ft_pmk_r0_sa *r0);
+static void wpa_ft_expire_pmk_r0(void *eloop_ctx, void *timeout_ctx);
+static void wpa_ft_free_pmk_r1(struct wpa_ft_pmk_r1_sa *r1);
+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: wpa_ft_expire_pmk_r0 called for "
+				      "non-expired entry %p, delete in %ds",
+				      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;
 
 	cache = os_zalloc(sizeof(*cache));
+	dl_list_init(&cache->pmk_r0);
+	dl_list_init(&cache->pmk_r1);
 
 	return cache;
 }
@@ -420,20 +500,14 @@ void wpa_ft_pmk_cache_deinit(struct wpa_ft_pmk_cache *cache)
 	struct wpa_ft_pmk_r0_sa *r0, *r0prev;
 	struct wpa_ft_pmk_r1_sa *r1, *r1prev;
 
-	r0 = cache->pmk_r0;
-	while (r0) {
-		r0prev = r0;
-		r0 = r0->next;
-		os_memset(r0prev->pmk_r0, 0, PMK_LEN);
-		os_free(r0prev);
+	dl_list_for_each_safe(r0, r0prev, &cache->pmk_r0,
+			      struct wpa_ft_pmk_r0_sa, list) {
+		wpa_ft_free_pmk_r0(r0);
 	}
 
-	r1 = cache->pmk_r1;
-	while (r1) {
-		r1prev = r1;
-		r1 = r1->next;
-		os_memset(r1prev->pmk_r1, 0, PMK_LEN);
-		os_free(r1prev);
+	dl_list_for_each_safe(r1, r1prev, &cache->pmk_r1,
+			      struct wpa_ft_pmk_r1_sa, list) {
+		wpa_ft_free_pmk_r1(r1);
 	}
 
 	os_free(cache);
@@ -442,12 +516,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,
+			       const 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)
@@ -457,9 +534,14 @@ 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;
 
-	r0->next = cache->pmk_r0;
-	cache->pmk_r0 = r0;
+	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;
 }
@@ -471,17 +553,16 @@ 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;
 
-	r0 = cache->pmk_r0;
-	while (r0) {
+	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,
 				    WPA_PMK_NAME_LEN) == 0) {
 			*r0_out = r0;
 			return 0;
 		}
-
-		r0 = r0->next;
 	}
 
 	*r0_out = NULL;
@@ -491,7 +572,8 @@ 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;
 	struct wpa_ft_pmk_r1_sa *r1;
@@ -507,8 +589,11 @@ static int wpa_ft_store_pmk_r1(struct wpa_authenticator *wpa_auth,
 	os_memcpy(r1->spa, spa, ETH_ALEN);
 	r1->pairwise = pairwise;
 
-	r1->next = cache->pmk_r1;
-	cache->pmk_r1 = r1;
+	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;
 }
@@ -521,8 +606,7 @@ static int wpa_ft_fetch_pmk_r1(struct wpa_authenticator *wpa_auth,
 	struct wpa_ft_pmk_cache *cache = wpa_auth->ft_pmk_cache;
 	struct wpa_ft_pmk_r1_sa *r1;
 
-	r1 = cache->pmk_r1;
-	while (r1) {
+	dl_list_for_each(r1, &cache->pmk_r1, struct wpa_ft_pmk_r1_sa, list) {
 		if (os_memcmp(r1->spa, spa, ETH_ALEN) == 0 &&
 		    os_memcmp_const(r1->pmk_r1_name, pmk_r1_name,
 				    WPA_PMK_NAME_LEN) == 0) {
@@ -531,8 +615,6 @@ static int wpa_ft_fetch_pmk_r1(struct wpa_authenticator *wpa_auth,
 				*pairwise = r1->pairwise;
 			return 0;
 		}
-
-		r1 = r1->next;
 	}
 
 	return -1;
@@ -785,6 +867,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 * 60;
 
 	if (sm->xxkey_len == 0) {
 		wpa_printf(MSG_DEBUG, "FT: XXKey not available for key "
@@ -798,7 +881,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);
 
 	wpa_derive_pmk_r1(pmk_r0, pmk_r0_name, r1kh, sm->addr,
 			  pmk_r1, sm->pmk_r1_name);
@@ -807,7 +890,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,
@@ -1828,6 +1912,9 @@ static int wpa_ft_rrb_build_r0(const u8 *kek, size_t kek_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;
 
 	if (!pmk_r0)
@@ -1840,6 +1927,15 @@ static int wpa_ft_rrb_build_r0(const u8 *kek, size_t kek_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);
+
 	struct tlv_list sess_tlv[] = {
 		{ .type = FT_RRB_PMK_R1, .len = sizeof(pmk_r1),
 		  .data = pmk_r1 },
@@ -1847,6 +1943,8 @@ static int wpa_ft_rrb_build_r0(const u8 *kek, size_t kek_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 },
 	};
 
@@ -2050,10 +2148,14 @@ static int wpa_ft_rrb_rx_r1(struct wpa_authenticator *wpa_auth,
 {
 	const u8 *f_r1kh_id, *f_s1kh_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;
 	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;
+	int max_expires_in = wpa_auth->conf.r0_key_lifetime * 60;
 
 	RRB_GET(FT_RRB_R1KH_ID, r1kh_id, msgtype, FT_R1KH_ID_LEN);
 	if (os_memcmp_const(f_r1kh_id, wpa_auth->conf.r1_key_holder,
@@ -2083,8 +2185,21 @@ 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 (expires_in <= 0 || expires_in > max_expires_in)
+		expires_in = max_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)
 		return -1;
 
 	ret = 0;
@@ -2416,7 +2531,8 @@ static void wpa_ft_generate_pmk_r1(struct wpa_authenticator *wpa_auth,
 
 void wpa_ft_push_pmk_r1(struct wpa_authenticator *wpa_auth, const u8 *addr)
 {
-	struct wpa_ft_pmk_r0_sa *r0;
+	struct wpa_ft_pmk_cache *cache = wpa_auth->ft_pmk_cache;
+	struct wpa_ft_pmk_r0_sa *r0, *r0found = NULL;
 	struct ft_remote_r1kh *r1kh;
 
 	if (!wpa_auth->conf.pmk_r1_push)
@@ -2424,13 +2540,14 @@ void wpa_ft_push_pmk_r1(struct wpa_authenticator *wpa_auth, const u8 *addr)
 	if (!wpa_auth->conf.r1kh_list)
 		return;
 
-	r0 = wpa_auth->ft_pmk_cache->pmk_r0;
-	while (r0) {
-		if (os_memcmp(r0->spa, addr, ETH_ALEN) == 0)
+	dl_list_for_each(r0, &cache->pmk_r0, struct wpa_ft_pmk_r0_sa, list) {
+		if (os_memcmp(r0->spa, addr, ETH_ALEN) == 0) {
+			r0found = r0;
 			break;
-		r0 = r0->next;
+		}
 	}
 
+	r0 = r0found;
 	if (r0 == NULL || r0->pmk_r1_pushed)
 		return;
 	r0->pmk_r1_pushed = 1;
-- 
2.1.4




More information about the Hostap mailing list