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;
 }
 
 
