2 * eap_leap.c EAP LEAP functionality.
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.
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.
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 * Copyright 2003 Alan DeKok <aland@freeradius.org>
25 * LEAP Packet Format in EAP Type-Data
26 * --- ------ ------ -- --- ---------
28 * 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
29 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
30 * | Type 0x11 | Version 0x01 | Unused 0x00 | Count 0x08 |
31 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
33 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
35 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
37 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-
39 * Count is 8 octets since the Peer challenge is 8 bytes.
40 * Count is 24 for EAP response, with MSCHAP response.
41 * Length is the total number of octets in the EAP-Message.
43 * The LEAP type (0x11) is *not* included in the type data...
52 * Allocate a new LEAP_PACKET
54 LEAP_PACKET *eapleap_alloc(void)
58 if ((rp = malloc(sizeof(LEAP_PACKET))) == NULL) {
59 radlog(L_ERR, "rlm_eap_leap: out of memory");
62 memset(rp, 0, sizeof(LEAP_PACKET));
69 void eapleap_free(LEAP_PACKET **leap_packet_ptr)
71 LEAP_PACKET *leap_packet;
73 if (!leap_packet_ptr) return;
74 leap_packet = *leap_packet_ptr;
75 if (leap_packet == NULL) return;
77 if (leap_packet->challenge) free(leap_packet->challenge);
78 if (leap_packet->name) free(leap_packet->name);
82 *leap_packet_ptr = NULL;
86 * Extract the data from the LEAP packet.
88 LEAP_PACKET *eapleap_extract(EAP_DS *eap_ds)
95 * LEAP can have EAP-Response or EAP-Request (step 5)
96 * messages sent to it.
100 ((eap_ds->response->code != PW_EAP_RESPONSE) &&
101 (eap_ds->response->code != PW_EAP_REQUEST)) ||
102 eap_ds->response->type.type != PW_EAP_LEAP ||
103 !eap_ds->response->type.data ||
104 (eap_ds->response->length < LEAP_HEADER_LEN) ||
105 (eap_ds->response->type.data[0] != 0x01)) { /* version 1 */
106 radlog(L_ERR, "rlm_eap_leap: corrupted data");
111 * Hmm... this cast isn't the best thing to do.
113 data = (leap_packet_t *)eap_ds->response->type.data;
116 * Some simple sanity checks on the incoming packet.
118 * See 'leap.txt' in this directory for a description
121 switch (eap_ds->response->code) {
122 case PW_EAP_RESPONSE:
123 if (data->count != 24) {
124 radlog(L_ERR, "rlm_eap_leap: Bad NTChallengeResponse in LEAP stage 3");
130 if (data->count != 8) {
131 radlog(L_ERR, "rlm_eap_leap: Bad AP Challenge in LEAP stage 5");
137 radlog(L_ERR, "rlm_eap_leap: Invalid EAP code %d",
138 eap_ds->response->code);
143 packet = eapleap_alloc();
144 if (!packet) return NULL;
147 * Remember code, length, and id.
149 packet->code = eap_ds->response->code;
150 packet->id = eap_ds->response->id;
153 * The size of the LEAP portion of the packet, not
154 * counting the EAP header and the type.
156 packet->length = eap_ds->response->length - EAP_HEADER_LEN - 1;
159 * Remember the length of the challenge.
161 packet->count = data->count;
163 packet->challenge = malloc(packet->count);
164 if (packet->challenge == NULL) {
165 radlog(L_ERR, "rlm_eap_leap: out of memory");
166 eapleap_free(&packet);
169 memcpy(packet->challenge, data->challenge, packet->count);
172 * The User-Name comes after the challenge.
174 * Length of the EAP-LEAP portion of the packet, minus
175 * 3 octets for data, minus the challenge size, is the
176 * length of the user name.
178 name_len = packet->length - 3 - packet->count;
180 packet->name = malloc(name_len + 1);
182 radlog(L_ERR, "rlm_eap_leap: out of memory");
183 eapleap_free(&packet);
186 memcpy(packet->name, &data->challenge[packet->count],
188 packet->name[name_len] = '\0';
189 packet->name_len = name_len;
196 * Get the NT-Password hash.
198 static int eapleap_ntpwdhash(unsigned char *ntpwdhash, VALUE_PAIR *password)
200 if (password->attribute == PW_PASSWORD) {
202 unsigned char unicode[512];
205 * Convert the password to NT's weird Unicode format.
207 memset(unicode, 0, sizeof(unicode));
208 for (i = 0; i < password->length; i++) {
210 * Yes, the *even* bytes have the values,
211 * and the *odd* bytes are zero.
213 unicode[(i << 1)] = password->strvalue[i];
217 * Get the NT Password hash.
219 md4_calc(ntpwdhash, unicode, password->length * 2);
221 } else { /* MUST be NT-Password */
222 if (password->length == 32) {
223 password->length = lrad_hex2bin(password->strvalue,
227 if (password->length != 16) {
228 radlog(L_ERR, "rlm_eap_leap: Bad NT-Password");
232 memcpy(ntpwdhash, password->strvalue, 16);
239 * Verify the MS-CHAP response from the user.
241 int eapleap_stage4(LEAP_PACKET *packet, VALUE_PAIR* password,
242 leap_session_t *session)
244 unsigned char ntpwdhash[16];
245 unsigned char response[24];
249 * No password or previous packet. Die.
251 if ((password == NULL) || (session == NULL)) {
255 if (!eapleap_ntpwdhash(ntpwdhash, password)) {
260 * Calculate and verify the CHAP challenge.
262 eapleap_mschap(ntpwdhash, session->peer_challenge, response);
263 if (memcmp(response, packet->challenge, 24) == 0) {
264 DEBUG2(" rlm_eap_leap: NtChallengeResponse from AP is valid");
265 memcpy(session->peer_response, response, sizeof(response));
269 DEBUG2(" rlm_eap_leap: FAILED incorrect NtChallengeResponse from AP");
274 * Verify ourselves to the AP
276 LEAP_PACKET *eapleap_stage6(LEAP_PACKET *packet, REQUEST *request,
277 VALUE_PAIR *user_name, VALUE_PAIR* password,
278 leap_session_t *session, VALUE_PAIR **reply_vps)
281 unsigned char ntpwdhash[16], ntpwdhashhash[16];
282 unsigned char buffer[256];
288 * No password or previous packet. Die.
290 if ((password == NULL) || (session == NULL)) {
294 reply = eapleap_alloc();
295 if (!reply) return NULL;
297 reply->code = PW_EAP_RESPONSE;
298 reply->length = LEAP_HEADER_LEN + 24 + user_name->length;
301 reply->challenge = malloc(reply->count);
302 if (reply->challenge == NULL) {
303 radlog(L_ERR, "rlm_eap_leap: out of memory");
304 eapleap_free(&reply);
309 * The LEAP packet also contains the user name.
311 reply->name = malloc(user_name->length + 1);
312 if (reply->name == NULL) {
313 radlog(L_ERR, "rlm_eap_leap: out of memory");
314 eapleap_free(&reply);
319 * Copy the name over, and ensure it's NUL terminated.
321 memcpy(reply->name, user_name->strvalue, user_name->length);
322 reply->name[user_name->length] = '\0';
323 reply->name_len = user_name->length;
326 * MPPE hash = ntpwdhash(ntpwdhash(unicode(pw)))
328 if (!eapleap_ntpwdhash(ntpwdhash, password)) {
329 eapleap_free(&reply);
332 md4_calc(ntpwdhashhash, ntpwdhash, 16);
335 * Calculate our response, to authenticate ourselves
338 eapleap_mschap(ntpwdhashhash, packet->challenge, reply->challenge);
341 * Calculate the leap:session-key attribute
343 vp = pairmake("Cisco-AVPair", "leap:session-key=", T_OP_ADD);
345 radlog(L_ERR, "rlm_eap_leap: Failed to create Cisco-AVPair attribute. LEAP cancelled.");
346 eapleap_free(&reply);
351 * And calculate the MPPE session key.
354 memcpy(p, ntpwdhashhash, 16); /* MPPEHASH */
356 memcpy(p, packet->challenge, 8); /* APC */
358 memcpy(p, reply->challenge, 24); /* APR */
360 memcpy(p, session->peer_challenge, 8); /* PC */
362 memcpy(p, session->peer_response, 24); /* PR */
366 * These 16 bytes are the session key to use.
368 librad_md5_calc(ntpwdhash, buffer, 16 + 8 + 24 + 8 + 24);
370 memcpy(vp->strvalue + vp->length, ntpwdhash, 16);
371 memset(vp->strvalue + vp->length + 16, 0,
372 sizeof(vp->strvalue) - (vp->length + 16));
375 rad_tunnel_pwencode(vp->strvalue + vp->length, &i,
376 request->secret, request->packet->vector);
378 pairadd(reply_vps, vp);
384 * If an EAP LEAP request needs to be initiated then
385 * create such a packet.
387 LEAP_PACKET *eapleap_initiate(UNUSED EAP_DS *eap_ds, VALUE_PAIR *user_name)
392 reply = eapleap_alloc();
394 radlog(L_ERR, "rlm_eap_leap: out of memory");
398 reply->code = PW_EAP_REQUEST;
399 reply->length = LEAP_HEADER_LEN + 8 + user_name->length;
400 reply->count = 8; /* random challenge */
402 reply->challenge = malloc(reply->count);
403 if (reply->challenge == NULL) {
404 radlog(L_ERR, "rlm_eap_leap: out of memory");
405 eapleap_free(&reply);
410 * Fill the challenge with random bytes.
412 for (i = 0; i < reply->count; i++) {
413 reply->challenge[i] = lrad_rand();
416 DEBUG2(" rlm_eap_leap: Issuing AP Challenge");
419 * The LEAP packet also contains the user name.
421 reply->name = malloc(user_name->length + 1);
422 if (reply->name == NULL) {
423 radlog(L_ERR, "rlm_eap_leap: out of memory");
424 eapleap_free(&reply);
429 * Copy the name over, and ensure it's NUL terminated.
431 memcpy(reply->name, user_name->strvalue, user_name->length);
432 reply->name[user_name->length] = '\0';
433 reply->name_len = user_name->length;
439 * compose the LEAP reply packet in the EAP reply typedata
441 int eapleap_compose(EAP_DS *eap_ds, LEAP_PACKET *reply)
446 * We need the name and the challenge.
448 switch (reply->code) {
450 case PW_EAP_RESPONSE:
451 eap_ds->request->type.type = PW_EAP_LEAP;
452 eap_ds->request->type.length = reply->length;
454 eap_ds->request->type.data = malloc(reply->length);
455 if (eap_ds->request->type.data == NULL) {
456 radlog(L_ERR, "rlm_eap_leap: out of memory");
459 data = (leap_packet_t *) eap_ds->request->type.data;
460 data->version = 0x01;
462 data->count = reply->count;
465 * N bytes of the challenge, followed by the user name.
467 memcpy(&data->challenge[0], reply->challenge, reply->count);
468 memcpy(&data->challenge[reply->count],
469 reply->name, reply->name_len);
473 * EAP-Success packets don't contain any data
474 * other than the header.
477 eap_ds->request->type.length = 0;
481 radlog(L_ERR, "rlm_eap_leap: Internal sanity check failed");
489 eap_ds->request->code = reply->code;