From patchwork Sat Sep 24 20:53:51 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: michael-dev X-Patchwork-Id: 674412 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from bombadil.infradead.org (bombadil.infradead.org [IPv6:2001:1868:205::9]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 3shNYv2G0qz9s5g for ; Sun, 25 Sep 2016 07:25:39 +1000 (AEST) Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.85_2 #1 (Red Hat Linux)) id 1bnuRo-0002E5-MI; Sat, 24 Sep 2016 21:25:24 +0000 Received: from merlin.infradead.org ([2001:4978:20e::2]) by bombadil.infradead.org with esmtps (Exim 4.85_2 #1 (Red Hat Linux)) id 1bnuPd-0007b6-B9 for hostap@bombadil.infradead.org; Sat, 24 Sep 2016 21:23:09 +0000 Received: from mail.fem.tu-ilmenau.de ([141.24.220.54]) by merlin.infradead.org with esmtp (Exim 4.85_2 #1 (Red Hat Linux)) id 1bnu34-0000zz-Ho for hostap@lists.infradead.org; Sat, 24 Sep 2016 20:59:51 +0000 Received: from localhost (localhost [127.0.0.1]) by mail.fem.tu-ilmenau.de (Postfix) with ESMTP id 71C52693C; Sat, 24 Sep 2016 22:54:18 +0200 (CEST) X-Virus-Scanned: amavisd-new at fem.tu-ilmenau.de Received: from mail.fem.tu-ilmenau.de ([127.0.0.1]) by localhost (mail.fem.tu-ilmenau.de [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id W+CghE1xrb+P; Sat, 24 Sep 2016 22:54:18 +0200 (CEST) Received: from mail-backup.fem.tu-ilmenau.de (mail-backup.net.fem.tu-ilmenau.de [10.42.40.22]) (using TLSv1 with cipher ADH-AES256-SHA (256/256 bits)) (No client certificate requested) by mail.fem.tu-ilmenau.de (Postfix) with ESMTPS; Sat, 24 Sep 2016 22:54:18 +0200 (CEST) Received: from a234.fem.tu-ilmenau.de (ray-controller.net.fem.tu-ilmenau.de [10.42.51.234]) by mail-backup.fem.tu-ilmenau.de (Postfix) with ESMTP id 4193E5604C; Sat, 24 Sep 2016 22:54:18 +0200 (CEST) Received: by a234.fem.tu-ilmenau.de (Postfix, from userid 1000) id 3030430212FA; Sat, 24 Sep 2016 22:54:18 +0200 (CEST) From: Michael Braun To: hostap@lists.infradead.org Subject: [PATCH v2 10/33] FT: add expiration to PMK-R0 and PMK-R1 cache Date: Sat, 24 Sep 2016 22:53:51 +0200 Message-Id: <1474750454-6626-11-git-send-email-michael-dev@fami-braun.de> X-Mailer: git-send-email 2.1.4 In-Reply-To: <1474750454-6626-1-git-send-email-michael-dev@fami-braun.de> References: <1474750454-6626-1-git-send-email-michael-dev@fami-braun.de> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20160924_165950_816360_A8112AFE X-CRM114-Status: GOOD ( 21.84 ) X-Spam-Score: -4.2 (----) X-Spam-Report: SpamAssassin version 3.4.1 on merlin.infradead.org summary: Content analysis details: (-4.2 points) pts rule name description ---- ---------------------- -------------------------------------------------- -2.3 RCVD_IN_DNSWL_MED RBL: Sender listed at http://www.dnswl.org/, medium trust [141.24.220.54 listed in list.dnswl.org] -1.9 BAYES_00 BODY: Bayes spam probability is 0 to 1% [score: 0.0000] X-BeenThere: hostap@lists.infradead.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: projekt-wlan@fem.tu-ilmenau.de, Michael Braun MIME-Version: 1.0 Sender: "Hostap" Errors-To: hostap-bounces+incoming=patchwork.ozlabs.org@lists.infradead.org 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 --- 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;