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