1252190Srpaulo/*
2252190Srpaulo * EAP server/peer: EAP-pwd shared routines
3252190Srpaulo * Copyright (c) 2010, Dan Harkins <dharkins@lounge.org>
4252190Srpaulo *
5252190Srpaulo * This software may be distributed under the terms of the BSD license.
6252190Srpaulo * See README for more details.
7252190Srpaulo */
8252190Srpaulo
9252190Srpaulo#include "includes.h"
10252190Srpaulo#include "common.h"
11252190Srpaulo#include "crypto/sha256.h"
12252190Srpaulo#include "crypto/crypto.h"
13252190Srpaulo#include "eap_defs.h"
14252190Srpaulo#include "eap_pwd_common.h"
15252190Srpaulo
16252190Srpaulo/* The random function H(x) = HMAC-SHA256(0^32, x) */
17252190Srpaulostruct crypto_hash * eap_pwd_h_init(void)
18252190Srpaulo{
19252190Srpaulo	u8 allzero[SHA256_MAC_LEN];
20252190Srpaulo	os_memset(allzero, 0, SHA256_MAC_LEN);
21252190Srpaulo	return crypto_hash_init(CRYPTO_HASH_ALG_HMAC_SHA256, allzero,
22252190Srpaulo				SHA256_MAC_LEN);
23252190Srpaulo}
24252190Srpaulo
25252190Srpaulo
26252190Srpaulovoid eap_pwd_h_update(struct crypto_hash *hash, const u8 *data, size_t len)
27252190Srpaulo{
28252190Srpaulo	crypto_hash_update(hash, data, len);
29252190Srpaulo}
30252190Srpaulo
31252190Srpaulo
32252190Srpaulovoid eap_pwd_h_final(struct crypto_hash *hash, u8 *digest)
33252190Srpaulo{
34252190Srpaulo	size_t len = SHA256_MAC_LEN;
35252190Srpaulo	crypto_hash_finish(hash, digest, &len);
36252190Srpaulo}
37252190Srpaulo
38252190Srpaulo
39252190Srpaulo/* a counter-based KDF based on NIST SP800-108 */
40252190Srpaulostatic int eap_pwd_kdf(const u8 *key, size_t keylen, const u8 *label,
41252190Srpaulo		       size_t labellen, u8 *result, size_t resultbitlen)
42252190Srpaulo{
43252190Srpaulo	struct crypto_hash *hash;
44252190Srpaulo	u8 digest[SHA256_MAC_LEN];
45252190Srpaulo	u16 i, ctr, L;
46252190Srpaulo	size_t resultbytelen, len = 0, mdlen;
47252190Srpaulo
48252190Srpaulo	resultbytelen = (resultbitlen + 7) / 8;
49252190Srpaulo	ctr = 0;
50252190Srpaulo	L = htons(resultbitlen);
51252190Srpaulo	while (len < resultbytelen) {
52252190Srpaulo		ctr++;
53252190Srpaulo		i = htons(ctr);
54252190Srpaulo		hash = crypto_hash_init(CRYPTO_HASH_ALG_HMAC_SHA256,
55252190Srpaulo					key, keylen);
56252190Srpaulo		if (hash == NULL)
57252190Srpaulo			return -1;
58252190Srpaulo		if (ctr > 1)
59252190Srpaulo			crypto_hash_update(hash, digest, SHA256_MAC_LEN);
60252190Srpaulo		crypto_hash_update(hash, (u8 *) &i, sizeof(u16));
61252190Srpaulo		crypto_hash_update(hash, label, labellen);
62252190Srpaulo		crypto_hash_update(hash, (u8 *) &L, sizeof(u16));
63252190Srpaulo		mdlen = SHA256_MAC_LEN;
64252190Srpaulo		if (crypto_hash_finish(hash, digest, &mdlen) < 0)
65252190Srpaulo			return -1;
66252190Srpaulo		if ((len + mdlen) > resultbytelen)
67252190Srpaulo			os_memcpy(result + len, digest, resultbytelen - len);
68252190Srpaulo		else
69252190Srpaulo			os_memcpy(result + len, digest, mdlen);
70252190Srpaulo		len += mdlen;
71252190Srpaulo	}
72252190Srpaulo
73252190Srpaulo	/* since we're expanding to a bit length, mask off the excess */
74252190Srpaulo	if (resultbitlen % 8) {
75252190Srpaulo		u8 mask = 0xff;
76252190Srpaulo		mask <<= (8 - (resultbitlen % 8));
77252190Srpaulo		result[resultbytelen - 1] &= mask;
78252190Srpaulo	}
79252190Srpaulo
80252190Srpaulo	return 0;
81252190Srpaulo}
82252190Srpaulo
83252190Srpaulo
84252190Srpaulo/*
85252190Srpaulo * compute a "random" secret point on an elliptic curve based
86252190Srpaulo * on the password and identities.
87252190Srpaulo */
88252190Srpauloint compute_password_element(EAP_PWD_group *grp, u16 num,
89252190Srpaulo			     u8 *password, int password_len,
90252190Srpaulo			     u8 *id_server, int id_server_len,
91252190Srpaulo			     u8 *id_peer, int id_peer_len, u8 *token)
92252190Srpaulo{
93252190Srpaulo	BIGNUM *x_candidate = NULL, *rnd = NULL, *cofactor = NULL;
94252190Srpaulo	struct crypto_hash *hash;
95252190Srpaulo	unsigned char pwe_digest[SHA256_MAC_LEN], *prfbuf = NULL, ctr;
96252190Srpaulo	int nid, is_odd, ret = 0;
97252190Srpaulo	size_t primebytelen, primebitlen;
98252190Srpaulo
99252190Srpaulo	switch (num) { /* from IANA registry for IKE D-H groups */
100252190Srpaulo        case 19:
101252190Srpaulo		nid = NID_X9_62_prime256v1;
102252190Srpaulo		break;
103252190Srpaulo        case 20:
104252190Srpaulo		nid = NID_secp384r1;
105252190Srpaulo		break;
106252190Srpaulo        case 21:
107252190Srpaulo		nid = NID_secp521r1;
108252190Srpaulo		break;
109252190Srpaulo        case 25:
110252190Srpaulo		nid = NID_X9_62_prime192v1;
111252190Srpaulo		break;
112252190Srpaulo        case 26:
113252190Srpaulo		nid = NID_secp224r1;
114252190Srpaulo		break;
115252190Srpaulo        default:
116252190Srpaulo		wpa_printf(MSG_INFO, "EAP-pwd: unsupported group %d", num);
117252190Srpaulo		return -1;
118252190Srpaulo	}
119252190Srpaulo
120252190Srpaulo	grp->pwe = NULL;
121252190Srpaulo	grp->order = NULL;
122252190Srpaulo	grp->prime = NULL;
123252190Srpaulo
124252190Srpaulo	if ((grp->group = EC_GROUP_new_by_curve_name(nid)) == NULL) {
125252190Srpaulo		wpa_printf(MSG_INFO, "EAP-pwd: unable to create EC_GROUP");
126252190Srpaulo		goto fail;
127252190Srpaulo	}
128252190Srpaulo
129252190Srpaulo	if (((rnd = BN_new()) == NULL) ||
130252190Srpaulo	    ((cofactor = BN_new()) == NULL) ||
131252190Srpaulo	    ((grp->pwe = EC_POINT_new(grp->group)) == NULL) ||
132252190Srpaulo	    ((grp->order = BN_new()) == NULL) ||
133252190Srpaulo	    ((grp->prime = BN_new()) == NULL) ||
134252190Srpaulo	    ((x_candidate = BN_new()) == NULL)) {
135252190Srpaulo		wpa_printf(MSG_INFO, "EAP-pwd: unable to create bignums");
136252190Srpaulo		goto fail;
137252190Srpaulo	}
138252190Srpaulo
139252190Srpaulo	if (!EC_GROUP_get_curve_GFp(grp->group, grp->prime, NULL, NULL, NULL))
140252190Srpaulo	{
141252190Srpaulo		wpa_printf(MSG_INFO, "EAP-pwd: unable to get prime for GFp "
142252190Srpaulo			   "curve");
143252190Srpaulo		goto fail;
144252190Srpaulo	}
145252190Srpaulo	if (!EC_GROUP_get_order(grp->group, grp->order, NULL)) {
146252190Srpaulo		wpa_printf(MSG_INFO, "EAP-pwd: unable to get order for curve");
147252190Srpaulo		goto fail;
148252190Srpaulo	}
149252190Srpaulo	if (!EC_GROUP_get_cofactor(grp->group, cofactor, NULL)) {
150252190Srpaulo		wpa_printf(MSG_INFO, "EAP-pwd: unable to get cofactor for "
151252190Srpaulo			   "curve");
152252190Srpaulo		goto fail;
153252190Srpaulo	}
154252190Srpaulo	primebitlen = BN_num_bits(grp->prime);
155252190Srpaulo	primebytelen = BN_num_bytes(grp->prime);
156252190Srpaulo	if ((prfbuf = os_malloc(primebytelen)) == NULL) {
157252190Srpaulo		wpa_printf(MSG_INFO, "EAP-pwd: unable to malloc space for prf "
158252190Srpaulo			   "buffer");
159252190Srpaulo		goto fail;
160252190Srpaulo	}
161252190Srpaulo	os_memset(prfbuf, 0, primebytelen);
162252190Srpaulo	ctr = 0;
163252190Srpaulo	while (1) {
164252190Srpaulo		if (ctr > 30) {
165252190Srpaulo			wpa_printf(MSG_INFO, "EAP-pwd: unable to find random "
166252190Srpaulo				   "point on curve for group %d, something's "
167252190Srpaulo				   "fishy", num);
168252190Srpaulo			goto fail;
169252190Srpaulo		}
170252190Srpaulo		ctr++;
171252190Srpaulo
172252190Srpaulo		/*
173252190Srpaulo		 * compute counter-mode password value and stretch to prime
174252190Srpaulo		 *    pwd-seed = H(token | peer-id | server-id | password |
175252190Srpaulo		 *		   counter)
176252190Srpaulo		 */
177252190Srpaulo		hash = eap_pwd_h_init();
178252190Srpaulo		if (hash == NULL)
179252190Srpaulo			goto fail;
180252190Srpaulo		eap_pwd_h_update(hash, token, sizeof(u32));
181252190Srpaulo		eap_pwd_h_update(hash, id_peer, id_peer_len);
182252190Srpaulo		eap_pwd_h_update(hash, id_server, id_server_len);
183252190Srpaulo		eap_pwd_h_update(hash, password, password_len);
184252190Srpaulo		eap_pwd_h_update(hash, &ctr, sizeof(ctr));
185252190Srpaulo		eap_pwd_h_final(hash, pwe_digest);
186252190Srpaulo
187252190Srpaulo		BN_bin2bn(pwe_digest, SHA256_MAC_LEN, rnd);
188252190Srpaulo
189252190Srpaulo		if (eap_pwd_kdf(pwe_digest, SHA256_MAC_LEN,
190252190Srpaulo				(u8 *) "EAP-pwd Hunting And Pecking",
191252190Srpaulo				os_strlen("EAP-pwd Hunting And Pecking"),
192252190Srpaulo				prfbuf, primebitlen) < 0)
193252190Srpaulo			goto fail;
194252190Srpaulo
195252190Srpaulo		BN_bin2bn(prfbuf, primebytelen, x_candidate);
196252190Srpaulo
197252190Srpaulo		/*
198252190Srpaulo		 * eap_pwd_kdf() returns a string of bits 0..primebitlen but
199252190Srpaulo		 * BN_bin2bn will treat that string of bits as a big endian
200252190Srpaulo		 * number. If the primebitlen is not an even multiple of 8
201252190Srpaulo		 * then excessive bits-- those _after_ primebitlen-- so now
202252190Srpaulo		 * we have to shift right the amount we masked off.
203252190Srpaulo		 */
204252190Srpaulo		if (primebitlen % 8)
205252190Srpaulo			BN_rshift(x_candidate, x_candidate,
206252190Srpaulo				  (8 - (primebitlen % 8)));
207252190Srpaulo
208252190Srpaulo		if (BN_ucmp(x_candidate, grp->prime) >= 0)
209252190Srpaulo			continue;
210252190Srpaulo
211252190Srpaulo		wpa_hexdump(MSG_DEBUG, "EAP-pwd: x_candidate",
212252190Srpaulo			    prfbuf, primebytelen);
213252190Srpaulo
214252190Srpaulo		/*
215252190Srpaulo		 * need to unambiguously identify the solution, if there is
216252190Srpaulo		 * one...
217252190Srpaulo		 */
218252190Srpaulo		if (BN_is_odd(rnd))
219252190Srpaulo			is_odd = 1;
220252190Srpaulo		else
221252190Srpaulo			is_odd = 0;
222252190Srpaulo
223252190Srpaulo		/*
224252190Srpaulo		 * solve the quadratic equation, if it's not solvable then we
225252190Srpaulo		 * don't have a point
226252190Srpaulo		 */
227252190Srpaulo		if (!EC_POINT_set_compressed_coordinates_GFp(grp->group,
228252190Srpaulo							     grp->pwe,
229252190Srpaulo							     x_candidate,
230252190Srpaulo							     is_odd, NULL))
231252190Srpaulo			continue;
232252190Srpaulo		/*
233252190Srpaulo		 * If there's a solution to the equation then the point must be
234252190Srpaulo		 * on the curve so why check again explicitly? OpenSSL code
235252190Srpaulo		 * says this is required by X9.62. We're not X9.62 but it can't
236252190Srpaulo		 * hurt just to be sure.
237252190Srpaulo		 */
238252190Srpaulo		if (!EC_POINT_is_on_curve(grp->group, grp->pwe, NULL)) {
239252190Srpaulo			wpa_printf(MSG_INFO, "EAP-pwd: point is not on curve");
240252190Srpaulo			continue;
241252190Srpaulo		}
242252190Srpaulo
243252190Srpaulo		if (BN_cmp(cofactor, BN_value_one())) {
244252190Srpaulo			/* make sure the point is not in a small sub-group */
245252190Srpaulo			if (!EC_POINT_mul(grp->group, grp->pwe, NULL, grp->pwe,
246252190Srpaulo					  cofactor, NULL)) {
247252190Srpaulo				wpa_printf(MSG_INFO, "EAP-pwd: cannot "
248252190Srpaulo					   "multiply generator by order");
249252190Srpaulo				continue;
250252190Srpaulo			}
251252190Srpaulo			if (EC_POINT_is_at_infinity(grp->group, grp->pwe)) {
252252190Srpaulo				wpa_printf(MSG_INFO, "EAP-pwd: point is at "
253252190Srpaulo					   "infinity");
254252190Srpaulo				continue;
255252190Srpaulo			}
256252190Srpaulo		}
257252190Srpaulo		/* if we got here then we have a new generator. */
258252190Srpaulo		break;
259252190Srpaulo	}
260252190Srpaulo	wpa_printf(MSG_DEBUG, "EAP-pwd: found a PWE in %d tries", ctr);
261252190Srpaulo	grp->group_num = num;
262252190Srpaulo	if (0) {
263252190Srpaulo fail:
264252190Srpaulo		EC_GROUP_free(grp->group);
265252190Srpaulo		grp->group = NULL;
266252190Srpaulo		EC_POINT_free(grp->pwe);
267252190Srpaulo		grp->pwe = NULL;
268252190Srpaulo		BN_free(grp->order);
269252190Srpaulo		grp->order = NULL;
270252190Srpaulo		BN_free(grp->prime);
271252190Srpaulo		grp->prime = NULL;
272252190Srpaulo		ret = 1;
273252190Srpaulo	}
274252190Srpaulo	/* cleanliness and order.... */
275252190Srpaulo	BN_free(cofactor);
276252190Srpaulo	BN_free(x_candidate);
277252190Srpaulo	BN_free(rnd);
278252190Srpaulo	os_free(prfbuf);
279252190Srpaulo
280252190Srpaulo	return ret;
281252190Srpaulo}
282252190Srpaulo
283252190Srpaulo
284252190Srpauloint compute_keys(EAP_PWD_group *grp, BN_CTX *bnctx, BIGNUM *k,
285252190Srpaulo		 BIGNUM *peer_scalar, BIGNUM *server_scalar,
286252190Srpaulo		 u8 *confirm_peer, u8 *confirm_server,
287252190Srpaulo		 u32 *ciphersuite, u8 *msk, u8 *emsk)
288252190Srpaulo{
289252190Srpaulo	struct crypto_hash *hash;
290252190Srpaulo	u8 mk[SHA256_MAC_LEN], *cruft;
291252190Srpaulo	u8 session_id[SHA256_MAC_LEN + 1];
292252190Srpaulo	u8 msk_emsk[EAP_MSK_LEN + EAP_EMSK_LEN];
293252190Srpaulo	int offset;
294252190Srpaulo
295252190Srpaulo	if ((cruft = os_malloc(BN_num_bytes(grp->prime))) == NULL)
296252190Srpaulo		return -1;
297252190Srpaulo
298252190Srpaulo	/*
299252190Srpaulo	 * first compute the session-id = TypeCode | H(ciphersuite | scal_p |
300252190Srpaulo	 *	scal_s)
301252190Srpaulo	 */
302252190Srpaulo	session_id[0] = EAP_TYPE_PWD;
303252190Srpaulo	hash = eap_pwd_h_init();
304252190Srpaulo	if (hash == NULL) {
305252190Srpaulo		os_free(cruft);
306252190Srpaulo		return -1;
307252190Srpaulo	}
308252190Srpaulo	eap_pwd_h_update(hash, (u8 *) ciphersuite, sizeof(u32));
309252190Srpaulo	offset = BN_num_bytes(grp->order) - BN_num_bytes(peer_scalar);
310252190Srpaulo	os_memset(cruft, 0, BN_num_bytes(grp->prime));
311252190Srpaulo	BN_bn2bin(peer_scalar, cruft + offset);
312252190Srpaulo	eap_pwd_h_update(hash, cruft, BN_num_bytes(grp->order));
313252190Srpaulo	offset = BN_num_bytes(grp->order) - BN_num_bytes(server_scalar);
314252190Srpaulo	os_memset(cruft, 0, BN_num_bytes(grp->prime));
315252190Srpaulo	BN_bn2bin(server_scalar, cruft + offset);
316252190Srpaulo	eap_pwd_h_update(hash, cruft, BN_num_bytes(grp->order));
317252190Srpaulo	eap_pwd_h_final(hash, &session_id[1]);
318252190Srpaulo
319252190Srpaulo	/* then compute MK = H(k | confirm-peer | confirm-server) */
320252190Srpaulo	hash = eap_pwd_h_init();
321252190Srpaulo	if (hash == NULL) {
322252190Srpaulo		os_free(cruft);
323252190Srpaulo		return -1;
324252190Srpaulo	}
325252190Srpaulo	offset = BN_num_bytes(grp->prime) - BN_num_bytes(k);
326252190Srpaulo	os_memset(cruft, 0, BN_num_bytes(grp->prime));
327252190Srpaulo	BN_bn2bin(k, cruft + offset);
328252190Srpaulo	eap_pwd_h_update(hash, cruft, BN_num_bytes(grp->prime));
329252190Srpaulo	os_free(cruft);
330252190Srpaulo	eap_pwd_h_update(hash, confirm_peer, SHA256_MAC_LEN);
331252190Srpaulo	eap_pwd_h_update(hash, confirm_server, SHA256_MAC_LEN);
332252190Srpaulo	eap_pwd_h_final(hash, mk);
333252190Srpaulo
334252190Srpaulo	/* stretch the mk with the session-id to get MSK | EMSK */
335252190Srpaulo	if (eap_pwd_kdf(mk, SHA256_MAC_LEN,
336252190Srpaulo			session_id, SHA256_MAC_LEN + 1,
337252190Srpaulo			msk_emsk, (EAP_MSK_LEN + EAP_EMSK_LEN) * 8) < 0) {
338252190Srpaulo		return -1;
339252190Srpaulo	}
340252190Srpaulo
341252190Srpaulo	os_memcpy(msk, msk_emsk, EAP_MSK_LEN);
342252190Srpaulo	os_memcpy(emsk, msk_emsk + EAP_MSK_LEN, EAP_EMSK_LEN);
343252190Srpaulo
344252190Srpaulo	return 1;
345252190Srpaulo}
346