]> git.ipfire.org Git - thirdparty/hostap.git/commitdiff
SAE: Add side-channel protection to PWE derivation with ECC
authorJouni Malinen <j@w1.fi>
Thu, 25 Jun 2015 08:35:39 +0000 (11:35 +0300)
committerJouni Malinen <j@w1.fi>
Fri, 26 Jun 2015 19:41:51 +0000 (22:41 +0300)
This replaces the earlier IEEE Std 802.11-2012 algorithm with the design
from P802.11-REVmc/D4.0. Things brings in a blinding technique for
determining whether the pwd-seed results in a suitable PWE value.

Signed-off-by: Jouni Malinen <j@w1.fi>
src/common/sae.c

index 2fec4b3b8b1f7eed812df0654a077d2628b2b6b2..e30246647faa8ae123ee7e18923f43122d22a6fc 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Simultaneous authentication of equals
- * Copyright (c) 2012-2013, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2012-2015, Jouni Malinen <j@w1.fi>
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -169,17 +169,107 @@ static void sae_pwd_seed_key(const u8 *addr1, const u8 *addr2, u8 *key)
 }
 
 
+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 -1;
+
+       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,
-                                struct crypto_ec_point *pwe)
+                                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], prime[SAE_MAX_ECC_PRIME_LEN];
-       struct crypto_bignum *x;
-       int y_bit;
+       u8 pwd_value[SAE_MAX_ECC_PRIME_LEN];
+       struct crypto_bignum *y_sqr, *x_cand;
+       int res;
        size_t bits;
 
-       if (crypto_bignum_to_bin(sae->tmp->prime, prime, sizeof(prime),
-                                sae->tmp->prime_len) < 0)
-               return -1;
+       *ret_x_cand = NULL;
 
        wpa_hexdump_key(MSG_DEBUG, "SAE: pwd-seed", pwd_seed, SHA256_MAC_LEN);
 
@@ -195,20 +285,23 @@ static int sae_test_pwd_seed_ecc(struct sae_data *sae, const u8 *pwd_seed,
        if (os_memcmp(pwd_value, prime, sae->tmp->prime_len) >= 0)
                return 0;
 
-       y_bit = pwd_seed[SHA256_MAC_LEN - 1] & 0x01;
-
-       x = crypto_bignum_init_set(pwd_value, sae->tmp->prime_len);
-       if (x == NULL)
+       x_cand = crypto_bignum_init_set(pwd_value, sae->tmp->prime_len);
+       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);
                return -1;
-       if (crypto_ec_point_solve_y_coord(sae->tmp->ec, pwe, x, y_bit) < 0) {
-               crypto_bignum_deinit(x, 0);
-               wpa_printf(MSG_DEBUG, "SAE: No solution found");
-               return 0;
        }
-       crypto_bignum_deinit(x, 0);
 
-       wpa_printf(MSG_DEBUG, "SAE: PWE found");
+       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;
 }
 
@@ -286,6 +379,42 @@ static int sae_test_pwd_seed_ffc(struct sae_data *sae, const u8 *pwd_seed,
 }
 
 
+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)
@@ -294,16 +423,25 @@ static int sae_derive_pwe_ecc(struct sae_data *sae, const u8 *addr1,
        u8 addrs[2 * ETH_ALEN];
        const u8 *addr[2];
        size_t len[2];
-       int found = 0;
-       struct crypto_ec_point *pwe_tmp;
+       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;
 
-       if (sae->tmp->pwe_ecc == NULL) {
-               sae->tmp->pwe_ecc = crypto_ec_point_init(sae->tmp->ec);
-               if (sae->tmp->pwe_ecc == NULL)
-                       return -1;
-       }
-       pwe_tmp = crypto_ec_point_init(sae->tmp->ec);
-       if (pwe_tmp == NULL)
+       prime_len = sae->tmp->prime_len;
+       if (crypto_bignum_to_bin(sae->tmp->prime, prime, sizeof(prime),
+                                prime_len) < 0)
+               return -1;
+       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 -1;
 
        wpa_hexdump_ascii_key(MSG_DEBUG, "SAE: password",
@@ -326,9 +464,9 @@ static int sae_derive_pwe_ecc(struct sae_data *sae, const u8 *addr1,
         * attacks that attempt to determine the number of iterations required
         * in the loop.
         */
-       for (counter = 1; counter <= k || !found; counter++) {
+       for (counter = 1; counter <= k || !x; counter++) {
                u8 pwd_seed[SHA256_MAC_LEN];
-               int res;
+               struct crypto_bignum *x_cand;
 
                if (counter > 200) {
                        /* This should not happen in practice */
@@ -340,25 +478,51 @@ static int sae_derive_pwe_ecc(struct sae_data *sae, const u8 *addr1,
                if (hmac_sha256_vector(addrs, sizeof(addrs), 2, addr, len,
                                       pwd_seed) < 0)
                        break;
+
                res = sae_test_pwd_seed_ecc(sae, pwd_seed,
-                                           found ? pwe_tmp :
-                                           sae->tmp->pwe_ecc);
+                                           prime, qr, qnr, &x_cand);
                if (res < 0)
-                       break;
-               if (res == 0)
-                       continue;
-               if (found) {
-                       wpa_printf(MSG_DEBUG, "SAE: Ignore this PWE (one was "
-                                  "already selected)");
-               } else {
-                       wpa_printf(MSG_DEBUG, "SAE: Use this PWE");
-                       found = 1;
+                       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));
+               } else if (res > 0) {
+                       crypto_bignum_deinit(x_cand, 1);
                }
        }
 
-       crypto_ec_point_deinit(pwe_tmp, 1);
+       if (!x) {
+               wpa_printf(MSG_DEBUG, "SAE: Could not generate PWE");
+               res = -1;
+               goto fail;
+       }
 
-       return found ? 0 : -1;
+       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;
 }