From patchwork Sat Sep 24 21:07:54 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: michael-dev X-Patchwork-Id: 674373 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 3shNDL3xyGz9sf6 for ; Sun, 25 Sep 2016 07:10:26 +1000 (AEST) Authentication-Results: ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=fami-braun.de header.i=@fami-braun.de header.b=RCVxoDuy; dkim-atps=neutral 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 1bnuD4-0001Wt-Ri; Sat, 24 Sep 2016 21:10:10 +0000 Received: from mo6-p00-ob.smtp.rzone.de ([2a01:238:20a:202:5300::5]) by bombadil.infradead.org with esmtps (Exim 4.85_2 #1 (Red Hat Linux)) id 1bnuBk-0000Q1-Dh for hostap@lists.infradead.org; Sat, 24 Sep 2016 21:09:02 +0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; t=1474751299; l=14179; s=domk; d=fami-braun.de; h=References:In-Reply-To:Date:Subject:To:From; bh=624CLYwKCUkWx/RR1Gnxm7Lm1Irw1VPTo03ShGYnNbg=; b=RCVxoDuydgkSITbYY+hl59/zf2u6W3cZMH5btT6INgzSwccL+tWXHuZwyN2OFMsbf7y k/ROPf9dTicYmAeakBphsyArigs3TxM/wh53v2RFJ4qKTZ2QeBkrvHHRfdXPnTA4sTTmQ HARS/wOttmBzzy+bjgyupWNQp2HFp5ifHrI= X-RZG-AUTH: :P20JeEWkefDI1ODZs1HHtgV3eF0OpFsRaGIBBWYxhJvJPtnXtogBWn6YvUkYzDKvBT07wx/0LJ4BNA== X-RZG-CLASS-ID: mo00 Received: from dynamic.fami-braun.de ([2a01:198:45f::254]) by smtp.strato.de (RZmta 39.3 AUTH) with ESMTPSA id d0632ds8OL8JHNh (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (curve secp521r1 with 521 ECDH bits, eq. 15360 bits RSA)) (Client did not present a certificate) for ; Sat, 24 Sep 2016 23:08:19 +0200 (CEST) Received: from dynamic.fami-braun.de (localhost [127.0.0.1]) by dynamic.fami-braun.de (fami-braun.de) with ESMTP id A56A0154369 for ; Sat, 24 Sep 2016 23:08:18 +0200 (CEST) Received: by dynamic.fami-braun.de (fami-braun.de, from userid 1001) id 8F8F015829D; Sat, 24 Sep 2016 23:08:18 +0200 (CEST) From: "M. 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 23:07:54 +0200 Message-Id: <1474751297-7277-11-git-send-email-michael-dev@fami-braun.de> X-Mailer: git-send-email 2.1.4 In-Reply-To: <1474751297-7277-1-git-send-email-michael-dev@fami-braun.de> References: <1474751297-7277-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_140848_931488_51F18EEC X-CRM114-Status: GOOD ( 20.62 ) X-Spam-Score: -2.7 (--) X-Spam-Report: SpamAssassin version 3.4.0 on bombadil.infradead.org summary: Content analysis details: (-2.7 points) pts rule name description ---- ---------------------- -------------------------------------------------- -0.7 RCVD_IN_DNSWL_LOW RBL: Sender listed at http://www.dnswl.org/, low trust [2a01:238:20a:202:5300:0:0:5 listed in] [list.dnswl.org] -1.9 BAYES_00 BODY: Bayes spam probability is 0 to 1% [score: 0.0000] -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid 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: , MIME-Version: 1.0 Sender: "Hostap" Errors-To: hostap-bounces+incoming=patchwork.ozlabs.org@lists.infradead.org From: Michael Braun 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;