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 <jouni@codeaurora.org>
This commit is contained in:
Jouni Malinen 2018-05-19 17:28:01 +03:00 committed by Jouni Malinen
parent d6a65a83fb
commit 9be19d0b9c
18 changed files with 365 additions and 61 deletions

View file

@ -2169,6 +2169,61 @@ static unsigned int parse_tls_flags(const char *val)
#endif /* EAP_SERVER */ #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, static int hostapd_config_fill(struct hostapd_config *conf,
struct hostapd_bss_config *bss, struct hostapd_bss_config *bss,
const char *buf, char *pos, int line) 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) { } else if (os_strcmp(buf, "sae_commit_override") == 0) {
wpabuf_free(bss->sae_commit_override); wpabuf_free(bss->sae_commit_override);
bss->sae_commit_override = wpabuf_parse_bin(pos); bss->sae_commit_override = wpabuf_parse_bin(pos);
#ifdef CONFIG_SAE
} else if (os_strcmp(buf, "sae_password") == 0) { } else if (os_strcmp(buf, "sae_password") == 0) {
os_free(bss->sae_password); if (parse_sae_password(bss, pos) < 0) {
bss->sae_password = os_strdup(pos); wpa_printf(MSG_ERROR, "Line %d: Invalid sae_password",
line);
return 1;
}
#endif /* CONFIG_SAE */
#endif /* CONFIG_TESTING_OPTIONS */ #endif /* CONFIG_TESTING_OPTIONS */
} else if (os_strcmp(buf, "vendor_elements") == 0) { } else if (os_strcmp(buf, "vendor_elements") == 0) {
if (parse_wpabuf_hex(line, buf, &bss->vendor_elements, pos)) if (parse_wpabuf_hex(line, buf, &bss->vendor_elements, pos))

View file

@ -1416,13 +1416,32 @@ own_ip_addr=127.0.0.1
#okc=1 #okc=1
# SAE password # 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 value is used if this separate parameter is not used, but
# wpa_passphrase follows the WPA-PSK constraints (8..63 characters) even though # 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 # 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. # 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:
#<password/credential>[|mac=<peer mac>][|id=<identifier>]
# Examples:
#sae_password=secret #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) # SAE threshold for anti-clogging mechanism (dot11RSNASAEAntiCloggingThreshold)
# This parameter defines how many open SAE instances can be in progress at the # This parameter defines how many open SAE instances can be in progress at the

View file

@ -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) void hostapd_config_free_bss(struct hostapd_bss_config *conf)
{ {
if (conf == NULL) if (conf == NULL)
@ -658,7 +674,7 @@ void hostapd_config_free_bss(struct hostapd_bss_config *conf)
wpabuf_free(conf->dpp_csign); wpabuf_free(conf->dpp_csign);
#endif /* CONFIG_DPP */ #endif /* CONFIG_DPP */
os_free(conf->sae_password); hostapd_config_free_sae_passwords(conf);
os_free(conf); os_free(conf);
} }

View file

