xref: /netbsd-src/external/bsd/wpa/dist/src/eap_common/eap_pwd_common.c (revision ba65fde2d7fefa7d39838fa5fa855e62bd606b5e)
1 /*
2  * EAP server/peer: EAP-pwd shared routines
3  * Copyright (c) 2010, Dan Harkins <dharkins@lounge.org>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the BSD license.
7  *
8  * Alternatively, this software may be distributed under the terms of the
9  * GNU General Public License version 2 as published by the Free Software
10  * Foundation.
11  *
12  * See README and COPYING for more details.
13  */
14 
15 #include "includes.h"
16 #include "common.h"
17 #include "eap_defs.h"
18 #include "eap_pwd_common.h"
19 
20 /* The random function H(x) = HMAC-SHA256(0^32, x) */
21 void H_Init(HMAC_CTX *ctx)
22 {
23 	u8 allzero[SHA256_DIGEST_LENGTH];
24 
25 	os_memset(allzero, 0, SHA256_DIGEST_LENGTH);
26 	HMAC_Init(ctx, allzero, SHA256_DIGEST_LENGTH, EVP_sha256());
27 }
28 
29 
30 void H_Update(HMAC_CTX *ctx, const u8 *data, int len)
31 {
32 	HMAC_Update(ctx, data, len);
33 }
34 
35 
36 void H_Final(HMAC_CTX *ctx, u8 *digest)
37 {
38 	unsigned int mdlen = SHA256_DIGEST_LENGTH;
39 
40 	HMAC_Final(ctx, digest, &mdlen);
41 	HMAC_CTX_cleanup(ctx);
42 }
43 
44 
45 /* a counter-based KDF based on NIST SP800-108 */
46 void eap_pwd_kdf(u8 *key, int keylen, u8 *label, int labellen,
47 		 u8 *result, int resultbitlen)
48 {
49 	HMAC_CTX hctx;
50 	unsigned char digest[SHA256_DIGEST_LENGTH];
51 	u16 i, ctr, L;
52 	int resultbytelen, len = 0;
53 	unsigned int mdlen = SHA256_DIGEST_LENGTH;
54 	unsigned char mask = 0xff;
55 
56 	resultbytelen = (resultbitlen + 7)/8;
57 	ctr = 0;
58 	L = htons(resultbitlen);
59 	while (len < resultbytelen) {
60 		ctr++; i = htons(ctr);
61 		HMAC_Init(&hctx, key, keylen, EVP_sha256());
62 		if (ctr > 1)
63 			HMAC_Update(&hctx, digest, mdlen);
64 		HMAC_Update(&hctx, (u8 *) &i, sizeof(u16));
65 		HMAC_Update(&hctx, label, labellen);
66 		HMAC_Update(&hctx, (u8 *) &L, sizeof(u16));
67 		HMAC_Final(&hctx, digest, &mdlen);
68 		if ((len + (int) mdlen) > resultbytelen)
69 			os_memcpy(result + len, digest, resultbytelen - len);
70 		else
71 			os_memcpy(result + len, digest, mdlen);
72 		len += mdlen;
73 		HMAC_CTX_cleanup(&hctx);
74 	}
75 
76 	/* since we're expanding to a bit length, mask off the excess */
77 	if (resultbitlen % 8) {
78 		mask <<= (8 - (resultbitlen % 8));
79 		result[resultbytelen - 1] &= mask;
80 	}
81 }
82 
83 
84 /*
85  * compute a "random" secret point on an elliptic curve based
86  * on the password and identities.
87  */
88 int compute_password_element(EAP_PWD_group *grp, u16 num,
89 			     u8 *password, int password_len,
90 			     u8 *id_server, int id_server_len,
91 			     u8 *id_peer, int id_peer_len, u8 *token)
92 {
93 	BIGNUM *x_candidate = NULL, *rnd = NULL, *cofactor = NULL;
94 	HMAC_CTX ctx;
95 	unsigned char pwe_digest[SHA256_DIGEST_LENGTH], *prfbuf = NULL, ctr;
96 	int nid, is_odd, primebitlen, primebytelen, ret = 0;
97 
98 	switch (num) { /* from IANA registry for IKE D-H groups */
99         case 19:
100 		nid = NID_X9_62_prime256v1;
101 		break;
102         case 20:
103 		nid = NID_secp384r1;
104 		break;
105         case 21:
106 		nid = NID_secp521r1;
107 		break;
108         case 25:
109 		nid = NID_X9_62_prime192v1;
110 		break;
111         case 26:
112 		nid = NID_secp224r1;
113 		break;
114         default:
115 		wpa_printf(MSG_INFO, "EAP-pwd: unsupported group %d", num);
116 		return -1;
117 	}
118 
119 	grp->pwe = NULL;
120 	grp->order = NULL;
121 	grp->prime = NULL;
122 
123 	if ((grp->group = EC_GROUP_new_by_curve_name(nid)) == NULL) {
124 		wpa_printf(MSG_INFO, "EAP-pwd: unable to create EC_GROUP");
125 		goto fail;
126 	}
127 
128 	if (((rnd = BN_new()) == NULL) ||
129 	    ((cofactor = BN_new()) == NULL) ||
130 	    ((grp->pwe = EC_POINT_new(grp->group)) == NULL) ||
131 	    ((grp->order = BN_new()) == NULL) ||
132 	    ((grp->prime = BN_new()) == NULL) ||
133 	    ((x_candidate = BN_new()) == NULL)) {
134 		wpa_printf(MSG_INFO, "EAP-pwd: unable to create bignums");
135 		goto fail;
136 	}
137 
138 	if (!EC_GROUP_get_curve_GFp(grp->group, grp->prime, NULL, NULL, NULL))
139 	{
140 		wpa_printf(MSG_INFO, "EAP-pwd: unable to get prime for GFp "
141 			   "curve");
142 		goto fail;
143 	}
144 	if (!EC_GROUP_get_order(grp->group, grp->order, NULL)) {
145 		wpa_printf(MSG_INFO, "EAP-pwd: unable to get order for curve");
146 		goto fail;
147 	}
148 	if (!EC_GROUP_get_cofactor(grp->group, cofactor, NULL)) {
149 		wpa_printf(MSG_INFO, "EAP-pwd: unable to get cofactor for "
150 			   "curve");
151 		goto fail;
152 	}
153 	primebitlen = BN_num_bits(grp->prime);
154 	primebytelen = BN_num_bytes(grp->prime);
155 	if ((prfbuf = os_malloc(primebytelen)) == NULL) {
156 		wpa_printf(MSG_INFO, "EAP-pwd: unable to malloc space for prf "
157 			   "buffer");
158 		goto fail;
159 	}
160 	os_memset(prfbuf, 0, primebytelen);
161 	ctr = 0;
162 	while (1) {
163 		if (ctr > 10) {
164 			wpa_printf(MSG_INFO, "EAP-pwd: unable to find random "
165 				   "point on curve for group %d, something's "
166 				   "fishy", num);
167 			goto fail;
168 		}
169 		ctr++;
170 
171 		/*
172 		 * compute counter-mode password value and stretch to prime
173 		 *    pwd-seed = H(token | peer-id | server-id | password |
174 		 *		   counter)
175 		 */
176 		H_Init(&ctx);
177 		H_Update(&ctx, token, sizeof(u32));
178 		H_Update(&ctx, id_peer, id_peer_len);
179 		H_Update(&ctx, id_server, id_server_len);
180 		H_Update(&ctx, password, password_len);
181 		H_Update(&ctx, &ctr, sizeof(ctr));
182 		H_Final(&ctx, pwe_digest);
183 
184 		BN_bin2bn(pwe_digest, SHA256_DIGEST_LENGTH, rnd);
185 
186 		eap_pwd_kdf(pwe_digest, SHA256_DIGEST_LENGTH,
187 			    (unsigned char *) "EAP-pwd Hunting And Pecking",
188 			    os_strlen("EAP-pwd Hunting And Pecking"),
189 			    prfbuf, primebitlen);
190 
191 		BN_bin2bn(prfbuf, primebytelen, x_candidate);
192 
193 		/*
194 		 * eap_pwd_kdf() returns a string of bits 0..primebitlen but
195 		 * BN_bin2bn will treat that string of bits as a big endian
196 		 * number. If the primebitlen is not an even multiple of 8
197 		 * then excessive bits-- those _after_ primebitlen-- so now
198 		 * we have to shift right the amount we masked off.
199 		 */
200 		if (primebitlen % 8)
201 			BN_rshift(x_candidate, x_candidate,
202 				  (8 - (primebitlen % 8)));
203 
204 		if (BN_ucmp(x_candidate, grp->prime) >= 0)
205 			continue;
206 
207 		wpa_hexdump(MSG_DEBUG, "EAP-pwd: x_candidate",
208 			    prfbuf, primebytelen);
209 
210 		/*
211 		 * need to unambiguously identify the solution, if there is
212 		 * one...
213 		 */
214 		if (BN_is_odd(rnd))
215 			is_odd = 1;
216 		else
217 			is_odd = 0;
218 
219 		/*
220 		 * solve the quadratic equation, if it's not solvable then we
221 		 * don't have a point
222 		 */
223 		if (!EC_POINT_set_compressed_coordinates_GFp(grp->group,
224 							     grp->pwe,
225 							     x_candidate,
226 							     is_odd, NULL))
227 			continue;
228 		/*
229 		 * If there's a solution to the equation then the point must be
230 		 * on the curve so why check again explicitly? OpenSSL code
231 		 * says this is required by X9.62. We're not X9.62 but it can't
232 		 * hurt just to be sure.
233 		 */
234 		if (!EC_POINT_is_on_curve(grp->group, grp->pwe, NULL)) {
235 			wpa_printf(MSG_INFO, "EAP-pwd: point is not on curve");
236 			continue;
237 		}
238 
239 		if (BN_cmp(cofactor, BN_value_one())) {
240 			/* make sure the point is not in a small sub-group */
241 			if (!EC_POINT_mul(grp->group, grp->pwe, NULL, grp->pwe,
242 					  cofactor, NULL)) {
243 				wpa_printf(MSG_INFO, "EAP-pwd: cannot "
244 					   "multiply generator by order");
245 				continue;
246 			}
247 			if (EC_POINT_is_at_infinity(grp->group, grp->pwe)) {
248 				wpa_printf(MSG_INFO, "EAP-pwd: point is at "
249 					   "infinity");
250 				continue;
251 			}
252 		}
253 		/* if we got here then we have a new generator. */
254 		break;
255 	}
256 	wpa_printf(MSG_DEBUG, "EAP-pwd: found a PWE in %d tries", ctr);
257 	grp->group_num = num;
258 	if (0) {
259  fail:
260 		EC_GROUP_free(grp->group);
261 		EC_POINT_free(grp->pwe);
262 		BN_free(grp->order);
263 		BN_free(grp->prime);
264 		os_free(grp);
265 		grp = NULL;
266 		ret = 1;
267 	}
268 	/* cleanliness and order.... */
269 	BN_free(cofactor);
270 	BN_free(x_candidate);
271 	BN_free(rnd);
272 	os_free(prfbuf);
273 
274 	return ret;
275 }
276 
277 
278 int compute_keys(EAP_PWD_group *grp, BN_CTX *bnctx, BIGNUM *k,
279 		 BIGNUM *peer_scalar, BIGNUM *server_scalar,
280 		 u8 *commit_peer, u8 *commit_server,
281 		 u32 *ciphersuite, u8 *msk, u8 *emsk)
282 {
283 	HMAC_CTX ctx;
284 	u8 mk[SHA256_DIGEST_LENGTH], *cruft;
285 	u8 session_id[SHA256_DIGEST_LENGTH + 1];
286 	u8 msk_emsk[EAP_MSK_LEN + EAP_EMSK_LEN];
287 	int offset;
288 
289 	if ((cruft = os_malloc(BN_num_bytes(grp->prime))) == NULL)
290 		return -1;
291 
292 	/*
293 	 * first compute the session-id = TypeCode | H(ciphersuite | scal_p |
294 	 *	scal_s)
295 	 */
296 	session_id[0] = EAP_TYPE_PWD;
297 	H_Init(&ctx);
298 	H_Update(&ctx, (u8 *)ciphersuite, sizeof(u32));
299 	offset = BN_num_bytes(grp->order) - BN_num_bytes(peer_scalar);
300 	os_memset(cruft, 0, BN_num_bytes(grp->prime));
301 	BN_bn2bin(peer_scalar, cruft + offset);
302 	H_Update(&ctx, cruft, BN_num_bytes(grp->order));
303 	offset = BN_num_bytes(grp->order) - BN_num_bytes(server_scalar);
304 	os_memset(cruft, 0, BN_num_bytes(grp->prime));
305 	BN_bn2bin(server_scalar, cruft + offset);
306 	H_Update(&ctx, cruft, BN_num_bytes(grp->order));
307 	H_Final(&ctx, &session_id[1]);
308 
309 	/* then compute MK = H(k | commit-peer | commit-server) */
310 	H_Init(&ctx);
311 	offset = BN_num_bytes(grp->prime) - BN_num_bytes(k);
312 	os_memset(cruft, 0, BN_num_bytes(grp->prime));
313 	BN_bn2bin(k, cruft + offset);
314 	H_Update(&ctx, cruft, BN_num_bytes(grp->prime));
315 	H_Update(&ctx, commit_peer, SHA256_DIGEST_LENGTH);
316 	H_Update(&ctx, commit_server, SHA256_DIGEST_LENGTH);
317 	H_Final(&ctx, mk);
318 
319 	/* stretch the mk with the session-id to get MSK | EMSK */
320 	eap_pwd_kdf(mk, SHA256_DIGEST_LENGTH,
321 		    session_id, SHA256_DIGEST_LENGTH+1,
322 		    msk_emsk, (EAP_MSK_LEN + EAP_EMSK_LEN) * 8);
323 
324 	os_memcpy(msk, msk_emsk, EAP_MSK_LEN);
325 	os_memcpy(emsk, msk_emsk + EAP_MSK_LEN, EAP_EMSK_LEN);
326 
327 	os_free(cruft);
328 
329 	return 1;
330 }
331