From 76e20f4fa7025b8e6887b9bd55a5072ca7d74677 Mon Sep 17 00:00:00 2001 From: Jouni Malinen Date: Sun, 12 Mar 2017 22:45:35 +0200 Subject: [PATCH] FILS: Add FILS SK auth PFS support in STA mode This adds an option to configure wpa_supplicant to use the perfect forward secrecy option in FILS shared key authentication. A new build option CONFIG_FILS_SK_PFS=y can be used to include this functionality. A new runtime network profile parameter fils_dh_group is used to enable this by specifying which DH group to use. For example, fils_dh_group=19 would use FILS SK PFS with a 256-bit random ECP group. Signed-off-by: Jouni Malinen --- src/rsn_supp/wpa.c | 123 ++++++++++++++++++++++++----- src/rsn_supp/wpa.h | 2 +- src/rsn_supp/wpa_i.h | 3 + wpa_supplicant/Android.mk | 4 + wpa_supplicant/Makefile | 4 + wpa_supplicant/config.c | 1 + wpa_supplicant/config_file.c | 1 + wpa_supplicant/config_ssid.h | 8 ++ wpa_supplicant/defconfig | 2 + wpa_supplicant/events.c | 3 +- wpa_supplicant/sme.c | 42 ++++++++-- wpa_supplicant/wpa_supplicant.conf | 5 ++ 12 files changed, 170 insertions(+), 28 deletions(-) diff --git a/src/rsn_supp/wpa.c b/src/rsn_supp/wpa.c index d3fd8ef50..5eb983447 100644 --- a/src/rsn_supp/wpa.c +++ b/src/rsn_supp/wpa.c @@ -2461,6 +2461,9 @@ void wpa_sm_deinit(struct wpa_sm *sm) #ifdef CONFIG_TESTING_OPTIONS wpabuf_free(sm->test_assoc_ie); #endif /* CONFIG_TESTING_OPTIONS */ +#ifdef CONFIG_FILS + crypto_ecdh_deinit(sm->fils_ecdh); +#endif /* CONFIG_FILS */ #ifdef CONFIG_OWE crypto_ecdh_deinit(sm->owe_ecdh); #endif /* CONFIG_OWE */ @@ -3265,10 +3268,11 @@ void wpa_sm_set_test_assoc_ie(struct wpa_sm *sm, struct wpabuf *buf) #ifdef CONFIG_FILS -struct wpabuf * fils_build_auth(struct wpa_sm *sm) +struct wpabuf * fils_build_auth(struct wpa_sm *sm, int dh_group) { struct wpabuf *buf = NULL; struct wpabuf *erp_msg; + struct wpabuf *pub = NULL; erp_msg = eapol_sm_build_erp_reauth_start(sm->eapol); if (!erp_msg && !sm->cur_pmksa) { @@ -3296,7 +3300,28 @@ struct wpabuf * fils_build_auth(struct wpa_sm *sm) wpa_hexdump(MSG_DEBUG, "FILS: Generated FILS Session", sm->fils_session, FILS_SESSION_LEN); - buf = wpabuf_alloc(1000 + sm->assoc_wpa_ie_len); +#ifdef CONFIG_FILS_SK_PFS + sm->fils_dh_group = dh_group; + if (dh_group) { + crypto_ecdh_deinit(sm->fils_ecdh); + sm->fils_ecdh = crypto_ecdh_init(dh_group); + if (!sm->fils_ecdh) { + wpa_printf(MSG_INFO, + "FILS: Could not initialize ECDH with group %d", + dh_group); + goto fail; + } + pub = crypto_ecdh_get_pubkey(sm->fils_ecdh, 1); + if (!pub) + goto fail; + wpa_hexdump_buf(MSG_DEBUG, "FILS: Element (DH public key)", + pub); + sm->fils_dh_elem_len = wpabuf_len(pub); + } +#endif /* CONFIG_FILS_SK_PFS */ + + buf = wpabuf_alloc(1000 + sm->assoc_wpa_ie_len + + (pub ? wpabuf_len(pub) : 0)); if (!buf) goto fail; @@ -3308,8 +3333,15 @@ struct wpabuf * fils_build_auth(struct wpa_sm *sm) /* Status Code */ wpabuf_put_le16(buf, WLAN_STATUS_SUCCESS); - /* TODO: Finite Cyclic Group when using PK or PFS */ - /* TODO: Element when using PK or PFS */ + /* TODO: FILS PK */ +#ifdef CONFIG_FILS_SK_PFS + if (dh_group) { + /* Finite Cyclic Group */ + wpabuf_put_le16(buf, dh_group); + /* Element */ + wpabuf_put_buf(buf, pub); + } +#endif /* CONFIG_FILS_SK_PFS */ /* RSNE */ wpa_hexdump(MSG_DEBUG, "FILS: RSNE in FILS Authentication frame", @@ -3354,6 +3386,7 @@ struct wpabuf * fils_build_auth(struct wpa_sm *sm) fail: wpabuf_free(erp_msg); + wpabuf_free(pub); return buf; } @@ -3368,6 +3401,7 @@ int fils_process_auth(struct wpa_sm *sm, const u8 *bssid, const u8 *data, u8 ick[FILS_ICK_MAX_LEN]; size_t ick_len; int res; + struct wpabuf *dh_ss = NULL; os_memcpy(sm->bssid, bssid, ETH_ALEN); @@ -3376,13 +3410,53 @@ int fils_process_auth(struct wpa_sm *sm, const u8 *bssid, const u8 *data, pos = data; end = data + len; - /* TODO: Finite Cyclic Group when using PK or PFS */ - /* TODO: Element when using PK or PFS */ + /* TODO: FILS PK */ +#ifdef CONFIG_FILS_SK_PFS + if (sm->fils_dh_group) { + u16 group; + + /* Using FILS PFS */ + + /* Finite Cyclic Group */ + if (end - pos < 2) { + wpa_printf(MSG_DEBUG, + "FILS: No room for Finite Cyclic Group"); + goto fail; + } + group = WPA_GET_LE16(pos); + pos += 2; + if (group != sm->fils_dh_group) { + wpa_printf(MSG_DEBUG, + "FILS: Unexpected change in Finite Cyclic Group: %u (expected %u)", + group, sm->fils_dh_group); + goto fail; + } + + /* Element */ + if ((size_t) (end - pos) < sm->fils_dh_elem_len) { + wpa_printf(MSG_DEBUG, "FILS: No room for Element"); + goto fail; + } + + if (!sm->fils_ecdh) { + wpa_printf(MSG_DEBUG, "FILS: No ECDH state available"); + goto fail; + } + dh_ss = crypto_ecdh_set_peerkey(sm->fils_ecdh, 1, pos, + sm->fils_dh_elem_len); + if (!dh_ss) { + wpa_printf(MSG_DEBUG, "FILS: ECDH operation failed"); + goto fail; + } + wpa_hexdump_buf_key(MSG_DEBUG, "FILS: DH_SS", dh_ss); + pos += sm->fils_dh_elem_len; + } +#endif /* CONFIG_FILS_SK_PFS */ wpa_hexdump(MSG_DEBUG, "FILS: Remaining IEs", pos, end - pos); if (ieee802_11_parse_elems(pos, end - pos, &elems, 1) == ParseFailed) { wpa_printf(MSG_DEBUG, "FILS: Could not parse elements"); - return -1; + goto fail; } /* RSNE */ @@ -3392,12 +3466,12 @@ int fils_process_auth(struct wpa_sm *sm, const u8 *bssid, const u8 *data, wpa_parse_wpa_ie_rsn(elems.rsn_ie - 2, elems.rsn_ie_len + 2, &rsn) < 0) { wpa_printf(MSG_DEBUG, "FILS: No RSN element"); - return -1; + goto fail; } if (!elems.fils_nonce) { wpa_printf(MSG_DEBUG, "FILS: No FILS Nonce field"); - return -1; + goto fail; } os_memcpy(sm->fils_anonce, elems.fils_nonce, FILS_NONCE_LEN); wpa_hexdump(MSG_DEBUG, "FILS: ANonce", sm->fils_anonce, FILS_NONCE_LEN); @@ -3412,7 +3486,7 @@ int fils_process_auth(struct wpa_sm *sm, const u8 *bssid, const u8 *data, if (rsn.num_pmkid != 1) { wpa_printf(MSG_DEBUG, "FILS: Invalid PMKID selection"); - return -1; + goto fail; } wpa_hexdump(MSG_DEBUG, "FILS: PMKID", rsn.pmkid, PMKID_LEN); if (os_memcmp(sm->cur_pmksa->pmkid, rsn.pmkid, PMKID_LEN) != 0) @@ -3420,7 +3494,7 @@ int fils_process_auth(struct wpa_sm *sm, const u8 *bssid, const u8 *data, wpa_printf(MSG_DEBUG, "FILS: PMKID mismatch"); wpa_hexdump(MSG_DEBUG, "FILS: Expected PMKID", sm->cur_pmksa->pmkid, PMKID_LEN); - return -1; + goto fail; } wpa_printf(MSG_DEBUG, "FILS: Matching PMKID - continue using PMKSA caching"); @@ -3435,7 +3509,7 @@ int fils_process_auth(struct wpa_sm *sm, const u8 *bssid, const u8 *data, /* FILS Session */ if (!elems.fils_session) { wpa_printf(MSG_DEBUG, "FILS: No FILS Session element"); - return -1; + goto fail; } wpa_hexdump(MSG_DEBUG, "FILS: FILS Session", elems.fils_session, FILS_SESSION_LEN); @@ -3444,7 +3518,7 @@ int fils_process_auth(struct wpa_sm *sm, const u8 *bssid, const u8 *data, wpa_printf(MSG_DEBUG, "FILS: Session mismatch"); wpa_hexdump(MSG_DEBUG, "FILS: Expected FILS Session", sm->fils_session, FILS_SESSION_LEN); - return -1; + goto fail; } /* FILS Wrapped Data */ @@ -3458,7 +3532,7 @@ int fils_process_auth(struct wpa_sm *sm, const u8 *bssid, const u8 *data, eapol_sm_process_erp_finish(sm->eapol, elems.fils_wrapped_data, elems.fils_wrapped_data_len); if (eapol_sm_failed(sm->eapol)) - return -1; + goto fail; rmsk_len = ERP_MAX_KEY_LEN; res = eapol_sm_get_key(sm->eapol, rmsk, rmsk_len); @@ -3467,18 +3541,22 @@ int fils_process_auth(struct wpa_sm *sm, const u8 *bssid, const u8 *data, res = eapol_sm_get_key(sm->eapol, rmsk, rmsk_len); } if (res) - return -1; + goto fail; res = fils_rmsk_to_pmk(sm->key_mgmt, rmsk, rmsk_len, - sm->fils_nonce, sm->fils_anonce, NULL, 0, + sm->fils_nonce, sm->fils_anonce, + dh_ss ? wpabuf_head(dh_ss) : NULL, + dh_ss ? wpabuf_len(dh_ss) : 0, sm->pmk, &sm->pmk_len); os_memset(rmsk, 0, sizeof(rmsk)); + wpabuf_clear_free(dh_ss); + dh_ss = NULL; if (res) - return -1; + goto fail; if (!sm->fils_erp_pmkid_set) { wpa_printf(MSG_DEBUG, "FILS: PMKID not available"); - return -1; + goto fail; } wpa_hexdump(MSG_DEBUG, "FILS: PMKID", sm->fils_erp_pmkid, PMKID_LEN); @@ -3493,7 +3571,7 @@ int fils_process_auth(struct wpa_sm *sm, const u8 *bssid, const u8 *data, if (!sm->cur_pmksa) { wpa_printf(MSG_DEBUG, "FILS: No remaining options to continue FILS authentication"); - return -1; + goto fail; } if (fils_pmk_to_ptk(sm->pmk, sm->pmk_len, sm->own_addr, sm->bssid, @@ -3501,7 +3579,7 @@ int fils_process_auth(struct wpa_sm *sm, const u8 *bssid, const u8 *data, ick, &ick_len, sm->key_mgmt, sm->pairwise_cipher) < 0) { wpa_printf(MSG_DEBUG, "FILS: Failed to derive PTK"); - return -1; + goto fail; } sm->ptk_set = 1; sm->tptk_set = 0; @@ -3509,12 +3587,15 @@ int fils_process_auth(struct wpa_sm *sm, const u8 *bssid, const u8 *data, res = fils_key_auth_sk(ick, ick_len, sm->fils_nonce, sm->fils_anonce, sm->own_addr, sm->bssid, - NULL, 0, NULL, 0, /* TODO: SK+PFS */ + NULL, 0, NULL, 0, /* TODO: PK */ sm->key_mgmt, sm->fils_key_auth_sta, sm->fils_key_auth_ap, &sm->fils_key_auth_len); os_memset(ick, 0, sizeof(ick)); return res; +fail: + wpabuf_clear_free(dh_ss); + return -1; } diff --git a/src/rsn_supp/wpa.h b/src/rsn_supp/wpa.h index df774ade8..cd9f56dca 100644 --- a/src/rsn_supp/wpa.h +++ b/src/rsn_supp/wpa.h @@ -435,7 +435,7 @@ extern unsigned int tdls_testing; int wpa_wnmsleep_install_key(struct wpa_sm *sm, u8 subelem_id, u8 *buf); void wpa_sm_set_test_assoc_ie(struct wpa_sm *sm, struct wpabuf *buf); -struct wpabuf * fils_build_auth(struct wpa_sm *sm); +struct wpabuf * fils_build_auth(struct wpa_sm *sm, int dh_group); int fils_process_auth(struct wpa_sm *sm, const u8 *bssid, const u8 *data, size_t len); struct wpabuf * fils_build_assoc_req(struct wpa_sm *sm, const u8 **kek, diff --git a/src/rsn_supp/wpa_i.h b/src/rsn_supp/wpa_i.h index fe5311772..d1dccec59 100644 --- a/src/rsn_supp/wpa_i.h +++ b/src/rsn_supp/wpa_i.h @@ -151,6 +151,9 @@ struct wpa_sm { unsigned int fils_cache_id_set:1; u8 fils_erp_pmkid[PMKID_LEN]; u8 fils_cache_id[FILS_CACHE_ID_LEN]; + struct crypto_ecdh *fils_ecdh; + int fils_dh_group; + size_t fils_dh_elem_len; #endif /* CONFIG_FILS */ #ifdef CONFIG_OWE diff --git a/wpa_supplicant/Android.mk b/wpa_supplicant/Android.mk index bf6ad882d..79493e35f 100644 --- a/wpa_supplicant/Android.mk +++ b/wpa_supplicant/Android.mk @@ -253,6 +253,10 @@ ifdef CONFIG_FILS L_CFLAGS += -DCONFIG_FILS NEED_SHA384=y NEED_AES_SIV=y +ifdef CONFIG_FILS_SK_PFS +L_CFLAGS += -DCONFIG_FILS_SK_PFS +NEED_ECC=y +endif endif ifdef CONFIG_WNM diff --git a/wpa_supplicant/Makefile b/wpa_supplicant/Makefile index 968f7bc3a..95bb52d38 100644 --- a/wpa_supplicant/Makefile +++ b/wpa_supplicant/Makefile @@ -286,6 +286,10 @@ ifdef CONFIG_FILS CFLAGS += -DCONFIG_FILS NEED_SHA384=y NEED_AES_SIV=y +ifdef CONFIG_FILS_SK_PFS +CFLAGS += -DCONFIG_FILS_SK_PFS +NEED_ECC=y +endif endif ifdef CONFIG_WNM diff --git a/wpa_supplicant/config.c b/wpa_supplicant/config.c index f56b49bb8..8bfb6a182 100644 --- a/wpa_supplicant/config.c +++ b/wpa_supplicant/config.c @@ -2147,6 +2147,7 @@ static const struct parse_data ssid_fields[] = { { INT_RANGE(mac_addr, 0, 2) }, { INT_RANGE(pbss, 0, 2) }, { INT_RANGE(wps_disabled, 0, 1) }, + { INT_RANGE(fils_dh_group, 0, 65535) }, }; #undef OFFSET diff --git a/wpa_supplicant/config_file.c b/wpa_supplicant/config_file.c index 72e0a55eb..e8f11493e 100644 --- a/wpa_supplicant/config_file.c +++ b/wpa_supplicant/config_file.c @@ -800,6 +800,7 @@ static void wpa_config_write_network(FILE *f, struct wpa_ssid *ssid) INT(vht_center_freq2); INT(pbss); INT(wps_disabled); + INT(fils_dh_group); #ifdef CONFIG_IEEE80211W write_int(f, "ieee80211w", ssid->ieee80211w, MGMT_FRAME_PROTECTION_DEFAULT); diff --git a/wpa_supplicant/config_ssid.h b/wpa_supplicant/config_ssid.h index 908b6415d..869165c89 100644 --- a/wpa_supplicant/config_ssid.h +++ b/wpa_supplicant/config_ssid.h @@ -814,6 +814,14 @@ struct wpa_ssid { * 1 = WPS disabled */ int wps_disabled; + + /** + * fils_dh_group - FILS DH Group + * + * 0 = PFS disabled with FILS shared key authentication + * 1-65535 DH Group to use for FILS PFS + */ + int fils_dh_group; }; #endif /* CONFIG_SSID_H */ diff --git a/wpa_supplicant/defconfig b/wpa_supplicant/defconfig index 7cb86dd35..307f82d85 100644 --- a/wpa_supplicant/defconfig +++ b/wpa_supplicant/defconfig @@ -558,6 +558,8 @@ CONFIG_PEERKEY=y # Note: This is an experimental and not yet complete implementation. This # should not be enabled for production use. #CONFIG_FILS=y +# FILS shared key authentication with PFS +#CONFIG_FILS_SK_PFS=y # Support RSN on IBSS networks # This is needed to be able to use mode=1 network profile with proto=RSN and diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c index 82a8b11aa..4ef8e28fa 100644 --- a/wpa_supplicant/events.c +++ b/wpa_supplicant/events.c @@ -2214,7 +2214,8 @@ static int wpa_supplicant_event_associnfo(struct wpa_supplicant *wpa_s, #ifdef CONFIG_FILS #ifdef CONFIG_SME - if (wpa_s->sme.auth_alg == WPA_AUTH_ALG_FILS && + if ((wpa_s->sme.auth_alg == WPA_AUTH_ALG_FILS || + wpa_s->sme.auth_alg == WPA_AUTH_ALG_FILS_SK_PFS) && (!data->assoc_info.resp_frame || fils_process_assoc_resp(wpa_s->wpa, data->assoc_info.resp_frame, diff --git a/wpa_supplicant/sme.c b/wpa_supplicant/sme.c index 76d1acd00..5ab56c827 100644 --- a/wpa_supplicant/sme.c +++ b/wpa_supplicant/sme.c @@ -563,12 +563,23 @@ static void sme_send_authentication(struct wpa_supplicant *wpa_s, 0) wpa_printf(MSG_DEBUG, "SME: Try to use FILS with PMKSA caching"); - resp = fils_build_auth(wpa_s->wpa); + resp = fils_build_auth(wpa_s->wpa, ssid->fils_dh_group); if (resp) { - params.auth_alg = WPA_AUTH_ALG_FILS; + int auth_alg; + + if (ssid->fils_dh_group) + wpa_printf(MSG_DEBUG, + "SME: Try to use FILS SK authentication with PFS (DH Group %u)", + ssid->fils_dh_group); + else + wpa_printf(MSG_DEBUG, + "SME: Try to use FILS SK authentication without PFS"); + auth_alg = ssid->fils_dh_group ? + WPA_AUTH_ALG_FILS_SK_PFS : WPA_AUTH_ALG_FILS; + params.auth_alg = auth_alg; params.auth_data = wpabuf_head(resp); params.auth_data_len = wpabuf_len(resp); - wpa_s->sme.auth_alg = WPA_AUTH_ALG_FILS; + wpa_s->sme.auth_alg = auth_alg; } } #endif /* CONFIG_FILS */ @@ -968,7 +979,27 @@ void sme_event_auth(struct wpa_supplicant *wpa_s, union wpa_event_data *data) #endif /* CONFIG_IEEE80211R */ #ifdef CONFIG_FILS - if (data->auth.auth_type == WLAN_AUTH_FILS_SK) { + if (data->auth.auth_type == WLAN_AUTH_FILS_SK || + data->auth.auth_type == WLAN_AUTH_FILS_SK_PFS) { + u16 expect_auth_type; + + expect_auth_type = wpa_s->sme.auth_alg == + WPA_AUTH_ALG_FILS_SK_PFS ? WLAN_AUTH_FILS_SK_PFS : + WLAN_AUTH_FILS_SK; + if (data->auth.auth_type != expect_auth_type) { + wpa_dbg(wpa_s, MSG_DEBUG, + "SME: FILS Authentication response used different auth alg (%u; expected %u)", + data->auth.auth_type, expect_auth_type); + wpa_msg(wpa_s, MSG_INFO, WPA_EVENT_DISCONNECTED "bssid=" + MACSTR + " reason=%d locally_generated=1", + MAC2STR(wpa_s->pending_bssid), + WLAN_REASON_DEAUTH_LEAVING); + wpas_connection_failed(wpa_s, wpa_s->pending_bssid); + wpa_supplicant_mark_disassoc(wpa_s); + return; + } + if (fils_process_auth(wpa_s->wpa, wpa_s->pending_bssid, data->auth.ies, data->auth.ies_len) < 0) { wpa_dbg(wpa_s, MSG_DEBUG, @@ -1010,7 +1041,8 @@ void sme_associate(struct wpa_supplicant *wpa_s, enum wpas_mode mode, os_memset(¶ms, 0, sizeof(params)); #ifdef CONFIG_FILS - if (auth_type == WLAN_AUTH_FILS_SK) { + if (auth_type == WLAN_AUTH_FILS_SK || + auth_type == WLAN_AUTH_FILS_SK_PFS) { struct wpabuf *buf; const u8 *snonce, *anonce; const unsigned int max_hlp = 20; diff --git a/wpa_supplicant/wpa_supplicant.conf b/wpa_supplicant/wpa_supplicant.conf index 8608681ee..6dac20e20 100644 --- a/wpa_supplicant/wpa_supplicant.conf +++ b/wpa_supplicant/wpa_supplicant.conf @@ -1248,6 +1248,11 @@ fast_reauth=1 # 1 = WPS disabled #wps_disabled=0 +# FILS DH Group +# 0 = PFS disabled with FILS shared key authentication (default) +# 1-65535 = DH Group to use for FILS PFS +#fils_dh_group=0 + # MAC address policy # 0 = use permanent MAC address # 1 = use random MAC address for each ESS connection