@ -237,6 +237,12 @@ struct fils_realm {
char 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 * struct hostapd_bss_config - Per-BSS configuration
@ -604,7 +610,7 @@ struct hostapd_bss_config {
unsigned int sae_sync; unsigned int sae_sync;
int sae_require_mfp; int sae_require_mfp;
int *sae_groups; int *sae_groups;
char *sae_password; struct sae_password_entry *sae_passwords;
char *wowlan_triggers; /* Wake-on-WLAN triggers */ char *wowlan_triggers; /* Wake-on-WLAN triggers */

View file

@ -369,9 +369,25 @@ static struct wpabuf * auth_build_sae_commit(struct hostapd_data *hapd,
struct sta_info *sta, int update) struct sta_info *sta, int update)
{ {
struct wpabuf *buf; 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) if (!password)
password = hapd->conf->ssid.wpa_passphrase; password = hapd->conf->ssid.wpa_passphrase;
if (!password) { if (!password) {
@ -381,17 +397,18 @@ static struct wpabuf * auth_build_sae_commit(struct hostapd_data *hapd,
if (update && if (update &&
sae_prepare_commit(hapd->own_addr, sta->addr, sae_prepare_commit(hapd->own_addr, sta->addr,
(u8 *) password, os_strlen(password), (u8 *) password, os_strlen(password), rx_id,
sta->sae) < 0) { sta->sae) < 0) {
wpa_printf(MSG_DEBUG, "SAE: Could not pick PWE"); wpa_printf(MSG_DEBUG, "SAE: Could not pick PWE");
return NULL; 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) if (buf == NULL)
return NULL; return NULL;
sae_write_commit(sta->sae, buf, sta->sae->tmp ? 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; return buf;
} }
@ -420,6 +437,8 @@ static int auth_sae_send_commit(struct hostapd_data *hapd,
int reply_res; int reply_res;
data = auth_build_sae_commit(hapd, sta, update); 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) if (data == NULL)
return WLAN_STATUS_UNSPECIFIED_FAILURE; 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)); MAC2STR(sta->addr));
goto remove_sta; 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) if (token && check_sae_token(hapd, sta->addr, token, token_len)
< 0) { < 0) {
wpa_printf(MSG_DEBUG, "SAE: Drop commit message with " wpa_printf(MSG_DEBUG, "SAE: Drop commit message with "

View file

@ -262,6 +262,10 @@ static int ieee802_11_parse_extension(const u8 *pos, size_t elen,
elems->owe_dh = pos; elems->owe_dh = pos;
elems->owe_dh_len = elen; elems->owe_dh_len = elen;
break; break;
case WLAN_EID_EXT_PASSWORD_IDENTIFIER:
elems->password_id = pos;
elems->password_id_len = elen;
break;
default: default:
if (show_errors) { if (show_errors) {
wpa_printf(MSG_MSGDUMP, wpa_printf(MSG_MSGDUMP,

View file

@ -83,6 +83,7 @@ struct ieee802_11_elems {
const u8 *owe_dh; const u8 *owe_dh;
const u8 *power_capab; const u8 *power_capab;
const u8 *roaming_cons_sel; const u8 *roaming_cons_sel;
const u8 *password_id;
u8 ssid_len; u8 ssid_len;
u8 supp_rates_len; u8 supp_rates_len;
@ -128,6 +129,7 @@ struct ieee802_11_elems {
u8 owe_dh_len; u8 owe_dh_len;
u8 power_capab_len; u8 power_capab_len;
u8 roaming_cons_sel_len; u8 roaming_cons_sel_len;
u8 password_id_len;
struct mb_ies_info mb_ies; struct mb_ies_info mb_ies;
}; };

View file

@ -203,6 +203,7 @@
#define WLAN_STATUS_AUTHORIZATION_DEENABLED 107 #define WLAN_STATUS_AUTHORIZATION_DEENABLED 107
#define WLAN_STATUS_FILS_AUTHENTICATION_FAILURE 112 #define WLAN_STATUS_FILS_AUTHENTICATION_FAILURE 112
#define WLAN_STATUS_UNKNOWN_AUTHENTICATION_SERVER 113 #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) */ /* Reason codes (IEEE Std 802.11-2016, 9.4.1.7, Table 9-45) */
#define WLAN_REASON_UNSPECIFIED 1 #define WLAN_REASON_UNSPECIFIED 1
@ -463,6 +464,7 @@
#define WLAN_EID_EXT_FILS_NONCE 13 #define WLAN_EID_EXT_FILS_NONCE 13
#define WLAN_EID_EXT_FUTURE_CHANNEL_GUIDANCE 14 #define WLAN_EID_EXT_FUTURE_CHANNEL_GUIDANCE 14
#define WLAN_EID_EXT_OWE_DH_PARAM 32 #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_CAPABILITIES 35
#define WLAN_EID_EXT_HE_OPERATION 36 #define WLAN_EID_EXT_HE_OPERATION 36

View file

@ -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->own_commit_element_ecc, 0);
crypto_ec_point_deinit(tmp->peer_commit_element_ecc, 0); crypto_ec_point_deinit(tmp->peer_commit_element_ecc, 0);
wpabuf_free(tmp->anti_clogging_token); wpabuf_free(tmp->anti_clogging_token);
os_free(tmp->pw_id);
bin_clear_free(tmp, sizeof(*tmp)); bin_clear_free(tmp, sizeof(*tmp));
sae->tmp = NULL; 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, static int sae_derive_pwe_ecc(struct sae_data *sae, const u8 *addr1,
const u8 *addr2, const u8 *password, const u8 *addr2, const u8 *password,
size_t password_len) size_t password_len, const char *identifier)
{ {
u8 counter, k = 40; u8 counter, k = 40;
u8 addrs[2 * ETH_ALEN]; u8 addrs[2 * ETH_ALEN];
const u8 *addr[2]; const u8 *addr[3];
size_t len[2]; size_t len[3];
size_t num_elem;
u8 dummy_password[32]; u8 dummy_password[32];
size_t dummy_password_len; size_t dummy_password_len;
int pwd_seed_odd = 0; 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", wpa_hexdump_ascii_key(MSG_DEBUG, "SAE: password",
password, password_len); password, password_len);
if (identifier)
wpa_printf(MSG_DEBUG, "SAE: password identifier: %s",
identifier);
/* /*
* H(salt, ikm) = HMAC-SHA256(salt, ikm) * 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), * pwd-seed = H(MAX(STA-A-MAC, STA-B-MAC) || MIN(STA-A-MAC, STA-B-MAC),
* base || counter) * base || counter)
*/ */
@ -471,8 +476,15 @@ static int sae_derive_pwe_ecc(struct sae_data *sae, const u8 *addr1,
addr[0] = password; addr[0] = password;
len[0] = password_len; len[0] = password_len;
addr[1] = &counter; num_elem = 1;
len[1] = sizeof(counter); 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 * 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); wpa_printf(MSG_DEBUG, "SAE: counter = %u", counter);
if (hmac_sha256_vector(addrs, sizeof(addrs), 2, addr, len, if (hmac_sha256_vector(addrs, sizeof(addrs), num_elem,
pwd_seed) < 0) addr, len, pwd_seed) < 0)
break; break;
res = sae_test_pwd_seed_ecc(sae, pwd_seed, 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, static int sae_derive_pwe_ffc(struct sae_data *sae, const u8 *addr1,
const u8 *addr2, const u8 *password, const u8 *addr2, const u8 *password,
size_t password_len) size_t password_len, const char *identifier)
{ {
u8 counter; u8 counter;
u8 addrs[2 * ETH_ALEN]; u8 addrs[2 * ETH_ALEN];
const u8 *addr[2]; const u8 *addr[3];
size_t len[2]; size_t len[3];
size_t num_elem;
int found = 0; int found = 0;
if (sae->tmp->pwe_ffc == NULL) { 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) * H(salt, ikm) = HMAC-SHA256(salt, ikm)
* pwd-seed = H(MAX(STA-A-MAC, STA-B-MAC) || MIN(STA-A-MAC, STA-B-MAC), * 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); sae_pwd_seed_key(addr1, addr2, addrs);
addr[0] = password; addr[0] = password;
len[0] = password_len; len[0] = password_len;
addr[1] = &counter; num_elem = 1;
len[1] = sizeof(counter); 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++) { for (counter = 1; !found; counter++) {
u8 pwd_seed[SHA256_MAC_LEN]; 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); wpa_printf(MSG_DEBUG, "SAE: counter = %u", counter);
if (hmac_sha256_vector(addrs, sizeof(addrs), 2, addr, len, if (hmac_sha256_vector(addrs, sizeof(addrs), num_elem,
pwd_seed) < 0) addr, len, pwd_seed) < 0)
break; break;
res = sae_test_pwd_seed_ffc(sae, pwd_seed, sae->tmp->pwe_ffc); res = sae_test_pwd_seed_ffc(sae, pwd_seed, sae->tmp->pwe_ffc);
if (res < 0) if (res < 0)
@ -702,13 +722,15 @@ fail:
int sae_prepare_commit(const u8 *addr1, const u8 *addr2, int sae_prepare_commit(const u8 *addr1, const u8 *addr2,
const u8 *password, size_t password_len, const u8 *password, size_t password_len,
struct sae_data *sae) const char *identifier, struct sae_data *sae)
{ {
if (sae->tmp == NULL || if (sae->tmp == NULL ||
(sae->tmp->ec && sae_derive_pwe_ecc(sae, addr1, addr2, password, (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, (sae->tmp->dh && sae_derive_pwe_ffc(sae, addr1, addr2, password,
password_len) < 0) || password_len,
identifier) < 0) ||
sae_derive_commit(sae) < 0) sae_derive_commit(sae) < 0)
return -1; return -1;
return 0; 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, void sae_write_commit(struct sae_data *sae, struct wpabuf *buf,
const struct wpabuf *token) const struct wpabuf *token, const char *identifier)
{ {
u8 *pos; 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", wpa_hexdump(MSG_DEBUG, "SAE: own commit-element",
pos, sae->tmp->prime_len); 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, static void sae_parse_commit_token(struct sae_data *sae, const u8 **pos,
const u8 *end, const u8 **token, const u8 *end, const u8 **token,
size_t *token_len) size_t *token_len)
{ {
if ((sae->tmp->ec ? 3 : 2) * sae->tmp->prime_len < end - *pos) { size_t scalar_elem_len, tlen;
size_t tlen = end - (*pos + (sae->tmp->ec ? 3 : 2) * const u8 *elem;
sae->tmp->prime_len);
wpa_hexdump(MSG_DEBUG, "SAE: Anti-Clogging Token", *pos, tlen); if (token)
if (token) *token = NULL;
*token = *pos; if (token_len)
if (token_len) *token_len = 0;
*token_len = tlen;
*pos += tlen; scalar_elem_len = (sae->tmp->ec ? 3 : 2) * sae->tmp->prime_len;
} else { if (scalar_elem_len >= (size_t) (end - *pos))
if (token) return; /* No extra data beyond peer scalar and element */
*token = NULL;
if (token_len) /* It is a bit difficult to parse this now that there is an
*token_len = 0; * 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) const u8 *end)
{ {
u8 prime[SAE_MAX_ECC_PRIME_LEN]; 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 " wpa_printf(MSG_DEBUG, "SAE: Not enough data for "
"commit-element"); "commit-element");
return WLAN_STATUS_UNSPECIFIED_FAILURE; 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; return WLAN_STATUS_UNSPECIFIED_FAILURE;
/* element x and y coordinates < p */ /* element x and y coordinates < p */
if (os_memcmp(pos, prime, sae->tmp->prime_len) >= 0 || if (os_memcmp(*pos, prime, sae->tmp->prime_len) >= 0 ||
os_memcmp(pos + sae->tmp->prime_len, prime, os_memcmp(*pos + sae->tmp->prime_len, prime,
sae->tmp->prime_len) >= 0) { sae->tmp->prime_len) >= 0) {
wpa_printf(MSG_DEBUG, "SAE: Invalid coordinates in peer " wpa_printf(MSG_DEBUG, "SAE: Invalid coordinates in peer "
"element"); "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)", 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)", 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); crypto_ec_point_deinit(sae->tmp->peer_commit_element_ecc, 0);
sae->tmp->peer_commit_element_ecc = 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) if (sae->tmp->peer_commit_element_ecc == NULL)
return WLAN_STATUS_UNSPECIFIED_FAILURE; 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; return WLAN_STATUS_UNSPECIFIED_FAILURE;
} }
*pos += 2 * sae->tmp->prime_len;
return WLAN_STATUS_SUCCESS; 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) const u8 *end)
{ {
struct crypto_bignum *res, *one; struct crypto_bignum *res, *one;
const u8 one_bin[1] = { 0x01 }; 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 " wpa_printf(MSG_DEBUG, "SAE: Not enough data for "
"commit-element"); "commit-element");
return WLAN_STATUS_UNSPECIFIED_FAILURE; 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); sae->tmp->prime_len);
crypto_bignum_deinit(sae->tmp->peer_commit_element_ffc, 0); crypto_bignum_deinit(sae->tmp->peer_commit_element_ffc, 0);
sae->tmp->peer_commit_element_ffc = 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) if (sae->tmp->peer_commit_element_ffc == NULL)
return WLAN_STATUS_UNSPECIFIED_FAILURE; return WLAN_STATUS_UNSPECIFIED_FAILURE;
/* 1 < element < p - 1 */ /* 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); crypto_bignum_deinit(res, 0);
*pos += sae->tmp->prime_len;
return WLAN_STATUS_SUCCESS; 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) const u8 *end)
{ {
if (sae->tmp->dh) 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, u16 sae_parse_commit(struct sae_data *sae, const u8 *data, size_t len,
const u8 **token, size_t *token_len, int *allowed_groups) 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; return res;
/* commit-element */ /* 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) if (res != WLAN_STATUS_SUCCESS)
return res; return res;

View file

@ -39,6 +39,7 @@ struct sae_temporary_data {
struct crypto_bignum *prime_buf; struct crypto_bignum *prime_buf;
struct crypto_bignum *order_buf; struct crypto_bignum *order_buf;
struct wpabuf *anti_clogging_token; struct wpabuf *anti_clogging_token;
char *pw_id;
}; };
enum sae_state { 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, int sae_prepare_commit(const u8 *addr1, const u8 *addr2,
const u8 *password, size_t password_len, 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); int sae_process_commit(struct sae_data *sae);
void sae_write_commit(struct sae_data *sae, struct wpabuf *buf, 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, u16 sae_parse_commit(struct sae_data *sae, const u8 *data, size_t len,
const u8 **token, size_t *token_len, int *allowed_groups); const u8 **token, size_t *token_len, int *allowed_groups);
void sae_write_confirm(struct sae_data *sae, struct wpabuf *buf); void sae_write_confirm(struct sae_data *sae, struct wpabuf *buf);

View file

@ -89,6 +89,9 @@ extern "C" {
#define WPA_EVENT_REGDOM_CHANGE "CTRL-EVENT-REGDOM-CHANGE " #define WPA_EVENT_REGDOM_CHANGE "CTRL-EVENT-REGDOM-CHANGE "
/** Channel switch (followed by freq=<MHz> and other channel parameters) */ /** Channel switch (followed by freq=<MHz> and other channel parameters) */
#define WPA_EVENT_CHANNEL_SWITCH "CTRL-EVENT-CHANNEL-SWITCH " #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 /** IP subnet status change notification
* *

View file

@ -2134,6 +2134,7 @@ static const struct parse_data ssid_fields[] = {
{ FUNC_KEY(psk) }, { FUNC_KEY(psk) },
{ INT(mem_only_psk) }, { INT(mem_only_psk) },
{ STR_KEY(sae_password) }, { STR_KEY(sae_password) },
{ STR(sae_password_id) },
{ FUNC(proto) }, { FUNC(proto) },
{ FUNC(key_mgmt) }, { FUNC(key_mgmt) },
{ INT(bg_scan_period) }, { INT(bg_scan_period) },
@ -2474,6 +2475,7 @@ void wpa_config_free_ssid(struct wpa_ssid *ssid)
str_clear_free(ssid->passphrase); str_clear_free(ssid->passphrase);
os_free(ssid->ext_psk); os_free(ssid->ext_psk);
str_clear_free(ssid->sae_password); str_clear_free(ssid->sae_password);
os_free(ssid->sae_password_id);
#ifdef IEEE8021X_EAPOL #ifdef IEEE8021X_EAPOL
eap_peer_config_free(&ssid->eap); eap_peer_config_free(&ssid->eap);
#endif /* IEEE8021X_EAPOL */ #endif /* IEEE8021X_EAPOL */

View file

@ -749,6 +749,7 @@ static void wpa_config_write_network(FILE *f, struct wpa_ssid *ssid)
write_psk(f, ssid); write_psk(f, ssid);
INT(mem_only_psk); INT(mem_only_psk);
STR(sae_password); STR(sae_password);
STR(sae_password_id);
write_proto(f, ssid); write_proto(f, ssid);
write_key_mgmt(f, ssid); write_key_mgmt(f, ssid);
INT_DEF(bg_scan_period, DEFAULT_BG_SCAN_PERIOD); INT_DEF(bg_scan_period, DEFAULT_BG_SCAN_PERIOD);

View file

@ -193,6 +193,14 @@ struct wpa_ssid {
*/ */
char *sae_password; 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 * ext_psk - PSK/passphrase name in external storage
* *

View file

@ -873,6 +873,7 @@ static int wpa_config_write_network(HKEY hk, struct wpa_ssid *ssid, int id)
write_bssid(netw, ssid); write_bssid(netw, ssid);
write_psk(netw, ssid); write_psk(netw, ssid);
STR(sae_password); STR(sae_password);
STR(sae_password_id);
write_proto(netw, ssid); write_proto(netw, ssid);
write_key_mgmt(netw, ssid); write_key_mgmt(netw, ssid);
write_pairwise(netw, ssid); write_pairwise(netw, ssid);

View file

@ -332,8 +332,14 @@ static int mesh_rsn_build_sae_commit(struct wpa_supplicant *wpa_s,
return -1; 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, return sae_prepare_commit(wpa_s->own_addr, sta->addr,
(u8 *) password, os_strlen(password), (u8 *) password, os_strlen(password),
ssid->sae_password_id,
sta->sae); sta->sae);
} }

View file

@ -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, if (sae_prepare_commit(wpa_s->own_addr, bssid,
(u8 *) password, os_strlen(password), (u8 *) password, os_strlen(password),
ssid->sae_password_id,
&wpa_s->sme.sae) < 0) { &wpa_s->sme.sae) < 0) {
wpa_printf(MSG_DEBUG, "SAE: Could not pick PWE"); wpa_printf(MSG_DEBUG, "SAE: Could not pick PWE");
return NULL; return NULL;
} }
len = wpa_s->sme.sae_token ? wpabuf_len(wpa_s->sme.sae_token) : 0; 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); buf = wpabuf_alloc(4 + SAE_COMMIT_MAX_LEN + len);
if (buf == NULL) if (buf == NULL)
return 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, 1); /* Transaction seq# */
wpabuf_put_le16(buf, WLAN_STATUS_SUCCESS); 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; return buf;
} }
@ -1005,6 +1009,16 @@ static int sme_sae_auth(struct wpa_supplicant *wpa_s, u16 auth_transaction,
return 0; 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) if (status_code != WLAN_STATUS_SUCCESS)
return -1; return -1;

View file

@ -954,6 +954,11 @@ fast_reauth=1
# used, but psk follows the WPA-PSK constraints (8..63 characters) even though # used, but psk follows the WPA-PSK constraints (8..63 characters) even though
# SAE passwords do not have such constraints. # 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) # eapol_flags: IEEE 802.1X/EAPOL options (bit field)
# Dynamic WEP key required for non-WPA mode # Dynamic WEP key required for non-WPA mode
# bit0 (1): require dynamically generated unicast WEP key # bit0 (1): require dynamically generated unicast WEP key