From 603cd880e7f90595482658a7136fa6a7be5cb485 Mon Sep 17 00:00:00 2001
From: Jouni Malinen <j@w1.fi>
Date: Fri, 7 Jan 2022 18:52:27 +0200
Subject: [PATCH 4/4] EAP-pwd: Derive the y coordinate for PWE with own
 implementation

The crypto_ec_point_solve_y_coord() wrapper function might not use
constant time operations in the crypto library and as such, could leak
side channel information about the password that is used to generate the
PWE in the hunting and pecking loop. As such, calculate the two possible
y coordinate values and pick the correct one to use with constant time
selection.

Signed-off-by: Jouni Malinen <j@w1.fi>
---
 src/eap_common/eap_pwd_common.c | 46 ++++++++++++++++++++++++++-------
 1 file changed, 36 insertions(+), 10 deletions(-)

--- a/src/eap_common/eap_pwd_common.c
+++ b/src/eap_common/eap_pwd_common.c
@@ -127,7 +127,8 @@ int compute_password_element(EAP_PWD_gro
 	u8 qr_or_qnr_bin[MAX_ECC_PRIME_LEN];
 	u8 x_bin[MAX_ECC_PRIME_LEN];
 	u8 prime_bin[MAX_ECC_PRIME_LEN];
-	struct crypto_bignum *tmp2 = NULL;
+	u8 x_y[2 * MAX_ECC_PRIME_LEN];
+	struct crypto_bignum *tmp2 = NULL, *y = NULL;
 	struct crypto_hash *hash;
 	unsigned char pwe_digest[SHA256_MAC_LEN], *prfbuf = NULL, ctr;
 	int ret = 0, res;
@@ -139,6 +140,7 @@ int compute_password_element(EAP_PWD_gro
 	u8 found_ctr = 0, is_odd = 0;
 	int cmp_prime;
 	unsigned int in_range;
+	unsigned int is_eq;
 
 	if (grp->pwe)
 		return -1;
@@ -151,11 +153,6 @@ int compute_password_element(EAP_PWD_gro
 	if (crypto_bignum_to_bin(prime, prime_bin, sizeof(prime_bin),
 				 primebytelen) < 0)
 		return -1;
-	grp->pwe = crypto_ec_point_init(grp->group);
-	if (!grp->pwe) {
-		wpa_printf(MSG_INFO, "EAP-pwd: unable to create bignums");
-		goto fail;
-	}
 
 	if ((prfbuf = os_malloc(primebytelen)) == NULL) {
 		wpa_printf(MSG_INFO, "EAP-pwd: unable to malloc space for prf "
@@ -261,10 +258,37 @@ int compute_password_element(EAP_PWD_gro
 	 */
 	crypto_bignum_deinit(x_candidate, 1);
 	x_candidate = crypto_bignum_init_set(x_bin, primebytelen);
-	if (!x_candidate ||
-	    crypto_ec_point_solve_y_coord(grp->group, grp->pwe, x_candidate,
-					  is_odd) != 0) {
-		wpa_printf(MSG_INFO, "EAP-pwd: Could not solve for y");
+	if (!x_candidate)
+		goto fail;
+
+	/* y = sqrt(x^3 + ax + b) mod p
+	 * if LSB(y) == LSB(pwd-seed): PWE = (x, y)
+	 * else: PWE = (x, p - y)
+	 *
+	 * Calculate y and the two possible values for PWE and after that,
+	 * use constant time selection to copy the correct alternative.
+	 */
+	y = crypto_ec_point_compute_y_sqr(grp->group, x_candidate);
+	if (!y ||
+	    dragonfly_sqrt(grp->group, y, y) < 0 ||
+	    crypto_bignum_to_bin(y, x_y, MAX_ECC_PRIME_LEN, primebytelen) < 0 ||
+	    crypto_bignum_sub(prime, y, y) < 0 ||
+	    crypto_bignum_to_bin(y, x_y + MAX_ECC_PRIME_LEN,
+				 MAX_ECC_PRIME_LEN, primebytelen) < 0) {
+		wpa_printf(MSG_DEBUG, "SAE: Could not solve y");
+		goto fail;
+	}
+
+	/* Constant time selection of the y coordinate from the two
+	 * options */
+	is_eq = const_time_eq(is_odd, x_y[primebytelen - 1] & 0x01);
+	const_time_select_bin(is_eq, x_y, x_y + MAX_ECC_PRIME_LEN,
+			      primebytelen, x_y + primebytelen);
+	os_memcpy(x_y, x_bin, primebytelen);
+	wpa_hexdump_key(MSG_DEBUG, "EAP-pwd: PWE", x_y, 2 * primebytelen);
+	grp->pwe = crypto_ec_point_from_bin(grp->group, x_y);
+	if (!grp->pwe) {
+		wpa_printf(MSG_DEBUG, "EAP-pwd: Could not generate PWE");
 		goto fail;
 	}
 
@@ -289,6 +313,7 @@ int compute_password_element(EAP_PWD_gro
 	/* cleanliness and order.... */
 	crypto_bignum_deinit(x_candidate, 1);
 	crypto_bignum_deinit(tmp2, 1);
+	crypto_bignum_deinit(y, 1);
 	crypto_bignum_deinit(qr, 1);
 	crypto_bignum_deinit(qnr, 1);
 	bin_clear_free(prfbuf, primebytelen);
@@ -296,6 +321,7 @@ int compute_password_element(EAP_PWD_gro
 	os_memset(qnr_bin, 0, sizeof(qnr_bin));
 	os_memset(qr_or_qnr_bin, 0, sizeof(qr_or_qnr_bin));
 	os_memset(pwe_digest, 0, sizeof(pwe_digest));
+	forced_memzero(x_y, sizeof(x_y));
 
 	return ret;
 }
