automake build system
[mech_eap.orig] / src / eap_common / eap_pwd_common.c
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 >>= ((resultbytelen * 8) - resultbitlen);
79                 result[0] &= 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                 if (BN_ucmp(x_candidate, grp->prime) >= 0)
193                         continue;
194
195                 wpa_hexdump(MSG_DEBUG, "EAP-pwd: x_candidate",
196                             prfbuf, primebytelen);
197
198                 /*
199                  * need to unambiguously identify the solution, if there is
200                  * one...
201                  */
202                 if (BN_is_odd(rnd))
203                         is_odd = 1;
204                 else
205                         is_odd = 0;
206
207                 /*
208                  * solve the quadratic equation, if it's not solvable then we
209                  * don't have a point
210                  */
211                 if (!EC_POINT_set_compressed_coordinates_GFp(grp->group,
212                                                              grp->pwe,
213                                                              x_candidate,
214                                                              is_odd, NULL))
215                         continue;
216                 /*
217                  * If there's a solution to the equation then the point must be
218                  * on the curve so why check again explicitly? OpenSSL code
219                  * says this is required by X9.62. We're not X9.62 but it can't
220                  * hurt just to be sure.
221                  */
222                 if (!EC_POINT_is_on_curve(grp->group, grp->pwe, NULL)) {
223                         wpa_printf(MSG_INFO, "EAP-pwd: point is not on curve");
224                         continue;
225                 }
226
227                 if (BN_cmp(cofactor, BN_value_one())) {
228                         /* make sure the point is not in a small sub-group */
229                         if (!EC_POINT_mul(grp->group, grp->pwe, NULL, grp->pwe,
230                                           cofactor, NULL)) {
231                                 wpa_printf(MSG_INFO, "EAP-pwd: cannot "
232                                            "multiply generator by order");
233                                 continue;
234                         }
235                         if (EC_POINT_is_at_infinity(grp->group, grp->pwe)) {
236                                 wpa_printf(MSG_INFO, "EAP-pwd: point is at "
237                                            "infinity");
238                                 continue;
239                         }
240                 }
241                 /* if we got here then we have a new generator. */
242                 break;
243         }
244         wpa_printf(MSG_DEBUG, "EAP-pwd: found a PWE in %d tries", ctr);
245         grp->group_num = num;
246         if (0) {
247  fail:
248                 EC_GROUP_free(grp->group);
249                 EC_POINT_free(grp->pwe);
250                 BN_free(grp->order);
251                 BN_free(grp->prime);
252                 os_free(grp);
253                 grp = NULL;
254                 ret = 1;
255         }
256         /* cleanliness and order.... */
257         BN_free(cofactor);
258         BN_free(x_candidate);
259         BN_free(rnd);
260         free(prfbuf);
261
262         return ret;
263 }
264
265
266 int compute_keys(EAP_PWD_group *grp, BN_CTX *bnctx, BIGNUM *k,
267                  EC_POINT *server_element, EC_POINT *peer_element,
268                  BIGNUM *server_scalar, BIGNUM *peer_scalar, u32 *ciphersuite,
269                  u8 *msk, u8 *emsk)
270 {
271         BIGNUM *scalar_sum, *x;
272         EC_POINT *element_sum;
273         HMAC_CTX ctx;
274         u8 mk[SHA256_DIGEST_LENGTH], *cruft;
275         u8 session_id[SHA256_DIGEST_LENGTH + 1];
276         u8 msk_emsk[EAP_MSK_LEN + EAP_EMSK_LEN];
277         int ret = -1;
278
279         if (((cruft = os_malloc(BN_num_bytes(grp->prime))) == NULL) ||
280             ((x = BN_new()) == NULL) ||
281             ((scalar_sum = BN_new()) == NULL) ||
282             ((element_sum = EC_POINT_new(grp->group)) == NULL))
283                 return -1;
284
285         /*
286          * first compute the session-id = TypeCode | H(ciphersuite | scal_p |
287          *      scal_s)
288          */
289         session_id[0] = EAP_TYPE_PWD;
290         H_Init(&ctx);
291         H_Update(&ctx, (u8 *)ciphersuite, sizeof(u32));
292         BN_bn2bin(peer_scalar, cruft);
293         H_Update(&ctx, cruft, BN_num_bytes(grp->order));
294         BN_bn2bin(server_scalar, cruft);
295         H_Update(&ctx, cruft, BN_num_bytes(grp->order));
296         H_Final(&ctx, &session_id[1]);
297
298         /*
299          * then compute MK = H(k | F(elem_p + elem_s) |
300          *                     (scal_p + scal_s) mod r)
301          */
302         H_Init(&ctx);
303
304         /* k */
305         os_memset(cruft, 0, BN_num_bytes(grp->prime));
306         BN_bn2bin(k, cruft);
307         H_Update(&ctx, cruft, BN_num_bytes(grp->prime));
308
309         /* x = F(elem_p + elem_s) */
310         if ((!EC_POINT_add(grp->group, element_sum, server_element,
311                            peer_element, bnctx)) ||
312             (!EC_POINT_get_affine_coordinates_GFp(grp->group, element_sum, x,
313                                                   NULL, bnctx)))
314                 goto fail;
315
316         os_memset(cruft, 0, BN_num_bytes(grp->prime));
317         BN_bn2bin(x, cruft);
318         H_Update(&ctx, cruft, BN_num_bytes(grp->prime));
319
320         /* (scal_p + scal_s) mod r */
321         BN_add(scalar_sum, server_scalar, peer_scalar);
322         BN_mod(scalar_sum, scalar_sum, grp->order, bnctx);
323         os_memset(cruft, 0, BN_num_bytes(grp->prime));
324         BN_bn2bin(scalar_sum, cruft);
325         H_Update(&ctx, cruft, BN_num_bytes(grp->order));
326         H_Final(&ctx, mk);
327
328         /* stretch the mk with the session-id to get MSK | EMSK */
329         eap_pwd_kdf(mk, SHA256_DIGEST_LENGTH,
330                     session_id, SHA256_DIGEST_LENGTH+1,
331                     msk_emsk, (EAP_MSK_LEN + EAP_EMSK_LEN) * 8);
332
333         os_memcpy(msk, msk_emsk, EAP_MSK_LEN);
334         os_memcpy(emsk, msk_emsk + EAP_MSK_LEN, EAP_EMSK_LEN);
335
336         ret = 1;
337
338 fail:
339         BN_free(x);
340         BN_free(scalar_sum);
341         EC_POINT_free(element_sum);
342         os_free(cruft);
343
344         return ret;
345 }