/* * 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"; static const u8 d_mult_table[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 16, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 16, 17, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 16, 17, 18, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 16, 17, 18, 19, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 16, 17, 18, 19, 20, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 16, 17, 18, 19, 20, 21, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 23, 24, 25, 26, 27, 28, 29, 30, 31, 16, 17, 18, 19, 20, 21, 22, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 24, 25, 26, 27, 28, 29, 30, 31, 16, 17, 18, 19, 20, 21, 22, 23, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 25, 26, 27, 28, 29, 30, 31, 16, 17, 18, 19, 20, 21, 22, 23, 24, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 26, 27, 28, 29, 30, 31, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 27, 28, 29, 30, 31, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 28, 29, 30, 31, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 29, 30, 31, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 30, 31, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 31, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 16, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 0, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 17, 16, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 1, 0, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 18, 17, 16, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 2, 1, 0, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 19, 18, 17, 16, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 3, 2, 1, 0, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 20, 19, 18, 17, 16, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 4, 3, 2, 1, 0, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 21, 20, 19, 18, 17, 16, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 5, 4, 3, 2, 1, 0, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 22, 21, 20, 19, 18, 17, 16, 31, 30, 29, 28, 27, 26, 25, 24, 23, 6, 5, 4, 3, 2, 1, 0, 15, 14, 13, 12, 11, 10, 9, 8, 7, 23, 22, 21, 20, 19, 18, 17, 16, 31, 30, 29, 28, 27, 26, 25, 24, 7, 6, 5, 4, 3, 2, 1, 0, 15, 14, 13, 12, 11, 10, 9, 8, 24, 23, 22, 21, 20, 19, 18, 17, 16, 31, 30, 29, 28, 27, 26, 25, 8, 7, 6, 5, 4, 3, 2, 1, 0, 15, 14, 13, 12, 11, 10, 9, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 31, 30, 29, 28, 27, 26, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 15, 14, 13, 12, 11, 10, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 31, 30, 29, 28, 27, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 15, 14, 13, 12, 11, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 31, 30, 29, 28, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 15, 14, 13, 12, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 31, 30, 29, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 15, 14, 13, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 31, 30, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 15, 14, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 31, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 15, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }; static const u8 d_perm_table[] = { 7, 2, 1, 30, 16, 20, 27, 11, 31, 6, 8, 13, 29, 5, 10, 21, 22, 3, 24, 0, 23, 25, 12, 9, 28, 14, 4, 15, 17, 18, 19, 26 }; static u8 d_permute(u8 val, unsigned int iter) { if (iter == 0) return val; return d_permute(d_perm_table[val], iter - 1); } static u8 d_invert(u8 val) { if (val > 0 && val < 16) return 16 - val; return val; } static char d_check_char(const char *str, size_t len) { size_t i; u8 val = 0; u8 dtable[256]; unsigned int iter = 1; int j; os_memset(dtable, 0x80, 256); for (i = 0; sae_pk_base32_table[i]; i++) dtable[(u8) sae_pk_base32_table[i]] = i; for (j = len - 1; j >= 0; j--) { u8 c, p; c = dtable[(u8) str[j]]; if (c == 0x80) continue; p = d_permute(c, iter); iter++; val = d_mult_table[val * 32 + p]; } return sae_pk_base32_table[d_invert(val)]; } bool sae_pk_valid_password(const char *pw) { int pos; size_t i, pw_len = os_strlen(pw); u8 sec_1b; u8 dtable[256]; os_memset(dtable, 0x80, 256); for (i = 0; sae_pk_base32_table[i]; i++) dtable[(u8) sae_pk_base32_table[i]] = i; /* SAE-PK password has at least three four character components * separated by hyphens. */ if (pw_len < 14 || pw_len % 5 != 4) { wpa_printf(MSG_DEBUG, "SAE-PK: Not a valid password (length)"); return false; } for (pos = 0; pw[pos]; pos++) { if (pos && pos % 5 == 4) { if (pw[pos] != '-') { wpa_printf(MSG_DEBUG, "SAE-PK: Not a valid password (separator)"); return false; } continue; } if (dtable[(u8) pw[pos]] == 0x80) { wpa_printf(MSG_DEBUG, "SAE-PK: Not a valid password (character)"); return false; } } /* Verify that the checksum character is valid */ if (pw[pw_len - 1] != d_check_char(pw, pw_len - 1)) { wpa_printf(MSG_DEBUG, "SAE-PK: Not a valid password (checksum)"); return false; } /* Verify that Sec_1b bits match */ sec_1b = dtable[(u8) pw[0]] & BIT(4); for (i = 5; i < pw_len; i += 5) { if (sec_1b != (dtable[(u8) pw[i]] & BIT(4))) { wpa_printf(MSG_DEBUG, "SAE-PK: Not a valid password (Sec_1b)"); return false; } } return true; } 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; } /* Base32 encode a password and add hyper separators and checksum */ 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 + 2); /* include room for ChkSum and nul */ 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; } } *pos = d_check_char(out, os_strlen(out)); 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; } u32 sae_pk_get_be19(const u8 *buf) { return (buf[0] << 11) | (buf[1] << 3) | (buf[2] >> 5); } /* shift left by two octets and three bits; fill in zeros from right; * len must be at least three */ void sae_pk_buf_shift_left_19(u8 *buf, size_t len) { u8 *dst, *src, *end; dst = buf; src = buf + 2; end = buf + len; while (src + 1 < end) { *dst++ = (src[0] << 3) | (src[1] >> 5); src++; } *dst++ = *src << 3; *dst++ = 0; *dst++ = 0; } static void sae_pk_buf_shift_left_1(u8 *buf, size_t len) { u8 *dst, *src, *end; dst = buf; src = buf; end = buf + len; while (src + 1 < end) { *dst++ = (src[0] << 1) | (src[1] >> 7); src++; } *dst++ = *src << 1; } int sae_pk_set_password(struct sae_data *sae, const char *password) { struct sae_temporary_data *tmp = sae->tmp; size_t len, pw_len; u8 *pw, *pos; int bits; u32 val = 0, val19; unsigned int val_bits = 0; if (!tmp) return -1; os_memset(tmp->fingerprint, 0, sizeof(tmp->fingerprint)); tmp->fingerprint_bytes = tmp->fingerprint_bits = 0; len = os_strlen(password); if (len < 1 || !sae_pk_valid_password(password)) return -1; pw = sae_pk_base32_decode(password, len, &pw_len); if (!pw) return -1; tmp->sec = (pw[0] & BIT(7)) ? 3 : 5; tmp->lambda = len - len / 5; tmp->fingerprint_bits = 8 * tmp->sec + 19 * tmp->lambda / 4 - 5; wpa_printf(MSG_DEBUG, "SAE-PK: Sec=%u Lambda=%zu fingerprint_bits=%zu", tmp->sec, tmp->lambda, tmp->fingerprint_bits); /* Construct Fingerprint from PasswordBase by prefixing with Sec zero * octets and skipping the Sec_1b bits */ pos = &tmp->fingerprint[tmp->sec]; bits = tmp->fingerprint_bits - 8 * tmp->sec; wpa_hexdump_key(MSG_DEBUG, "SAE-PK: PasswordBase", pw, pw_len); while (bits > 0) { if (val_bits < 8) { sae_pk_buf_shift_left_1(pw, pw_len); /* Sec_1b */ val19 = sae_pk_get_be19(pw); sae_pk_buf_shift_left_19(pw, pw_len); val = (val << 19) | val19; val_bits += 19; } if (val_bits >= 8) { if (bits < 8) break; *pos++ = (val >> (val_bits - 8)) & 0xff; val_bits -= 8; bits -= 8; } } if (bits > 0) { val >>= val_bits - bits; *pos++ = val << (8 - bits); } tmp->fingerprint_bytes = pos - tmp->fingerprint; wpa_hexdump_key(MSG_DEBUG, "SAE-PK: Fingerprint", tmp->fingerprint, tmp->fingerprint_bytes); bin_clear_free(pw, pw_len); return 0; } static size_t sae_group_2_hash_len(int group) { switch (group) { case 19: return 32; case 20: return 48; case 21: return 64; } return 0; } void sae_deinit_pk(struct sae_pk *pk) { if (pk) { wpabuf_free(pk->m); crypto_ec_key_deinit(pk->key); #ifdef CONFIG_TESTING_OPTIONS crypto_ec_key_deinit(pk->sign_key_override); #endif /* CONFIG_TESTING_OPTIONS */ wpabuf_free(pk->pubkey); os_free(pk); } } struct sae_pk * sae_parse_pk(const char *val) { struct sae_pk *pk; const char *pos; #ifdef CONFIG_TESTING_OPTIONS const char *pos2; #endif /* CONFIG_TESTING_OPTIONS */ size_t len; unsigned char *der; size_t der_len, b_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++; b_len = os_strlen(pos); #ifdef CONFIG_TESTING_OPTIONS pos2 = os_strchr(pos, ':'); if (pos2) { b_len = pos2 - pos; pos2++; } #endif /* CONFIG_TESTING_OPTIONS */ der = base64_decode(pos, b_len, &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; #ifdef CONFIG_TESTING_OPTIONS if (pos2) { der = base64_decode(pos2, os_strlen(pos2), &der_len); if (!der) { wpa_printf(MSG_INFO, "SAE: Failed to base64 decode PK key"); goto fail; } pk->sign_key_override = crypto_ec_key_parse_priv(der, der_len); bin_clear_free(der, der_len); if (!pk->sign_key_override) goto fail; } #endif /* CONFIG_TESTING_OPTIONS */ 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 *sig = NULL; size_t need; 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; struct crypto_ec_key *key; if (!tmp) return -1; pk = tmp->ap_pk; if (!sae->pk || !pk) return 0; key = pk->key; #ifdef CONFIG_TESTING_OPTIONS if (tmp->omit_pk_elem) return 0; if (pk->sign_key_override) { wpa_printf(MSG_INFO, "TESTING: Override SAE-PK signing key"); key = pk->sign_key_override; } #endif /* CONFIG_TESTING_OPTIONS */ if (tmp->kek_len != 32 && tmp->kek_len != 48 && tmp->kek_len != 64) { wpa_printf(MSG_INFO, "SAE-PK: No KEK available for writing 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_group_2_hash_len(pk->group); 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(key, hash, hash_len); if (!sig) goto fail; wpa_hexdump_buf(MSG_DEBUG, "SAE-PK: KeyAuth = Sig_AP()", sig); /* TODO: fragmentation if any of the elements needs it for a group * using sufficiently large primes (none of the currently supported * ones do) */ encr_mod_len = wpabuf_len(pk->m) + AES_BLOCK_SIZE; need = 4 + wpabuf_len(pk->pubkey) + 3 + wpabuf_len(sig) + 6 + encr_mod_len; if (wpabuf_tailroom(buf) < need) { wpa_printf(MSG_INFO, "SAE-PK: No room in message buffer for SAE-PK elements (%zu < %zu)", wpabuf_tailroom(buf), need); goto fail; } /* FILS Public Key element */ wpabuf_put_u8(buf, WLAN_EID_EXTENSION); wpabuf_put_u8(buf, 2 + wpabuf_len(pk->pubkey)); wpabuf_put_u8(buf, WLAN_EID_EXT_FILS_PUBLIC_KEY); wpabuf_put_u8(buf, 2); /* Key Type: ECDSA public key */ wpabuf_put_buf(buf, pk->pubkey); /* FILS Key Confirmation element (KeyAuth) */ wpabuf_put_u8(buf, WLAN_EID_EXTENSION); wpabuf_put_u8(buf, 1 + wpabuf_len(sig)); wpabuf_put_u8(buf, WLAN_EID_EXT_FILS_KEY_CONFIRM); /* KeyAuth = Sig_AP(eleAP || eleSTA || scaAP || scaSTA || M || K_AP || * AP-BSSID || STA-MAC) */ wpabuf_put_buf(buf, sig); /* SAE-PK element */ wpabuf_put_u8(buf, WLAN_EID_VENDOR_SPECIFIC); wpabuf_put_u8(buf, 4 + encr_mod_len); wpabuf_put_be32(buf, SAE_PK_IE_VENDOR_TYPE); /* EncryptedModifier = AES-SIV-Q(M); no AAD */ encr_mod = wpabuf_put(buf, 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); ret = 0; fail: 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, int group) { struct sae_temporary_data *tmp = sae->tmp; u8 *hash_data, *pos; size_t hash_len, hash_data_len; u8 hash[SAE_MAX_HASH_LEN]; int res; if (!tmp->fingerprint_bytes) { 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 + 19*Lambda/4 - 5) */ hash_len = sae_group_2_hash_len(group); 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); if (tmp->fingerprint_bits > hash_len * 8) { wpa_printf(MSG_INFO, "SAE-PK: Not enough hash output bits for the fingerprint"); return false; } if (tmp->fingerprint_bits % 8) { size_t extra; /* Zero out the extra bits in the last octet */ extra = 8 - tmp->fingerprint_bits % 8; pos = &hash[tmp->fingerprint_bits / 8]; *pos = (*pos >> extra) << extra; } wpa_hexdump(MSG_DEBUG, "SAE-PK: Fingerprint", hash, tmp->fingerprint_bytes); res = os_memcmp_const(hash, tmp->fingerprint, tmp->fingerprint_bytes); if (res) { wpa_printf(MSG_DEBUG, "SAE-PK: K_AP fingerprint mismatch"); wpa_hexdump(MSG_DEBUG, "SAE-PK: Expected fingerprint", tmp->fingerprint, tmp->fingerprint_bytes); 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 *k_ap; u8 m[SAE_PK_M_LEN]; size_t k_ap_len; struct crypto_ec_key *key; int res; u8 hash[SAE_MAX_HASH_LEN]; size_t hash_len; int group; struct ieee802_11_elems elems; if (!tmp) return -1; if (!sae->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 checking 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); if (ieee802_11_parse_elems(ies, ies_len, &elems, 1) == ParseFailed) { wpa_printf(MSG_INFO, "SAE-PK: Failed to parse confirm IEs"); return -1; } if (!elems.fils_pk || !elems.fils_key_confirm || !elems.sae_pk) { wpa_printf(MSG_INFO, "SAE-PK: Not all mandatory IEs included in confirm"); return -1; } /* TODO: Fragment reassembly */ if (elems.sae_pk_len < SAE_PK_M_LEN + AES_BLOCK_SIZE) { wpa_printf(MSG_INFO, "SAE-PK: No room for EncryptedModifier in SAE-PK element"); return -1; } wpa_hexdump(MSG_DEBUG, "SAE-PK: EncryptedModifier", elems.sae_pk, SAE_PK_M_LEN + AES_BLOCK_SIZE); if (aes_siv_decrypt(tmp->kek, tmp->kek_len, elems.sae_pk, 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); if (elems.fils_pk[0] != 2) { wpa_printf(MSG_INFO, "SAE-PK: Unsupported public key type %u", elems.fils_pk[0]); return -1; } k_ap_len = elems.fils_pk_len - 1; k_ap = elems.fils_pk + 1; 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 */ 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); if (!sae_pk_valid_fingerprint(sae, m, SAE_PK_M_LEN, k_ap, k_ap_len, group)) { crypto_ec_key_deinit(key); return -1; } wpa_hexdump(MSG_DEBUG, "SAE-PK: Received KeyAuth", elems.fils_key_confirm, elems.fils_key_confirm_len); hash_len = sae_group_2_hash_len(group); 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, elems.fils_key_confirm, elems.fils_key_confirm_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; }