From 9be19d0b9c4e4948e70fbfeb9076d30af9d0071f Mon Sep 17 00:00:00 2001 From: Jouni Malinen Date: Sat, 19 May 2018 17:28:01 +0300 Subject: [PATCH] SAE: Add support for using the optional Password Identifier This extends the SAE implementation in both infrastructure and mesh BSS cases to allow an optional Password Identifier to be used. This uses the mechanism added in P802.11REVmd/D1.0. The Password Identifier is configured in a wpa_supplicant network profile as a new string parameter sae_password_id. In hostapd configuration, the existing sae_password parameter has been extended to allow the password identifier (and also a peer MAC address) to be set. In addition, multiple sae_password entries can now be provided to hostapd to allow multiple per-peer and per-identifier passwords to be set. Signed-off-by: Jouni Malinen --- hostapd/config_file.c | 64 ++++++++- hostapd/hostapd.conf | 23 ++- src/ap/ap_config.c | 18 ++- src/ap/ap_config.h | 8 +- src/ap/ieee802_11.c | 40 +++++- src/common/ieee802_11_common.c | 4 + src/common/ieee802_11_common.h | 2 + src/common/ieee802_11_defs.h | 2 + src/common/sae.c | 218 ++++++++++++++++++++++------- src/common/sae.h | 5 +- src/common/wpa_ctrl.h | 3 + wpa_supplicant/config.c | 2 + wpa_supplicant/config_file.c | 1 + wpa_supplicant/config_ssid.h | 8 ++ wpa_supplicant/config_winreg.c | 1 + wpa_supplicant/mesh_rsn.c | 6 + wpa_supplicant/sme.c | 16 ++- wpa_supplicant/wpa_supplicant.conf | 5 + 18 files changed, 365 insertions(+), 61 deletions(-) diff --git a/hostapd/config_file.c b/hostapd/config_file.c index 151b9fc5c..502ea3d94 100644 --- a/hostapd/config_file.c +++ b/hostapd/config_file.c @@ -2169,6 +2169,61 @@ static unsigned int parse_tls_flags(const char *val) #endif /* EAP_SERVER */ +#ifdef CONFIG_SAE +static int parse_sae_password(struct hostapd_bss_config *bss, const char *val) +{ + struct sae_password_entry *pw; + const char *pos = val, *pos2, *end = NULL; + + pw = os_zalloc(sizeof(*pw)); + if (!pw) + return -1; + os_memset(pw->peer_addr, 0xff, ETH_ALEN); /* default to wildcard */ + + pos2 = os_strstr(pos, "|mac="); + if (pos2) { + end = pos2; + pos2 += 5; + if (hwaddr_aton(pos2, pw->peer_addr) < 0) + goto fail; + pos = pos2 + ETH_ALEN * 3 - 1; + } + + pos2 = os_strstr(pos, "|id="); + if (pos2) { + if (!end) + end = pos2; + pos2 += 4; + pw->identifier = os_strdup(pos2); + if (!pw->identifier) + goto fail; + } + + if (!end) { + pw->password = os_strdup(val); + if (!pw->password) + goto fail; + } else { + pw->password = os_malloc(end - val + 1); + if (!pw->password) + goto fail; + os_memcpy(pw->password, val, end - val); + pw->password[end - val] = '\0'; + } + + pw->next = bss->sae_passwords; + bss->sae_passwords = pw; + + return 0; +fail: + str_clear_free(pw->password); + os_free(pw->identifier); + os_free(pw); + return -1; +} +#endif /* CONFIG_SAE */ + + static int hostapd_config_fill(struct hostapd_config *conf, struct hostapd_bss_config *bss, const char *buf, char *pos, int line) @@ -3727,9 +3782,14 @@ static int hostapd_config_fill(struct hostapd_config *conf, } else if (os_strcmp(buf, "sae_commit_override") == 0) { wpabuf_free(bss->sae_commit_override); bss->sae_commit_override = wpabuf_parse_bin(pos); +#ifdef CONFIG_SAE } else if (os_strcmp(buf, "sae_password") == 0) { - os_free(bss->sae_password); - bss->sae_password = os_strdup(pos); + if (parse_sae_password(bss, pos) < 0) { + wpa_printf(MSG_ERROR, "Line %d: Invalid sae_password", + line); + return 1; + } +#endif /* CONFIG_SAE */ #endif /* CONFIG_TESTING_OPTIONS */ } else if (os_strcmp(buf, "vendor_elements") == 0) { if (parse_wpabuf_hex(line, buf, &bss->vendor_elements, pos)) diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf index b5a271866..64dd8c28b 100644 --- a/hostapd/hostapd.conf +++ b/hostapd/hostapd.conf @@ -1416,13 +1416,32 @@ own_ip_addr=127.0.0.1 #okc=1 # SAE password -# This parameter can be used to set a password for SAE. By default, the +# This parameter can be used to set passwords for SAE. By default, the # wpa_passphrase value is used if this separate parameter is not used, but # wpa_passphrase follows the WPA-PSK constraints (8..63 characters) even though # SAE passwords do not have such constraints. If the BSS enabled both SAE and -# WPA-PSK and both values are set, SAE uses the sae_password value and WPA-PSK +# WPA-PSK and both values are set, SAE uses the sae_password values and WPA-PSK # uses the wpa_passphrase value. +# +# Each sae_password entry is added to a list of available passwords. This +# corresponds to the dot11RSNAConfigPasswordValueEntry. sae_password value +# starts with the password (dot11RSNAConfigPasswordCredential). That value can +# be followed by optional peer MAC address (dot11RSNAConfigPasswordPeerMac) and +# by optional password identifier (dot11RSNAConfigPasswordIdentifier). If the +# peer MAC address is not included or is set to the wildcard address +# (ff:ff:ff:ff:ff:ff), the entry is available for any station to use. If a +# specific peer MAC address is included, only a station with that MAC address +# is allowed to use the entry. If the password identifier (with non-zero length) +# is included, the entry is limited to be used only with that specified +# identifier. The last matching (based on peer MAC address and identifier) entry +# is used to select which password to use. Setting sae_password to an empty +# string has a special meaning of removing all previously added entries. +# sae_password uses the following encoding: +#[|mac=][|id=] +# Examples: #sae_password=secret +#sae_password=really secret|mac=ff:ff:ff:ff:ff:ff +#sae_password=example secret|mac=02:03:04:05:06:07|id=pw identifier # SAE threshold for anti-clogging mechanism (dot11RSNASAEAntiCloggingThreshold) # This parameter defines how many open SAE instances can be in progress at the diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c index 056a79035..1c28d662a 100644 --- a/src/ap/ap_config.c +++ b/src/ap/ap_config.c @@ -481,6 +481,22 @@ static void hostapd_config_free_fils_realms(struct hostapd_bss_config *conf) } +static void hostapd_config_free_sae_passwords(struct hostapd_bss_config *conf) +{ + struct sae_password_entry *pw, *tmp; + + pw = conf->sae_passwords; + conf->sae_passwords = NULL; + while (pw) { + tmp = pw; + pw = pw->next; + str_clear_free(tmp->password); + os_free(tmp->identifier); + os_free(tmp); + } +} + + void hostapd_config_free_bss(struct hostapd_bss_config *conf) { if (conf == NULL) @@ -658,7 +674,7 @@ void hostapd_config_free_bss(struct hostapd_bss_config *conf) wpabuf_free(conf->dpp_csign); #endif /* CONFIG_DPP */ - os_free(conf->sae_password); + hostapd_config_free_sae_passwords(conf); os_free(conf); } diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h index bc6489a84..7d47cb5ac 100644 --- a/src/ap/ap_config.h +++ b/src/ap/ap_config.h @@ -237,6 +237,12 @@ struct fils_realm { char realm[]; }; +struct sae_password_entry { + struct sae_password_entry *next; + char *password; + char *identifier; + u8 peer_addr[ETH_ALEN]; +}; /** * struct hostapd_bss_config - Per-BSS configuration @@ -604,7 +610,7 @@ struct hostapd_bss_config { unsigned int sae_sync; int sae_require_mfp; int *sae_groups; - char *sae_password; + struct sae_password_entry *sae_passwords; char *wowlan_triggers; /* Wake-on-WLAN triggers */ diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c index 68c0da24d..9f6d24420 100644 --- a/src/ap/ieee802_11.c +++ b/src/ap/ieee802_11.c @@ -369,9 +369,25 @@ static struct wpabuf * auth_build_sae_commit(struct hostapd_data *hapd, struct sta_info *sta, int update) { struct wpabuf *buf; - const char *password; + const char *password = NULL; + struct sae_password_entry *pw; + const char *rx_id = NULL; - password = hapd->conf->sae_password; + if (sta->sae->tmp) + rx_id = sta->sae->tmp->pw_id; + + for (pw = hapd->conf->sae_passwords; pw; pw = pw->next) { + if (!is_broadcast_ether_addr(pw->peer_addr) && + os_memcmp(pw->peer_addr, sta->addr, ETH_ALEN) != 0) + continue; + if ((rx_id && !pw->identifier) || (!rx_id && pw->identifier)) + continue; + if (rx_id && pw->identifier && + os_strcmp(rx_id, pw->identifier) != 0) + continue; + password = pw->password; + break; + } if (!password) password = hapd->conf->ssid.wpa_passphrase; if (!password) { @@ -381,17 +397,18 @@ static struct wpabuf * auth_build_sae_commit(struct hostapd_data *hapd, if (update && sae_prepare_commit(hapd->own_addr, sta->addr, - (u8 *) password, os_strlen(password), + (u8 *) password, os_strlen(password), rx_id, sta->sae) < 0) { wpa_printf(MSG_DEBUG, "SAE: Could not pick PWE"); return NULL; } - buf = wpabuf_alloc(SAE_COMMIT_MAX_LEN); + buf = wpabuf_alloc(SAE_COMMIT_MAX_LEN + + (rx_id ? 3 + os_strlen(rx_id) : 0)); if (buf == NULL) return NULL; sae_write_commit(sta->sae, buf, sta->sae->tmp ? - sta->sae->tmp->anti_clogging_token : NULL); + sta->sae->tmp->anti_clogging_token : NULL, rx_id); return buf; } @@ -420,6 +437,8 @@ static int auth_sae_send_commit(struct hostapd_data *hapd, int reply_res; data = auth_build_sae_commit(hapd, sta, update); + if (!data && sta->sae->tmp && sta->sae->tmp->pw_id) + return WLAN_STATUS_UNKNOWN_PASSWORD_IDENTIFIER; if (data == NULL) return WLAN_STATUS_UNSPECIFIED_FAILURE; @@ -932,6 +951,17 @@ static void handle_auth_sae(struct hostapd_data *hapd, struct sta_info *sta, MAC2STR(sta->addr)); goto remove_sta; } + + if (resp == WLAN_STATUS_UNKNOWN_PASSWORD_IDENTIFIER) { + wpa_msg(hapd->msg_ctx, MSG_INFO, + WPA_EVENT_SAE_UNKNOWN_PASSWORD_IDENTIFIER + MACSTR, MAC2STR(sta->addr)); + sae_clear_retransmit_timer(hapd, sta); + sae_set_state(sta, SAE_NOTHING, + "Unknown Password Identifier"); + goto remove_sta; + } + if (token && check_sae_token(hapd, sta->addr, token, token_len) < 0) { wpa_printf(MSG_DEBUG, "SAE: Drop commit message with " diff --git a/src/common/ieee802_11_common.c b/src/common/ieee802_11_common.c index dcae84ce0..e1ef27795 100644 --- a/src/common/ieee802_11_common.c +++ b/src/common/ieee802_11_common.c @@ -262,6 +262,10 @@ static int ieee802_11_parse_extension(const u8 *pos, size_t elen, elems->owe_dh = pos; elems->owe_dh_len = elen; break; + case WLAN_EID_EXT_PASSWORD_IDENTIFIER: + elems->password_id = pos; + elems->password_id_len = elen; + break; default: if (show_errors) { wpa_printf(MSG_MSGDUMP, diff --git a/src/common/ieee802_11_common.h b/src/common/ieee802_11_common.h index 5b0893bba..ff7e51de3 100644 --- a/src/common/ieee802_11_common.h +++ b/src/common/ieee802_11_common.h @@ -83,6 +83,7 @@ struct ieee802_11_elems { const u8 *owe_dh; const u8 *power_capab; const u8 *roaming_cons_sel; + const u8 *password_id; u8 ssid_len; u8 supp_rates_len; @@ -128,6 +129,7 @@ struct ieee802_11_elems { u8 owe_dh_len; u8 power_capab_len; u8 roaming_cons_sel_len; + u8 password_id_len; struct mb_ies_info mb_ies; }; diff --git a/src/common/ieee802_11_defs.h b/src/common/ieee802_11_defs.h index ff2912518..7e3d8dbc1 100644 --- a/src/common/ieee802_11_defs.h +++ b/src/common/ieee802_11_defs.h @@ -203,6 +203,7 @@ #define WLAN_STATUS_AUTHORIZATION_DEENABLED 107 #define WLAN_STATUS_FILS_AUTHENTICATION_FAILURE 112 #define WLAN_STATUS_UNKNOWN_AUTHENTICATION_SERVER 113 +#define WLAN_STATUS_UNKNOWN_PASSWORD_IDENTIFIER 123 /* Reason codes (IEEE Std 802.11-2016, 9.4.1.7, Table 9-45) */ #define WLAN_REASON_UNSPECIFIED 1 @@ -463,6 +464,7 @@ #define WLAN_EID_EXT_FILS_NONCE 13 #define WLAN_EID_EXT_FUTURE_CHANNEL_GUIDANCE 14 #define WLAN_EID_EXT_OWE_DH_PARAM 32 +#define WLAN_EID_EXT_PASSWORD_IDENTIFIER 33 #define WLAN_EID_EXT_HE_CAPABILITIES 35 #define WLAN_EID_EXT_HE_OPERATION 36 diff --git a/src/common/sae.c b/src/common/sae.c index fa28dfb1d..981e788dc 100644 --- a/src/common/sae.c +++ b/src/common/sae.c @@ -94,6 +94,7 @@ void sae_clear_temp_data(struct sae_data *sae) crypto_ec_point_deinit(tmp->own_commit_element_ecc, 0); crypto_ec_point_deinit(tmp->peer_commit_element_ecc, 0); wpabuf_free(tmp->anti_clogging_token); + os_free(tmp->pw_id); bin_clear_free(tmp, sizeof(*tmp)); sae->tmp = NULL; } @@ -423,12 +424,13 @@ static int get_random_qr_qnr(const u8 *prime, size_t prime_len, static int sae_derive_pwe_ecc(struct sae_data *sae, const u8 *addr1, const u8 *addr2, const u8 *password, - size_t password_len) + size_t password_len, const char *identifier) { u8 counter, k = 40; u8 addrs[2 * ETH_ALEN]; - const u8 *addr[2]; - size_t len[2]; + const u8 *addr[3]; + size_t len[3]; + size_t num_elem; u8 dummy_password[32]; size_t dummy_password_len; int pwd_seed_odd = 0; @@ -460,10 +462,13 @@ static int sae_derive_pwe_ecc(struct sae_data *sae, const u8 *addr1, wpa_hexdump_ascii_key(MSG_DEBUG, "SAE: password", password, password_len); + if (identifier) + wpa_printf(MSG_DEBUG, "SAE: password identifier: %s", + identifier); /* * H(salt, ikm) = HMAC-SHA256(salt, ikm) - * base = password + * base = password [|| identifier] * pwd-seed = H(MAX(STA-A-MAC, STA-B-MAC) || MIN(STA-A-MAC, STA-B-MAC), * base || counter) */ @@ -471,8 +476,15 @@ static int sae_derive_pwe_ecc(struct sae_data *sae, const u8 *addr1, addr[0] = password; len[0] = password_len; - addr[1] = &counter; - len[1] = sizeof(counter); + num_elem = 1; + if (identifier) { + addr[num_elem] = (const u8 *) identifier; + len[num_elem] = os_strlen(identifier); + num_elem++; + } + addr[num_elem] = &counter; + len[num_elem] = sizeof(counter); + num_elem++; /* * Continue for at least k iterations to protect against side-channel @@ -490,8 +502,8 @@ static int sae_derive_pwe_ecc(struct sae_data *sae, const u8 *addr1, } wpa_printf(MSG_DEBUG, "SAE: counter = %u", counter); - if (hmac_sha256_vector(addrs, sizeof(addrs), 2, addr, len, - pwd_seed) < 0) + if (hmac_sha256_vector(addrs, sizeof(addrs), num_elem, + addr, len, pwd_seed) < 0) break; res = sae_test_pwd_seed_ecc(sae, pwd_seed, @@ -550,12 +562,13 @@ fail: static int sae_derive_pwe_ffc(struct sae_data *sae, const u8 *addr1, const u8 *addr2, const u8 *password, - size_t password_len) + size_t password_len, const char *identifier) { u8 counter; u8 addrs[2 * ETH_ALEN]; - const u8 *addr[2]; - size_t len[2]; + const u8 *addr[3]; + size_t len[3]; + size_t num_elem; int found = 0; if (sae->tmp->pwe_ffc == NULL) { @@ -570,14 +583,21 @@ static int sae_derive_pwe_ffc(struct sae_data *sae, const u8 *addr1, /* * H(salt, ikm) = HMAC-SHA256(salt, ikm) * pwd-seed = H(MAX(STA-A-MAC, STA-B-MAC) || MIN(STA-A-MAC, STA-B-MAC), - * password || counter) + * password [|| identifier] || counter) */ sae_pwd_seed_key(addr1, addr2, addrs); addr[0] = password; len[0] = password_len; - addr[1] = &counter; - len[1] = sizeof(counter); + num_elem = 1; + if (identifier) { + addr[num_elem] = (const u8 *) identifier; + len[num_elem] = os_strlen(identifier); + num_elem++; + } + addr[num_elem] = &counter; + len[num_elem] = sizeof(counter); + num_elem++; for (counter = 1; !found; counter++) { u8 pwd_seed[SHA256_MAC_LEN]; @@ -590,8 +610,8 @@ static int sae_derive_pwe_ffc(struct sae_data *sae, const u8 *addr1, } wpa_printf(MSG_DEBUG, "SAE: counter = %u", counter); - if (hmac_sha256_vector(addrs, sizeof(addrs), 2, addr, len, - pwd_seed) < 0) + if (hmac_sha256_vector(addrs, sizeof(addrs), num_elem, + addr, len, pwd_seed) < 0) break; res = sae_test_pwd_seed_ffc(sae, pwd_seed, sae->tmp->pwe_ffc); if (res < 0) @@ -702,13 +722,15 @@ fail: int sae_prepare_commit(const u8 *addr1, const u8 *addr2, const u8 *password, size_t password_len, - struct sae_data *sae) + const char *identifier, struct sae_data *sae) { if (sae->tmp == NULL || (sae->tmp->ec && sae_derive_pwe_ecc(sae, addr1, addr2, password, - password_len) < 0) || + password_len, + identifier) < 0) || (sae->tmp->dh && sae_derive_pwe_ffc(sae, addr1, addr2, password, - password_len) < 0) || + password_len, + identifier) < 0) || sae_derive_commit(sae) < 0) return -1; return 0; @@ -848,7 +870,7 @@ int sae_process_commit(struct sae_data *sae) void sae_write_commit(struct sae_data *sae, struct wpabuf *buf, - const struct wpabuf *token) + const struct wpabuf *token, const char *identifier) { u8 *pos; @@ -882,6 +904,16 @@ void sae_write_commit(struct sae_data *sae, struct wpabuf *buf, wpa_hexdump(MSG_DEBUG, "SAE: own commit-element", pos, sae->tmp->prime_len); } + + if (identifier) { + /* Password Identifier element */ + wpabuf_put_u8(buf, WLAN_EID_EXTENSION); + wpabuf_put_u8(buf, 1 + os_strlen(identifier)); + wpabuf_put_u8(buf, WLAN_EID_EXT_PASSWORD_IDENTIFIER); + wpabuf_put_str(buf, identifier); + wpa_printf(MSG_DEBUG, "SAE: own Password Identifier: %s", + identifier); + } } @@ -927,25 +959,70 @@ u16 sae_group_allowed(struct sae_data *sae, int *allowed_groups, u16 group) } +static int sae_is_password_id_elem(const u8 *pos, const u8 *end) +{ + return end - pos >= 3 && + pos[0] == WLAN_EID_EXTENSION && + pos[1] >= 1 && + end - pos - 2 >= pos[1] && + pos[2] == WLAN_EID_EXT_PASSWORD_IDENTIFIER; +} + + static void sae_parse_commit_token(struct sae_data *sae, const u8 **pos, const u8 *end, const u8 **token, size_t *token_len) { - if ((sae->tmp->ec ? 3 : 2) * sae->tmp->prime_len < end - *pos) { - size_t tlen = end - (*pos + (sae->tmp->ec ? 3 : 2) * - sae->tmp->prime_len); - wpa_hexdump(MSG_DEBUG, "SAE: Anti-Clogging Token", *pos, tlen); - if (token) - *token = *pos; - if (token_len) - *token_len = tlen; - *pos += tlen; - } else { - if (token) - *token = NULL; - if (token_len) - *token_len = 0; + size_t scalar_elem_len, tlen; + const u8 *elem; + + if (token) + *token = NULL; + if (token_len) + *token_len = 0; + + scalar_elem_len = (sae->tmp->ec ? 3 : 2) * sae->tmp->prime_len; + if (scalar_elem_len >= (size_t) (end - *pos)) + return; /* No extra data beyond peer scalar and element */ + + /* It is a bit difficult to parse this now that there is an + * optional variable length Anti-Clogging Token field and + * optional variable length Password Identifier element in the + * frame. We are sending out fixed length Anti-Clogging Token + * fields, so use that length as a requirement for the received + * token and check for the presence of possible Password + * Identifier element based on the element header information. + */ + tlen = end - (*pos + scalar_elem_len); + + if (tlen < SHA256_MAC_LEN) { + wpa_printf(MSG_DEBUG, + "SAE: Too short optional data (%u octets) to include our Anti-Clogging Token", + (unsigned int) tlen); + return; + } + + elem = *pos + scalar_elem_len; + if (sae_is_password_id_elem(elem, end)) { + /* Password Identifier element takes out all available + * extra octets, so there can be no Anti-Clogging token in + * this frame. */ + return; } + + elem += SHA256_MAC_LEN; + if (sae_is_password_id_elem(elem, end)) { + /* Password Identifier element is included in the end, so + * remove its length from the Anti-Clogging token field. */ + tlen -= 2 + elem[1]; + } + + wpa_hexdump(MSG_DEBUG, "SAE: Anti-Clogging Token", *pos, tlen); + if (token) + *token = *pos; + if (token_len) + *token_len = tlen; + *pos += tlen; } @@ -997,12 +1074,12 @@ static u16 sae_parse_commit_scalar(struct sae_data *sae, const u8 **pos, } -static u16 sae_parse_commit_element_ecc(struct sae_data *sae, const u8 *pos, +static u16 sae_parse_commit_element_ecc(struct sae_data *sae, const u8 **pos, const u8 *end) { u8 prime[SAE_MAX_ECC_PRIME_LEN]; - if (2 * sae->tmp->prime_len > end - pos) { + if (2 * sae->tmp->prime_len > end - *pos) { wpa_printf(MSG_DEBUG, "SAE: Not enough data for " "commit-element"); return WLAN_STATUS_UNSPECIFIED_FAILURE; @@ -1013,8 +1090,8 @@ static u16 sae_parse_commit_element_ecc(struct sae_data *sae, const u8 *pos, return WLAN_STATUS_UNSPECIFIED_FAILURE; /* element x and y coordinates < p */ - if (os_memcmp(pos, prime, sae->tmp->prime_len) >= 0 || - os_memcmp(pos + sae->tmp->prime_len, prime, + if (os_memcmp(*pos, prime, sae->tmp->prime_len) >= 0 || + os_memcmp(*pos + sae->tmp->prime_len, prime, sae->tmp->prime_len) >= 0) { wpa_printf(MSG_DEBUG, "SAE: Invalid coordinates in peer " "element"); @@ -1022,13 +1099,13 @@ static u16 sae_parse_commit_element_ecc(struct sae_data *sae, const u8 *pos, } wpa_hexdump(MSG_DEBUG, "SAE: Peer commit-element(x)", - pos, sae->tmp->prime_len); + *pos, sae->tmp->prime_len); wpa_hexdump(MSG_DEBUG, "SAE: Peer commit-element(y)", - pos + sae->tmp->prime_len, sae->tmp->prime_len); + *pos + sae->tmp->prime_len, sae->tmp->prime_len); crypto_ec_point_deinit(sae->tmp->peer_commit_element_ecc, 0); sae->tmp->peer_commit_element_ecc = - crypto_ec_point_from_bin(sae->tmp->ec, pos); + crypto_ec_point_from_bin(sae->tmp->ec, *pos); if (sae->tmp->peer_commit_element_ecc == NULL) return WLAN_STATUS_UNSPECIFIED_FAILURE; @@ -1038,27 +1115,29 @@ static u16 sae_parse_commit_element_ecc(struct sae_data *sae, const u8 *pos, return WLAN_STATUS_UNSPECIFIED_FAILURE; } + *pos += 2 * sae->tmp->prime_len; + return WLAN_STATUS_SUCCESS; } -static u16 sae_parse_commit_element_ffc(struct sae_data *sae, const u8 *pos, +static u16 sae_parse_commit_element_ffc(struct sae_data *sae, const u8 **pos, const u8 *end) { struct crypto_bignum *res, *one; const u8 one_bin[1] = { 0x01 }; - if (sae->tmp->prime_len > end - pos) { + if (sae->tmp->prime_len > end - *pos) { wpa_printf(MSG_DEBUG, "SAE: Not enough data for " "commit-element"); return WLAN_STATUS_UNSPECIFIED_FAILURE; } - wpa_hexdump(MSG_DEBUG, "SAE: Peer commit-element", pos, + wpa_hexdump(MSG_DEBUG, "SAE: Peer commit-element", *pos, sae->tmp->prime_len); crypto_bignum_deinit(sae->tmp->peer_commit_element_ffc, 0); sae->tmp->peer_commit_element_ffc = - crypto_bignum_init_set(pos, sae->tmp->prime_len); + crypto_bignum_init_set(*pos, sae->tmp->prime_len); if (sae->tmp->peer_commit_element_ffc == NULL) return WLAN_STATUS_UNSPECIFIED_FAILURE; /* 1 < element < p - 1 */ @@ -1086,11 +1165,13 @@ static u16 sae_parse_commit_element_ffc(struct sae_data *sae, const u8 *pos, } crypto_bignum_deinit(res, 0); + *pos += sae->tmp->prime_len; + return WLAN_STATUS_SUCCESS; } -static u16 sae_parse_commit_element(struct sae_data *sae, const u8 *pos, +static u16 sae_parse_commit_element(struct sae_data *sae, const u8 **pos, const u8 *end) { if (sae->tmp->dh) @@ -1099,6 +1180,44 @@ static u16 sae_parse_commit_element(struct sae_data *sae, const u8 *pos, } +static int sae_parse_password_identifier(struct sae_data *sae, + const u8 *pos, const u8 *end) +{ + wpa_hexdump(MSG_DEBUG, "SAE: Possible elements at the end of the frame", + pos, end - pos); + if (!sae_is_password_id_elem(pos, end)) { + if (sae->tmp->pw_id) { + wpa_printf(MSG_DEBUG, + "SAE: No Password Identifier included, but expected one (%s)", + sae->tmp->pw_id); + return WLAN_STATUS_UNKNOWN_PASSWORD_IDENTIFIER; + } + os_free(sae->tmp->pw_id); + sae->tmp->pw_id = NULL; + return WLAN_STATUS_SUCCESS; /* No Password Identifier */ + } + + if (sae->tmp->pw_id && + (pos[1] - 1 != (int) os_strlen(sae->tmp->pw_id) || + os_memcmp(sae->tmp->pw_id, pos + 3, pos[1] - 1) != 0)) { + wpa_printf(MSG_DEBUG, + "SAE: The included Password Identifier does not match the expected one (%s)", + sae->tmp->pw_id); + return WLAN_STATUS_UNKNOWN_PASSWORD_IDENTIFIER; + } + + os_free(sae->tmp->pw_id); + sae->tmp->pw_id = os_malloc(pos[1]); + if (!sae->tmp->pw_id) + return WLAN_STATUS_UNSPECIFIED_FAILURE; + os_memcpy(sae->tmp->pw_id, pos + 3, pos[1] - 1); + sae->tmp->pw_id[pos[1] - 1] = '\0'; + wpa_hexdump_ascii(MSG_DEBUG, "SAE: Received Password Identifier", + sae->tmp->pw_id, pos[1] - 1); + return WLAN_STATUS_SUCCESS; +} + + u16 sae_parse_commit(struct sae_data *sae, const u8 *data, size_t len, const u8 **token, size_t *token_len, int *allowed_groups) { @@ -1122,7 +1241,12 @@ u16 sae_parse_commit(struct sae_data *sae, const u8 *data, size_t len, return res; /* commit-element */ - res = sae_parse_commit_element(sae, pos, end); + res = sae_parse_commit_element(sae, &pos, end); + if (res != WLAN_STATUS_SUCCESS) + return res; + + /* Optional Password Identifier element */ + res = sae_parse_password_identifier(sae, pos, end); if (res != WLAN_STATUS_SUCCESS) return res; diff --git a/src/common/sae.h b/src/common/sae.h index 7c07bfba2..3fbcb58d1 100644 --- a/src/common/sae.h +++ b/src/common/sae.h @@ -39,6 +39,7 @@ struct sae_temporary_data { struct crypto_bignum *prime_buf; struct crypto_bignum *order_buf; struct wpabuf *anti_clogging_token; + char *pw_id; }; enum sae_state { @@ -63,10 +64,10 @@ void sae_clear_data(struct sae_data *sae); int sae_prepare_commit(const u8 *addr1, const u8 *addr2, const u8 *password, size_t password_len, - struct sae_data *sae); + const char *identifier, struct sae_data *sae); int sae_process_commit(struct sae_data *sae); void sae_write_commit(struct sae_data *sae, struct wpabuf *buf, - const struct wpabuf *token); + const struct wpabuf *token, const char *identifier); u16 sae_parse_commit(struct sae_data *sae, const u8 *data, size_t len, const u8 **token, size_t *token_len, int *allowed_groups); void sae_write_confirm(struct sae_data *sae, struct wpabuf *buf); diff --git a/src/common/wpa_ctrl.h b/src/common/wpa_ctrl.h index c7a7c1e27..4eb7356fb 100644 --- a/src/common/wpa_ctrl.h +++ b/src/common/wpa_ctrl.h @@ -89,6 +89,9 @@ extern "C" { #define WPA_EVENT_REGDOM_CHANGE "CTRL-EVENT-REGDOM-CHANGE " /** Channel switch (followed by freq= and other channel parameters) */ #define WPA_EVENT_CHANNEL_SWITCH "CTRL-EVENT-CHANNEL-SWITCH " +/** SAE authentication failed due to unknown password identifier */ +#define WPA_EVENT_SAE_UNKNOWN_PASSWORD_IDENTIFIER \ + "CTRL-EVENT-SAE-UNKNOWN-PASSWORD-IDENTIFIER " /** IP subnet status change notification * diff --git a/wpa_supplicant/config.c b/wpa_supplicant/config.c index f65bbb02f..8ec60629b 100644 --- a/wpa_supplicant/config.c +++ b/wpa_supplicant/config.c @@ -2134,6 +2134,7 @@ static const struct parse_data ssid_fields[] = { { FUNC_KEY(psk) }, { INT(mem_only_psk) }, { STR_KEY(sae_password) }, + { STR(sae_password_id) }, { FUNC(proto) }, { FUNC(key_mgmt) }, { INT(bg_scan_period) }, @@ -2474,6 +2475,7 @@ void wpa_config_free_ssid(struct wpa_ssid *ssid) str_clear_free(ssid->passphrase); os_free(ssid->ext_psk); str_clear_free(ssid->sae_password); + os_free(ssid->sae_password_id); #ifdef IEEE8021X_EAPOL eap_peer_config_free(&ssid->eap); #endif /* IEEE8021X_EAPOL */ diff --git a/wpa_supplicant/config_file.c b/wpa_supplicant/config_file.c index d186f78eb..aa73f9df6 100644 --- a/wpa_supplicant/config_file.c +++ b/wpa_supplicant/config_file.c @@ -749,6 +749,7 @@ static void wpa_config_write_network(FILE *f, struct wpa_ssid *ssid) write_psk(f, ssid); INT(mem_only_psk); STR(sae_password); + STR(sae_password_id); write_proto(f, ssid); write_key_mgmt(f, ssid); INT_DEF(bg_scan_period, DEFAULT_BG_SCAN_PERIOD); diff --git a/wpa_supplicant/config_ssid.h b/wpa_supplicant/config_ssid.h index 9fd56c32f..5b872fac0 100644 --- a/wpa_supplicant/config_ssid.h +++ b/wpa_supplicant/config_ssid.h @@ -193,6 +193,14 @@ struct wpa_ssid { */ char *sae_password; + /** + * sae_password_id - SAE password identifier + * + * This parameter can be used to identify a specific SAE password. If + * not included, the default SAE password is used instead. + */ + char *sae_password_id; + /** * ext_psk - PSK/passphrase name in external storage * diff --git a/wpa_supplicant/config_winreg.c b/wpa_supplicant/config_winreg.c index 8b8992bd7..0ce1830b4 100644 --- a/wpa_supplicant/config_winreg.c +++ b/wpa_supplicant/config_winreg.c @@ -873,6 +873,7 @@ static int wpa_config_write_network(HKEY hk, struct wpa_ssid *ssid, int id) write_bssid(netw, ssid); write_psk(netw, ssid); STR(sae_password); + STR(sae_password_id); write_proto(netw, ssid); write_key_mgmt(netw, ssid); write_pairwise(netw, ssid); diff --git a/wpa_supplicant/mesh_rsn.c b/wpa_supplicant/mesh_rsn.c index 25dcde5c6..e74cb16b0 100644 --- a/wpa_supplicant/mesh_rsn.c +++ b/wpa_supplicant/mesh_rsn.c @@ -332,8 +332,14 @@ static int mesh_rsn_build_sae_commit(struct wpa_supplicant *wpa_s, return -1; } + if (sta->sae->tmp && !sta->sae->tmp->pw_id && ssid->sae_password_id) { + sta->sae->tmp->pw_id = os_strdup(ssid->sae_password_id); + if (!sta->sae->tmp->pw_id) + return -1; + } return sae_prepare_commit(wpa_s->own_addr, sta->addr, (u8 *) password, os_strlen(password), + ssid->sae_password_id, sta->sae); } diff --git a/wpa_supplicant/sme.c b/wpa_supplicant/sme.c index 1348e1c29..d57195f15 100644 --- a/wpa_supplicant/sme.c +++ b/wpa_supplicant/sme.c @@ -117,12 +117,15 @@ static struct wpabuf * sme_auth_build_sae_commit(struct wpa_supplicant *wpa_s, if (sae_prepare_commit(wpa_s->own_addr, bssid, (u8 *) password, os_strlen(password), + ssid->sae_password_id, &wpa_s->sme.sae) < 0) { wpa_printf(MSG_DEBUG, "SAE: Could not pick PWE"); return NULL; } len = wpa_s->sme.sae_token ? wpabuf_len(wpa_s->sme.sae_token) : 0; + if (ssid->sae_password_id) + len += 4 + os_strlen(ssid->sae_password_id); buf = wpabuf_alloc(4 + SAE_COMMIT_MAX_LEN + len); if (buf == NULL) return NULL; @@ -130,7 +133,8 @@ static struct wpabuf * sme_auth_build_sae_commit(struct wpa_supplicant *wpa_s, wpabuf_put_le16(buf, 1); /* Transaction seq# */ wpabuf_put_le16(buf, WLAN_STATUS_SUCCESS); } - sae_write_commit(&wpa_s->sme.sae, buf, wpa_s->sme.sae_token); + sae_write_commit(&wpa_s->sme.sae, buf, wpa_s->sme.sae_token, + ssid->sae_password_id); return buf; } @@ -1005,6 +1009,16 @@ static int sme_sae_auth(struct wpa_supplicant *wpa_s, u16 auth_transaction, return 0; } + if (auth_transaction == 1 && + status_code == WLAN_STATUS_UNKNOWN_PASSWORD_IDENTIFIER) { + const u8 *bssid = sa ? sa : wpa_s->pending_bssid; + + wpa_msg(wpa_s, MSG_INFO, + WPA_EVENT_SAE_UNKNOWN_PASSWORD_IDENTIFIER MACSTR, + MAC2STR(bssid)); + return -1; + } + if (status_code != WLAN_STATUS_SUCCESS) return -1; diff --git a/wpa_supplicant/wpa_supplicant.conf b/wpa_supplicant/wpa_supplicant.conf index 892e73501..a235ea0bf 100644 --- a/wpa_supplicant/wpa_supplicant.conf +++ b/wpa_supplicant/wpa_supplicant.conf @@ -954,6 +954,11 @@ fast_reauth=1 # used, but psk follows the WPA-PSK constraints (8..63 characters) even though # SAE passwords do not have such constraints. # +# sae_password_id: SAE password identifier +# This parameter can be used to set an identifier for the SAE password. By +# default, no such identifier is used. If set, the specified identifier value +# is used by the other peer to select which password to use for authentication. +# # eapol_flags: IEEE 802.1X/EAPOL options (bit field) # Dynamic WEP key required for non-WPA mode # bit0 (1): require dynamically generated unicast WEP key -- 2.39.5