68983488fe0591bf9e814cad108f33f0aedcf165
[freeradius.git] / src / modules / rlm_eap / types / rlm_eap_leap / eap_leap.c
1 /*
2  * eap_leap.c  EAP LEAP functionality.
3  *
4  * Version:     $Id$
5  *
6  *   This program is free software; you can redistribute it and/or modify
7  *   it under the terms of the GNU General Public License as published by
8  *   the Free Software Foundation; either version 2 of the License, or
9  *   (at your option) any later version.
10  *
11  *   This program is distributed in the hope that it will be useful,
12  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *   GNU General Public License for more details.
15  *
16  *   You should have received a copy of the GNU General Public License
17  *   along with this program; if not, write to the Free Software
18  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  *
20  * Copyright 2003 Alan DeKok <aland@freeradius.org>
21  * Copyright 2006 The FreeRADIUS server project
22  */
23
24 /*
25  *
26  *  LEAP Packet Format in EAP Type-Data
27  *  --- ------ ------ -- --- ---------
28  *    0            1               2                    3
29  *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
30  *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
31  *  |     Type 0x11 |  Version 0x01 | Unused 0x00   | Count 0x08    |
32  *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
33  *  |          Peer Challenge                              |
34  *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
35  *  |          Peer Challenge                              |
36  *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
37  *  |   User Name .....
38  *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-
39  *
40  *  Count is 8 octets since the Peer challenge is 8 bytes.
41  *  Count is 24 for EAP response, with MSCHAP response.
42  *  Length is the total number of octets in the EAP-Message.
43  *
44  *  The LEAP type (0x11) is *not* included in the type data...
45  */
46
47 RCSID("$Id$")
48
49 #include <stdio.h>
50 #include <stdlib.h>
51 #include "eap.h"
52 #include "eap_leap.h"
53 /*
54  *   Extract the data from the LEAP packet.
55  */
56 leap_packet_t *eapleap_extract(EAP_DS *eap_ds)
57 {
58         leap_packet_raw_t       *data;
59         leap_packet_t   *packet;
60         int             name_len;
61
62         /*
63          *      LEAP can have EAP-Response or EAP-Request (step 5)
64          *      messages sent to it.
65          */
66         if (!eap_ds ||
67             !eap_ds->response ||
68             ((eap_ds->response->code != PW_EAP_RESPONSE) &&
69              (eap_ds->response->code != PW_EAP_REQUEST)) ||
70             eap_ds->response->type.num != PW_EAP_LEAP ||
71             !eap_ds->response->type.data ||
72             (eap_ds->response->length < LEAP_HEADER_LEN) ||
73             (eap_ds->response->type.data[0] != 0x01)) { /* version 1 */
74                 ERROR("rlm_eap_leap: corrupted data");
75                 return NULL;
76         }
77
78         /*
79          *      Hmm... this cast isn't the best thing to do.
80          */
81         data = (leap_packet_raw_t *)eap_ds->response->type.data;
82
83         /*
84          *      Some simple sanity checks on the incoming packet.
85          *
86          *      See 'leap.txt' in this directory for a description
87          *      of the stages.
88          */
89         switch (eap_ds->response->code) {
90         case PW_EAP_RESPONSE:
91                 if (data->count != 24) {
92                         ERROR("rlm_eap_leap: Bad NTChallengeResponse in LEAP stage 3");
93                         return NULL;
94                 }
95                 break;
96
97         case PW_EAP_REQUEST:
98                 if (data->count != 8) {
99                         ERROR("rlm_eap_leap: Bad AP Challenge in LEAP stage 5");
100                         return NULL;
101                 }
102                 break;
103
104         default:
105                 ERROR("rlm_eap_leap: Invalid EAP code %d",
106                        eap_ds->response->code);
107                 return NULL;
108                 break;
109         }
110
111         packet = talloc(eap_ds, leap_packet_t);
112         if (!packet) return NULL;
113
114         /*
115          *      Remember code, length, and id.
116          */
117         packet->code = eap_ds->response->code;
118         packet->id = eap_ds->response->id;
119
120         /*
121          *      The size of the LEAP portion of the packet, not
122          *      counting the EAP header and the type.
123          */
124         packet->length = eap_ds->response->length - EAP_HEADER_LEN - 1;
125
126         /*
127          *      Remember the length of the challenge.
128          */
129         packet->count = data->count;
130
131         packet->challenge = talloc_array(packet, uint8_t, packet->count);
132         if (!packet->challenge) {
133                 talloc_free(packet);
134                 return NULL;
135         }
136         memcpy(packet->challenge, data->challenge, packet->count);
137
138         /*
139          *      The User-Name comes after the challenge.
140          *
141          *      Length of the EAP-LEAP portion of the packet, minus
142          *      3 octets for data, minus the challenge size, is the
143          *      length of the user name.
144          */
145         name_len = packet->length - 3 - packet->count;
146         if (name_len > 0) {
147                 packet->name = talloc_array(packet, char, name_len + 1);
148                 if (!packet->name) {
149                         talloc_free(packet);
150                         return NULL;
151                 }
152                 memcpy(packet->name, &data->challenge[packet->count],
153                        name_len);
154                 packet->name[name_len] = '\0';
155                 packet->name_len = name_len;
156         }
157
158         return packet;
159 }
160
161 /*
162  *  Get the NT-Password hash.
163  */
164 static int eapleap_ntpwdhash(unsigned char *ntpwdhash, VALUE_PAIR *password)
165 {
166         if ((password->da->attr == PW_USER_PASSWORD) ||
167             (password->da->attr == PW_CLEARTEXT_PASSWORD)) {
168                 size_t i;
169                 unsigned char unicode[512];
170
171                 /*
172                  *      Convert the password to NT's weird Unicode format.
173                  */
174                 memset(unicode, 0, sizeof(unicode));
175                 for (i = 0; i < password->length; i++) {
176                         /*
177                          *  Yes, the *even* bytes have the values,
178                          *  and the *odd* bytes are zero.
179                          */
180                         unicode[(i << 1)] = password->vp_strvalue[i];
181                 }
182
183                 /*
184                  *  Get the NT Password hash.
185                  */
186                 fr_md4_calc(ntpwdhash, unicode, password->length * 2);
187
188         } else {                /* MUST be NT-Password */
189                 uint8_t *p = NULL;
190
191                 if (password->length == 32) {
192                         p = talloc_array(password, uint8_t, 16);
193                         password->length = fr_hex2bin(p, password->vp_strvalue, 16);
194                 }
195                 if (password->length != 16) {
196                         ERROR("rlm_eap_leap: Bad NT-Password");
197                         return 0;
198                 }
199
200                 if (p) {
201                         pairmemcpy(password, p, 16);
202                         talloc_free(p);
203                 }
204
205                 memcpy(ntpwdhash, password->vp_octets, 16);
206         }
207         return 1;
208 }
209
210
211 /*
212  *      Verify the MS-CHAP response from the user.
213  */
214 int eapleap_stage4(leap_packet_t *packet, VALUE_PAIR* password,
215                    leap_session_t *session)
216 {
217         unsigned char ntpwdhash[16];
218         unsigned char response[24];
219
220
221         /*
222          *      No password or previous packet.  Die.
223          */
224         if ((!password) || (!session)) {
225                 return 0;
226         }
227
228         if (!eapleap_ntpwdhash(ntpwdhash, password)) {
229                 return 0;
230         }
231
232         /*
233          *      Calculate and verify the CHAP challenge.
234          */
235         eapleap_mschap(ntpwdhash, session->peer_challenge, response);
236         if (memcmp(response, packet->challenge, 24) == 0) {
237                 DEBUG2("  rlm_eap_leap: NtChallengeResponse from AP is valid");
238                 memcpy(session->peer_response, response, sizeof(response));
239                 return 1;
240         }
241
242         DEBUG2("  rlm_eap_leap: FAILED incorrect NtChallengeResponse from AP");
243         return 0;
244 }
245
246 /*
247  *      Verify ourselves to the AP
248  */
249 leap_packet_t *eapleap_stage6(leap_packet_t *packet, REQUEST *request,
250                             VALUE_PAIR *user_name, VALUE_PAIR* password,
251                             leap_session_t *session)
252 {
253         size_t i;
254         uint8_t ntpwdhash[16], ntpwdhashhash[16];
255         uint8_t *p, buffer[256];
256         leap_packet_t *reply;
257         char *q;
258         VALUE_PAIR *vp;
259
260         /*
261          *      No password or previous packet.  Die.
262          */
263         if ((!password) || (!session)) {
264                 return NULL;
265         }
266
267         reply = talloc(session, leap_packet_t);
268         if (!reply) return NULL;
269
270         reply->code = PW_EAP_RESPONSE;
271         reply->length = LEAP_HEADER_LEN + 24 + user_name->length;
272         reply->count = 24;
273
274         reply->challenge = talloc_array(reply, uint8_t, reply->count);
275         if (!reply->challenge) {
276                 talloc_free(reply);
277                 return NULL;
278         }
279
280         /*
281          *      The LEAP packet also contains the user name.
282          */
283         reply->name = talloc_array(reply, char, user_name->length + 1);
284         if (!reply->name) {
285                 talloc_free(reply);
286                 return NULL;
287         }
288
289         /*
290          *      Copy the name over, and ensure it's NUL terminated.
291          */
292         memcpy(reply->name, user_name->vp_strvalue, user_name->length);
293         reply->name[user_name->length] = '\0';
294         reply->name_len = user_name->length;
295
296         /*
297          *  MPPE hash = ntpwdhash(ntpwdhash(unicode(pw)))
298          */
299         if (!eapleap_ntpwdhash(ntpwdhash, password)) {
300                 talloc_free(reply);
301                 return NULL;
302         }
303         fr_md4_calc(ntpwdhashhash, ntpwdhash, 16);
304
305         /*
306          *      Calculate our response, to authenticate ourselves
307          *      to the AP.
308          */
309         eapleap_mschap(ntpwdhashhash, packet->challenge, reply->challenge);
310
311         /*
312          *  Calculate the leap:session-key attribute
313          */
314         vp = pairmake_reply("Cisco-AVPair", NULL, T_OP_ADD);
315         if (!vp) {
316                 ERROR("rlm_eap_leap: Failed to create Cisco-AVPair attribute.  LEAP cancelled.");
317                 talloc_free(reply);
318                 return NULL;
319         }
320
321         /*
322          *      And calculate the MPPE session key.
323          */
324         p = buffer;
325         memcpy(p, ntpwdhashhash, 16); /* MPPEHASH */
326         p += 16;
327         memcpy(p, packet->challenge, 8); /* APC */
328         p += 8;
329         memcpy(p, reply->challenge, 24); /* APR */
330         p += 24;
331         memcpy(p, session->peer_challenge, 8); /* PC */
332         p += 8;
333         memcpy(p, session->peer_response, 24); /* PR */
334
335         /*
336          *      These 16 bytes are the session key to use.
337          */
338         fr_md5_calc(ntpwdhash, buffer, 16 + 8 + 24 + 8 + 24);
339
340         q = talloc_array(vp, char, 16 + sizeof("leap:session-key="));
341         strcpy(q, "leap:session-key=");
342
343         memcpy(q + 17, ntpwdhash, 16);
344
345         i = 16;
346         rad_tunnel_pwencode(q + 17, &i,
347                             request->client->secret, request->packet->vector);
348         vp->length = 17 + i;
349 //      talloc_free(vp->vp_strvalue);
350         vp->vp_strvalue = q;
351         vp->type = VT_DATA;
352
353         return reply;
354 }
355
356 /*
357  *      If an EAP LEAP request needs to be initiated then
358  *      create such a packet.
359  */
360 leap_packet_t *eapleap_initiate(EAP_DS *eap_ds, VALUE_PAIR *user_name)
361 {
362         int i;
363         leap_packet_t   *reply;
364
365         reply = talloc(eap_ds, leap_packet_t);
366         if (!reply)  {
367                 return NULL;
368         }
369
370         reply->code = PW_EAP_REQUEST;
371         reply->length = LEAP_HEADER_LEN + 8 + user_name->length;
372         reply->count = 8;       /* random challenge */
373
374         reply->challenge = talloc_array(reply, uint8_t, reply->count);
375         if (!reply->challenge) {
376                 talloc_free(reply);
377                 return NULL;
378         }
379
380         /*
381          *      Fill the challenge with random bytes.
382          */
383         for (i = 0; i < reply->count; i++) {
384                 reply->challenge[i] = fr_rand();
385         }
386
387         DEBUG2("  rlm_eap_leap: Issuing AP Challenge");
388
389         /*
390          *      The LEAP packet also contains the user name.
391          */
392         reply->name = talloc_array(reply, char, user_name->length + 1);
393         if (!reply->name) {
394                 talloc_free(reply);
395                 return NULL;
396         }
397
398         /*
399          *      Copy the name over, and ensure it's NUL terminated.
400          */
401         memcpy(reply->name, user_name->vp_strvalue, user_name->length);
402         reply->name[user_name->length] = '\0';
403         reply->name_len = user_name->length;
404
405         return reply;
406 }
407
408 /*
409  * compose the LEAP reply packet in the EAP reply typedata
410  */
411 int eapleap_compose(EAP_DS *eap_ds, leap_packet_t *reply)
412 {
413         leap_packet_raw_t *data;
414
415         /*
416          *  We need the name and the challenge.
417          */
418         switch (reply->code) {
419         case PW_EAP_REQUEST:
420         case PW_EAP_RESPONSE:
421                 eap_ds->request->type.num = PW_EAP_LEAP;
422                 eap_ds->request->type.length = reply->length;
423
424                 eap_ds->request->type.data = talloc_array(eap_ds->request,
425                                                           uint8_t,
426                                                           reply->length);
427                 if (!eap_ds->request->type.data) {
428                         return 0;
429                 }
430                 data = (leap_packet_raw_t *) eap_ds->request->type.data;
431                 data->version = 0x01;
432                 data->unused = 0;
433                 data->count = reply->count;
434
435                 /*
436                  *      N bytes of the challenge, followed by the user name.
437                  */
438                 memcpy(&data->challenge[0], reply->challenge, reply->count);
439                 memcpy(&data->challenge[reply->count],
440                        reply->name, reply->name_len);
441                 break;
442
443                 /*
444                  *      EAP-Success packets don't contain any data
445                  *      other than the header.
446                  */
447         case PW_EAP_SUCCESS:
448                 eap_ds->request->type.length = 0;
449                 break;
450
451         default:
452                 ERROR("rlm_eap_leap: Internal sanity check failed");
453                 return 0;
454                 break;
455         }
456
457         /*
458          *      Set the EAP code.
459          */
460         eap_ds->request->code = reply->code;
461
462         return 1;
463 }