SAE: Minimize timing differences in PWE derivation
The QR test result can provide information about the password to an attacker, so try to minimize differences in how the sae_test_pwd_seed_ecc() result is used. (CVE-2019-9494) Use heap memory for the dummy password to allow the same password length to be used even with long passwords. Use constant time selection functions to track the real vs. dummy variables so that the exact same operations can be performed for both QR test results. Signed-off-by: Jouni Malinen <jouni@codeaurora.org>
This commit is contained in:
parent
aaf65feac6
commit
6513db3e96
1 changed files with 57 additions and 49 deletions
106
src/common/sae.c
106
src/common/sae.c
|
@ -9,6 +9,7 @@
|
||||||
#include "includes.h"
|
#include "includes.h"
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
#include "utils/const_time.h"
|
||||||
#include "crypto/crypto.h"
|
#include "crypto/crypto.h"
|
||||||
#include "crypto/sha256.h"
|
#include "crypto/sha256.h"
|
||||||
#include "crypto/random.h"
|
#include "crypto/random.h"
|
||||||
|
@ -292,15 +293,12 @@ static int sae_test_pwd_seed_ecc(struct sae_data *sae, const u8 *pwd_seed,
|
||||||
const u8 *prime,
|
const u8 *prime,
|
||||||
const struct crypto_bignum *qr,
|
const struct crypto_bignum *qr,
|
||||||
const struct crypto_bignum *qnr,
|
const struct crypto_bignum *qnr,
|
||||||
struct crypto_bignum **ret_x_cand)
|
u8 *pwd_value)
|
||||||
{
|
{
|
||||||
u8 pwd_value[SAE_MAX_ECC_PRIME_LEN];
|
|
||||||
struct crypto_bignum *y_sqr, *x_cand;
|
struct crypto_bignum *y_sqr, *x_cand;
|
||||||
int res;
|
int res;
|
||||||
size_t bits;
|
size_t bits;
|
||||||
|
|
||||||
*ret_x_cand = NULL;
|
|
||||||
|
|
||||||
wpa_hexdump_key(MSG_DEBUG, "SAE: pwd-seed", pwd_seed, SHA256_MAC_LEN);
|
wpa_hexdump_key(MSG_DEBUG, "SAE: pwd-seed", pwd_seed, SHA256_MAC_LEN);
|
||||||
|
|
||||||
/* pwd-value = KDF-z(pwd-seed, "SAE Hunting and Pecking", p) */
|
/* pwd-value = KDF-z(pwd-seed, "SAE Hunting and Pecking", p) */
|
||||||
|
@ -309,7 +307,7 @@ static int sae_test_pwd_seed_ecc(struct sae_data *sae, const u8 *pwd_seed,
|
||||||
prime, sae->tmp->prime_len, pwd_value, bits) < 0)
|
prime, sae->tmp->prime_len, pwd_value, bits) < 0)
|
||||||
return -1;
|
return -1;
|
||||||
if (bits % 8)
|
if (bits % 8)
|
||||||
buf_shift_right(pwd_value, sizeof(pwd_value), 8 - bits % 8);
|
buf_shift_right(pwd_value, sae->tmp->prime_len, 8 - bits % 8);
|
||||||
wpa_hexdump_key(MSG_DEBUG, "SAE: pwd-value",
|
wpa_hexdump_key(MSG_DEBUG, "SAE: pwd-value",
|
||||||
pwd_value, sae->tmp->prime_len);
|
pwd_value, sae->tmp->prime_len);
|
||||||
|
|
||||||
|
@ -320,20 +318,13 @@ static int sae_test_pwd_seed_ecc(struct sae_data *sae, const u8 *pwd_seed,
|
||||||
if (!x_cand)
|
if (!x_cand)
|
||||||
return -1;
|
return -1;
|
||||||
y_sqr = crypto_ec_point_compute_y_sqr(sae->tmp->ec, x_cand);
|
y_sqr = crypto_ec_point_compute_y_sqr(sae->tmp->ec, x_cand);
|
||||||
if (!y_sqr) {
|
crypto_bignum_deinit(x_cand, 1);
|
||||||
crypto_bignum_deinit(x_cand, 1);
|
if (!y_sqr)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
|
||||||
|
|
||||||
res = is_quadratic_residue_blind(sae, prime, bits, qr, qnr, y_sqr);
|
res = is_quadratic_residue_blind(sae, prime, bits, qr, qnr, y_sqr);
|
||||||
crypto_bignum_deinit(y_sqr, 1);
|
crypto_bignum_deinit(y_sqr, 1);
|
||||||
if (res <= 0) {
|
return res;
|
||||||
crypto_bignum_deinit(x_cand, 1);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
*ret_x_cand = x_cand;
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -454,25 +445,30 @@ static int sae_derive_pwe_ecc(struct sae_data *sae, const u8 *addr1,
|
||||||
const u8 *addr[3];
|
const u8 *addr[3];
|
||||||
size_t len[3];
|
size_t len[3];
|
||||||
size_t num_elem;
|
size_t num_elem;
|
||||||
u8 dummy_password[32];
|
u8 *dummy_password, *tmp_password;
|
||||||
size_t dummy_password_len;
|
|
||||||
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, *qnr;
|
struct crypto_bignum *x = NULL, *qr = NULL, *qnr = NULL;
|
||||||
|
u8 x_bin[SAE_MAX_ECC_PRIME_LEN];
|
||||||
|
u8 x_cand_bin[SAE_MAX_ECC_PRIME_LEN];
|
||||||
size_t bits;
|
size_t bits;
|
||||||
int res;
|
int res = -1;
|
||||||
|
u8 found = 0; /* 0 (false) or 0xff (true) to be used as const_time_*
|
||||||
|
* mask */
|
||||||
|
|
||||||
dummy_password_len = password_len;
|
os_memset(x_bin, 0, sizeof(x_bin));
|
||||||
if (dummy_password_len > sizeof(dummy_password))
|
|
||||||
dummy_password_len = sizeof(dummy_password);
|
dummy_password = os_malloc(password_len);
|
||||||
if (random_get_bytes(dummy_password, dummy_password_len) < 0)
|
tmp_password = os_malloc(password_len);
|
||||||
return -1;
|
if (!dummy_password || !tmp_password ||
|
||||||
|
random_get_bytes(dummy_password, password_len) < 0)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
prime_len = sae->tmp->prime_len;
|
prime_len = sae->tmp->prime_len;
|
||||||
if (crypto_bignum_to_bin(sae->tmp->prime, prime, sizeof(prime),
|
if (crypto_bignum_to_bin(sae->tmp->prime, prime, sizeof(prime),
|
||||||
prime_len) < 0)
|
prime_len) < 0)
|
||||||
return -1;
|
goto fail;
|
||||||
bits = crypto_ec_prime_len_bits(sae->tmp->ec);
|
bits = crypto_ec_prime_len_bits(sae->tmp->ec);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -481,7 +477,7 @@ static int sae_derive_pwe_ecc(struct sae_data *sae, const u8 *addr1,
|
||||||
*/
|
*/
|
||||||
if (get_random_qr_qnr(prime, prime_len, sae->tmp->prime, bits,
|
if (get_random_qr_qnr(prime, prime_len, sae->tmp->prime, bits,
|
||||||
&qr, &qnr) < 0)
|
&qr, &qnr) < 0)
|
||||||
return -1;
|
goto fail;
|
||||||
|
|
||||||
wpa_hexdump_ascii_key(MSG_DEBUG, "SAE: password",
|
wpa_hexdump_ascii_key(MSG_DEBUG, "SAE: password",
|
||||||
password, password_len);
|
password, password_len);
|
||||||
|
@ -497,7 +493,7 @@ static int sae_derive_pwe_ecc(struct sae_data *sae, const u8 *addr1,
|
||||||
*/
|
*/
|
||||||
sae_pwd_seed_key(addr1, addr2, addrs);
|
sae_pwd_seed_key(addr1, addr2, addrs);
|
||||||
|
|
||||||
addr[0] = password;
|
addr[0] = tmp_password;
|
||||||
len[0] = password_len;
|
len[0] = password_len;
|
||||||
num_elem = 1;
|
num_elem = 1;
|
||||||
if (identifier) {
|
if (identifier) {
|
||||||
|
@ -514,9 +510,8 @@ static int sae_derive_pwe_ecc(struct sae_data *sae, const u8 *addr1,
|
||||||
* attacks that attempt to determine the number of iterations required
|
* attacks that attempt to determine the number of iterations required
|
||||||
* in the loop.
|
* in the loop.
|
||||||
*/
|
*/
|
||||||
for (counter = 1; counter <= k || !x; counter++) {
|
for (counter = 1; counter <= k || !found; counter++) {
|
||||||
u8 pwd_seed[SHA256_MAC_LEN];
|
u8 pwd_seed[SHA256_MAC_LEN];
|
||||||
struct crypto_bignum *x_cand;
|
|
||||||
|
|
||||||
if (counter > 200) {
|
if (counter > 200) {
|
||||||
/* This should not happen in practice */
|
/* This should not happen in practice */
|
||||||
|
@ -524,40 +519,49 @@ static int sae_derive_pwe_ecc(struct sae_data *sae, const u8 *addr1,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
wpa_printf(MSG_DEBUG, "SAE: counter = %u", counter);
|
wpa_printf(MSG_DEBUG, "SAE: counter = %03u", counter);
|
||||||
|
const_time_select_bin(found, dummy_password, password,
|
||||||
|
password_len, tmp_password);
|
||||||
if (hmac_sha256_vector(addrs, sizeof(addrs), num_elem,
|
if (hmac_sha256_vector(addrs, sizeof(addrs), num_elem,
|
||||||
addr, len, pwd_seed) < 0)
|
addr, len, pwd_seed) < 0)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
res = sae_test_pwd_seed_ecc(sae, pwd_seed,
|
res = sae_test_pwd_seed_ecc(sae, pwd_seed,
|
||||||
prime, qr, qnr, &x_cand);
|
prime, qr, qnr, x_cand_bin);
|
||||||
|
const_time_select_bin(found, x_bin, x_cand_bin, prime_len,
|
||||||
|
x_bin);
|
||||||
|
pwd_seed_odd = const_time_select_u8(
|
||||||
|
found, pwd_seed_odd,
|
||||||
|
pwd_seed[SHA256_MAC_LEN - 1] & 0x01);
|
||||||
|
os_memset(pwd_seed, 0, sizeof(pwd_seed));
|
||||||
if (res < 0)
|
if (res < 0)
|
||||||
goto fail;
|
goto fail;
|
||||||
if (res > 0 && !x) {
|
/* Need to minimize differences in handling res == 0 and 1 here
|
||||||
wpa_printf(MSG_DEBUG,
|
* to avoid differences in timing and instruction cache access,
|
||||||
"SAE: Selected pwd-seed with counter %u",
|
* so use const_time_select_*() to make local copies of the
|
||||||
counter);
|
* values based on whether this loop iteration was the one that
|
||||||
x = x_cand;
|
* found the pwd-seed/x. */
|
||||||
pwd_seed_odd = pwd_seed[SHA256_MAC_LEN - 1] & 0x01;
|
|
||||||
os_memset(pwd_seed, 0, sizeof(pwd_seed));
|
|
||||||
|
|
||||||
/*
|
/* found is 0 or 0xff here and res is 0 or 1. Bitwise OR of them
|
||||||
* Use a dummy password for the following rounds, if
|
* (with res converted to 0/0xff) handles this in constant time.
|
||||||
* any.
|
*/
|
||||||
*/
|
found |= res * 0xff;
|
||||||
addr[0] = dummy_password;
|
wpa_printf(MSG_DEBUG, "SAE: pwd-seed result %d found=0x%02x",
|
||||||
len[0] = dummy_password_len;
|
res, found);
|
||||||
} else if (res > 0) {
|
|
||||||
crypto_bignum_deinit(x_cand, 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!x) {
|
if (!found) {
|
||||||
wpa_printf(MSG_DEBUG, "SAE: Could not generate PWE");
|
wpa_printf(MSG_DEBUG, "SAE: Could not generate PWE");
|
||||||
res = -1;
|
res = -1;
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
x = crypto_bignum_init_set(x_bin, prime_len);
|
||||||
|
if (!x) {
|
||||||
|
res = -1;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
if (!sae->tmp->pwe_ecc)
|
if (!sae->tmp->pwe_ecc)
|
||||||
sae->tmp->pwe_ecc = crypto_ec_point_init(sae->tmp->ec);
|
sae->tmp->pwe_ecc = crypto_ec_point_init(sae->tmp->ec);
|
||||||
if (!sae->tmp->pwe_ecc)
|
if (!sae->tmp->pwe_ecc)
|
||||||
|
@ -566,7 +570,6 @@ static int sae_derive_pwe_ecc(struct sae_data *sae, const u8 *addr1,
|
||||||
res = crypto_ec_point_solve_y_coord(sae->tmp->ec,
|
res = crypto_ec_point_solve_y_coord(sae->tmp->ec,
|
||||||
sae->tmp->pwe_ecc, x,
|
sae->tmp->pwe_ecc, x,
|
||||||
pwd_seed_odd);
|
pwd_seed_odd);
|
||||||
crypto_bignum_deinit(x, 1);
|
|
||||||
if (res < 0) {
|
if (res < 0) {
|
||||||
/*
|
/*
|
||||||
* This should not happen since we already checked that there
|
* This should not happen since we already checked that there
|
||||||
|
@ -578,6 +581,11 @@ static int sae_derive_pwe_ecc(struct sae_data *sae, const u8 *addr1,
|
||||||
fail:
|
fail:
|
||||||
crypto_bignum_deinit(qr, 0);
|
crypto_bignum_deinit(qr, 0);
|
||||||
crypto_bignum_deinit(qnr, 0);
|
crypto_bignum_deinit(qnr, 0);
|
||||||
|
os_free(dummy_password);
|
||||||
|
bin_clear_free(tmp_password, password_len);
|
||||||
|
crypto_bignum_deinit(x, 1);
|
||||||
|
os_memset(x_bin, 0, sizeof(x_bin));
|
||||||
|
os_memset(x_cand_bin, 0, sizeof(x_cand_bin));
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue