aboutsummaryrefslogtreecommitdiffstats
path: root/package/network/services/hostapd/patches/061-0005-SAE-Minimize-timing-differences-in-PWE-derivation.patch
blob: e72a9cbe5acf1c623e0f4bac2ea02491e8c2b029 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
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;
 }