hostapd: fix CVE-2019-9494
authorStefan Lippers-Hollmann <s.l-h@gmx.de>
Thu, 11 Apr 2019 00:53:10 +0000 (02:53 +0200)
committerJo-Philipp Wich <jo@mein.io>
Thu, 11 Apr 2019 09:26:01 +0000 (11:26 +0200)
SAE side-channel attacks

Published: April 10, 2019
Identifiers:
- VU#871675
- CVE-2019-9494 (cache attack against SAE)
Latest version available from: https://w1.fi/security/2019-1/

Vulnerability

Number of potential side channel attacks were discovered in the SAE
implementations used by both hostapd (AP) and wpa_supplicant
(infrastructure BSS station/mesh station). SAE (Simultaneous
Authentication of Equals) is also known as WPA3-Personal. The discovered
side channel attacks may be able to leak information about the used
password based on observable timing differences and cache access
patterns. This might result in full password recovery when combined with
an offline dictionary attack and if the password is not strong enough to
protect against dictionary attacks.

Cache attack

A novel cache-based attack against SAE handshake was discovered. This
attack targets SAE with ECC groups. ECC group 19 being the mandatory
group to support and the most likely used group for SAE today, so this
attack applies to the most common SAE use case. Even though the PWE
derivation iteration in SAE has protections against timing attacks, this
new cache-based attack enables an attacker to determine which code
branch is taken in the iteration if the attacker is able to run
unprivileged code on the victim machine (e.g., an app installed on a
smart phone or potentially a JavaScript code on a web site loaded by a
web browser). This depends on the used CPU not providing sufficient
protection to prevent unprivileged applications from observing memory
access patterns through the shared cache (which is the most likely case
with today's designs).

The attacker can use information about the selected branch to learn
information about the password and combine this information from number
of handshake instances with an offline dictionary attack. With
sufficient number of handshakes and sufficiently weak password, this
might result in full discovery of the used password.

This attack requires the attacker to be able to run a program on the
target device. This is not commonly the case on access points, so the
most likely target for this would be a client device using SAE in an
infrastructure BSS or mesh BSS.

The commits listed in the end of this advisory change the SAE
implementation shared by hostapd and wpa_supplicant to perform the PWE
derivation loop using operations that use constant time and memory
access pattern to minimize the externally observable differences from
operations that depend on the password even for the case where the
attacker might be able to run unprivileged code on the same device.

Timing attack

The timing attack applies to the MODP groups 22, 23, and 24 where the
PWE generation algorithm defined for SAE can have sufficient timing
differences for an attacker to be able to determine how many rounds were
needed to find the PWE based on the used password and MAC
addresses. When the attack is repeated with multiple times, the attacker
may be able to gather enough information about the password to be able
to recover it fully using an offline dictionary attack if the password
is not strong enough to protect against dictionary attacks. This attack
could be performed by an attacker in radio range of an access point or a
station enabling the specific MODP groups.

This timing attack requires the applicable MODP groups to be enabled
explicitly in hostapd/wpa_supplicant configuration (sae_groups
parameter). All versions of hostapd/wpa_supplicant have disabled these
groups by default.

While this security advisory lists couple of commits introducing
additional protection for MODP groups in SAE, it should be noted that
the groups 22, 23, and 24 are not considered strong enough to meet the
current expectation for a secure system. As such, their use is
discouraged even if the additional protection mechanisms in the
implementation are included.

Vulnerable versions/configurations

All wpa_supplicant and hostapd versions with SAE support (CONFIG_SAE=y
in the build configuration and SAE being enabled in the runtime
configuration).

Acknowledgments

Thanks to Mathy Vanhoef (New York University Abu Dhabi) and Eyal Ronen
(Tel Aviv University) for discovering the issues and for discussions on
how to address them.

Possible mitigation steps

- Merge the following commits to wpa_supplicant/hostapd and rebuild:

  OpenSSL: Use constant time operations for private bignums
  Add helper functions for constant time operations
  OpenSSL: Use constant time selection for crypto_bignum_legendre()
  SAE: Minimize timing differences in PWE derivation
  SAE: Avoid branches in is_quadratic_residue_blind()
  SAE: Mask timing of MODP groups 22, 23, 24
  SAE: Use const_time selection for PWE in FFC
  SAE: Use constant time operations in sae_test_pwd_seed_ffc()

  These patches are available from https://w1.fi/security/2019-1/

- Update to wpa_supplicant/hostapd v2.8 or newer, once available

- In addition to either of the above alternatives, disable MODP groups
  1, 2, 5, 22, 23, and 24 by removing them from hostapd/wpa_supplicant
  sae_groups runtime configuration parameter, if they were explicitly
  enabled since those groups are not considered strong enough to meet
  current security expectations. The groups 22, 23, and 24 are related
  to the discovered side channel (timing) attack. The other groups in
  the list are consider too weak to provide sufficient security. Note
  that all these groups have been disabled by default in all
  hostapd/wpa_supplicant versions and these would be used only if
  explicitly enabled in the configuration.

- Use strong passwords to prevent dictionary attacks

Signed-off-by: Stefan Lippers-Hollmann <s.l-h@gmx.de>
[bump PKG_RELEASE]
Signed-off-by: Jo-Philipp Wich <jo@mein.io>
package/network/services/hostapd/Makefile
package/network/services/hostapd/patches/061-0001-OpenSSL-Use-constant-time-operations-for-private-big.patch [new file with mode: 0644]
package/network/services/hostapd/patches/061-0002-Add-helper-functions-for-constant-time-operations.patch [new file with mode: 0644]
package/network/services/hostapd/patches/061-0003-OpenSSL-Use-constant-time-selection-for-crypto_bignu.patch [new file with mode: 0644]
package/network/services/hostapd/patches/061-0005-SAE-Minimize-timing-differences-in-PWE-derivation.patch [new file with mode: 0644]
package/network/services/hostapd/patches/061-0006-SAE-Avoid-branches-in-is_quadratic_residue_blind.patch [new file with mode: 0644]
package/network/services/hostapd/patches/061-0007-SAE-Mask-timing-of-MODP-groups-22-23-24.patch [new file with mode: 0644]
package/network/services/hostapd/patches/061-0008-SAE-Use-const_time-selection-for-PWE-in-FFC.patch [new file with mode: 0644]
package/network/services/hostapd/patches/061-0009-SAE-Use-constant-time-operations-in-sae_test_pwd_see.patch [new file with mode: 0644]

index f531e660ff48753d7f2cd1a603cf053768c4b44b..6af396f35bd825d141506586d8e1dbd70d094271 100644 (file)
@@ -7,7 +7,7 @@
 include $(TOPDIR)/rules.mk
 
 PKG_NAME:=hostapd
-PKG_RELEASE:=2
+PKG_RELEASE:=3
 
 PKG_SOURCE_URL:=http://w1.fi/hostap.git
 PKG_SOURCE_PROTO:=git
diff --git a/package/network/services/hostapd/patches/061-0001-OpenSSL-Use-constant-time-operations-for-private-big.patch b/package/network/services/hostapd/patches/061-0001-OpenSSL-Use-constant-time-operations-for-private-big.patch
new file mode 100644 (file)
index 0000000..7a73b09
--- /dev/null
@@ -0,0 +1,88 @@
+From d42c477cc794163a3757956bbffca5cea000923c Mon Sep 17 00:00:00 2001
+From: Jouni Malinen <jouni@codeaurora.org>
+Date: Tue, 26 Feb 2019 11:43:03 +0200
+Subject: [PATCH 01/14] OpenSSL: Use constant time operations for private
+ bignums
+
+This helps in reducing measurable timing differences in operations
+involving private information. BoringSSL has removed BN_FLG_CONSTTIME
+and expects specific constant time functions to be called instead, so a
+bit different approach is needed depending on which library is used.
+
+The main operation that needs protection against side channel attacks is
+BN_mod_exp() that depends on private keys (the public key validation
+step in crypto_dh_derive_secret() is an exception that can use the
+faster version since it does not depend on private keys).
+
+crypto_bignum_div() is currently used only in SAE FFC case with not
+safe-prime groups and only with values that do not depend on private
+keys, so it is not critical to protect it.
+
+crypto_bignum_inverse() is currently used only in SAE FFC PWE
+derivation. The additional protection here is targeting only OpenSSL.
+BoringSSL may need conversion to using BN_mod_inverse_blinded().
+
+This is related to CVE-2019-9494 and CVE-2019-9495.
+
+Signed-off-by: Jouni Malinen <jouni@codeaurora.org>
+---
+ src/crypto/crypto_openssl.c | 20 +++++++++++++++-----
+ 1 file changed, 15 insertions(+), 5 deletions(-)
+
+--- a/src/crypto/crypto_openssl.c
++++ b/src/crypto/crypto_openssl.c
+@@ -549,7 +549,8 @@ int crypto_mod_exp(const u8 *base, size_
+           bn_result == NULL)
+               goto error;
+-      if (BN_mod_exp(bn_result, bn_base, bn_exp, bn_modulus, ctx) != 1)
++      if (BN_mod_exp_mont_consttime(bn_result, bn_base, bn_exp, bn_modulus,
++                                    ctx, NULL) != 1)
+               goto error;
+       *result_len = BN_bn2bin(bn_result, result);
+@@ -1295,8 +1296,9 @@ int crypto_bignum_exptmod(const struct c
+       bnctx = BN_CTX_new();
+       if (bnctx == NULL)
+               return -1;
+-      res = BN_mod_exp((BIGNUM *) d, (const BIGNUM *) a, (const BIGNUM *) b,
+-                       (const BIGNUM *) c, bnctx);
++      res = BN_mod_exp_mont_consttime((BIGNUM *) d, (const BIGNUM *) a,
++                                      (const BIGNUM *) b, (const BIGNUM *) c,
++                                      bnctx, NULL);
+       BN_CTX_free(bnctx);
+       return res ? 0 : -1;
+@@ -1315,6 +1317,11 @@ int crypto_bignum_inverse(const struct c
+       bnctx = BN_CTX_new();
+       if (bnctx == NULL)
+               return -1;
++#ifdef OPENSSL_IS_BORINGSSL
++      /* TODO: use BN_mod_inverse_blinded() ? */
++#else /* OPENSSL_IS_BORINGSSL */
++      BN_set_flags((BIGNUM *) a, BN_FLG_CONSTTIME);
++#endif /* OPENSSL_IS_BORINGSSL */
+       res = BN_mod_inverse((BIGNUM *) c, (const BIGNUM *) a,
+                            (const BIGNUM *) b, bnctx);
+       BN_CTX_free(bnctx);
+@@ -1348,6 +1355,9 @@ int crypto_bignum_div(const struct crypt
+       bnctx = BN_CTX_new();
+       if (bnctx == NULL)
+               return -1;
++#ifndef OPENSSL_IS_BORINGSSL
++      BN_set_flags((BIGNUM *) a, BN_FLG_CONSTTIME);
++#endif /* OPENSSL_IS_BORINGSSL */
+       res = BN_div((BIGNUM *) c, NULL, (const BIGNUM *) a,
+                    (const BIGNUM *) b, bnctx);
+       BN_CTX_free(bnctx);
+@@ -1439,8 +1449,8 @@ int crypto_bignum_legendre(const struct
+           /* exp = (p-1) / 2 */
+           !BN_sub(exp, (const BIGNUM *) p, BN_value_one()) ||
+           !BN_rshift1(exp, exp) ||
+-          !BN_mod_exp(tmp, (const BIGNUM *) a, exp, (const BIGNUM *) p,
+-                      bnctx))
++          !BN_mod_exp_mont_consttime(tmp, (const BIGNUM *) a, exp,
++                                     (const BIGNUM *) p, bnctx, NULL))
+               goto fail;
+       if (BN_is_word(tmp, 1))
diff --git a/package/network/services/hostapd/patches/061-0002-Add-helper-functions-for-constant-time-operations.patch b/package/network/services/hostapd/patches/061-0002-Add-helper-functions-for-constant-time-operations.patch
new file mode 100644 (file)
index 0000000..87e41ae
--- /dev/null
@@ -0,0 +1,212 @@
+From 6e34f618d37ddbb5854c42e2ad4fca83492fa7b7 Mon Sep 17 00:00:00 2001
+From: Jouni Malinen <jouni@codeaurora.org>
+Date: Wed, 27 Feb 2019 18:38:30 +0200
+Subject: [PATCH 02/14] Add helper functions for constant time operations
+
+These functions can be used to help implement constant time operations
+for various cryptographic operations that must minimize externally
+observable differences in processing (both in timing and also in
+internal cache use, etc.).
+
+This is related to CVE-2019-9494 and CVE-2019-9495.
+
+Signed-off-by: Jouni Malinen <jouni@codeaurora.org>
+---
+ src/utils/const_time.h | 191 +++++++++++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 191 insertions(+)
+ create mode 100644 src/utils/const_time.h
+
+--- /dev/null
++++ b/src/utils/const_time.h
+@@ -0,0 +1,191 @@
++/*
++ * Helper functions for constant time operations
++ * Copyright (c) 2019, The Linux Foundation
++ *
++ * This software may be distributed under the terms of the BSD license.
++ * See README for more details.
++ *
++ * These helper functions can be used to implement logic that needs to minimize
++ * externally visible differences in execution path by avoiding use of branches,
++ * avoiding early termination or other time differences, and forcing same memory
++ * access pattern regardless of values.
++ */
++
++#ifndef CONST_TIME_H
++#define CONST_TIME_H
++
++
++#if defined(__clang__)
++#define NO_UBSAN_UINT_OVERFLOW \
++      __attribute__((no_sanitize("unsigned-integer-overflow")))
++#else
++#define NO_UBSAN_UINT_OVERFLOW
++#endif
++
++
++/**
++ * const_time_fill_msb - Fill all bits with MSB value
++ * @val: Input value
++ * Returns: Value with all the bits set to the MSB of the input val
++ */
++static inline unsigned int const_time_fill_msb(unsigned int val)
++{
++      /* Move the MSB to LSB and multiple by -1 to fill in all bits. */
++      return (val >> (sizeof(val) * 8 - 1)) * ~0U;
++}
++
++
++/* Returns: -1 if val is zero; 0 if val is not zero */
++static inline unsigned int const_time_is_zero(unsigned int val)
++      NO_UBSAN_UINT_OVERFLOW
++{
++      /* Set MSB to 1 for 0 and fill rest of bits with the MSB value */
++      return const_time_fill_msb(~val & (val - 1));
++}
++
++
++/* Returns: -1 if a == b; 0 if a != b */
++static inline unsigned int const_time_eq(unsigned int a, unsigned int b)
++{
++      return const_time_is_zero(a ^ b);
++}
++
++
++/* Returns: -1 if a == b; 0 if a != b */
++static inline u8 const_time_eq_u8(unsigned int a, unsigned int b)
++{
++      return (u8) const_time_eq(a, b);
++}
++
++
++/**
++ * const_time_eq_bin - Constant time memory comparison
++ * @a: First buffer to compare
++ * @b: Second buffer to compare
++ * @len: Number of octets to compare
++ * Returns: -1 if buffers are equal, 0 if not
++ *
++ * This function is meant for comparing passwords or hash values where
++ * difference in execution time or memory access pattern could provide external
++ * observer information about the location of the difference in the memory
++ * buffers. The return value does not behave like memcmp(), i.e.,
++ * const_time_eq_bin() cannot be used to sort items into a defined order. Unlike
++ * memcmp(), the execution time of const_time_eq_bin() does not depend on the
++ * contents of the compared memory buffers, but only on the total compared
++ * length.
++ */
++static inline unsigned int const_time_eq_bin(const void *a, const void *b,
++                                           size_t len)
++{
++      const u8 *aa = a;
++      const u8 *bb = b;
++      size_t i;
++      u8 res = 0;
++
++      for (i = 0; i < len; i++)
++              res |= aa[i] ^ bb[i];
++
++      return const_time_is_zero(res);
++}
++
++
++/**
++ * const_time_select - Constant time unsigned int selection
++ * @mask: 0 (false) or -1 (true) to identify which value to select
++ * @true_val: Value to select for the true case
++ * @false_val: Value to select for the false case
++ * Returns: true_val if mask == -1, false_val if mask == 0
++ */
++static inline unsigned int const_time_select(unsigned int mask,
++                                           unsigned int true_val,
++                                           unsigned int false_val)
++{
++      return (mask & true_val) | (~mask & false_val);
++}
++
++
++/**
++ * const_time_select_int - Constant time int selection
++ * @mask: 0 (false) or -1 (true) to identify which value to select
++ * @true_val: Value to select for the true case
++ * @false_val: Value to select for the false case
++ * Returns: true_val if mask == -1, false_val if mask == 0
++ */
++static inline int const_time_select_int(unsigned int mask, int true_val,
++                                      int false_val)
++{
++      return (int) const_time_select(mask, (unsigned int) true_val,
++                                     (unsigned int) false_val);
++}
++
++
++/**
++ * const_time_select_u8 - Constant time u8 selection
++ * @mask: 0 (false) or -1 (true) to identify which value to select
++ * @true_val: Value to select for the true case
++ * @false_val: Value to select for the false case
++ * Returns: true_val if mask == -1, false_val if mask == 0
++ */
++static inline u8 const_time_select_u8(u8 mask, u8 true_val, u8 false_val)
++{
++      return (u8) const_time_select(mask, true_val, false_val);
++}
++
++
++/**
++ * const_time_select_s8 - Constant time s8 selection
++ * @mask: 0 (false) or -1 (true) to identify which value to select
++ * @true_val: Value to select for the true case
++ * @false_val: Value to select for the false case
++ * Returns: true_val if mask == -1, false_val if mask == 0
++ */
++static inline s8 const_time_select_s8(u8 mask, s8 true_val, s8 false_val)
++{
++      return (s8) const_time_select(mask, (unsigned int) true_val,
++                                    (unsigned int) false_val);
++}
++
++
++/**
++ * const_time_select_bin - Constant time binary buffer selection copy
++ * @mask: 0 (false) or -1 (true) to identify which value to copy
++ * @true_val: Buffer to copy for the true case
++ * @false_val: Buffer to copy for the false case
++ * @len: Number of octets to copy
++ * @dst: Destination buffer for the copy
++ *
++ * This function copies the specified buffer into the destination buffer using
++ * operations with identical memory access pattern regardless of which buffer
++ * is being copied.
++ */
++static inline void const_time_select_bin(u8 mask, const u8 *true_val,
++                                       const u8 *false_val, size_t len,
++                                       u8 *dst)
++{
++      size_t i;
++
++      for (i = 0; i < len; i++)
++              dst[i] = const_time_select_u8(mask, true_val[i], false_val[i]);
++}
++
++
++static inline int const_time_memcmp(const void *a, const void *b, size_t len)
++{
++      const u8 *aa = a;
++      const u8 *bb = b;
++      int diff, res = 0;
++      unsigned int mask;
++
++      if (len == 0)
++              return 0;
++      do {
++              len--;
++              diff = (int) aa[len] - (int) bb[len];
++              mask = const_time_is_zero((unsigned int) diff);
++              res = const_time_select_int(mask, res, diff);
++      } while (len);
++
++      return res;
++}
++
++#endif /* CONST_TIME_H */
diff --git a/package/network/services/hostapd/patches/061-0003-OpenSSL-Use-constant-time-selection-for-crypto_bignu.patch b/package/network/services/hostapd/patches/061-0003-OpenSSL-Use-constant-time-selection-for-crypto_bignu.patch
new file mode 100644 (file)
index 0000000..0d89b46
--- /dev/null
@@ -0,0 +1,55 @@
+From c93461c1d98f52681717a088776ab32fd97872b0 Mon Sep 17 00:00:00 2001
+From: Jouni Malinen <jouni@codeaurora.org>
+Date: Fri, 8 Mar 2019 00:24:12 +0200
+Subject: [PATCH 03/14] OpenSSL: Use constant time selection for
+ crypto_bignum_legendre()
+
+Get rid of the branches that depend on the result of the Legendre
+operation. This is needed to avoid leaking information about different
+temporary results in blinding mechanisms.
+
+This is related to CVE-2019-9494 and CVE-2019-9495.
+
+Signed-off-by: Jouni Malinen <jouni@codeaurora.org>
+---
+ src/crypto/crypto_openssl.c | 15 +++++++++------
+ 1 file changed, 9 insertions(+), 6 deletions(-)
+
+--- a/src/crypto/crypto_openssl.c
++++ b/src/crypto/crypto_openssl.c
+@@ -24,6 +24,7 @@
+ #endif /* CONFIG_ECC */
+ #include "common.h"
++#include "utils/const_time.h"
+ #include "wpabuf.h"
+ #include "dh_group5.h"
+ #include "sha1.h"
+@@ -1435,6 +1436,7 @@ int crypto_bignum_legendre(const struct
+       BN_CTX *bnctx;
+       BIGNUM *exp = NULL, *tmp = NULL;
+       int res = -2;
++      unsigned int mask;
+       if (TEST_FAIL())
+               return -2;
+@@ -1453,12 +1455,13 @@ int crypto_bignum_legendre(const struct
+                                      (const BIGNUM *) p, bnctx, NULL))
+               goto fail;
+-      if (BN_is_word(tmp, 1))
+-              res = 1;
+-      else if (BN_is_zero(tmp))
+-              res = 0;
+-      else
+-              res = -1;
++      /* Return 1 if tmp == 1, 0 if tmp == 0, or -1 otherwise. Need to use
++       * constant time selection to avoid branches here. */
++      res = -1;
++      mask = const_time_eq(BN_is_word(tmp, 1), 1);
++      res = const_time_select_int(mask, 1, res);
++      mask = const_time_eq(BN_is_zero(tmp), 1);
++      res = const_time_select_int(mask, 0, res);
+ fail:
+       BN_clear_free(tmp);
diff --git a/package/network/services/hostapd/patches/061-0005-SAE-Minimize-timing-differences-in-PWE-derivation.patch b/package/network/services/hostapd/patches/061-0005-SAE-Minimize-timing-differences-in-PWE-derivation.patch
new file mode 100644 (file)
index 0000000..e72a9cb
--- /dev/null
@@ -0,0 +1,242 @@
+From 6513db3e96c43c2e36805cf5ead349765d18eaf7 Mon Sep 17 00:00:00 2001
+From: Jouni Malinen <jouni@codeaurora.org>
+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 <jouni@codeaurora.org>
+---
+ 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;
+ }
diff --git a/package/network/services/hostapd/patches/061-0006-SAE-Avoid-branches-in-is_quadratic_residue_blind.patch b/package/network/services/hostapd/patches/061-0006-SAE-Avoid-branches-in-is_quadratic_residue_blind.patch
new file mode 100644 (file)
index 0000000..6d93cb2
--- /dev/null
@@ -0,0 +1,139 @@
+From 362704dda04507e7ebb8035122e83d9f0ae7c320 Mon Sep 17 00:00:00 2001
+From: Jouni Malinen <jouni@codeaurora.org>
+Date: Tue, 26 Feb 2019 19:34:38 +0200
+Subject: [PATCH 06/14] SAE: Avoid branches in is_quadratic_residue_blind()
+
+Make the non-failure path in the function proceed without branches based
+on r_odd and in constant time to minimize risk of observable differences
+in timing or cache use. (CVE-2019-9494)
+
+Signed-off-by: Jouni Malinen <jouni@codeaurora.org>
+---
+ src/common/sae.c | 64 ++++++++++++++++++++++++++++++++------------------------
+ 1 file changed, 37 insertions(+), 27 deletions(-)
+
+--- a/src/common/sae.c
++++ b/src/common/sae.c
+@@ -209,12 +209,14 @@ get_rand_1_to_p_1(const u8 *prime, size_
+ 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 u8 *qr, const u8 *qnr,
+                                     const struct crypto_bignum *y_sqr)
+ {
+-      struct crypto_bignum *r, *num;
++      struct crypto_bignum *r, *num, *qr_or_qnr = NULL;
+       int r_odd, check, res = -1;
++      u8 qr_or_qnr_bin[SAE_MAX_ECC_PRIME_LEN];
++      size_t prime_len = sae->tmp->prime_len;
++      unsigned int mask;
+       /*
+        * Use the blinding technique to mask y_sqr while determining
+@@ -225,7 +227,7 @@ static int is_quadratic_residue_blind(st
+        * 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);
++      r = get_rand_1_to_p_1(prime, prime_len, bits, &r_odd);
+       if (!r)
+               return -1;
+@@ -235,41 +237,45 @@ static int is_quadratic_residue_blind(st
+           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;
+-      }
++      /*
++       * Need to minimize differences in handling different cases, so try to
++       * avoid branches and timing differences.
++       *
++       * If r_odd:
++       * num = (num * qr) module p
++       * LGR(num, p) = 1 ==> quadratic residue
++       * else:
++       * num = (num * qnr) module p
++       * LGR(num, p) = -1 ==> quadratic residue
++       */
++      mask = const_time_is_zero(r_odd);
++      const_time_select_bin(mask, qnr, qr, prime_len, qr_or_qnr_bin);
++      qr_or_qnr = crypto_bignum_init_set(qr_or_qnr_bin, prime_len);
++      if (!qr_or_qnr ||
++          crypto_bignum_mulmod(num, qr_or_qnr, sae->tmp->prime, num) < 0)
++              goto fail;
++      /* r_odd is 0 or 1; branchless version of check = r_odd ? 1 : -1, */
++      check = const_time_select_int(mask, -1, 1);
+       res = crypto_bignum_legendre(num, sae->tmp->prime);
+       if (res == -2) {
+               res = -1;
+               goto fail;
+       }
+-      res = res == check;
++      /* branchless version of res = res == check
++       * (res is -1, 0, or 1; check is -1 or 1) */
++      mask = const_time_eq(res, check);
++      res = const_time_select_int(mask, 1, 0);
+ fail:
+       crypto_bignum_deinit(num, 1);
+       crypto_bignum_deinit(r, 1);
++      crypto_bignum_deinit(qr_or_qnr, 1);
+       return res;
+ }
+ static int sae_test_pwd_seed_ecc(struct sae_data *sae, const u8 *pwd_seed,
+-                               const u8 *prime,
+-                               const struct crypto_bignum *qr,
+-                               const struct crypto_bignum *qnr,
++                               const u8 *prime, const u8 *qr, const u8 *qnr,
+                                u8 *pwd_value)
+ {
+       struct crypto_bignum *y_sqr, *x_cand;
+@@ -429,6 +435,8 @@ static int sae_derive_pwe_ecc(struct sae
+       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];
++      u8 qr_bin[SAE_MAX_ECC_PRIME_LEN];
++      u8 qnr_bin[SAE_MAX_ECC_PRIME_LEN];
+       size_t bits;
+       int res = -1;
+       u8 found = 0; /* 0 (false) or 0xff (true) to be used as const_time_*
+@@ -453,7 +461,9 @@ static int sae_derive_pwe_ecc(struct sae
+        * (qnr) modulo p for blinding purposes during the loop.
+        */
+       if (get_random_qr_qnr(prime, prime_len, sae->tmp->prime, bits,
+-                            &qr, &qnr) < 0)
++                            &qr, &qnr) < 0 ||
++          crypto_bignum_to_bin(qr, qr_bin, sizeof(qr_bin), prime_len) < 0 ||
++          crypto_bignum_to_bin(qnr, qnr_bin, sizeof(qnr_bin), prime_len) < 0)
+               goto fail;
+       wpa_hexdump_ascii_key(MSG_DEBUG, "SAE: password",
+@@ -504,7 +514,7 @@ static int sae_derive_pwe_ecc(struct sae
+                       break;
+               res = sae_test_pwd_seed_ecc(sae, pwd_seed,
+-                                          prime, qr, qnr, x_cand_bin);
++                                          prime, qr_bin, qnr_bin, x_cand_bin);
+               const_time_select_bin(found, x_bin, x_cand_bin, prime_len,
+                                     x_bin);
+               pwd_seed_odd = const_time_select_u8(
diff --git a/package/network/services/hostapd/patches/061-0007-SAE-Mask-timing-of-MODP-groups-22-23-24.patch b/package/network/services/hostapd/patches/061-0007-SAE-Mask-timing-of-MODP-groups-22-23-24.patch
new file mode 100644 (file)
index 0000000..229d2b1
--- /dev/null
@@ -0,0 +1,113 @@
+From 90839597cc4016b33f00055b12d59174c62770a3 Mon Sep 17 00:00:00 2001
+From: Jouni Malinen <jouni@codeaurora.org>
+Date: Sat, 2 Mar 2019 12:24:09 +0200
+Subject: [PATCH 07/14] SAE: Mask timing of MODP groups 22, 23, 24
+
+These groups have significant probability of coming up with pwd-value
+that is equal or greater than the prime and as such, need for going
+through the PWE derivation loop multiple times. This can result in
+sufficient timing different to allow an external observer to determine
+how many rounds are needed and that can leak information about the used
+password.
+
+Force at least 40 loop rounds for these MODP groups similarly to the ECC
+group design to mask timing. This behavior is not described in IEEE Std
+802.11-2016 for SAE, but it does not result in different values (i.e.,
+only different timing), so such implementation specific countermeasures
+can be done without breaking interoperability with other implementation.
+
+Note: These MODP groups 22, 23, and 24 are not considered sufficiently
+strong to be used with SAE (or more or less anything else). As such,
+they should never be enabled in runtime configuration for any production
+use cases. These changes to introduce additional protection to mask
+timing is only for completeness of implementation and not an indication
+that these groups should be used.
+
+This is related to CVE-2019-9494.
+
+Signed-off-by: Jouni Malinen <jouni@codeaurora.org>
+---
+ src/common/sae.c | 38 ++++++++++++++++++++++++++++----------
+ 1 file changed, 28 insertions(+), 10 deletions(-)
+
+--- a/src/common/sae.c
++++ b/src/common/sae.c
+@@ -578,22 +578,27 @@ fail:
+ }
++static int sae_modp_group_require_masking(int group)
++{
++      /* Groups for which pwd-value is likely to be >= p frequently */
++      return group == 22 || group == 23 || group == 24;
++}
++
++
+ static int sae_derive_pwe_ffc(struct sae_data *sae, const u8 *addr1,
+                             const u8 *addr2, const u8 *password,
+                             size_t password_len, const char *identifier)
+ {
+-      u8 counter;
++      u8 counter, k;
+       u8 addrs[2 * ETH_ALEN];
+       const u8 *addr[3];
+       size_t len[3];
+       size_t num_elem;
+       int found = 0;
++      struct crypto_bignum *pwe = NULL;
+-      if (sae->tmp->pwe_ffc == NULL) {
+-              sae->tmp->pwe_ffc = crypto_bignum_init();
+-              if (sae->tmp->pwe_ffc == NULL)
+-                      return -1;
+-      }
++      crypto_bignum_deinit(sae->tmp->pwe_ffc, 1);
++      sae->tmp->pwe_ffc = NULL;
+       wpa_hexdump_ascii_key(MSG_DEBUG, "SAE: password",
+                             password, password_len);
+@@ -617,7 +622,9 @@ static int sae_derive_pwe_ffc(struct sae
+       len[num_elem] = sizeof(counter);
+       num_elem++;
+-      for (counter = 1; !found; counter++) {
++      k = sae_modp_group_require_masking(sae->group) ? 40 : 1;
++
++      for (counter = 1; counter <= k || !found; counter++) {
+               u8 pwd_seed[SHA256_MAC_LEN];
+               int res;
+@@ -627,19 +634,30 @@ static int sae_derive_pwe_ffc(struct sae
+                       break;
+               }
+-              wpa_printf(MSG_DEBUG, "SAE: counter = %u", counter);
++              wpa_printf(MSG_DEBUG, "SAE: counter = %02u", counter);
+               if (hmac_sha256_vector(addrs, sizeof(addrs), num_elem,
+                                      addr, len, pwd_seed) < 0)
+                       break;
+-              res = sae_test_pwd_seed_ffc(sae, pwd_seed, sae->tmp->pwe_ffc);
++              if (!pwe) {
++                      pwe = crypto_bignum_init();
++                      if (!pwe)
++                              break;
++              }
++              res = sae_test_pwd_seed_ffc(sae, pwd_seed, pwe);
+               if (res < 0)
+                       break;
+               if (res > 0) {
+-                      wpa_printf(MSG_DEBUG, "SAE: Use this PWE");
+                       found = 1;
++                      if (!sae->tmp->pwe_ffc) {
++                              wpa_printf(MSG_DEBUG, "SAE: Use this PWE");
++                              sae->tmp->pwe_ffc = pwe;
++                              pwe = NULL;
++                      }
+               }
+       }
++      crypto_bignum_deinit(pwe, 1);
++
+       return found ? 0 : -1;
+ }
diff --git a/package/network/services/hostapd/patches/061-0008-SAE-Use-const_time-selection-for-PWE-in-FFC.patch b/package/network/services/hostapd/patches/061-0008-SAE-Use-const_time-selection-for-PWE-in-FFC.patch
new file mode 100644 (file)
index 0000000..47e1b3c
--- /dev/null
@@ -0,0 +1,100 @@
+From f8f20717f87eff1f025f48ed585c7684debacf72 Mon Sep 17 00:00:00 2001
+From: Jouni Malinen <jouni@codeaurora.org>
+Date: Sat, 2 Mar 2019 12:45:33 +0200
+Subject: [PATCH 08/14] SAE: Use const_time selection for PWE in FFC
+
+This is an initial step towards making the FFC case use strictly
+constant time operations similarly to the ECC case.
+sae_test_pwd_seed_ffc() does not yet have constant time behavior,
+though.
+
+This is related to CVE-2019-9494.
+
+Signed-off-by: Jouni Malinen <jouni@codeaurora.org>
+---
+ src/common/sae.c | 53 +++++++++++++++++++++++++++++++++++------------------
+ 1 file changed, 35 insertions(+), 18 deletions(-)
+
+--- a/src/common/sae.c
++++ b/src/common/sae.c
+@@ -589,17 +589,28 @@ static int sae_derive_pwe_ffc(struct sae
+                             const u8 *addr2, const u8 *password,
+                             size_t password_len, const char *identifier)
+ {
+-      u8 counter, k;
++      u8 counter, k, sel_counter = 0;
+       u8 addrs[2 * ETH_ALEN];
+       const u8 *addr[3];
+       size_t len[3];
+       size_t num_elem;
+-      int found = 0;
+-      struct crypto_bignum *pwe = NULL;
++      u8 found = 0; /* 0 (false) or 0xff (true) to be used as const_time_*
++                     * mask */
++      u8 mask;
++      struct crypto_bignum *pwe;
++      size_t prime_len = sae->tmp->prime_len * 8;
++      u8 *pwe_buf;
+       crypto_bignum_deinit(sae->tmp->pwe_ffc, 1);
+       sae->tmp->pwe_ffc = NULL;
++      /* Allocate a buffer to maintain selected and candidate PWE for constant
++       * time selection. */
++      pwe_buf = os_zalloc(prime_len * 2);
++      pwe = crypto_bignum_init();
++      if (!pwe_buf || !pwe)
++              goto fail;
++
+       wpa_hexdump_ascii_key(MSG_DEBUG, "SAE: password",
+                             password, password_len);
+@@ -638,27 +649,33 @@ static int sae_derive_pwe_ffc(struct sae
+               if (hmac_sha256_vector(addrs, sizeof(addrs), num_elem,
+                                      addr, len, pwd_seed) < 0)
+                       break;
+-              if (!pwe) {
+-                      pwe = crypto_bignum_init();
+-                      if (!pwe)
+-                              break;
+-              }
+               res = sae_test_pwd_seed_ffc(sae, pwd_seed, pwe);
++              /* res is -1 for fatal failure, 0 if a valid PWE was not found,
++               * or 1 if a valid PWE was found. */
+               if (res < 0)
+                       break;
+-              if (res > 0) {
+-                      found = 1;
+-                      if (!sae->tmp->pwe_ffc) {
+-                              wpa_printf(MSG_DEBUG, "SAE: Use this PWE");
+-                              sae->tmp->pwe_ffc = pwe;
+-                              pwe = NULL;
+-                      }
+-              }
++              /* Store the candidate PWE into the second half of pwe_buf and
++               * the selected PWE in the beginning of pwe_buf using constant
++               * time selection. */
++              if (crypto_bignum_to_bin(pwe, pwe_buf + prime_len, prime_len,
++                                       prime_len) < 0)
++                      break;
++              const_time_select_bin(found, pwe_buf, pwe_buf + prime_len,
++                                    prime_len, pwe_buf);
++              sel_counter = const_time_select_u8(found, sel_counter, counter);
++              mask = const_time_eq_u8(res, 1);
++              found = const_time_select_u8(found, found, mask);
+       }
+-      crypto_bignum_deinit(pwe, 1);
++      if (!found)
++              goto fail;
+-      return found ? 0 : -1;
++      wpa_printf(MSG_DEBUG, "SAE: Use PWE from counter = %02u", sel_counter);
++      sae->tmp->pwe_ffc = crypto_bignum_init_set(pwe_buf, prime_len);
++fail:
++      crypto_bignum_deinit(pwe, 1);
++      bin_clear_free(pwe_buf, prime_len * 2);
++      return sae->tmp->pwe_ffc ? 0 : -1;
+ }
diff --git a/package/network/services/hostapd/patches/061-0009-SAE-Use-constant-time-operations-in-sae_test_pwd_see.patch b/package/network/services/hostapd/patches/061-0009-SAE-Use-constant-time-operations-in-sae_test_pwd_see.patch
new file mode 100644 (file)
index 0000000..150cbeb
--- /dev/null
@@ -0,0 +1,133 @@
+From cff138b0747fa39765cbc641b66cfa5d7f1735d1 Mon Sep 17 00:00:00 2001
+From: Jouni Malinen <jouni@codeaurora.org>
+Date: Sat, 2 Mar 2019 16:05:56 +0200
+Subject: [PATCH 09/14] 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>
+---
+ src/common/sae.c | 75 ++++++++++++++++++++++++++++++++++----------------------
+ 1 file changed, 46 insertions(+), 29 deletions(-)
+
+--- a/src/common/sae.c
++++ b/src/common/sae.c
+@@ -311,14 +311,17 @@ static int sae_test_pwd_seed_ecc(struct
+ }
++/* 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,
+                                struct crypto_bignum *pwe)
+ {
+       u8 pwd_value[SAE_MAX_PRIME_LEN];
+       size_t bits = sae->tmp->prime_len * 8;
+       u8 exp[1];
+-      struct crypto_bignum *a, *b;
+-      int res;
++      struct crypto_bignum *a, *b = NULL;
++      int res, is_val;
++      u8 pwd_value_valid;
+       wpa_hexdump_key(MSG_DEBUG, "SAE: pwd-seed", pwd_seed, SHA256_MAC_LEN);
+@@ -330,16 +333,29 @@ static int sae_test_pwd_seed_ffc(struct
+       wpa_hexdump_key(MSG_DEBUG, "SAE: pwd-value", pwd_value,
+                       sae->tmp->prime_len);
+-      if (os_memcmp(pwd_value, sae->tmp->dh->prime, sae->tmp->prime_len) >= 0)
+-      {
+-              wpa_printf(MSG_DEBUG, "SAE: pwd-value >= p");
+-              return 0;
+-      }
++      /* Check whether pwd-value < p */
++      res = const_time_memcmp(pwd_value, sae->tmp->dh->prime,
++                              sae->tmp->prime_len);
++      /* 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 */
++      res = -1;
+       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) {
+               /*
+                * r = (p-1)/2 for the group used here, so this becomes:
+@@ -353,33 +369,34 @@ static int sae_test_pwd_seed_ffc(struct
+               b = crypto_bignum_init_set(exp, sizeof(exp));
+               if (b == NULL ||
+                   crypto_bignum_sub(sae->tmp->prime, b, b) < 0 ||
+-                  crypto_bignum_div(b, sae->tmp->order, b) < 0) {
+-                      crypto_bignum_deinit(b, 0);
+-                      b = NULL;
+-              }
++                  crypto_bignum_div(b, sae->tmp->order, b) < 0)
++                      goto fail;
+       }
+-      if (a == NULL || b == NULL)
+-              res = -1;
+-      else
+-              res = crypto_bignum_exptmod(a, b, sae->tmp->prime, pwe);
+-
+-      crypto_bignum_deinit(a, 0);
+-      crypto_bignum_deinit(b, 0);
+-
+-      if (res < 0) {
+-              wpa_printf(MSG_DEBUG, "SAE: Failed to calculate PWE");
+-              return -1;
+-      }
+-
+-      /* if (PWE > 1) --> found */
+-      if (crypto_bignum_is_zero(pwe) || crypto_bignum_is_one(pwe)) {
+-              wpa_printf(MSG_DEBUG, "SAE: PWE <= 1");
+-              return 0;
+-      }
++      if (!b)
++              goto fail;
+-      wpa_printf(MSG_DEBUG, "SAE: PWE found");
+-      return 1;
++      res = crypto_bignum_exptmod(a, b, sae->tmp->prime, pwe);
++      if (res < 0)
++              goto fail;
++
++      /* There were no fatal errors in calculations, so determine the return
++       * 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);
++
++fail:
++      crypto_bignum_deinit(a, 1);
++      crypto_bignum_deinit(b, 1);
++      return res;
+ }