diff --git a/hostapd/Android.mk b/hostapd/Android.mk index c389cb75d..6cfbe5c22 100644 --- a/hostapd/Android.mk +++ b/hostapd/Android.mk @@ -250,6 +250,10 @@ endif ifdef CONFIG_SAE L_CFLAGS += -DCONFIG_SAE OBJS += src/common/sae.c +ifdef CONFIG_SAE_PK +L_CFLAGS += -DCONFIG_SAE_PK +OBJS += src/common/sae_pk.c +endif NEED_ECC=y NEED_DH_GROUPS=y NEED_HMAC_SHA256_KDF=y diff --git a/hostapd/Makefile b/hostapd/Makefile index 5b0d3b8e3..3cc64c965 100644 --- a/hostapd/Makefile +++ b/hostapd/Makefile @@ -294,6 +294,10 @@ endif ifdef CONFIG_SAE CFLAGS += -DCONFIG_SAE OBJS += ../src/common/sae.o +ifdef CONFIG_SAE_PK +CFLAGS += -DCONFIG_SAE_PK +OBJS += ../src/common/sae_pk.o +endif NEED_ECC=y NEED_DH_GROUPS=y NEED_HMAC_SHA256_KDF=y diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c index c4bf434f2..a10bbdcfb 100644 --- a/src/ap/ieee802_11.c +++ b/src/ap/ieee802_11.c @@ -515,7 +515,7 @@ static struct wpabuf * auth_build_sae_commit(struct hostapd_data *hapd, if (update && use_pt && sae_prepare_commit_pt(sta->sae, pt, hapd->own_addr, sta->addr, - NULL) < 0) + NULL, NULL) < 0) return NULL; if (update && !use_pt && diff --git a/src/common/sae.c b/src/common/sae.c index 1b4ec6d81..a298212ca 100644 --- a/src/common/sae.c +++ b/src/common/sae.c @@ -112,6 +112,9 @@ void sae_clear_temp_data(struct sae_data *sae) wpabuf_free(tmp->own_rejected_groups); wpabuf_free(tmp->peer_rejected_groups); os_free(tmp->pw_id); +#ifdef CONFIG_SAE_PK + bin_clear_free(tmp->pw, tmp->pw_len); +#endif /* CONFIG_SAE_PK */ bin_clear_free(tmp, sizeof(*tmp)); sae->tmp = NULL; } @@ -1052,10 +1055,17 @@ sae_derive_pt_group(int group, const u8 *ssid, size_t ssid_len, wpa_printf(MSG_DEBUG, "SAE: Derive PT - group %d", group); + if (ssid_len > 32) + return NULL; + pt = os_zalloc(sizeof(*pt)); if (!pt) return NULL; +#ifdef CONFIG_SAE_PK + os_memcpy(pt->ssid, ssid, ssid_len); + pt->ssid_len = ssid_len; +#endif /* CONFIG_SAE_PK */ pt->group = group; pt->ec = crypto_ec_init(group); if (pt->ec) { @@ -1355,13 +1365,14 @@ int sae_prepare_commit(const u8 *addr1, const u8 *addr2, return -1; sae->tmp->h2e = 0; + sae->tmp->pk = 0; return sae_derive_commit(sae); } int sae_prepare_commit_pt(struct sae_data *sae, const struct sae_pt *pt, const u8 *addr1, const u8 *addr2, - int *rejected_groups) + int *rejected_groups, const struct sae_pk *pk) { if (!sae->tmp) return -1; @@ -1377,6 +1388,20 @@ int sae_prepare_commit_pt(struct sae_data *sae, const struct sae_pt *pt, return -1; } +#ifdef CONFIG_SAE_PK + os_memcpy(sae->tmp->ssid, pt->ssid, pt->ssid_len); + sae->tmp->ssid_len = pt->ssid_len; + sae->tmp->ap_pk = pk; + /* TODO: Could support alternative groups as long as the combination + * meets the requirements. */ + if (pk && pk->group != sae->group) { + wpa_printf(MSG_DEBUG, + "SAE-PK: Reject attempt to use group %d since K_AP use group %d", + sae->group, pk->group); + sae->tmp->reject_group = true; + return -1; + } +#endif /* CONFIG_SAE_PK */ sae->tmp->own_addr_higher = os_memcmp(addr1, addr2, ETH_ALEN) > 0; wpabuf_free(sae->tmp->own_rejected_groups); sae->tmp->own_rejected_groups = NULL; @@ -1515,7 +1540,7 @@ static int sae_derive_keys(struct sae_data *sae, const u8 *k) const u8 *salt; struct wpabuf *rejected_groups = NULL; u8 keyseed[SAE_MAX_HASH_LEN]; - u8 keys[SAE_MAX_HASH_LEN + SAE_PMK_LEN]; + u8 keys[2 * SAE_MAX_HASH_LEN + SAE_PMK_LEN]; struct crypto_bignum *tmp; int ret = -1; size_t hash_len, salt_len, prime_len = sae->tmp->prime_len; @@ -1530,6 +1555,9 @@ static int sae_derive_keys(struct sae_data *sae, const u8 *k) * KCK || PMK = KDF-Hash-Length(keyseed, "SAE KCK and PMK", * (commit-scalar + peer-commit-scalar) modulo r) * PMKID = L((commit-scalar + peer-commit-scalar) modulo r, 0, 128) + * + * When SAE-PK is used, + * KCK || PMK || KEK = KDF-Hash-Length(keyseed, "SAE-PK keys", context) */ if (!sae->tmp->h2e) hash_len = SHA256_MAC_LEN; @@ -1589,15 +1617,32 @@ static int sae_derive_keys(struct sae_data *sae, const u8 *k) * octets). */ crypto_bignum_to_bin(tmp, val, sizeof(val), sae->tmp->order_len); wpa_hexdump(MSG_DEBUG, "SAE: PMKID", val, SAE_PMKID_LEN); - if (sae_kdf_hash(hash_len, keyseed, "SAE KCK and PMK", + if (!sae->tmp->pk && + sae_kdf_hash(hash_len, keyseed, "SAE KCK and PMK", val, sae->tmp->order_len, keys, hash_len + SAE_PMK_LEN) < 0) goto fail; +#ifdef CONFIG_SAE_PK + if (sae->tmp->pk && + sae_kdf_hash(hash_len, keyseed, "SAE-PK keys", + val, sae->tmp->order_len, + keys, 2 * hash_len + SAE_PMK_LEN) < 0) + goto fail; +#endif /* CONFIG_SAE_PK */ forced_memzero(keyseed, sizeof(keyseed)); os_memcpy(sae->tmp->kck, keys, hash_len); sae->tmp->kck_len = hash_len; os_memcpy(sae->pmk, keys + hash_len, SAE_PMK_LEN); os_memcpy(sae->pmkid, val, SAE_PMKID_LEN); +#ifdef CONFIG_SAE_PK + if (sae->tmp->pk) { + os_memcpy(sae->tmp->kek, keys + hash_len + SAE_PMK_LEN, + hash_len); + sae->tmp->kek_len = hash_len; + wpa_hexdump_key(MSG_DEBUG, "SAE: KEK for SAE-PK", + sae->tmp->kek, sae->tmp->kek_len); + } +#endif /* CONFIG_SAE_PK */ forced_memzero(keys, sizeof(keys)); wpa_hexdump_key(MSG_DEBUG, "SAE: KCK", sae->tmp->kck, sae->tmp->kck_len); @@ -2189,13 +2234,14 @@ static int sae_cn_confirm_ffc(struct sae_data *sae, const u8 *sc, } -void sae_write_confirm(struct sae_data *sae, struct wpabuf *buf) +int sae_write_confirm(struct sae_data *sae, struct wpabuf *buf) { const u8 *sc; size_t hash_len; + int res; if (sae->tmp == NULL) - return; + return -1; hash_len = sae->tmp->kck_len; @@ -2206,17 +2252,26 @@ void sae_write_confirm(struct sae_data *sae, struct wpabuf *buf) sae->send_confirm++; if (sae->tmp->ec) - sae_cn_confirm_ecc(sae, sc, sae->tmp->own_commit_scalar, - sae->tmp->own_commit_element_ecc, - sae->peer_commit_scalar, - sae->tmp->peer_commit_element_ecc, - wpabuf_put(buf, hash_len)); + res = sae_cn_confirm_ecc(sae, sc, sae->tmp->own_commit_scalar, + sae->tmp->own_commit_element_ecc, + sae->peer_commit_scalar, + sae->tmp->peer_commit_element_ecc, + wpabuf_put(buf, hash_len)); else - sae_cn_confirm_ffc(sae, sc, sae->tmp->own_commit_scalar, - sae->tmp->own_commit_element_ffc, - sae->peer_commit_scalar, - sae->tmp->peer_commit_element_ffc, - wpabuf_put(buf, hash_len)); + res = sae_cn_confirm_ffc(sae, sc, sae->tmp->own_commit_scalar, + sae->tmp->own_commit_element_ffc, + sae->peer_commit_scalar, + sae->tmp->peer_commit_element_ffc, + wpabuf_put(buf, hash_len)); + if (res) + return res; + +#ifdef CONFIG_SAE_PK + if (sae_write_confirm_pk(sae, buf) < 0) + return -1; +#endif /* CONFIG_SAE_PK */ + + return 0; } @@ -2270,6 +2325,12 @@ int sae_check_confirm(struct sae_data *sae, const u8 *data, size_t len) return -1; } +#ifdef CONFIG_SAE_PK + if (sae_check_confirm_pk(sae, data + 2 + hash_len, + len - 2 - hash_len) < 0) + return -1; +#endif /* CONFIG_SAE_PK */ + return 0; } diff --git a/src/common/sae.h b/src/common/sae.h index 7966d70e4..61b228819 100644 --- a/src/common/sae.h +++ b/src/common/sae.h @@ -16,11 +16,24 @@ #define SAE_MAX_ECC_PRIME_LEN 66 #define SAE_MAX_HASH_LEN 64 #define SAE_COMMIT_MAX_LEN (2 + 3 * SAE_MAX_PRIME_LEN + 255) +#ifdef CONFIG_SAE_PK +#define SAE_CONFIRM_MAX_LEN ((2 + SAE_MAX_HASH_LEN) + 1500) +#else /* CONFIG_SAE_PK */ #define SAE_CONFIRM_MAX_LEN (2 + SAE_MAX_HASH_LEN) +#endif /* CONFIG_SAE_PK */ +#define SAE_PK_M_LEN 16 /* Special value returned by sae_parse_commit() */ #define SAE_SILENTLY_DISCARD 65535 +struct sae_pk { + struct wpabuf *m; + struct crypto_ec_key *key; + int group; + struct wpabuf *pubkey; /* DER encoded subjectPublicKey */ +}; + + struct sae_temporary_data { u8 kck[SAE_MAX_HASH_LEN]; size_t kck_len; @@ -47,7 +60,22 @@ struct sae_temporary_data { struct wpabuf *own_rejected_groups; struct wpabuf *peer_rejected_groups; unsigned int h2e:1; + unsigned int pk:1; unsigned int own_addr_higher:1; + +#ifdef CONFIG_SAE_PK + u8 kek[SAE_MAX_HASH_LEN]; + size_t kek_len; + const struct sae_pk *ap_pk; + u8 own_addr[ETH_ALEN]; + u8 peer_addr[ETH_ALEN]; + u8 *pw; + size_t pw_len; + size_t lambda; + u8 ssid[32]; + size_t ssid_len; + bool reject_group; +#endif /* CONFIG_SAE_PK */ }; struct sae_pt { @@ -58,6 +86,10 @@ struct sae_pt { const struct dh_group *dh; struct crypto_bignum *ffc_pt; +#ifdef CONFIG_SAE_PK + u8 ssid[32]; + size_t ssid_len; +#endif /* CONFIG_SAE_PK */ }; enum sae_state { @@ -86,17 +118,19 @@ int sae_prepare_commit(const u8 *addr1, const u8 *addr2, const char *identifier, struct sae_data *sae); int sae_prepare_commit_pt(struct sae_data *sae, const struct sae_pt *pt, const u8 *addr1, const u8 *addr2, - int *rejected_groups); + int *rejected_groups, const struct sae_pk *pk); int sae_process_commit(struct sae_data *sae); int sae_write_commit(struct sae_data *sae, struct wpabuf *buf, 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, int h2e); -void sae_write_confirm(struct sae_data *sae, struct wpabuf *buf); +int sae_write_confirm(struct sae_data *sae, struct wpabuf *buf); int sae_check_confirm(struct sae_data *sae, const u8 *data, size_t len); u16 sae_group_allowed(struct sae_data *sae, int *allowed_groups, u16 group); const char * sae_state_txt(enum sae_state state); +size_t sae_ecc_prime_len_2_hash_len(size_t prime_len); +size_t sae_ffc_prime_len_2_hash_len(size_t prime_len); struct sae_pt * sae_derive_pt(int *groups, const u8 *ssid, size_t ssid_len, const u8 *password, size_t password_len, const char *identifier); @@ -108,4 +142,22 @@ sae_derive_pwe_from_pt_ffc(const struct sae_pt *pt, const u8 *addr1, const u8 *addr2); void sae_deinit_pt(struct sae_pt *pt); +/* sae_pk.c */ +#ifdef CONFIG_SAE_PK +bool sae_pk_valid_password(const char *pw); +#else /* CONFIG_SAE_PK */ +static inline bool sae_pk_valid_password(const char *pw) +{ + return false; +} +#endif /* CONFIG_SAE_PK */ +char * sae_pk_base32_encode(const u8 *src, size_t len_bits); +u8 * sae_pk_base32_decode(const char *src, size_t len, size_t *out_len); +int sae_pk_set_password(struct sae_data *sae, const char *password); +void sae_deinit_pk(struct sae_pk *pk); +struct sae_pk * sae_parse_pk(const char *val); +int sae_write_confirm_pk(struct sae_data *sae, struct wpabuf *buf); +int sae_check_confirm_pk(struct sae_data *sae, const u8 *ies, size_t ies_len); +int sae_hash(size_t hash_len, const u8 *data, size_t len, u8 *hash); + #endif /* SAE_H */ diff --git a/src/common/sae_pk.c b/src/common/sae_pk.c new file mode 100644 index 000000000..df0f7db9d --- /dev/null +++ b/src/common/sae_pk.c @@ -0,0 +1,653 @@ +/* + * SAE-PK + * Copyright (c) 2020, The Linux Foundation + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" +#include + +#include "utils/common.h" +#include "utils/base64.h" +#include "common/ieee802_11_defs.h" +#include "common/ieee802_11_common.h" +#include "crypto/crypto.h" +#include "crypto/aes.h" +#include "crypto/aes_siv.h" +#include "sae.h" + + +/* RFC 4648 base 32 alphabet with lowercase characters */ +static const char *sae_pk_base32_table = "abcdefghijklmnopqrstuvwxyz234567"; + + +bool sae_pk_valid_password(const char *pw) +{ + int pos; + + /* Minimum password length for SAE-PK is not defined, but the automatic + * password style determination is more reliable if at least one hyphen + * is forced to be present in the password. */ + if (os_strlen(pw) < 6) + return false; + + for (pos = 0; pw[pos]; pos++) { + if (pos && pos % 5 == 4) { + if (pw[pos] != '-') + return false; + continue; + } + if (!os_strchr(sae_pk_base32_table, pw[pos])) + return false; + } + if (pos == 0) + return false; + return pw[pos - 1] != '-'; +} + + +static char * add_char(const char *start, char *pos, u8 idx, size_t *bits) +{ + if (*bits == 0) + return pos; + if (*bits > 5) + *bits -= 5; + else + *bits = 0; + + if ((pos - start) % 5 == 4) + *pos++ = '-'; + *pos++ = sae_pk_base32_table[idx]; + return pos; +} + + +char * sae_pk_base32_encode(const u8 *src, size_t len_bits) +{ + char *out, *pos; + size_t olen, extra_pad, i; + u64 block = 0; + u8 val; + size_t len = (len_bits + 7) / 8; + size_t left = len_bits; + int j; + + if (len == 0 || len >= SIZE_MAX / 8) + return NULL; + olen = len * 8 / 5 + 1; + olen += olen / 4; /* hyphen separators */ + pos = out = os_zalloc(olen + 1); + if (!out) + return NULL; + + extra_pad = (5 - len % 5) % 5; + for (i = 0; i < len + extra_pad; i++) { + val = i < len ? src[i] : 0; + block <<= 8; + block |= val; + if (i % 5 == 4) { + for (j = 7; j >= 0; j--) + pos = add_char(out, pos, + (block >> j * 5) & 0x1f, &left); + block = 0; + } + } + + return out; +} + + +u8 * sae_pk_base32_decode(const char *src, size_t len, size_t *out_len) +{ + u8 dtable[256], *out, *pos, tmp; + u64 block = 0; + size_t i, count, olen; + int pad = 0; + size_t extra_pad; + + os_memset(dtable, 0x80, 256); + for (i = 0; sae_pk_base32_table[i]; i++) + dtable[(u8) sae_pk_base32_table[i]] = i; + dtable['='] = 0; + + count = 0; + for (i = 0; i < len; i++) { + if (dtable[(u8) src[i]] != 0x80) + count++; + } + + if (count == 0) + return NULL; + extra_pad = (8 - count % 8) % 8; + + olen = (count + extra_pad) / 8 * 5; + pos = out = os_malloc(olen); + if (!out) + return NULL; + + count = 0; + for (i = 0; i < len + extra_pad; i++) { + u8 val; + + if (i >= len) + val = '='; + else + val = src[i]; + tmp = dtable[val]; + if (tmp == 0x80) + continue; + + if (val == '=') + pad++; + block <<= 5; + block |= tmp; + count++; + if (count == 8) { + *pos++ = (block >> 32) & 0xff; + *pos++ = (block >> 24) & 0xff; + *pos++ = (block >> 16) & 0xff; + *pos++ = (block >> 8) & 0xff; + *pos++ = block & 0xff; + count = 0; + block = 0; + if (pad) { + /* Leave in all the available bits with zero + * padding to full octets from right. */ + pos -= pad * 5 / 8; + break; + } + } + } + + *out_len = pos - out; + return out; +} + + +int sae_pk_set_password(struct sae_data *sae, const char *password) +{ + struct sae_temporary_data *tmp = sae->tmp; + size_t len; + + len = os_strlen(password); + if (!tmp || len < 1) + return -1; + + bin_clear_free(tmp->pw, tmp->pw_len); + tmp->pw = sae_pk_base32_decode(password, len, &tmp->pw_len); + tmp->lambda = len - len / 5; + return tmp->pw ? 0 : -1; +} + + +void sae_deinit_pk(struct sae_pk *pk) +{ + if (pk) { + wpabuf_free(pk->m); + crypto_ec_key_deinit(pk->key); + wpabuf_free(pk->pubkey); + os_free(pk); + } +} + + +struct sae_pk * sae_parse_pk(const char *val) +{ + struct sae_pk *pk; + const char *pos; + size_t len; + unsigned char *der; + size_t der_len; + + /* : */ + + pos = os_strchr(val, ':'); + if (!pos || (pos - val) & 0x01) + return NULL; + len = (pos - val) / 2; + if (len != SAE_PK_M_LEN) { + wpa_printf(MSG_INFO, "SAE: Unexpected Modifier M length %zu", + len); + return NULL; + } + + pk = os_zalloc(sizeof(*pk)); + if (!pk) + return NULL; + pk->m = wpabuf_alloc(len); + if (!pk->m || hexstr2bin(val, wpabuf_put(pk->m, len), len)) { + wpa_printf(MSG_INFO, "SAE: Failed to parse m"); + goto fail; + } + + pos++; + der = base64_decode(pos, os_strlen(pos), &der_len); + if (!der) { + wpa_printf(MSG_INFO, "SAE: Failed to base64 decode PK key"); + goto fail; + } + + pk->key = crypto_ec_key_parse_priv(der, der_len); + bin_clear_free(der, der_len); + if (!pk->key) + goto fail; + pk->group = crypto_ec_key_group(pk->key); + pk->pubkey = crypto_ec_key_get_subject_public_key(pk->key); + if (!pk->pubkey) + goto fail; + + return pk; +fail: + sae_deinit_pk(pk); + return NULL; +} + + +int sae_hash(size_t hash_len, const u8 *data, size_t len, u8 *hash) +{ + if (hash_len == 32) + return sha256_vector(1, &data, &len, hash); +#ifdef CONFIG_SHA384 + if (hash_len == 48) + return sha384_vector(1, &data, &len, hash); +#endif /* CONFIG_SHA384 */ +#ifdef CONFIG_SHA512 + if (hash_len == 64) + return sha512_vector(1, &data, &len, hash); +#endif /* CONFIG_SHA512 */ + return -1; +} + + +static int sae_pk_hash_sig_data(struct sae_data *sae, size_t hash_len, + bool ap, const u8 *m, size_t m_len, + const u8 *pubkey, size_t pubkey_len, u8 *hash) +{ + struct sae_temporary_data *tmp = sae->tmp; + struct wpabuf *sig_data; + u8 *pos; + int ret = -1; + + /* Signed data for KeyAuth: eleAP || eleSTA || scaAP || scaSTA || + * M || K_AP || AP-BSSID || STA-MAC */ + sig_data = wpabuf_alloc(tmp->prime_len * 6 + m_len + pubkey_len + + 2 * ETH_ALEN); + if (!sig_data) + goto fail; + pos = wpabuf_put(sig_data, 2 * tmp->prime_len); + if (crypto_ec_point_to_bin(tmp->ec, ap ? tmp->own_commit_element_ecc : + tmp->peer_commit_element_ecc, + pos, pos + tmp->prime_len) < 0) + goto fail; + pos = wpabuf_put(sig_data, 2 * tmp->prime_len); + if (crypto_ec_point_to_bin(tmp->ec, ap ? tmp->peer_commit_element_ecc : + tmp->own_commit_element_ecc, + pos, pos + tmp->prime_len) < 0) + goto fail; + if (crypto_bignum_to_bin(ap ? tmp->own_commit_scalar : + sae->peer_commit_scalar, + wpabuf_put(sig_data, tmp->prime_len), + tmp->prime_len, tmp->prime_len) < 0 || + crypto_bignum_to_bin(ap ? sae->peer_commit_scalar : + tmp->own_commit_scalar, + wpabuf_put(sig_data, tmp->prime_len), + tmp->prime_len, tmp->prime_len) < 0) + goto fail; + wpabuf_put_data(sig_data, m, m_len); + wpabuf_put_data(sig_data, pubkey, pubkey_len); + wpabuf_put_data(sig_data, ap ? tmp->own_addr : tmp->peer_addr, + ETH_ALEN); + wpabuf_put_data(sig_data, ap ? tmp->peer_addr : tmp->own_addr, + ETH_ALEN); + wpa_hexdump_buf_key(MSG_DEBUG, "SAE-PK: Data to be signed for KeyAuth", + sig_data); + if (sae_hash(hash_len, wpabuf_head(sig_data), wpabuf_len(sig_data), + hash) < 0) + goto fail; + wpa_hexdump(MSG_DEBUG, "SAE-PK: hash(data to be signed)", + hash, hash_len); + ret = 0; +fail: + wpabuf_free(sig_data); + return ret; +} + + +int sae_write_confirm_pk(struct sae_data *sae, struct wpabuf *buf) +{ + struct sae_temporary_data *tmp = sae->tmp; + struct wpabuf *elem = NULL, *sig = NULL; + size_t extra; + int ret = -1; + u8 *encr_mod; + size_t encr_mod_len; + const struct sae_pk *pk; + u8 hash[SAE_MAX_HASH_LEN]; + size_t hash_len; + + if (!tmp) + return -1; + + pk = tmp->ap_pk; + if (!pk) + return 0; + + if (tmp->kek_len != 32 && tmp->kek_len != 48 && tmp->kek_len != 64) { + wpa_printf(MSG_INFO, "SAE-PK: No KEK available for confirm"); + return -1; + } + + if (!tmp->ec) { + /* Only ECC groups are supported for SAE-PK in the current + * implementation. */ + wpa_printf(MSG_INFO, + "SAE-PK: SAE commit did not use an ECC group"); + return -1; + } + + hash_len = sae_ecc_prime_len_2_hash_len(tmp->prime_len); + if (sae_pk_hash_sig_data(sae, hash_len, true, wpabuf_head(pk->m), + wpabuf_len(pk->m), wpabuf_head(pk->pubkey), + wpabuf_len(pk->pubkey), hash) < 0) + goto fail; + sig = crypto_ec_key_sign(pk->key, hash, hash_len); + if (!sig) + goto fail; + wpa_hexdump_buf(MSG_DEBUG, "SAE-PK: KeyAuth = Sig_AP()", sig); + + elem = wpabuf_alloc(1500 + wpabuf_len(sig)); + if (!elem) + goto fail; + + /* EncryptedModifier = AES-SIV-Q(M); no AAD */ + encr_mod_len = wpabuf_len(pk->m) + AES_BLOCK_SIZE; + wpabuf_put_u8(elem, encr_mod_len); + encr_mod = wpabuf_put(elem, encr_mod_len); + if (aes_siv_encrypt(tmp->kek, tmp->kek_len, + wpabuf_head(pk->m), wpabuf_len(pk->m), + 0, NULL, NULL, encr_mod) < 0) + goto fail; + wpa_hexdump(MSG_DEBUG, "SAE-PK: EncryptedModifier", + encr_mod, encr_mod_len); + + /* FILS Public Key element */ + wpabuf_put_u8(elem, WLAN_EID_EXTENSION); + wpabuf_put_u8(elem, 2 + wpabuf_len(pk->pubkey)); + wpabuf_put_u8(elem, WLAN_EID_EXT_FILS_PUBLIC_KEY); + wpabuf_put_u8(elem, 3); /* Key Type: ECDSA public key */ + wpabuf_put_buf(elem, pk->pubkey); + + /* FILS Key Confirmation element (KeyAuth) */ + wpabuf_put_u8(elem, WLAN_EID_EXTENSION); + wpabuf_put_u8(elem, 1 + wpabuf_len(sig)); + wpabuf_put_u8(elem, WLAN_EID_EXT_FILS_KEY_CONFIRM); + /* KeyAuth = Sig_AP(eleAP || eleSTA || scaAP || scaSTA || M || K_AP || + * AP-BSSID || STA-MAC) */ + wpabuf_put_buf(elem, sig); + + /* TODO: fragmentation */ + extra = 6; /* Vendor specific element header */ + + if (wpabuf_tailroom(elem) < extra + wpabuf_len(buf)) { + wpa_printf(MSG_INFO, + "SAE-PK: No room in message buffer for SAE-PK element (%zu < %zu)", + wpabuf_tailroom(buf), extra + wpabuf_len(buf)); + goto fail; + } + + /* SAE-PK element */ + wpabuf_put_u8(buf, WLAN_EID_VENDOR_SPECIFIC); + wpabuf_put_u8(buf, 4 + wpabuf_len(elem)); + wpabuf_put_be32(buf, SAE_PK_IE_VENDOR_TYPE); + wpabuf_put_buf(buf, elem); + + ret = 0; +fail: + wpabuf_free(elem); + wpabuf_free(sig); + return ret; + +} + + +static bool sae_pk_valid_fingerprint(struct sae_data *sae, + const u8 *m, size_t m_len, + const u8 *k_ap, size_t k_ap_len) +{ + struct sae_temporary_data *tmp = sae->tmp; + size_t sec, i; + u8 *fingerprint_exp, *hash_data, *pos; + size_t hash_len, hash_data_len, fingerprint_bits, fingerprint_bytes; + u8 hash[SAE_MAX_HASH_LEN]; + int res; + + if (!tmp->pw || tmp->pw_len < 1) { + wpa_printf(MSG_DEBUG, + "SAE-PK: No PW available for K_AP fingerprint check"); + return false; + } + + /* Fingerprint = L(Hash(SSID || M || K_AP), 0, 8*Sec + 5*Lambda - 2) */ + + hash_len = sae_ecc_prime_len_2_hash_len(tmp->prime_len); + hash_data_len = tmp->ssid_len + m_len + k_ap_len; + hash_data = os_malloc(hash_data_len); + if (!hash_data) + return false; + pos = hash_data; + os_memcpy(pos, tmp->ssid, tmp->ssid_len); + pos += tmp->ssid_len; + os_memcpy(pos, m, m_len); + pos += m_len; + os_memcpy(pos, k_ap, k_ap_len); + + wpa_hexdump_key(MSG_DEBUG, "SAE-PK: SSID || M || K_AP", + hash_data, hash_data_len); + res = sae_hash(hash_len, hash_data, hash_data_len, hash); + bin_clear_free(hash_data, hash_data_len); + if (res < 0) + return false; + wpa_hexdump(MSG_DEBUG, "SAE-PK: Hash(SSID || M || K_AP)", + hash, hash_len); + + wpa_hexdump_key(MSG_DEBUG, "SAE-PK: PW", tmp->pw, tmp->pw_len); + sec = (tmp->pw[0] >> 6) + 2; + fingerprint_bits = 8 * sec + 5 * tmp->lambda - 2; + wpa_printf(MSG_DEBUG, "SAE-PK: Sec=%zu Lambda=%zu fingerprint_bits=%zu", + sec, tmp->lambda, fingerprint_bits); + if (fingerprint_bits > hash_len * 8) { + wpa_printf(MSG_INFO, + "SAE-PK: Not enough hash output bits for the fingerprint"); + return false; + } + fingerprint_bytes = (fingerprint_bits + 7) / 8; + if (fingerprint_bits % 8) { + size_t extra; + + /* Zero out the extra bits in the last octet */ + extra = 8 - fingerprint_bits % 8; + pos = &hash[fingerprint_bits / 8]; + *pos = (*pos >> extra) << extra; + } + wpa_hexdump(MSG_DEBUG, "SAE-PK: Fingerprint", hash, fingerprint_bytes); + + fingerprint_exp = os_zalloc(sec + tmp->pw_len); + if (!fingerprint_exp) + return false; + pos = fingerprint_exp + sec; + for (i = 0; i < tmp->pw_len; i++) { + u8 next = i + 1 < tmp->pw_len ? tmp->pw[i + 1] : 0; + + *pos++ = tmp->pw[i] << 2 | next >> 6; + } + + wpa_hexdump(MSG_DEBUG, "SAE-PK: Fingerprint_Expected", + fingerprint_exp, fingerprint_bytes); + res = os_memcmp_const(hash, fingerprint_exp, fingerprint_bytes); + bin_clear_free(fingerprint_exp, tmp->pw_len); + + if (res) { + wpa_printf(MSG_DEBUG, "SAE-PK: K_AP fingerprint mismatch"); + return false; + } + + wpa_printf(MSG_DEBUG, "SAE-PK: Valid K_AP fingerprint"); + return true; +} + + +int sae_check_confirm_pk(struct sae_data *sae, const u8 *ies, size_t ies_len) +{ + struct sae_temporary_data *tmp = sae->tmp; + const u8 *sae_pk, *pos, *end, *encr_mod, *k_ap, *key_auth; + u8 m[SAE_PK_M_LEN]; + size_t k_ap_len, key_auth_len; + struct crypto_ec_key *key; + int res; + u8 hash[SAE_MAX_HASH_LEN]; + size_t hash_len; + int group; + + if (!tmp) + return -1; + if (!tmp->pk || tmp->ap_pk) + return 0; + + if (tmp->kek_len != 32 && tmp->kek_len != 48 && tmp->kek_len != 64) { + wpa_printf(MSG_INFO, "SAE-PK: No KEK available for confirm"); + return -1; + } + + if (!tmp->ec) { + /* Only ECC groups are supported for SAE-PK in the current + * implementation. */ + wpa_printf(MSG_INFO, + "SAE-PK: SAE commit did not use an ECC group"); + return -1; + } + + wpa_hexdump(MSG_DEBUG, "SAE-PK: Received confirm IEs", ies, ies_len); + sae_pk = get_vendor_ie(ies, ies_len, SAE_PK_IE_VENDOR_TYPE); + if (!sae_pk) { + wpa_printf(MSG_INFO, "SAE-PK: No SAE-PK element included"); + return -1; + } + /* TODO: Fragment reassembly */ + pos = sae_pk + 2; + end = pos + sae_pk[1]; + + if (end - pos < 4 + 1 + SAE_PK_M_LEN + AES_BLOCK_SIZE) { + wpa_printf(MSG_INFO, + "SAE-PK: No room for EncryptedModifier in SAE-PK element"); + return -1; + } + pos += 4; + if (*pos != SAE_PK_M_LEN + AES_BLOCK_SIZE) { + wpa_printf(MSG_INFO, + "SAE-PK: Unexpected EncryptedModifier length %u", + *pos); + return -1; + } + pos++; + encr_mod = pos; + pos += SAE_PK_M_LEN + AES_BLOCK_SIZE; + + if (end - pos < 4 || pos[0] != WLAN_EID_EXTENSION || pos[1] < 2 || + pos[1] > end - pos - 2 || + pos[2] != WLAN_EID_EXT_FILS_PUBLIC_KEY) { + wpa_printf(MSG_INFO, + "SAE-PK: No FILS Public Key element in SAE-PK element"); + return -1; + } + if (pos[3] != 3) { + wpa_printf(MSG_INFO, "SAE-PK: Unsupported public key type %u", + pos[3]); + return -1; + } + k_ap_len = pos[1] - 2; + pos += 4; + k_ap = pos; + pos += k_ap_len; + + if (end - pos < 4 || pos[0] != WLAN_EID_EXTENSION || pos[1] < 1 || + pos[1] > end - pos - 2 || + pos[2] != WLAN_EID_EXT_FILS_KEY_CONFIRM) { + wpa_printf(MSG_INFO, + "SAE-PK: No FILS Key Confirm element in SAE-PK element"); + return -1; + } + key_auth_len = pos[1] - 1; + pos += 3; + key_auth = pos; + pos += key_auth_len; + + if (pos < end) { + wpa_hexdump(MSG_DEBUG, + "SAE-PK: Extra data at the end of SAE-PK element", + pos, end - pos); + } + + wpa_hexdump(MSG_DEBUG, "SAE-PK: EncryptedModifier", + encr_mod, SAE_PK_M_LEN + AES_BLOCK_SIZE); + + if (aes_siv_decrypt(tmp->kek, tmp->kek_len, + encr_mod, SAE_PK_M_LEN + AES_BLOCK_SIZE, + 0, NULL, NULL, m) < 0) { + wpa_printf(MSG_INFO, + "SAE-PK: Failed to decrypt EncryptedModifier"); + return -1; + } + wpa_hexdump_key(MSG_DEBUG, "SAE-PK: Modifier M", m, SAE_PK_M_LEN); + + wpa_hexdump(MSG_DEBUG, "SAE-PK: Received K_AP", k_ap, k_ap_len); + /* TODO: Check against the public key, if one is stored in the network + * profile */ + + if (!sae_pk_valid_fingerprint(sae, m, SAE_PK_M_LEN, k_ap, k_ap_len)) + return -1; + + key = crypto_ec_key_parse_pub(k_ap, k_ap_len); + if (!key) { + wpa_printf(MSG_INFO, "SAE-PK: Failed to parse K_AP"); + return -1; + } + + group = crypto_ec_key_group(key); + /* TODO: Could support alternative groups as long as the combination + * meets the requirements. */ + if (group != sae->group) { + wpa_printf(MSG_INFO, + "SAE-PK: K_AP group %d does not match SAE group %d", + group, sae->group); + crypto_ec_key_deinit(key); + return -1; + } + + wpa_hexdump(MSG_DEBUG, "SAE-PK: Received KeyAuth", + key_auth, key_auth_len); + + hash_len = sae_ecc_prime_len_2_hash_len(tmp->prime_len); + if (sae_pk_hash_sig_data(sae, hash_len, false, m, SAE_PK_M_LEN, + k_ap, k_ap_len, hash) < 0) { + crypto_ec_key_deinit(key); + return -1; + } + + res = crypto_ec_key_verify_signature(key, hash, hash_len, + key_auth, key_auth_len); + crypto_ec_key_deinit(key); + + if (res != 1) { + wpa_printf(MSG_INFO, + "SAE-PK: Invalid or incorrect signature in KeyAuth"); + return -1; + } + + wpa_printf(MSG_DEBUG, "SAE-PK: Valid KeyAuth signature received"); + + /* TODO: Store validated public key into network profile */ + + return 0; +} diff --git a/wpa_supplicant/Android.mk b/wpa_supplicant/Android.mk index a0c9e23c8..0f24d7b71 100644 --- a/wpa_supplicant/Android.mk +++ b/wpa_supplicant/Android.mk @@ -235,6 +235,10 @@ endif ifdef CONFIG_SAE L_CFLAGS += -DCONFIG_SAE OBJS += src/common/sae.c +ifdef CONFIG_SAE_PK +L_CFLAGS += -DCONFIG_SAE_PK +OBJS += src/common/sae_pk.c +endif NEED_ECC=y NEED_DH_GROUPS=y NEED_HMAC_SHA256_KDF=y diff --git a/wpa_supplicant/Makefile b/wpa_supplicant/Makefile index f4878807d..09ac7a493 100644 --- a/wpa_supplicant/Makefile +++ b/wpa_supplicant/Makefile @@ -267,6 +267,10 @@ endif ifdef CONFIG_SAE CFLAGS += -DCONFIG_SAE OBJS += ../src/common/sae.o +ifdef CONFIG_SAE_PK +CFLAGS += -DCONFIG_SAE_PK +OBJS += ../src/common/sae_pk.o +endif NEED_ECC=y NEED_DH_GROUPS=y NEED_HMAC_SHA256_KDF=y diff --git a/wpa_supplicant/sme.c b/wpa_supplicant/sme.c index 753e40965..d03b0ce8a 100644 --- a/wpa_supplicant/sme.c +++ b/wpa_supplicant/sme.c @@ -157,7 +157,7 @@ static struct wpabuf * sme_auth_build_sae_commit(struct wpa_supplicant *wpa_s, if (use_pt && sae_prepare_commit_pt(&wpa_s->sme.sae, ssid->pt, wpa_s->own_addr, bssid, - wpa_s->sme.sae_rejected_groups) < 0) + wpa_s->sme.sae_rejected_groups, NULL) < 0) return NULL; if (!use_pt && sae_prepare_commit(wpa_s->own_addr, bssid,