ae953e361125c1cb299393e62e018981d1ce00f3
[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 #include <freeradius-devel/ident.h>
48 RCSID("$Id$")
49
50 #include <stdio.h>
51 #include <stdlib.h>
52 #include "eap.h"
53 #include "eap_leap.h"
54
55 /*
56  *      Allocate a new LEAP_PACKET
57  */
58 LEAP_PACKET *eapleap_alloc(void)
59 {
60         LEAP_PACKET   *rp;
61
62         if ((rp = malloc(sizeof(LEAP_PACKET))) == NULL) {
63                 radlog(L_ERR, "rlm_eap_leap: out of memory");
64                 return NULL;
65         }
66         memset(rp, 0, sizeof(LEAP_PACKET));
67         return rp;
68 }
69
70 /*
71  *      Free LEAP_PACKET
72  */
73 void eapleap_free(LEAP_PACKET **leap_packet_ptr)
74 {
75         LEAP_PACKET *leap_packet;
76
77         if (!leap_packet_ptr) return;
78         leap_packet = *leap_packet_ptr;
79         if (leap_packet == NULL) return;
80
81         if (leap_packet->challenge) free(leap_packet->challenge);
82         if (leap_packet->name) free(leap_packet->name);
83
84         free(leap_packet);
85
86         *leap_packet_ptr = NULL;
87 }
88
89 /*
90  *   Extract the data from the LEAP packet.
91  */
92 LEAP_PACKET *eapleap_extract(EAP_DS *eap_ds)
93 {
94         leap_packet_t   *data;
95         LEAP_PACKET     *packet;
96         int             name_len;
97
98         /*
99          *      LEAP can have EAP-Response or EAP-Request (step 5)
100          *      messages sent to it.
101          */
102         if (!eap_ds ||
103             !eap_ds->response ||
104             ((eap_ds->response->code != PW_EAP_RESPONSE) &&
105              (eap_ds->response->code != PW_EAP_REQUEST)) ||
106             eap_ds->response->type.type != PW_EAP_LEAP ||
107             !eap_ds->response->type.data ||
108             (eap_ds->response->length < LEAP_HEADER_LEN) ||
109             (eap_ds->response->type.data[0] != 0x01)) { /* version 1 */
110                 radlog(L_ERR, "rlm_eap_leap: corrupted data");
111                 return NULL;
112         }
113
114         /*
115          *      Hmm... this cast isn't the best thing to do.
116          */
117         data = (leap_packet_t *)eap_ds->response->type.data;
118
119         /*
120          *      Some simple sanity checks on the incoming packet.
121          *
122          *      See 'leap.txt' in this directory for a description
123          *      of the stages.
124          */
125         switch (eap_ds->response->code) {
126         case PW_EAP_RESPONSE:
127                 if (data->count != 24) {
128                         radlog(L_ERR, "rlm_eap_leap: Bad NTChallengeResponse in LEAP stage 3");
129                         return NULL;
130                 }
131                 break;
132
133         case PW_EAP_REQUEST:
134                 if (data->count != 8) {
135                         radlog(L_ERR, "rlm_eap_leap: Bad AP Challenge in LEAP stage 5");
136                         return NULL;
137                 }
138                 break;
139
140         default:
141                 radlog(L_ERR, "rlm_eap_leap: Invalid EAP code %d",
142                        eap_ds->response->code);
143                 return NULL;
144                 break;
145         }
146
147         packet = eapleap_alloc();
148         if (!packet) return NULL;
149
150         /*
151          *      Remember code, length, and id.
152          */
153         packet->code = eap_ds->response->code;
154         packet->id = eap_ds->response->id;
155
156         /*
157          *      The size of the LEAP portion of the packet, not
158          *      counting the EAP header and the type.
159          */
160         packet->length = eap_ds->response->length - EAP_HEADER_LEN - 1;
161
162         /*
163          *      Remember the length of the challenge.
164          */
165         packet->count = data->count;
166
167         packet->challenge = malloc(packet->count);
168         if (packet->challenge == NULL) {
169                 radlog(L_ERR, "rlm_eap_leap: out of memory");
170                 eapleap_free(&packet);
171                 return NULL;
172         }
173         memcpy(packet->challenge, data->challenge, packet->count);
174
175         /*
176          *      The User-Name comes after the challenge.
177          *
178          *      Length of the EAP-LEAP portion of the packet, minus
179          *      3 octets for data, minus the challenge size, is the
180          *      length of the user name.
181          */
182         name_len = packet->length - 3 - packet->count;
183         if (name_len > 0) {
184                 packet->name = malloc(name_len + 1);
185                 if (!packet->name) {
186                         radlog(L_ERR, "rlm_eap_leap: out of memory");
187                         eapleap_free(&packet);
188                         return NULL;
189                 }
190                 memcpy(packet->name, &data->challenge[packet->count],
191                        name_len);
192                 packet->name[name_len] = '\0';
193                 packet->name_len = name_len;
194         }
195
196         return packet;
197 }
198
199 /*
200  *  Get the NT-Password hash.
201  */
202 static int eapleap_ntpwdhash(unsigned char *ntpwdhash, VALUE_PAIR *password)
203 {
204         if ((password->attribute == PW_USER_PASSWORD) ||
205             (password->attribute == PW_CLEARTEXT_PASSWORD)) {
206                 size_t i;
207                 unsigned char unicode[512];
208
209                 /*
210                  *      Convert the password to NT's weird Unicode format.
211                  */
212                 memset(unicode, 0, sizeof(unicode));
213                 for (i = 0; i < password->length; i++) {
214                         /*
215                          *  Yes, the *even* bytes have the values,
216                          *  and the *odd* bytes are zero.
217                          */
218                         unicode[(i << 1)] = password->vp_strvalue[i];
219                 }
220
221                 /*
222                  *  Get the NT Password hash.
223                  */
224                 fr_md4_calc(ntpwdhash, unicode, password->length * 2);
225
226         } else {                /* MUST be NT-Password */
227                 if (password->length == 32) {
228                         password->length = fr_hex2bin(password->vp_strvalue,
229                                                         password->vp_octets,
230                                                         16);
231                 }
232                 if (password->length != 16) {
233                         radlog(L_ERR, "rlm_eap_leap: Bad NT-Password");
234                         return 0;
235                 }
236
237                 memcpy(ntpwdhash, password->vp_strvalue, 16);
238         }
239         return 1;
240 }
241
242
243 /*
244  *      Verify the MS-CHAP response from the user.
245  */
246 int eapleap_stage4(LEAP_PACKET *packet, VALUE_PAIR* password,
247                    leap_session_t *session)
248 {
249         unsigned char ntpwdhash[16];
250         unsigned char response[24];
251
252
253         /*
254          *      No password or previous packet.  Die.
255          */
256         if ((password == NULL) || (session == NULL)) {
257                 return 0;
258         }
259
260         if (!eapleap_ntpwdhash(ntpwdhash, password)) {
261                 return 0;
262         }
263
264         /*
265          *      Calculate and verify the CHAP challenge.
266          */
267         eapleap_mschap(ntpwdhash, session->peer_challenge, response);
268         if (memcmp(response, packet->challenge, 24) == 0) {
269                 DEBUG2("  rlm_eap_leap: NtChallengeResponse from AP is valid");
270                 memcpy(session->peer_response, response, sizeof(response));
271                 return 1;
272         }
273
274         DEBUG2("  rlm_eap_leap: FAILED incorrect NtChallengeResponse from AP");
275         return 0;
276 }
277
278 /*
279  *      Verify ourselves to the AP
280  */
281 LEAP_PACKET *eapleap_stage6(LEAP_PACKET *packet, REQUEST *request,
282                             VALUE_PAIR *user_name, VALUE_PAIR* password,
283                             leap_session_t *session, VALUE_PAIR **reply_vps)
284 {
285         size_t i;
286         unsigned char ntpwdhash[16], ntpwdhashhash[16];
287         unsigned char buffer[256];
288         LEAP_PACKET *reply;
289         unsigned char *p;
290         VALUE_PAIR *vp;
291
292         /*
293          *      No password or previous packet.  Die.
294          */
295         if ((password == NULL) || (session == NULL)) {
296                 return NULL;
297         }
298
299         reply = eapleap_alloc();
300         if (!reply) return NULL;
301
302         reply->code = PW_EAP_RESPONSE;
303         reply->length = LEAP_HEADER_LEN + 24 + user_name->length;
304         reply->count = 24;
305
306         reply->challenge = malloc(reply->count);
307         if (reply->challenge == NULL) {
308                 radlog(L_ERR, "rlm_eap_leap: out of memory");
309                 eapleap_free(&reply);
310                 return NULL;
311         }
312
313         /*
314          *      The LEAP packet also contains the user name.
315          */
316         reply->name = malloc(user_name->length + 1);
317         if (reply->name == NULL) {
318                 radlog(L_ERR, "rlm_eap_leap: out of memory");
319                 eapleap_free(&reply);
320                 return NULL;
321         }
322
323         /*
324          *      Copy the name over, and ensure it's NUL terminated.
325          */
326         memcpy(reply->name, user_name->vp_strvalue, user_name->length);
327         reply->name[user_name->length] = '\0';
328         reply->name_len = user_name->length;
329
330         /*
331          *  MPPE hash = ntpwdhash(ntpwdhash(unicode(pw)))
332          */
333         if (!eapleap_ntpwdhash(ntpwdhash, password)) {
334                 eapleap_free(&reply);
335                 return NULL;
336         }
337         fr_md4_calc(ntpwdhashhash, ntpwdhash, 16);
338
339         /*
340          *      Calculate our response, to authenticate ourselves
341          *      to the AP.
342          */
343         eapleap_mschap(ntpwdhashhash, packet->challenge, reply->challenge);
344
345         /*
346          *  Calculate the leap:session-key attribute
347          */
348         vp = pairmake("Cisco-AVPair", "leap:session-key=", T_OP_ADD);
349         if (!vp) {
350                 radlog(L_ERR, "rlm_eap_leap: Failed to create Cisco-AVPair attribute.  LEAP cancelled.");
351                 eapleap_free(&reply);
352                 return NULL;
353         }
354
355         /*
356          *      And calculate the MPPE session key.
357          */
358         p = buffer;
359         memcpy(p, ntpwdhashhash, 16); /* MPPEHASH */
360         p += 16;
361         memcpy(p, packet->challenge, 8); /* APC */
362         p += 8;
363         memcpy(p, reply->challenge, 24); /* APR */
364         p += 24;
365         memcpy(p, session->peer_challenge, 8); /* PC */
366         p += 8;
367         memcpy(p, session->peer_response, 24); /* PR */
368         p += 24;
369
370         /*
371          *      These 16 bytes are the session key to use.
372          */
373         fr_md5_calc(ntpwdhash, buffer, 16 + 8 + 24 + 8 + 24);
374
375         memcpy(vp->vp_strvalue + vp->length, ntpwdhash, 16);
376         memset(vp->vp_strvalue + vp->length + 16, 0,
377                sizeof(vp->vp_strvalue) - (vp->length + 16));
378
379         i = 16;
380         rad_tunnel_pwencode(vp->vp_strvalue + vp->length, &i,
381                             request->client->secret, request->packet->vector);
382         vp->length += i;
383         pairadd(reply_vps, vp);
384
385         return reply;
386 }
387
388 /*
389  *      If an EAP LEAP request needs to be initiated then
390  *      create such a packet.
391  */
392 LEAP_PACKET *eapleap_initiate(UNUSED EAP_DS *eap_ds, VALUE_PAIR *user_name)
393 {
394         int i;
395         LEAP_PACKET     *reply;
396
397         reply = eapleap_alloc();
398         if (reply == NULL)  {
399                 radlog(L_ERR, "rlm_eap_leap: out of memory");
400                 return NULL;
401         }
402
403         reply->code = PW_EAP_REQUEST;
404         reply->length = LEAP_HEADER_LEN + 8 + user_name->length;
405         reply->count = 8;       /* random challenge */
406
407         reply->challenge = malloc(reply->count);
408         if (reply->challenge == NULL) {
409                 radlog(L_ERR, "rlm_eap_leap: out of memory");
410                 eapleap_free(&reply);
411                 return NULL;
412         }
413
414         /*
415          *      Fill the challenge with random bytes.
416          */
417         for (i = 0; i < reply->count; i++) {
418                 reply->challenge[i] = fr_rand();
419         }
420
421         DEBUG2("  rlm_eap_leap: Issuing AP Challenge");
422
423         /*
424          *      The LEAP packet also contains the user name.
425          */
426         reply->name = malloc(user_name->length + 1);
427         if (reply->name == NULL) {
428                 radlog(L_ERR, "rlm_eap_leap: out of memory");
429                 eapleap_free(&reply);
430                 return NULL;
431         }
432
433         /*
434          *      Copy the name over, and ensure it's NUL terminated.
435          */
436         memcpy(reply->name, user_name->vp_strvalue, user_name->length);
437         reply->name[user_name->length] = '\0';
438         reply->name_len = user_name->length;
439
440         return reply;
441 }
442
443 /*
444  * compose the LEAP reply packet in the EAP reply typedata
445  */
446 int eapleap_compose(EAP_DS *eap_ds, LEAP_PACKET *reply)
447 {
448         leap_packet_t *data;
449
450         /*
451          *  We need the name and the challenge.
452          */
453         switch (reply->code) {
454         case PW_EAP_REQUEST:
455         case PW_EAP_RESPONSE:
456                 eap_ds->request->type.type = PW_EAP_LEAP;
457                 eap_ds->request->type.length = reply->length;
458
459                 eap_ds->request->type.data = malloc(reply->length);
460                 if (eap_ds->request->type.data == NULL) {
461                         radlog(L_ERR, "rlm_eap_leap: out of memory");
462                         return 0;
463                 }
464                 data = (leap_packet_t *) eap_ds->request->type.data;
465                 data->version = 0x01;
466                 data->unused = 0;
467                 data->count = reply->count;
468
469                 /*
470                  *      N bytes of the challenge, followed by the user name.
471                  */
472                 memcpy(&data->challenge[0], reply->challenge, reply->count);
473                 memcpy(&data->challenge[reply->count],
474                        reply->name, reply->name_len);
475                 break;
476
477                 /*
478                  *      EAP-Success packets don't contain any data
479                  *      other than the header.
480                  */
481         case PW_EAP_SUCCESS:
482                 eap_ds->request->type.length = 0;
483                 break;
484
485         default:
486                 radlog(L_ERR, "rlm_eap_leap: Internal sanity check failed");
487                 return 0;
488                 break;
489         }
490
491         /*
492          *      Set the EAP code.
493          */
494         eap_ds->request->code = reply->code;
495
496         return 1;
497 }