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 void 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 */
223 * FIXME: Check for broken (32-character)
226 memcpy(ntpwdhash, password->strvalue, 16);
232 * Verify the MS-CHAP response from the user.
234 int eapleap_stage4(LEAP_PACKET *packet, VALUE_PAIR* password,
235 leap_session_t *session)
237 unsigned char ntpwdhash[16];
238 unsigned char response[24];
242 * No password or previous packet. Die.
244 if ((password == NULL) || (session == NULL)) {
248 eapleap_ntpwdhash(ntpwdhash, password);
251 * Calculate and verify the CHAP challenge.
253 lrad_mschap(ntpwdhash, session->peer_challenge, response);
254 if (memcmp(response, packet->challenge, 24) == 0) {
255 DEBUG2(" rlm_eap_leap: NtChallengeResponse from AP is valid");
256 memcpy(session->peer_response, response, sizeof(response));
260 DEBUG2(" rlm_eap_leap: FAILED incorrect NtChallengeResponse from AP");
265 * Verify ourselves to the AP
267 LEAP_PACKET *eapleap_stage6(LEAP_PACKET *packet, REQUEST *request,
268 VALUE_PAIR *user_name, VALUE_PAIR* password,
269 leap_session_t *session, VALUE_PAIR **reply_vps)
272 unsigned char ntpwdhash[16], ntpwdhashhash[16];
273 unsigned char buffer[256];
279 * No password or previous packet. Die.
281 if ((password == NULL) || (session == NULL)) {
285 reply = eapleap_alloc();
286 if (!reply) return NULL;
288 reply->code = PW_EAP_RESPONSE;
289 reply->length = LEAP_HEADER_LEN + 24 + user_name->length;
292 reply->challenge = malloc(reply->count);
293 if (reply->challenge == NULL) {
294 radlog(L_ERR, "rlm_eap_leap: out of memory");
295 eapleap_free(&reply);
300 * The LEAP packet also contains the user name.
302 reply->name = malloc(user_name->length + 1);
303 if (reply->name == NULL) {
304 radlog(L_ERR, "rlm_eap_leap: out of memory");
305 eapleap_free(&reply);
310 * Copy the name over, and ensure it's NUL terminated.
312 memcpy(reply->name, user_name->strvalue, user_name->length);
313 reply->name[user_name->length] = '\0';
314 reply->name_len = user_name->length;
317 * MPPE hash = ntpwdhash(ntpwdhash(unicode(pw)))
319 eapleap_ntpwdhash(ntpwdhash, password);
320 md4_calc(ntpwdhashhash, ntpwdhash, 16);
323 * Calculate our response, to authenticate ourselves
326 lrad_mschap(ntpwdhashhash, packet->challenge, reply->challenge);
329 * Calculate the leap:session-key attribute
331 vp = pairmake("Cisco-AVPair", "leap:session-key=", T_OP_ADD);
333 radlog(L_ERR, "rlm_eap_leap: Failed to create Cisco-AVPair attribute. LEAP cancelled.");
334 eapleap_free(&reply);
339 * And calculate the MPPE session key.
342 memcpy(p, ntpwdhashhash, 16); /* MPPEHASH */
344 memcpy(p, packet->challenge, 8); /* APC */
346 memcpy(p, reply->challenge, 24); /* APR */
348 memcpy(p, session->peer_challenge, 8); /* PC */
350 memcpy(p, session->peer_response, 24); /* PR */
354 * These 16 bytes are the session key to use.
356 librad_md5_calc(ntpwdhash, buffer, 16 + 8 + 24 + 8 + 24);
358 memcpy(vp->strvalue + vp->length, ntpwdhash, 16);
359 memset(vp->strvalue + vp->length + 16, 0,
360 sizeof(vp->strvalue) - (vp->length + 16));
363 rad_tunnel_pwencode(vp->strvalue + vp->length, &i,
364 request->secret, request->packet->vector);
366 pairadd(reply_vps, vp);
372 * If an EAP LEAP request needs to be initiated then
373 * create such a packet.
375 LEAP_PACKET *eapleap_initiate(EAP_DS *eap_ds, VALUE_PAIR *user_name)
380 reply = eapleap_alloc();
382 radlog(L_ERR, "rlm_eap_leap: out of memory");
386 reply->code = PW_EAP_REQUEST;
387 reply->length = LEAP_HEADER_LEN + 8 + user_name->length;
388 reply->count = 8; /* random challenge */
390 reply->challenge = malloc(reply->count);
391 if (reply->challenge == NULL) {
392 radlog(L_ERR, "rlm_eap_leap: out of memory");
393 eapleap_free(&reply);
398 * Fill the challenge with random bytes.
400 for (i = 0; i < reply->count; i++) {
401 reply->challenge[i] = lrad_rand();
404 DEBUG2(" rlm_eap_leap: Issuing AP Challenge");
407 * The LEAP packet also contains the user name.
409 reply->name = malloc(user_name->length + 1);
410 if (reply->name == NULL) {
411 radlog(L_ERR, "rlm_eap_leap: out of memory");
412 eapleap_free(&reply);
417 * Copy the name over, and ensure it's NUL terminated.
419 memcpy(reply->name, user_name->strvalue, user_name->length);
420 reply->name[user_name->length] = '\0';
421 reply->name_len = user_name->length;
427 * compose the LEAP reply packet in the EAP reply typedata
429 int eapleap_compose(EAP_DS *eap_ds, LEAP_PACKET *reply)
434 * We need the name and the challenge.
436 switch (reply->code) {
438 case PW_EAP_RESPONSE:
439 eap_ds->request->type.type = PW_EAP_LEAP;
440 eap_ds->request->type.length = reply->length;
442 eap_ds->request->type.data = malloc(reply->length);
443 if (eap_ds->request->type.data == NULL) {
444 radlog(L_ERR, "rlm_eap_leap: out of memory");
447 data = (leap_packet_t *) eap_ds->request->type.data;
448 data->version = 0x01;
450 data->count = reply->count;
453 * N bytes of the challenge, followed by the user name.
455 memcpy(&data->challenge[0], reply->challenge, reply->count);
456 memcpy(&data->challenge[reply->count],
457 reply->name, reply->name_len);
461 * EAP-Success packets don't contain any data
462 * other than the header.
465 eap_ds->request->type.length = 0;
469 radlog(L_ERR, "rlm_eap_leap: Internal sanity check failed");
477 eap_ds->request->code = reply->code;