SAE: Derive the y coordinate for PWE with own implementation

The crypto_ec_point_solve_y_coord() wrapper function might not use
constant time operations in the crypto library and as such, could leak
side channel information about the password that is used to generate the
PWE in the hunting and pecking loop. As such, calculate the two possible
y coordinate values and pick the correct one to use with constant time
selection.

Signed-off-by: Jouni Malinen <j@w1.fi>
This commit is contained in:
Jouni Malinen 2022-01-07 13:47:16 +02:00
parent 8ebd8aacc2
commit 6c380f4c87

View file

@ -290,14 +290,16 @@ static int sae_derive_pwe_ecc(struct sae_data *sae, const u8 *addr1,
int pwd_seed_odd = 0; int pwd_seed_odd = 0;
u8 prime[SAE_MAX_ECC_PRIME_LEN]; u8 prime[SAE_MAX_ECC_PRIME_LEN];
size_t prime_len; size_t prime_len;
struct crypto_bignum *x = NULL, *qr = NULL, *qnr = NULL; struct crypto_bignum *x = NULL, *y = NULL, *qr = NULL, *qnr = NULL;
u8 x_bin[SAE_MAX_ECC_PRIME_LEN]; u8 x_bin[SAE_MAX_ECC_PRIME_LEN];
u8 x_cand_bin[SAE_MAX_ECC_PRIME_LEN]; u8 x_cand_bin[SAE_MAX_ECC_PRIME_LEN];
u8 qr_bin[SAE_MAX_ECC_PRIME_LEN]; u8 qr_bin[SAE_MAX_ECC_PRIME_LEN];
u8 qnr_bin[SAE_MAX_ECC_PRIME_LEN]; u8 qnr_bin[SAE_MAX_ECC_PRIME_LEN];
u8 x_y[2 * SAE_MAX_ECC_PRIME_LEN];
int res = -1; int res = -1;
u8 found = 0; /* 0 (false) or 0xff (true) to be used as const_time_* u8 found = 0; /* 0 (false) or 0xff (true) to be used as const_time_*
* mask */ * mask */
unsigned int is_eq;
os_memset(x_bin, 0, sizeof(x_bin)); os_memset(x_bin, 0, sizeof(x_bin));
@ -396,25 +398,42 @@ static int sae_derive_pwe_ecc(struct sae_data *sae, const u8 *addr1,
goto fail; goto fail;
} }
if (!sae->tmp->pwe_ecc) /* y = sqrt(x^3 + ax + b) mod p
sae->tmp->pwe_ecc = crypto_ec_point_init(sae->tmp->ec); * if LSB(save) == LSB(y): PWE = (x, y)
if (!sae->tmp->pwe_ecc) * else: PWE = (x, p - y)
res = -1; *
else * Calculate y and the two possible values for PWE and after that,
res = crypto_ec_point_solve_y_coord(sae->tmp->ec, * use constant time selection to copy the correct alternative.
sae->tmp->pwe_ecc, x, */
pwd_seed_odd); y = crypto_ec_point_compute_y_sqr(sae->tmp->ec, x);
if (res < 0) { if (!y ||
/* dragonfly_sqrt(sae->tmp->ec, y, y) < 0 ||
* This should not happen since we already checked that there crypto_bignum_to_bin(y, x_y, SAE_MAX_ECC_PRIME_LEN,
* is a result. prime_len) < 0 ||
*/ crypto_bignum_sub(sae->tmp->prime, y, y) < 0 ||
crypto_bignum_to_bin(y, x_y + SAE_MAX_ECC_PRIME_LEN,
SAE_MAX_ECC_PRIME_LEN, prime_len) < 0) {
wpa_printf(MSG_DEBUG, "SAE: Could not solve y"); wpa_printf(MSG_DEBUG, "SAE: Could not solve y");
goto fail;
}
is_eq = const_time_eq(pwd_seed_odd, x_y[prime_len - 1] & 0x01);
const_time_select_bin(is_eq, x_y, x_y + SAE_MAX_ECC_PRIME_LEN,
prime_len, x_y + prime_len);
os_memcpy(x_y, x_bin, prime_len);
wpa_hexdump_key(MSG_DEBUG, "SAE: PWE", x_y, 2 * prime_len);
crypto_ec_point_deinit(sae->tmp->pwe_ecc, 1);
sae->tmp->pwe_ecc = crypto_ec_point_from_bin(sae->tmp->ec, x_y);
if (!sae->tmp->pwe_ecc) {
wpa_printf(MSG_DEBUG, "SAE: Could not generate PWE");
res = -1;
} }
fail: fail:
forced_memzero(x_y, sizeof(x_y));
crypto_bignum_deinit(qr, 0); crypto_bignum_deinit(qr, 0);
crypto_bignum_deinit(qnr, 0); crypto_bignum_deinit(qnr, 0);
crypto_bignum_deinit(y, 1);
os_free(stub_password); os_free(stub_password);
bin_clear_free(tmp_password, password_len); bin_clear_free(tmp_password, password_len);
crypto_bignum_deinit(x, 1); crypto_bignum_deinit(x, 1);