f2e37c4ca8
1. Add changes in 4-way handshake path to allow SAE key mgmt. 2. Support for configuring WAP3 at init time, added Kconfig option. 3. Handle and propagate error conditions properly. 4. Link changes from WiFi library.
1462 lines
40 KiB
C
1462 lines
40 KiB
C
/*
|
|
* Simultaneous authentication of equals
|
|
* Copyright (c) 2012-2016, Jouni Malinen <j@w1.fi>
|
|
*
|
|
* This software may be distributed under the terms of the BSD license.
|
|
* See README for more details.
|
|
*/
|
|
|
|
#ifdef CONFIG_WPA3_SAE
|
|
|
|
#include "utils/includes.h"
|
|
#include "utils/common.h"
|
|
#include "crypto/crypto.h"
|
|
#include "crypto/sha256.h"
|
|
#include "crypto/random.h"
|
|
#include "crypto/dh_groups.h"
|
|
#include "ieee802_11_defs.h"
|
|
#include "sae.h"
|
|
#include "esp_wifi_crypto_types.h"
|
|
|
|
/*TBD Move the this api to proper files once they are taken out of lib*/
|
|
void wpabuf_clear_free(struct wpabuf *buf)
|
|
{
|
|
if (buf) {
|
|
os_memset(wpabuf_mhead(buf), 0, wpabuf_len(buf));
|
|
wpabuf_free(buf);
|
|
}
|
|
}
|
|
|
|
void bin_clear_free(void *bin, size_t len)
|
|
{
|
|
if (bin) {
|
|
os_memset(bin, 0, len);
|
|
os_free(bin);
|
|
}
|
|
}
|
|
|
|
int sae_set_group(struct sae_data *sae, int group)
|
|
{
|
|
struct sae_temporary_data *tmp;
|
|
|
|
sae_clear_data(sae);
|
|
tmp = sae->tmp = os_zalloc(sizeof(*tmp));
|
|
if (tmp == NULL)
|
|
return ESP_FAIL;
|
|
|
|
/* First, check if this is an ECC group */
|
|
tmp->ec = crypto_ec_init(group);
|
|
if (tmp->ec) {
|
|
wpa_printf(MSG_DEBUG, "SAE: Selecting supported ECC group %d",
|
|
group);
|
|
sae->group = group;
|
|
tmp->prime_len = crypto_ec_prime_len(tmp->ec);
|
|
tmp->prime = crypto_ec_get_prime(tmp->ec);
|
|
tmp->order = crypto_ec_get_order(tmp->ec);
|
|
return ESP_OK;
|
|
}
|
|
|
|
/* Not an ECC group, check FFC */
|
|
tmp->dh = dh_groups_get(group);
|
|
if (tmp->dh) {
|
|
wpa_printf(MSG_DEBUG, "SAE: Selecting supported FFC group %d",
|
|
group);
|
|
sae->group = group;
|
|
tmp->prime_len = tmp->dh->prime_len;
|
|
if (tmp->prime_len > SAE_MAX_PRIME_LEN) {
|
|
sae_clear_data(sae);
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
tmp->prime_buf = crypto_bignum_init_set(tmp->dh->prime,
|
|
tmp->prime_len);
|
|
if (tmp->prime_buf == NULL) {
|
|
sae_clear_data(sae);
|
|
return ESP_FAIL;
|
|
}
|
|
tmp->prime = tmp->prime_buf;
|
|
|
|
tmp->order_buf = crypto_bignum_init_set(tmp->dh->order,
|
|
tmp->dh->order_len);
|
|
if (tmp->order_buf == NULL) {
|
|
sae_clear_data(sae);
|
|
return ESP_FAIL;
|
|
}
|
|
tmp->order = tmp->order_buf;
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
/* Unsupported group */
|
|
wpa_printf(MSG_DEBUG,
|
|
"SAE: Group %d not supported by the crypto library", group);
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
void sae_clear_temp_data(struct sae_data *sae)
|
|
{
|
|
struct sae_temporary_data *tmp;
|
|
if (sae == NULL || sae->tmp == NULL)
|
|
return;
|
|
tmp = sae->tmp;
|
|
crypto_ec_deinit(tmp->ec);
|
|
crypto_bignum_deinit(tmp->prime_buf, 0);
|
|
crypto_bignum_deinit(tmp->order_buf, 0);
|
|
crypto_bignum_deinit(tmp->sae_rand, 1);
|
|
crypto_bignum_deinit(tmp->pwe_ffc, 1);
|
|
crypto_bignum_deinit(tmp->own_commit_scalar, 0);
|
|
crypto_bignum_deinit(tmp->own_commit_element_ffc, 0);
|
|
crypto_bignum_deinit(tmp->peer_commit_element_ffc, 0);
|
|
crypto_ec_point_deinit(tmp->pwe_ecc, 1);
|
|
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;
|
|
}
|
|
|
|
void sae_clear_data(struct sae_data *sae)
|
|
{
|
|
if (sae == NULL)
|
|
return;
|
|
sae_clear_temp_data(sae);
|
|
crypto_bignum_deinit(sae->peer_commit_scalar, 0);
|
|
os_memset(sae, 0, sizeof(*sae));
|
|
}
|
|
|
|
static void buf_shift_right(u8 *buf, size_t len, size_t bits)
|
|
{
|
|
size_t i;
|
|
for (i = len - 1; i > 0; i--)
|
|
buf[i] = (buf[i - 1] << (8 - bits)) | (buf[i] >> bits);
|
|
buf[0] >>= bits;
|
|
}
|
|
|
|
static struct crypto_bignum * sae_get_rand(struct sae_data *sae)
|
|
{
|
|
u8 val[SAE_MAX_PRIME_LEN];
|
|
int iter = 0;
|
|
struct crypto_bignum *bn = NULL;
|
|
int order_len_bits = crypto_bignum_bits(sae->tmp->order);
|
|
size_t order_len = (order_len_bits + 7) / 8;
|
|
|
|
if (order_len > sizeof(val))
|
|
return NULL;
|
|
|
|
for (;;) {
|
|
if (iter++ > 100 || random_get_bytes(val, order_len) < 0)
|
|
return NULL;
|
|
if (order_len_bits % 8)
|
|
buf_shift_right(val, order_len, 8 - order_len_bits % 8);
|
|
bn = crypto_bignum_init_set(val, order_len);
|
|
if (bn == NULL)
|
|
return NULL;
|
|
if (crypto_bignum_is_zero(bn) ||
|
|
crypto_bignum_is_one(bn) ||
|
|
crypto_bignum_cmp(bn, sae->tmp->order) >= 0) {
|
|
crypto_bignum_deinit(bn, 0);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
os_memset(val, 0, order_len);
|
|
return bn;
|
|
}
|
|
|
|
static struct crypto_bignum * sae_get_rand_and_mask(struct sae_data *sae)
|
|
{
|
|
crypto_bignum_deinit(sae->tmp->sae_rand, 1);
|
|
sae->tmp->sae_rand = sae_get_rand(sae);
|
|
if (sae->tmp->sae_rand == NULL)
|
|
return NULL;
|
|
return sae_get_rand(sae);
|
|
}
|
|
|
|
static void sae_pwd_seed_key(const u8 *addr1, const u8 *addr2, u8 *key)
|
|
{
|
|
wpa_printf(MSG_DEBUG, "SAE: PWE derivation - addr1=" MACSTR
|
|
" addr2=" MACSTR, MAC2STR(addr1), MAC2STR(addr2));
|
|
if (os_memcmp(addr1, addr2, ETH_ALEN) > 0) {
|
|
os_memcpy(key, addr1, ETH_ALEN);
|
|
os_memcpy(key + ETH_ALEN, addr2, ETH_ALEN);
|
|
} else {
|
|
os_memcpy(key, addr2, ETH_ALEN);
|
|
os_memcpy(key + ETH_ALEN, addr1, ETH_ALEN);
|
|
}
|
|
}
|
|
|
|
static struct crypto_bignum *
|
|
get_rand_1_to_p_1(const u8 *prime, size_t prime_len, size_t prime_bits,
|
|
int *r_odd)
|
|
{
|
|
for (;;) {
|
|
struct crypto_bignum *r;
|
|
u8 tmp[SAE_MAX_ECC_PRIME_LEN];
|
|
|
|
if (random_get_bytes(tmp, prime_len) < 0)
|
|
break;
|
|
if (prime_bits % 8)
|
|
buf_shift_right(tmp, prime_len, 8 - prime_bits % 8);
|
|
if (os_memcmp(tmp, prime, prime_len) >= 0)
|
|
continue;
|
|
r = crypto_bignum_init_set(tmp, prime_len);
|
|
if (!r)
|
|
break;
|
|
if (crypto_bignum_is_zero(r)) {
|
|
crypto_bignum_deinit(r, 0);
|
|
continue;
|
|
}
|
|
|
|
*r_odd = tmp[prime_len - 1] & 0x01;
|
|
return r;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int is_quadratic_residue_blind(struct sae_data *sae,
|
|
const u8 *prime, size_t bits,
|
|
const struct crypto_bignum *qr,
|
|
const struct crypto_bignum *qnr,
|
|
const struct crypto_bignum *y_sqr)
|
|
{
|
|
struct crypto_bignum *r, *num;
|
|
int r_odd, check, res = -1;
|
|
|
|
/*
|
|
* Use the blinding technique to mask y_sqr while determining
|
|
* whether it is a quadratic residue modulo p to avoid leaking
|
|
* timing information while determining the Legendre symbol.
|
|
*
|
|
* v = y_sqr
|
|
* r = a random number between 1 and p-1, inclusive
|
|
* num = (v * r * r) modulo p
|
|
*/
|
|
r = get_rand_1_to_p_1(prime, sae->tmp->prime_len, bits, &r_odd);
|
|
if (!r)
|
|
return ESP_FAIL;
|
|
|
|
num = crypto_bignum_init();
|
|
if (!num ||
|
|
crypto_bignum_mulmod(y_sqr, r, sae->tmp->prime, num) < 0 ||
|
|
crypto_bignum_mulmod(num, r, sae->tmp->prime, num) < 0)
|
|
goto fail;
|
|
|
|
if (r_odd) {
|
|
/*
|
|
* num = (num * qr) module p
|
|
* LGR(num, p) = 1 ==> quadratic residue
|
|
*/
|
|
if (crypto_bignum_mulmod(num, qr, sae->tmp->prime, num) < 0)
|
|
goto fail;
|
|
check = 1;
|
|
} else {
|
|
/*
|
|
* num = (num * qnr) module p
|
|
* LGR(num, p) = -1 ==> quadratic residue
|
|
*/
|
|
if (crypto_bignum_mulmod(num, qnr, sae->tmp->prime, num) < 0)
|
|
goto fail;
|
|
check = -1;
|
|
}
|
|
|
|
res = crypto_bignum_legendre(num, sae->tmp->prime);
|
|
if (res == -2) {
|
|
res = -1;
|
|
goto fail;
|
|
}
|
|
res = res == check;
|
|
fail:
|
|
crypto_bignum_deinit(num, 1);
|
|
crypto_bignum_deinit(r, 1);
|
|
return res;
|
|
}
|
|
|
|
static int sae_test_pwd_seed_ecc(struct sae_data *sae, const u8 *pwd_seed,
|
|
const u8 *prime,
|
|
const struct crypto_bignum *qr,
|
|
const struct crypto_bignum *qnr,
|
|
struct crypto_bignum **ret_x_cand)
|
|
{
|
|
u8 pwd_value[SAE_MAX_ECC_PRIME_LEN];
|
|
struct crypto_bignum *y_sqr, *x_cand;
|
|
int res;
|
|
size_t bits;
|
|
|
|
*ret_x_cand = NULL;
|
|
|
|
wpa_hexdump_key(MSG_DEBUG, "SAE: pwd-seed", pwd_seed, SHA256_MAC_LEN);
|
|
|
|
/* pwd-value = KDF-z(pwd-seed, "SAE Hunting and Pecking", p) */
|
|
bits = crypto_ec_prime_len_bits(sae->tmp->ec);
|
|
if (sha256_prf_bits(pwd_seed, SHA256_MAC_LEN, "SAE Hunting and Pecking",
|
|
prime, sae->tmp->prime_len, pwd_value, bits) < 0)
|
|
return ESP_FAIL;
|
|
if (bits % 8)
|
|
buf_shift_right(pwd_value, sizeof(pwd_value), 8 - bits % 8);
|
|
wpa_hexdump_key(MSG_DEBUG, "SAE: pwd-value",
|
|
pwd_value, sae->tmp->prime_len);
|
|
|
|
if (os_memcmp(pwd_value, prime, sae->tmp->prime_len) >= 0)
|
|
return ESP_OK;
|
|
|
|
x_cand = crypto_bignum_init_set(pwd_value, sae->tmp->prime_len);
|
|
if (!x_cand)
|
|
return ESP_FAIL;
|
|
y_sqr = crypto_ec_point_compute_y_sqr(sae->tmp->ec, x_cand);
|
|
if (!y_sqr) {
|
|
crypto_bignum_deinit(x_cand, 1);
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
res = is_quadratic_residue_blind(sae, prime, bits, qr, qnr, y_sqr);
|
|
crypto_bignum_deinit(y_sqr, 1);
|
|
if (res <= 0) {
|
|
crypto_bignum_deinit(x_cand, 1);
|
|
return res;
|
|
}
|
|
|
|
*ret_x_cand = x_cand;
|
|
return 1;
|
|
}
|
|
|
|
static int sae_test_pwd_seed_ffc(struct sae_data *sae, const u8 *pwd_seed,
|
|
struct crypto_bignum *pwe)
|
|
{
|
|
u8 pwd_value[SAE_MAX_PRIME_LEN];
|
|
size_t bits = sae->tmp->prime_len * 8;
|
|
u8 exp[1];
|
|
struct crypto_bignum *a, *b;
|
|
int res;
|
|
|
|
wpa_hexdump_key(MSG_DEBUG, "SAE: pwd-seed", pwd_seed, SHA256_MAC_LEN);
|
|
|
|
/* pwd-value = KDF-z(pwd-seed, "SAE Hunting and Pecking", p) */
|
|
if (sha256_prf_bits(pwd_seed, SHA256_MAC_LEN, "SAE Hunting and Pecking",
|
|
sae->tmp->dh->prime, sae->tmp->prime_len, pwd_value,
|
|
bits) < 0)
|
|
return ESP_FAIL;
|
|
wpa_hexdump_key(MSG_DEBUG, "SAE: pwd-value", pwd_value,
|
|
sae->tmp->prime_len);
|
|
|
|
if (os_memcmp(pwd_value, sae->tmp->dh->prime, sae->tmp->prime_len) >= 0)
|
|
{
|
|
wpa_printf(MSG_DEBUG, "SAE: pwd-value >= p");
|
|
return ESP_OK;
|
|
}
|
|
|
|
/* PWE = pwd-value^((p-1)/r) modulo p */
|
|
|
|
a = crypto_bignum_init_set(pwd_value, sae->tmp->prime_len);
|
|
|
|
if (sae->tmp->dh->safe_prime) {
|
|
/*
|
|
* r = (p-1)/2 for the group used here, so this becomes:
|
|
* PWE = pwd-value^2 modulo p
|
|
*/
|
|
exp[0] = 2;
|
|
b = crypto_bignum_init_set(exp, sizeof(exp));
|
|
} else {
|
|
/* Calculate exponent: (p-1)/r */
|
|
exp[0] = 1;
|
|
b = crypto_bignum_init_set(exp, sizeof(exp));
|
|
if (b == NULL ||
|
|
crypto_bignum_sub(sae->tmp->prime, b, b) < 0 ||
|
|
crypto_bignum_div(b, sae->tmp->order, b) < 0) {
|
|
crypto_bignum_deinit(b, 0);
|
|
b = NULL;
|
|
}
|
|
}
|
|
|
|
if (a == NULL || b == NULL)
|
|
res = -1;
|
|
else
|
|
res = crypto_bignum_exptmod(a, b, sae->tmp->prime, pwe);
|
|
|
|
crypto_bignum_deinit(a, 0);
|
|
crypto_bignum_deinit(b, 0);
|
|
|
|
if (res < 0) {
|
|
wpa_printf(MSG_DEBUG, "SAE: Failed to calculate PWE");
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
/* if (PWE > 1) --> found */
|
|
if (crypto_bignum_is_zero(pwe) || crypto_bignum_is_one(pwe)) {
|
|
wpa_printf(MSG_DEBUG, "SAE: PWE <= 1");
|
|
return ESP_OK;
|
|
}
|
|
|
|
wpa_printf(MSG_DEBUG, "SAE: PWE found");
|
|
return 1;
|
|
}
|
|
|
|
static int get_random_qr_qnr(const u8 *prime, size_t prime_len,
|
|
const struct crypto_bignum *prime_bn,
|
|
size_t prime_bits, struct crypto_bignum **qr,
|
|
struct crypto_bignum **qnr)
|
|
{
|
|
*qr = NULL;
|
|
*qnr = NULL;
|
|
|
|
while (!(*qr) || !(*qnr)) {
|
|
u8 tmp[SAE_MAX_ECC_PRIME_LEN];
|
|
struct crypto_bignum *q;
|
|
int res;
|
|
|
|
if (random_get_bytes(tmp, prime_len) < 0)
|
|
break;
|
|
if (prime_bits % 8)
|
|
buf_shift_right(tmp, prime_len, 8 - prime_bits % 8);
|
|
if (os_memcmp(tmp, prime, prime_len) >= 0)
|
|
continue;
|
|
q = crypto_bignum_init_set(tmp, prime_len);
|
|
if (!q)
|
|
break;
|
|
res = crypto_bignum_legendre(q, prime_bn);
|
|
|
|
if (res == 1 && !(*qr))
|
|
*qr = q;
|
|
else if (res == -1 && !(*qnr))
|
|
*qnr = q;
|
|
else
|
|
crypto_bignum_deinit(q, 0);
|
|
}
|
|
|
|
return (*qr && *qnr) ? 0 : -1;
|
|
}
|
|
|
|
static int sae_derive_pwe_ecc(struct sae_data *sae, const u8 *addr1,
|
|
const u8 *addr2, const u8 *password,
|
|
size_t password_len, const char *identifier)
|
|
{
|
|
u8 counter, k = 40;
|
|
u8 addrs[2 * ETH_ALEN];
|
|
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;
|
|
u8 prime[SAE_MAX_ECC_PRIME_LEN];
|
|
size_t prime_len;
|
|
struct crypto_bignum *x = NULL, *qr, *qnr;
|
|
size_t bits;
|
|
int res;
|
|
|
|
dummy_password_len = password_len;
|
|
if (dummy_password_len > sizeof(dummy_password))
|
|
dummy_password_len = sizeof(dummy_password);
|
|
if (random_get_bytes(dummy_password, dummy_password_len) < 0)
|
|
return ESP_FAIL;
|
|
|
|
prime_len = sae->tmp->prime_len;
|
|
if (crypto_bignum_to_bin(sae->tmp->prime, prime, sizeof(prime),
|
|
prime_len) < 0)
|
|
return ESP_FAIL;
|
|
bits = crypto_ec_prime_len_bits(sae->tmp->ec);
|
|
|
|
/*
|
|
* Create a random quadratic residue (qr) and quadratic non-residue
|
|
* (qnr) modulo p for blinding purposes during the loop.
|
|
*/
|
|
if (get_random_qr_qnr(prime, prime_len, sae->tmp->prime, bits,
|
|
&qr, &qnr) < 0)
|
|
return ESP_FAIL;
|
|
|
|
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 [|| identifier]
|
|
* pwd-seed = H(MAX(STA-A-MAC, STA-B-MAC) || MIN(STA-A-MAC, STA-B-MAC),
|
|
* base || counter)
|
|
*/
|
|
sae_pwd_seed_key(addr1, addr2, addrs);
|
|
|
|
addr[0] = password;
|
|
len[0] = password_len;
|
|
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
|
|
* attacks that attempt to determine the number of iterations required
|
|
* in the loop.
|
|
*/
|
|
for (counter = 1; counter <= k || !x; counter++) {
|
|
u8 pwd_seed[SHA256_MAC_LEN];
|
|
struct crypto_bignum *x_cand;
|
|
|
|
if (counter > 200) {
|
|
/* This should not happen in practice */
|
|
wpa_printf(MSG_DEBUG, "SAE: Failed to derive PWE");
|
|
break;
|
|
}
|
|
|
|
wpa_printf(MSG_DEBUG, "SAE: counter = %u", counter);
|
|
if (hmac_sha256_vector(addrs, sizeof(addrs), num_elem,
|
|
addr, len, pwd_seed) < 0)
|
|
break;
|
|
|
|
res = sae_test_pwd_seed_ecc(sae, pwd_seed,
|
|
prime, qr, qnr, &x_cand);
|
|
if (res < 0)
|
|
goto fail;
|
|
if (res > 0 && !x) {
|
|
wpa_printf(MSG_DEBUG,
|
|
"SAE: Selected pwd-seed with counter %u",
|
|
counter);
|
|
x = x_cand;
|
|
pwd_seed_odd = pwd_seed[SHA256_MAC_LEN - 1] & 0x01;
|
|
os_memset(pwd_seed, 0, sizeof(pwd_seed));
|
|
|
|
/*
|
|
* Use a dummy password for the following rounds, if
|
|
* any.
|
|
*/
|
|
addr[0] = dummy_password;
|
|
len[0] = dummy_password_len;
|
|
} else if (res > 0) {
|
|
crypto_bignum_deinit(x_cand, 1);
|
|
}
|
|
}
|
|
|
|
if (!x) {
|
|
wpa_printf(MSG_DEBUG, "SAE: Could not generate PWE");
|
|
res = -1;
|
|
goto fail;
|
|
}
|
|
|
|
if (!sae->tmp->pwe_ecc)
|
|
sae->tmp->pwe_ecc = crypto_ec_point_init(sae->tmp->ec);
|
|
if (!sae->tmp->pwe_ecc)
|
|
res = -1;
|
|
else
|
|
res = crypto_ec_point_solve_y_coord(sae->tmp->ec,
|
|
sae->tmp->pwe_ecc, x,
|
|
pwd_seed_odd);
|
|
crypto_bignum_deinit(x, 1);
|
|
if (res < 0) {
|
|
/*
|
|
* This should not happen since we already checked that there
|
|
* is a result.
|
|
*/
|
|
wpa_printf(MSG_DEBUG, "SAE: Could not solve y");
|
|
}
|
|
|
|
fail:
|
|
crypto_bignum_deinit(qr, 0);
|
|
crypto_bignum_deinit(qnr, 0);
|
|
|
|
return res;
|
|
}
|
|
|
|
static int sae_derive_pwe_ffc(struct sae_data *sae, const u8 *addr1,
|
|
const u8 *addr2, const u8 *password,
|
|
size_t password_len, const char *identifier)
|
|
{
|
|
u8 counter;
|
|
u8 addrs[2 * ETH_ALEN];
|
|
const u8 *addr[3];
|
|
size_t len[3];
|
|
size_t num_elem;
|
|
int found = 0;
|
|
|
|
if (sae->tmp->pwe_ffc == NULL) {
|
|
sae->tmp->pwe_ffc = crypto_bignum_init();
|
|
if (sae->tmp->pwe_ffc == NULL)
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
wpa_hexdump_ascii_key(MSG_DEBUG, "SAE: password",
|
|
password, password_len);
|
|
|
|
/*
|
|
* 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 [|| identifier] || counter)
|
|
*/
|
|
sae_pwd_seed_key(addr1, addr2, addrs);
|
|
|
|
addr[0] = password;
|
|
len[0] = password_len;
|
|
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];
|
|
int res;
|
|
|
|
if (counter > 200) {
|
|
/* This should not happen in practice */
|
|
wpa_printf(MSG_DEBUG, "SAE: Failed to derive PWE");
|
|
break;
|
|
}
|
|
|
|
wpa_printf(MSG_DEBUG, "SAE: counter = %u", counter);
|
|
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)
|
|
break;
|
|
if (res > 0) {
|
|
wpa_printf(MSG_DEBUG, "SAE: Use this PWE");
|
|
found = 1;
|
|
}
|
|
}
|
|
|
|
return found ? 0 : -1;
|
|
}
|
|
|
|
static int sae_derive_commit_element_ecc(struct sae_data *sae,
|
|
struct crypto_bignum *mask)
|
|
{
|
|
/* COMMIT-ELEMENT = inverse(scalar-op(mask, PWE)) */
|
|
if (!sae->tmp->own_commit_element_ecc) {
|
|
sae->tmp->own_commit_element_ecc =
|
|
crypto_ec_point_init(sae->tmp->ec);
|
|
if (!sae->tmp->own_commit_element_ecc)
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
if (crypto_ec_point_mul(sae->tmp->ec, sae->tmp->pwe_ecc, mask,
|
|
sae->tmp->own_commit_element_ecc) < 0 ||
|
|
crypto_ec_point_invert(sae->tmp->ec,
|
|
sae->tmp->own_commit_element_ecc) < 0) {
|
|
wpa_printf(MSG_DEBUG, "SAE: Could not compute commit-element");
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
static int sae_derive_commit_element_ffc(struct sae_data *sae,
|
|
struct crypto_bignum *mask)
|
|
{
|
|
/* COMMIT-ELEMENT = inverse(scalar-op(mask, PWE)) */
|
|
if (!sae->tmp->own_commit_element_ffc) {
|
|
sae->tmp->own_commit_element_ffc = crypto_bignum_init();
|
|
if (!sae->tmp->own_commit_element_ffc)
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
if (crypto_bignum_exptmod(sae->tmp->pwe_ffc, mask, sae->tmp->prime,
|
|
sae->tmp->own_commit_element_ffc) < 0 ||
|
|
crypto_bignum_inverse(sae->tmp->own_commit_element_ffc,
|
|
sae->tmp->prime,
|
|
sae->tmp->own_commit_element_ffc) < 0) {
|
|
wpa_printf(MSG_DEBUG, "SAE: Could not compute commit-element");
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
static int sae_derive_commit(struct sae_data *sae)
|
|
{
|
|
struct crypto_bignum *mask;
|
|
int ret = -1;
|
|
unsigned int counter = 0;
|
|
|
|
do {
|
|
counter++;
|
|
if (counter > 100) {
|
|
/*
|
|
* This cannot really happen in practice if the random
|
|
* number generator is working. Anyway, to avoid even a
|
|
* theoretical infinite loop, break out after 100
|
|
* attemps.
|
|
*/
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
mask = sae_get_rand_and_mask(sae);
|
|
if (mask == NULL) {
|
|
wpa_printf(MSG_DEBUG, "SAE: Could not get rand/mask");
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
/* commit-scalar = (rand + mask) modulo r */
|
|
if (!sae->tmp->own_commit_scalar) {
|
|
sae->tmp->own_commit_scalar = crypto_bignum_init();
|
|
if (!sae->tmp->own_commit_scalar)
|
|
goto fail;
|
|
}
|
|
crypto_bignum_add(sae->tmp->sae_rand, mask,
|
|
sae->tmp->own_commit_scalar);
|
|
crypto_bignum_mod(sae->tmp->own_commit_scalar, sae->tmp->order,
|
|
sae->tmp->own_commit_scalar);
|
|
} while (crypto_bignum_is_zero(sae->tmp->own_commit_scalar) ||
|
|
crypto_bignum_is_one(sae->tmp->own_commit_scalar));
|
|
|
|
if ((sae->tmp->ec && sae_derive_commit_element_ecc(sae, mask) < 0) ||
|
|
(sae->tmp->dh && sae_derive_commit_element_ffc(sae, mask) < 0))
|
|
goto fail;
|
|
|
|
ret = 0;
|
|
fail:
|
|
crypto_bignum_deinit(mask, 1);
|
|
return ret;
|
|
}
|
|
|
|
int sae_prepare_commit(const u8 *addr1, const u8 *addr2,
|
|
const u8 *password, size_t password_len,
|
|
const char *identifier, struct sae_data *sae)
|
|
{
|
|
if (sae->tmp == NULL ||
|
|
(sae->tmp->ec && sae_derive_pwe_ecc(sae, addr1, addr2, password,
|
|
password_len,
|
|
identifier) < 0) ||
|
|
(sae->tmp->dh && sae_derive_pwe_ffc(sae, addr1, addr2, password,
|
|
password_len,
|
|
identifier) < 0) ||
|
|
sae_derive_commit(sae) < 0)
|
|
return ESP_FAIL;
|
|
return ESP_OK;
|
|
}
|
|
|
|
static int sae_derive_k_ecc(struct sae_data *sae, u8 *k)
|
|
{
|
|
struct crypto_ec_point *K;
|
|
int ret = -1;
|
|
|
|
K = crypto_ec_point_init(sae->tmp->ec);
|
|
if (K == NULL)
|
|
goto fail;
|
|
|
|
/*
|
|
* K = scalar-op(rand, (elem-op(scalar-op(peer-commit-scalar, PWE),
|
|
* PEER-COMMIT-ELEMENT)))
|
|
* If K is identity element (point-at-infinity), reject
|
|
* k = F(K) (= x coordinate)
|
|
*/
|
|
|
|
if (crypto_ec_point_mul(sae->tmp->ec, sae->tmp->pwe_ecc,
|
|
sae->peer_commit_scalar, K) < 0 ||
|
|
crypto_ec_point_add(sae->tmp->ec, K,
|
|
sae->tmp->peer_commit_element_ecc, K) < 0 ||
|
|
crypto_ec_point_mul(sae->tmp->ec, K, sae->tmp->sae_rand, K) < 0 ||
|
|
crypto_ec_point_is_at_infinity(sae->tmp->ec, K) ||
|
|
crypto_ec_point_to_bin(sae->tmp->ec, K, k, NULL) < 0) {
|
|
wpa_printf(MSG_DEBUG, "SAE: Failed to calculate K and k");
|
|
goto fail;
|
|
}
|
|
|
|
wpa_hexdump_key(MSG_DEBUG, "SAE: k", k, sae->tmp->prime_len);
|
|
|
|
ret = 0;
|
|
fail:
|
|
crypto_ec_point_deinit(K, 1);
|
|
return ret;
|
|
}
|
|
|
|
static int sae_derive_k_ffc(struct sae_data *sae, u8 *k)
|
|
{
|
|
struct crypto_bignum *K;
|
|
int ret = -1;
|
|
|
|
K = crypto_bignum_init();
|
|
if (K == NULL)
|
|
goto fail;
|
|
|
|
/*
|
|
* K = scalar-op(rand, (elem-op(scalar-op(peer-commit-scalar, PWE),
|
|
* PEER-COMMIT-ELEMENT)))
|
|
* If K is identity element (one), reject.
|
|
* k = F(K) (= x coordinate)
|
|
*/
|
|
|
|
if (crypto_bignum_exptmod(sae->tmp->pwe_ffc, sae->peer_commit_scalar,
|
|
sae->tmp->prime, K) < 0 ||
|
|
crypto_bignum_mulmod(K, sae->tmp->peer_commit_element_ffc,
|
|
sae->tmp->prime, K) < 0 ||
|
|
crypto_bignum_exptmod(K, sae->tmp->sae_rand, sae->tmp->prime, K) < 0
|
|
||
|
|
crypto_bignum_is_one(K) ||
|
|
crypto_bignum_to_bin(K, k, SAE_MAX_PRIME_LEN, sae->tmp->prime_len) <
|
|
0) {
|
|
wpa_printf(MSG_DEBUG, "SAE: Failed to calculate K and k");
|
|
goto fail;
|
|
}
|
|
|
|
wpa_hexdump_key(MSG_DEBUG, "SAE: k", k, sae->tmp->prime_len);
|
|
|
|
ret = 0;
|
|
fail:
|
|
crypto_bignum_deinit(K, 1);
|
|
return ret;
|
|
}
|
|
|
|
static int sae_derive_keys(struct sae_data *sae, const u8 *k)
|
|
{
|
|
u8 null_key[SAE_KEYSEED_KEY_LEN], val[SAE_MAX_PRIME_LEN];
|
|
u8 keyseed[SHA256_MAC_LEN];
|
|
u8 keys[SAE_KCK_LEN + SAE_PMK_LEN];
|
|
struct crypto_bignum *tmp;
|
|
int ret = -1;
|
|
|
|
tmp = crypto_bignum_init();
|
|
if (tmp == NULL)
|
|
goto fail;
|
|
|
|
/* keyseed = H(<0>32, k)
|
|
* KCK || PMK = KDF-512(keyseed, "SAE KCK and PMK",
|
|
* (commit-scalar + peer-commit-scalar) modulo r)
|
|
* PMKID = L((commit-scalar + peer-commit-scalar) modulo r, 0, 128)
|
|
*/
|
|
|
|
os_memset(null_key, 0, sizeof(null_key));
|
|
hmac_sha256(null_key, sizeof(null_key), k, sae->tmp->prime_len,
|
|
keyseed);
|
|
wpa_hexdump_key(MSG_DEBUG, "SAE: keyseed", keyseed, sizeof(keyseed));
|
|
|
|
crypto_bignum_add(sae->tmp->own_commit_scalar, sae->peer_commit_scalar,
|
|
tmp);
|
|
crypto_bignum_mod(tmp, sae->tmp->order, tmp);
|
|
crypto_bignum_to_bin(tmp, val, sizeof(val), sae->tmp->prime_len);
|
|
wpa_hexdump(MSG_DEBUG, "SAE: PMKID", val, SAE_PMKID_LEN);
|
|
if (sha256_prf(keyseed, sizeof(keyseed), "SAE KCK and PMK",
|
|
val, sae->tmp->prime_len, keys, sizeof(keys)) < 0)
|
|
goto fail;
|
|
os_memset(keyseed, 0, sizeof(keyseed));
|
|
os_memcpy(sae->tmp->kck, keys, SAE_KCK_LEN);
|
|
os_memcpy(sae->pmk, keys + SAE_KCK_LEN, SAE_PMK_LEN);
|
|
os_memcpy(sae->pmkid, val, SAE_PMKID_LEN);
|
|
os_memset(keys, 0, sizeof(keys));
|
|
wpa_hexdump_key(MSG_DEBUG, "SAE: KCK", sae->tmp->kck, SAE_KCK_LEN);
|
|
wpa_hexdump_key(MSG_DEBUG, "SAE: PMK", sae->pmk, SAE_PMK_LEN);
|
|
|
|
ret = 0;
|
|
fail:
|
|
crypto_bignum_deinit(tmp, 0);
|
|
return ret;
|
|
}
|
|
|
|
int sae_process_commit(struct sae_data *sae)
|
|
{
|
|
u8 k[SAE_MAX_PRIME_LEN];
|
|
if (sae->tmp == NULL ||
|
|
(sae->tmp->ec && sae_derive_k_ecc(sae, k) < 0) ||
|
|
(sae->tmp->dh && sae_derive_k_ffc(sae, k) < 0) ||
|
|
sae_derive_keys(sae, k) < 0)
|
|
return ESP_FAIL;
|
|
return ESP_OK;
|
|
}
|
|
|
|
int sae_write_commit(struct sae_data *sae, struct wpabuf *buf,
|
|
const struct wpabuf *token, const char *identifier)
|
|
{
|
|
u8 *pos;
|
|
|
|
if (sae->tmp == NULL)
|
|
return ESP_FAIL;
|
|
|
|
wpabuf_put_le16(buf, sae->group); /* Finite Cyclic Group */
|
|
if (token) {
|
|
wpabuf_put_buf(buf, token);
|
|
wpa_hexdump(MSG_DEBUG, "SAE: Anti-clogging token",
|
|
wpabuf_head(token), wpabuf_len(token));
|
|
}
|
|
pos = wpabuf_put(buf, sae->tmp->prime_len);
|
|
if (crypto_bignum_to_bin(sae->tmp->own_commit_scalar, pos,
|
|
sae->tmp->prime_len, sae->tmp->prime_len) < 0) {
|
|
wpa_printf(MSG_ERROR, "SAE: failed bignum operation on own commit scalar");
|
|
return ESP_FAIL;
|
|
}
|
|
wpa_hexdump(MSG_DEBUG, "SAE: own commit-scalar",
|
|
pos, sae->tmp->prime_len);
|
|
if (sae->tmp->ec) {
|
|
pos = wpabuf_put(buf, 2 * sae->tmp->prime_len);
|
|
if (crypto_ec_point_to_bin(sae->tmp->ec,
|
|
sae->tmp->own_commit_element_ecc,
|
|
pos, pos + sae->tmp->prime_len) < 0) {
|
|
wpa_printf(MSG_ERROR, "SAE: failed bignum op while deriving ec point");
|
|
return ESP_FAIL;
|
|
}
|
|
wpa_hexdump(MSG_DEBUG, "SAE: own commit-element(x)",
|
|
pos, sae->tmp->prime_len);
|
|
wpa_hexdump(MSG_DEBUG, "SAE: own commit-element(y)",
|
|
pos + sae->tmp->prime_len, sae->tmp->prime_len);
|
|
} else {
|
|
pos = wpabuf_put(buf, sae->tmp->prime_len);
|
|
if (crypto_bignum_to_bin(sae->tmp->own_commit_element_ffc, pos,
|
|
sae->tmp->prime_len, sae->tmp->prime_len) < 0) {
|
|
wpa_printf(MSG_ERROR, "SAE: failed bignum operation on commit elem ffc");
|
|
return ESP_FAIL;
|
|
}
|
|
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);
|
|
}
|
|
return ESP_OK;
|
|
}
|
|
|
|
u16 sae_group_allowed(struct sae_data *sae, int *allowed_groups, u16 group)
|
|
{
|
|
if (allowed_groups) {
|
|
int i;
|
|
for (i = 0; allowed_groups[i] > 0; i++) {
|
|
if (allowed_groups[i] == group)
|
|
break;
|
|
}
|
|
if (allowed_groups[i] != group) {
|
|
wpa_printf(MSG_DEBUG, "SAE: Proposed group %u not "
|
|
"enabled in the current configuration",
|
|
group);
|
|
return WLAN_STATUS_FINITE_CYCLIC_GROUP_NOT_SUPPORTED;
|
|
}
|
|
}
|
|
|
|
if (sae->state == SAE_COMMITTED && group != sae->group) {
|
|
wpa_printf(MSG_DEBUG, "SAE: Do not allow group to be changed");
|
|
return WLAN_STATUS_FINITE_CYCLIC_GROUP_NOT_SUPPORTED;
|
|
}
|
|
|
|
if (group != sae->group && sae_set_group(sae, group) < 0) {
|
|
wpa_printf(MSG_DEBUG, "SAE: Unsupported Finite Cyclic Group %u",
|
|
group);
|
|
return WLAN_STATUS_FINITE_CYCLIC_GROUP_NOT_SUPPORTED;
|
|
}
|
|
|
|
if (sae->tmp == NULL) {
|
|
wpa_printf(MSG_DEBUG, "SAE: Group information not yet initialized");
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
}
|
|
|
|
if (sae->tmp->dh && !allowed_groups) {
|
|
wpa_printf(MSG_DEBUG, "SAE: Do not allow FFC group %u without "
|
|
"explicit configuration enabling it", group);
|
|
return WLAN_STATUS_FINITE_CYCLIC_GROUP_NOT_SUPPORTED;
|
|
}
|
|
|
|
return WLAN_STATUS_SUCCESS;
|
|
}
|
|
|
|
static int sae_is_password_id_elem(const u8 *pos, const u8 *end)
|
|
{
|
|
int ret = end - pos >= 3 &&
|
|
pos[0] == WLAN_EID_EXTENSION &&
|
|
pos[1] >= 1 &&
|
|
end - pos - 2 >= pos[1] &&
|
|
pos[2] == WLAN_EID_EXT_PASSWORD_IDENTIFIER;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void sae_parse_commit_token(struct sae_data *sae, const u8 **pos,
|
|
const u8 *end, const u8 **token,
|
|
size_t *token_len)
|
|
{
|
|
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;
|
|
}
|
|
|
|
static u16 sae_parse_commit_scalar(struct sae_data *sae, const u8 **pos,
|
|
const u8 *end)
|
|
{
|
|
struct crypto_bignum *peer_scalar;
|
|
|
|
if (sae->tmp->prime_len > end - *pos) {
|
|
wpa_printf(MSG_DEBUG, "SAE: Not enough data for scalar");
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
}
|
|
|
|
peer_scalar = crypto_bignum_init_set(*pos, sae->tmp->prime_len);
|
|
if (peer_scalar == NULL)
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
|
|
/*
|
|
* IEEE Std 802.11-2012, 11.3.8.6.1: If there is a protocol instance for
|
|
* the peer and it is in Authenticated state, the new Commit Message
|
|
* shall be dropped if the peer-scalar is identical to the one used in
|
|
* the existing protocol instance.
|
|
*/
|
|
if (sae->state == SAE_ACCEPTED && sae->peer_commit_scalar &&
|
|
crypto_bignum_cmp(sae->peer_commit_scalar, peer_scalar) == 0) {
|
|
wpa_printf(MSG_DEBUG, "SAE: Do not accept re-use of previous "
|
|
"peer-commit-scalar");
|
|
crypto_bignum_deinit(peer_scalar, 0);
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
}
|
|
|
|
/* 1 < scalar < r */
|
|
if (crypto_bignum_is_zero(peer_scalar) ||
|
|
crypto_bignum_is_one(peer_scalar) ||
|
|
crypto_bignum_cmp(peer_scalar, sae->tmp->order) >= 0) {
|
|
wpa_printf(MSG_DEBUG, "SAE: Invalid peer scalar");
|
|
crypto_bignum_deinit(peer_scalar, 0);
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
}
|
|
|
|
crypto_bignum_deinit(sae->peer_commit_scalar, 0);
|
|
sae->peer_commit_scalar = peer_scalar;
|
|
wpa_hexdump(MSG_DEBUG, "SAE: Peer commit-scalar",
|
|
*pos, sae->tmp->prime_len);
|
|
*pos += sae->tmp->prime_len;
|
|
|
|
return WLAN_STATUS_SUCCESS;
|
|
}
|
|
|
|
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) {
|
|
wpa_printf(MSG_DEBUG, "SAE: Not enough data for "
|
|
"commit-element");
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
}
|
|
|
|
if (crypto_bignum_to_bin(sae->tmp->prime, prime, sizeof(prime),
|
|
sae->tmp->prime_len) < 0)
|
|
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,
|
|
sae->tmp->prime_len) >= 0) {
|
|
wpa_printf(MSG_DEBUG, "SAE: Invalid coordinates in peer "
|
|
"element");
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
}
|
|
|
|
wpa_hexdump(MSG_DEBUG, "SAE: Peer commit-element(x)",
|
|
*pos, sae->tmp->prime_len);
|
|
wpa_hexdump(MSG_DEBUG, "SAE: Peer commit-element(y)",
|
|
*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);
|
|
if (sae->tmp->peer_commit_element_ecc == NULL)
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
|
|
if (!crypto_ec_point_is_on_curve(sae->tmp->ec,
|
|
sae->tmp->peer_commit_element_ecc)) {
|
|
wpa_printf(MSG_DEBUG, "SAE: Peer element is not on curve");
|
|
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,
|
|
const u8 *end)
|
|
{
|
|
struct crypto_bignum *res, *one;
|
|
const u8 one_bin[1] = { 0x01 };
|
|
|
|
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,
|
|
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);
|
|
if (sae->tmp->peer_commit_element_ffc == NULL)
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
/* 1 < element < p - 1 */
|
|
res = crypto_bignum_init();
|
|
one = crypto_bignum_init_set(one_bin, sizeof(one_bin));
|
|
if (!res || !one ||
|
|
crypto_bignum_sub(sae->tmp->prime, one, res) ||
|
|
crypto_bignum_is_zero(sae->tmp->peer_commit_element_ffc) ||
|
|
crypto_bignum_is_one(sae->tmp->peer_commit_element_ffc) ||
|
|
crypto_bignum_cmp(sae->tmp->peer_commit_element_ffc, res) >= 0) {
|
|
crypto_bignum_deinit(res, 0);
|
|
crypto_bignum_deinit(one, 0);
|
|
wpa_printf(MSG_DEBUG, "SAE: Invalid peer element");
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
}
|
|
crypto_bignum_deinit(one, 0);
|
|
|
|
/* scalar-op(r, ELEMENT) = 1 modulo p */
|
|
if (crypto_bignum_exptmod(sae->tmp->peer_commit_element_ffc,
|
|
sae->tmp->order, sae->tmp->prime, res) < 0 ||
|
|
!crypto_bignum_is_one(res)) {
|
|
wpa_printf(MSG_DEBUG, "SAE: Invalid peer element (scalar-op)");
|
|
crypto_bignum_deinit(res, 0);
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
}
|
|
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,
|
|
const u8 *end)
|
|
{
|
|
if (sae->tmp->dh)
|
|
return sae_parse_commit_element_ffc(sae, pos, end);
|
|
return sae_parse_commit_element_ecc(sae, pos, end);
|
|
}
|
|
|
|
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';
|
|
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)
|
|
{
|
|
const u8 *pos = data, *end = data + len;
|
|
u16 res;
|
|
|
|
/* Check Finite Cyclic Group */
|
|
if (end - pos < 2)
|
|
return WLAN_STATUS_UNSPECIFIED_FAILURE;
|
|
res = sae_group_allowed(sae, allowed_groups, WPA_GET_LE16(pos));
|
|
if (res != WLAN_STATUS_SUCCESS)
|
|
return res;
|
|
pos += 2;
|
|
|
|
/* Optional Anti-Clogging Token */
|
|
sae_parse_commit_token(sae, &pos, end, token, token_len);
|
|
|
|
/* commit-scalar */
|
|
res = sae_parse_commit_scalar(sae, &pos, end);
|
|
if (res != WLAN_STATUS_SUCCESS)
|
|
return res;
|
|
|
|
/* commit-element */
|
|
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;
|
|
|
|
/*
|
|
* Check whether peer-commit-scalar and PEER-COMMIT-ELEMENT are same as
|
|
* the values we sent which would be evidence of a reflection attack.
|
|
*/
|
|
if (!sae->tmp->own_commit_scalar ||
|
|
crypto_bignum_cmp(sae->tmp->own_commit_scalar,
|
|
sae->peer_commit_scalar) != 0 ||
|
|
(sae->tmp->dh &&
|
|
(!sae->tmp->own_commit_element_ffc ||
|
|
crypto_bignum_cmp(sae->tmp->own_commit_element_ffc,
|
|
sae->tmp->peer_commit_element_ffc) != 0)) ||
|
|
(sae->tmp->ec &&
|
|
(!sae->tmp->own_commit_element_ecc ||
|
|
crypto_ec_point_cmp(sae->tmp->ec,
|
|
sae->tmp->own_commit_element_ecc,
|
|
sae->tmp->peer_commit_element_ecc) != 0)))
|
|
return WLAN_STATUS_SUCCESS; /* scalars/elements are different */
|
|
|
|
/*
|
|
* This is a reflection attack - return special value to trigger caller
|
|
* to silently discard the frame instead of replying with a specific
|
|
* status code.
|
|
*/
|
|
return SAE_SILENTLY_DISCARD;
|
|
}
|
|
|
|
static void sae_cn_confirm(struct sae_data *sae, const u8 *sc,
|
|
const struct crypto_bignum *scalar1,
|
|
const u8 *element1, size_t element1_len,
|
|
const struct crypto_bignum *scalar2,
|
|
const u8 *element2, size_t element2_len,
|
|
u8 *confirm)
|
|
{
|
|
const u8 *addr[5];
|
|
size_t len[5];
|
|
u8 scalar_b1[SAE_MAX_PRIME_LEN], scalar_b2[SAE_MAX_PRIME_LEN];
|
|
|
|
/* Confirm
|
|
* CN(key, X, Y, Z, ...) =
|
|
* HMAC-SHA256(key, D2OS(X) || D2OS(Y) || D2OS(Z) | ...)
|
|
* confirm = CN(KCK, send-confirm, commit-scalar, COMMIT-ELEMENT,
|
|
* peer-commit-scalar, PEER-COMMIT-ELEMENT)
|
|
* verifier = CN(KCK, peer-send-confirm, peer-commit-scalar,
|
|
* PEER-COMMIT-ELEMENT, commit-scalar, COMMIT-ELEMENT)
|
|
*/
|
|
addr[0] = sc;
|
|
len[0] = 2;
|
|
crypto_bignum_to_bin(scalar1, scalar_b1, sizeof(scalar_b1),
|
|
sae->tmp->prime_len);
|
|
addr[1] = scalar_b1;
|
|
len[1] = sae->tmp->prime_len;
|
|
addr[2] = element1;
|
|
len[2] = element1_len;
|
|
crypto_bignum_to_bin(scalar2, scalar_b2, sizeof(scalar_b2),
|
|
sae->tmp->prime_len);
|
|
addr[3] = scalar_b2;
|
|
len[3] = sae->tmp->prime_len;
|
|
addr[4] = element2;
|
|
len[4] = element2_len;
|
|
hmac_sha256_vector(sae->tmp->kck, sizeof(sae->tmp->kck), 5, addr, len,
|
|
confirm);
|
|
}
|
|
|
|
static int sae_cn_confirm_ecc(struct sae_data *sae, const u8 *sc,
|
|
const struct crypto_bignum *scalar1,
|
|
const struct crypto_ec_point *element1,
|
|
const struct crypto_bignum *scalar2,
|
|
const struct crypto_ec_point *element2,
|
|
u8 *confirm)
|
|
{
|
|
u8 element_b1[2 * SAE_MAX_ECC_PRIME_LEN];
|
|
u8 element_b2[2 * SAE_MAX_ECC_PRIME_LEN];
|
|
|
|
if (crypto_ec_point_to_bin(sae->tmp->ec, element1, element_b1,
|
|
element_b1 + sae->tmp->prime_len) < 0) {
|
|
wpa_printf(MSG_ERROR, "SAE: failed bignum op while deriving ec point");
|
|
return ESP_FAIL;
|
|
}
|
|
if (crypto_ec_point_to_bin(sae->tmp->ec, element2, element_b2,
|
|
element_b2 + sae->tmp->prime_len) < 0) {
|
|
wpa_printf(MSG_ERROR, "SAE: failed bignum op while deriving ec point");
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
sae_cn_confirm(sae, sc, scalar1, element_b1, 2 * sae->tmp->prime_len,
|
|
scalar2, element_b2, 2 * sae->tmp->prime_len, confirm);
|
|
return ESP_OK;
|
|
}
|
|
|
|
static int sae_cn_confirm_ffc(struct sae_data *sae, const u8 *sc,
|
|
const struct crypto_bignum *scalar1,
|
|
const struct crypto_bignum *element1,
|
|
const struct crypto_bignum *scalar2,
|
|
const struct crypto_bignum *element2,
|
|
u8 *confirm)
|
|
{
|
|
u8 element_b1[SAE_MAX_PRIME_LEN];
|
|
u8 element_b2[SAE_MAX_PRIME_LEN];
|
|
|
|
if (crypto_bignum_to_bin(element1, element_b1, sizeof(element_b1),
|
|
sae->tmp->prime_len) < 0) {
|
|
wpa_printf(MSG_ERROR, "SAE: failed bignum op while generating SAE confirm - e1");
|
|
return ESP_FAIL;
|
|
}
|
|
if (crypto_bignum_to_bin(element2, element_b2, sizeof(element_b2),
|
|
sae->tmp->prime_len) < 0) {
|
|
wpa_printf(MSG_ERROR, "SAE: failed bignum op while generating SAE confirm - e2");
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
sae_cn_confirm(sae, sc, scalar1, element_b1, sae->tmp->prime_len,
|
|
scalar2, element_b2, sae->tmp->prime_len, confirm);
|
|
return ESP_OK;
|
|
}
|
|
|
|
int sae_write_confirm(struct sae_data *sae, struct wpabuf *buf)
|
|
{
|
|
const u8 *sc;
|
|
|
|
if (sae->tmp == NULL)
|
|
return ESP_FAIL;
|
|
|
|
/* Send-Confirm */
|
|
sc = wpabuf_put(buf, 0);
|
|
wpabuf_put_le16(buf, sae->send_confirm);
|
|
if (sae->send_confirm < 0xffff)
|
|
sae->send_confirm++;
|
|
|
|
if (sae->tmp->ec) {
|
|
if (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, SHA256_MAC_LEN))) {
|
|
wpa_printf(MSG_ERROR, "SAE: failed generate SAE confirm (ecc)");
|
|
return ESP_FAIL;
|
|
}
|
|
} else {
|
|
if (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, SHA256_MAC_LEN))) {
|
|
wpa_printf(MSG_ERROR, "SAE: failed generate SAE confirm (ffc)");
|
|
return ESP_FAIL;
|
|
}
|
|
}
|
|
return ESP_OK;
|
|
}
|
|
|
|
int sae_check_confirm(struct sae_data *sae, const u8 *data, size_t len)
|
|
{
|
|
u8 verifier[SHA256_MAC_LEN];
|
|
|
|
if (len < 2 + SHA256_MAC_LEN) {
|
|
wpa_printf(MSG_DEBUG, "SAE: Too short confirm message");
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
wpa_printf(MSG_DEBUG, "SAE: peer-send-confirm %u", WPA_GET_LE16(data));
|
|
|
|
if (sae->tmp == NULL) {
|
|
wpa_printf(MSG_DEBUG, "SAE: Temporary data not yet available");
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
if (sae->tmp->ec) {
|
|
if (sae_cn_confirm_ecc(sae, data, sae->peer_commit_scalar,
|
|
sae->tmp->peer_commit_element_ecc,
|
|
sae->tmp->own_commit_scalar,
|
|
sae->tmp->own_commit_element_ecc,
|
|
verifier)) {
|
|
wpa_printf(MSG_ERROR, "SAE: failed to check SAE confirm (ecc)");
|
|
return ESP_FAIL;
|
|
}
|
|
} else {
|
|
if (sae_cn_confirm_ffc(sae, data, sae->peer_commit_scalar,
|
|
sae->tmp->peer_commit_element_ffc,
|
|
sae->tmp->own_commit_scalar,
|
|
sae->tmp->own_commit_element_ffc,
|
|
verifier)) {
|
|
wpa_printf(MSG_ERROR, "SAE: failed check SAE confirm (ffc)");
|
|
return ESP_FAIL;
|
|
}
|
|
}
|
|
|
|
if (os_memcmp(verifier, data + 2, SHA256_MAC_LEN) != 0) {
|
|
wpa_printf(MSG_DEBUG, "SAE: Confirm mismatch");
|
|
wpa_hexdump(MSG_DEBUG, "SAE: Received confirm",
|
|
data + 2, SHA256_MAC_LEN);
|
|
wpa_hexdump(MSG_DEBUG, "SAE: Calculated verifier",
|
|
verifier, SHA256_MAC_LEN);
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
const char * sae_state_txt(enum sae_state state)
|
|
{
|
|
switch (state) {
|
|
case SAE_NOTHING:
|
|
return "Nothing";
|
|
case SAE_COMMITTED:
|
|
return "Committed";
|
|
case SAE_CONFIRMED:
|
|
return "Confirmed";
|
|
case SAE_ACCEPTED:
|
|
return "Accepted";
|
|
}
|
|
return "?";
|
|
}
|
|
|
|
#endif /* CONFIG_WPA3_SAE */
|