SAE: Use constant time operations in sae_test_pwd_seed_ffc()

Try to avoid showing externally visible timing or memory access
differences regardless of whether the derived pwd-value is smaller than
the group prime.

This is related to CVE-2019-9494.

Signed-off-by: Jouni Malinen <jouni@codeaurora.org>
This commit is contained in:
Jouni Malinen 2019-03-02 16:05:56 +02:00 committed by Jouni Malinen
parent f8f20717f8
commit cff138b074

View file

@ -334,14 +334,17 @@ static int sae_test_pwd_seed_ecc(struct sae_data *sae, const u8 *pwd_seed,
} }
/* Returns -1 on fatal failure, 0 if PWE cannot be derived from the provided
* pwd-seed, or 1 if a valid PWE was derived from pwd-seed. */
static int sae_test_pwd_seed_ffc(struct sae_data *sae, const u8 *pwd_seed, static int sae_test_pwd_seed_ffc(struct sae_data *sae, const u8 *pwd_seed,
struct crypto_bignum *pwe) struct crypto_bignum *pwe)
{ {
u8 pwd_value[SAE_MAX_PRIME_LEN]; u8 pwd_value[SAE_MAX_PRIME_LEN];
size_t bits = sae->tmp->prime_len * 8; size_t bits = sae->tmp->prime_len * 8;
u8 exp[1]; u8 exp[1];
struct crypto_bignum *a, *b; struct crypto_bignum *a, *b = NULL;
int res; int res, is_val;
u8 pwd_value_valid;
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);
@ -353,16 +356,29 @@ static int sae_test_pwd_seed_ffc(struct sae_data *sae, const u8 *pwd_seed,
wpa_hexdump_key(MSG_DEBUG, "SAE: pwd-value", pwd_value, wpa_hexdump_key(MSG_DEBUG, "SAE: pwd-value", pwd_value,
sae->tmp->prime_len); sae->tmp->prime_len);
if (os_memcmp(pwd_value, sae->tmp->dh->prime, sae->tmp->prime_len) >= 0) /* Check whether pwd-value < p */
{ res = const_time_memcmp(pwd_value, sae->tmp->dh->prime,
wpa_printf(MSG_DEBUG, "SAE: pwd-value >= p"); sae->tmp->prime_len);
return 0; /* pwd-value >= p is invalid, so res is < 0 for the valid cases and
} * the negative sign can be used to fill the mask for constant time
* selection */
pwd_value_valid = const_time_fill_msb(res);
/* If pwd-value >= p, force pwd-value to be < p and perform the
* calculations anyway to hide timing difference. The derived PWE will
* be ignored in that case. */
pwd_value[0] = const_time_select_u8(pwd_value_valid, pwd_value[0], 0);
/* PWE = pwd-value^((p-1)/r) modulo p */ /* PWE = pwd-value^((p-1)/r) modulo p */
res = -1;
a = crypto_bignum_init_set(pwd_value, sae->tmp->prime_len); a = crypto_bignum_init_set(pwd_value, sae->tmp->prime_len);
if (!a)
goto fail;
/* This is an optimization based on the used group that does not depend
* on the password in any way, so it is fine to use separate branches
* for this step without constant time operations. */
if (sae->tmp->dh->safe_prime) { if (sae->tmp->dh->safe_prime) {
/* /*
* r = (p-1)/2 for the group used here, so this becomes: * r = (p-1)/2 for the group used here, so this becomes:
@ -376,33 +392,34 @@ static int sae_test_pwd_seed_ffc(struct sae_data *sae, const u8 *pwd_seed,
b = crypto_bignum_init_set(exp, sizeof(exp)); b = crypto_bignum_init_set(exp, sizeof(exp));
if (b == NULL || if (b == NULL ||
crypto_bignum_sub(sae->tmp->prime, b, b) < 0 || crypto_bignum_sub(sae->tmp->prime, b, b) < 0 ||
crypto_bignum_div(b, sae->tmp->order, b) < 0) { crypto_bignum_div(b, sae->tmp->order, b) < 0)
crypto_bignum_deinit(b, 0); goto fail;
b = NULL;
}
} }
if (a == NULL || b == NULL) if (!b)
res = -1; goto fail;
else
res = crypto_bignum_exptmod(a, b, sae->tmp->prime, pwe); res = crypto_bignum_exptmod(a, b, sae->tmp->prime, pwe);
if (res < 0)
goto fail;
crypto_bignum_deinit(a, 0); /* There were no fatal errors in calculations, so determine the return
crypto_bignum_deinit(b, 0); * value using constant time operations. We get here for number of
* invalid cases which are cleared here after having performed all the
* computation. PWE is valid if pwd-value was less than prime and
* PWE > 1. Start with pwd-value check first and then use constant time
* operations to clear res to 0 if PWE is 0 or 1.
*/
res = const_time_select_u8(pwd_value_valid, 1, 0);
is_val = crypto_bignum_is_zero(pwe);
res = const_time_select_u8(const_time_is_zero(is_val), res, 0);
is_val = crypto_bignum_is_one(pwe);
res = const_time_select_u8(const_time_is_zero(is_val), res, 0);
if (res < 0) { fail:
wpa_printf(MSG_DEBUG, "SAE: Failed to calculate PWE"); crypto_bignum_deinit(a, 1);
return -1; crypto_bignum_deinit(b, 1);
} return res;
/* if (PWE > 1) --> found */
if (crypto_bignum_is_zero(pwe) || crypto_bignum_is_one(pwe)) {
wpa_printf(MSG_DEBUG, "SAE: PWE <= 1");
return 0;
}
wpa_printf(MSG_DEBUG, "SAE: PWE found");
return 1;
} }