|
@@ -0,0 +1,242 @@
|
|
|
+From 6513db3e96c43c2e36805cf5ead349765d18eaf7 Mon Sep 17 00:00:00 2001
|
|
|
+From: Jouni Malinen <[email protected]>
|
|
|
+Date: Tue, 26 Feb 2019 13:05:09 +0200
|
|
|
+Subject: [PATCH 05/14] 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 <[email protected]>
|
|
|
+---
|
|
|
+ src/common/sae.c | 106 ++++++++++++++++++++++++++++++-------------------------
|
|
|
+ 1 file changed, 57 insertions(+), 49 deletions(-)
|
|
|
+
|
|
|
+--- a/src/common/sae.c
|
|
|
++++ b/src/common/sae.c
|
|
|
+@@ -9,6 +9,7 @@
|
|
|
+ #include "includes.h"
|
|
|
+
|
|
|
+ #include "common.h"
|
|
|
++#include "utils/const_time.h"
|
|
|
+ #include "crypto/crypto.h"
|
|
|
+ #include "crypto/sha256.h"
|
|
|
+ #include "crypto/random.h"
|
|
|
+@@ -269,15 +270,12 @@ static int sae_test_pwd_seed_ecc(struct
|
|
|
+ const u8 *prime,
|
|
|
+ const struct crypto_bignum *qr,
|
|
|
+ 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;
|
|
|
+ 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) */
|
|
|
+@@ -286,7 +284,7 @@ static int sae_test_pwd_seed_ecc(struct
|
|
|
+ prime, sae->tmp->prime_len, pwd_value, bits) < 0)
|
|
|
+ return -1;
|
|
|
+ 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",
|
|
|
+ pwd_value, sae->tmp->prime_len);
|
|
|
+
|
|
|
+@@ -297,20 +295,13 @@ static int sae_test_pwd_seed_ecc(struct
|
|
|
+ if (!x_cand)
|
|
|
+ return -1;
|
|
|
+ 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;
|
|
|
+- }
|
|
|
+
|
|
|
+ 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;
|
|
|
++ return res;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+@@ -431,25 +422,30 @@ static int sae_derive_pwe_ecc(struct sae
|
|
|
+ const u8 *addr[3];
|
|
|
+ size_t len[3];
|
|
|
+ size_t num_elem;
|
|
|
+- u8 dummy_password[32];
|
|
|
+- size_t dummy_password_len;
|
|
|
++ u8 *dummy_password, *tmp_password;
|
|
|
+ int pwd_seed_odd = 0;
|
|
|
+ u8 prime[SAE_MAX_ECC_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;
|
|
|
+- 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 -1;
|
|
|
++ int res = -1;
|
|
|
++ u8 found = 0; /* 0 (false) or 0xff (true) to be used as const_time_*
|
|
|
++ * mask */
|
|
|
++
|
|
|
++ os_memset(x_bin, 0, sizeof(x_bin));
|
|
|
++
|
|
|
++ dummy_password = os_malloc(password_len);
|
|
|
++ tmp_password = os_malloc(password_len);
|
|
|
++ if (!dummy_password || !tmp_password ||
|
|
|
++ random_get_bytes(dummy_password, password_len) < 0)
|
|
|
++ goto fail;
|
|
|
+
|
|
|
+ prime_len = sae->tmp->prime_len;
|
|
|
+ if (crypto_bignum_to_bin(sae->tmp->prime, prime, sizeof(prime),
|
|
|
+ prime_len) < 0)
|
|
|
+- return -1;
|
|
|
++ goto fail;
|
|
|
+ bits = crypto_ec_prime_len_bits(sae->tmp->ec);
|
|
|
+
|
|
|
+ /*
|
|
|
+@@ -458,7 +454,7 @@ static int sae_derive_pwe_ecc(struct sae
|
|
|
+ */
|
|
|
+ if (get_random_qr_qnr(prime, prime_len, sae->tmp->prime, bits,
|
|
|
+ &qr, &qnr) < 0)
|
|
|
+- return -1;
|
|
|
++ goto fail;
|
|
|
+
|
|
|
+ wpa_hexdump_ascii_key(MSG_DEBUG, "SAE: password",
|
|
|
+ password, password_len);
|
|
|
+@@ -474,7 +470,7 @@ static int sae_derive_pwe_ecc(struct sae
|
|
|
+ */
|
|
|
+ sae_pwd_seed_key(addr1, addr2, addrs);
|
|
|
+
|
|
|
+- addr[0] = password;
|
|
|
++ addr[0] = tmp_password;
|
|
|
+ len[0] = password_len;
|
|
|
+ num_elem = 1;
|
|
|
+ if (identifier) {
|
|
|
+@@ -491,9 +487,8 @@ static int sae_derive_pwe_ecc(struct sae
|
|
|
+ * attacks that attempt to determine the number of iterations required
|
|
|
+ * in the loop.
|
|
|
+ */
|
|
|
+- for (counter = 1; counter <= k || !x; counter++) {
|
|
|
++ for (counter = 1; counter <= k || !found; counter++) {
|
|
|
+ u8 pwd_seed[SHA256_MAC_LEN];
|
|
|
+- struct crypto_bignum *x_cand;
|
|
|
+
|
|
|
+ if (counter > 200) {
|
|
|
+ /* This should not happen in practice */
|
|
|
+@@ -501,40 +496,49 @@ static int sae_derive_pwe_ecc(struct sae
|
|
|
+ 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,
|
|
|
+ addr, len, pwd_seed) < 0)
|
|
|
+ break;
|
|
|
+
|
|
|
+ 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)
|
|
|
+ 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);
|
|
|
+- }
|
|
|
++ /* Need to minimize differences in handling res == 0 and 1 here
|
|
|
++ * to avoid differences in timing and instruction cache access,
|
|
|
++ * so use const_time_select_*() to make local copies of the
|
|
|
++ * values based on whether this loop iteration was the one that
|
|
|
++ * found the pwd-seed/x. */
|
|
|
++
|
|
|
++ /* found is 0 or 0xff here and res is 0 or 1. Bitwise OR of them
|
|
|
++ * (with res converted to 0/0xff) handles this in constant time.
|
|
|
++ */
|
|
|
++ found |= res * 0xff;
|
|
|
++ wpa_printf(MSG_DEBUG, "SAE: pwd-seed result %d found=0x%02x",
|
|
|
++ res, found);
|
|
|
+ }
|
|
|
+
|
|
|
+- if (!x) {
|
|
|
++ if (!found) {
|
|
|
+ wpa_printf(MSG_DEBUG, "SAE: Could not generate PWE");
|
|
|
+ res = -1;
|
|
|
+ goto fail;
|
|
|
+ }
|
|
|
+
|
|
|
++ x = crypto_bignum_init_set(x_bin, prime_len);
|
|
|
++ if (!x) {
|
|
|
++ 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)
|
|
|
+@@ -543,7 +547,6 @@ static int sae_derive_pwe_ecc(struct sae
|
|
|
+ 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
|
|
|
+@@ -555,6 +558,11 @@ static int sae_derive_pwe_ecc(struct sae
|
|
|
+ fail:
|
|
|
+ crypto_bignum_deinit(qr, 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;
|
|
|
+ }
|