2 * WPA Supplicant / EAP-MSCHAPV2 (draft-kamath-pppext-eap-mschapv2-00.txt)
3 * Copyright (c) 2004-2005, Jouni Malinen <jkmaline@cc.hut.fi>
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License version 2 as
7 * published by the Free Software Foundation.
9 * Alternatively, this software may be distributed under the terms of BSD
12 * See README and COPYING for more details.
21 #include "wpa_supplicant.h"
22 #include "config_ssid.h"
27 struct eap_mschapv2_hdr {
30 u16 length; /* including code, identifier, and length */
31 u8 type; /* EAP_TYPE_MSCHAPV2 */
32 u8 op_code; /* MSCHAPV2_OP_* */
33 u8 mschapv2_id; /* usually same as identifier */
34 u8 ms_length[2]; /* Note: misaligned; length - 5 */
35 /* followed by data */
36 } __attribute__ ((packed));
38 #define MSCHAPV2_OP_CHALLENGE 1
39 #define MSCHAPV2_OP_RESPONSE 2
40 #define MSCHAPV2_OP_SUCCESS 3
41 #define MSCHAPV2_OP_FAILURE 4
42 #define MSCHAPV2_OP_CHANGE_PASSWORD 7
44 #define MSCHAPV2_RESP_LEN 49
46 #define ERROR_RESTRICTED_LOGON_HOURS 646
47 #define ERROR_ACCT_DISABLED 647
48 #define ERROR_PASSWD_EXPIRED 648
49 #define ERROR_NO_DIALIN_PERMISSION 649
50 #define ERROR_AUTHENTICATION_FAILURE 691
51 #define ERROR_CHANGING_PASSWORD 709
53 #define PASSWD_CHANGE_CHAL_LEN 16
54 #define MSCHAPV2_KEY_LEN 16
57 struct eap_mschapv2_data {
59 int auth_response_valid;
62 u8 passwd_change_challenge[PASSWD_CHANGE_CHAL_LEN];
63 int passwd_change_challenge_valid;
64 int passwd_change_version;
66 /* Optional challenge values generated in EAP-FAST Phase 1 negotiation
77 size_t prev_challenge_len;
81 static void eap_mschapv2_deinit(struct eap_sm *sm, void *priv);
84 static void * eap_mschapv2_init(struct eap_sm *sm)
86 struct eap_mschapv2_data *data;
87 data = malloc(sizeof(*data));
90 memset(data, 0, sizeof(*data));
92 if (sm->peer_challenge) {
93 data->peer_challenge = malloc(16);
94 if (data->peer_challenge == NULL) {
95 eap_mschapv2_deinit(sm, data);
98 memcpy(data->peer_challenge, sm->peer_challenge, 16);
101 if (sm->auth_challenge) {
102 data->auth_challenge = malloc(16);
103 if (data->auth_challenge == NULL) {
104 eap_mschapv2_deinit(sm, data);
107 memcpy(data->auth_challenge, sm->auth_challenge, 16);
110 data->phase2 = sm->init_phase2;
116 static void eap_mschapv2_deinit(struct eap_sm *sm, void *priv)
118 struct eap_mschapv2_data *data = priv;
119 free(data->peer_challenge);
120 free(data->auth_challenge);
121 free(data->prev_challenge);
126 static u8 * eap_mschapv2_challenge(struct eap_sm *sm,
127 struct eap_mschapv2_data *data,
128 struct eap_method_ret *ret,
129 const struct eap_mschapv2_hdr *req,
132 struct wpa_ssid *config = eap_get_config(sm);
133 u8 *challenge, *peer_challenge, *username, *pos;
135 size_t len, challenge_len, username_len;
136 struct eap_mschapv2_hdr *resp;
137 u8 password_hash[16], password_hash_hash[16];
142 /* MSCHAPv2 does not include optional domain name in the
143 * challenge-response calculation, so remove domain prefix
145 username = config->identity;
146 username_len = config->identity_len;
147 for (i = 0; i < username_len; i++) {
148 if (username[i] == '\\') {
149 username_len -= i + 1;
155 wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: Received challenge");
156 len = be_to_host16(req->length);
157 pos = (u8 *) (req + 1);
158 challenge_len = *pos++;
159 if (challenge_len != 16) {
160 wpa_printf(MSG_INFO, "EAP-MSCHAPV2: Invalid challenge length "
161 "%lu", (unsigned long) challenge_len);
166 if (len < 10 || len - 10 < challenge_len) {
167 wpa_printf(MSG_INFO, "EAP-MSCHAPV2: Too short challenge"
168 " packet: len=%lu challenge_len=%lu",
169 (unsigned long) len, (unsigned long) challenge_len);
174 if (data->passwd_change_challenge_valid) {
175 wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: Using challenge from the "
177 challenge = data->passwd_change_challenge;
180 pos += challenge_len;
181 wpa_hexdump_ascii(MSG_DEBUG, "EAP-MSCHAPV2: Authentication Servername",
182 pos, len - challenge_len - 10);
185 ret->methodState = METHOD_MAY_CONT;
186 ret->decision = DECISION_FAIL;
187 ret->allowNotifications = TRUE;
189 wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: Generating Challenge Response");
191 *respDataLen = sizeof(*resp) + 1 + MSCHAPV2_RESP_LEN +
192 config->identity_len;
193 resp = malloc(*respDataLen);
196 memset(resp, 0, *respDataLen);
197 resp->code = EAP_CODE_RESPONSE;
198 resp->identifier = req->identifier;
199 resp->length = host_to_be16(*respDataLen);
200 resp->type = EAP_TYPE_MSCHAPV2;
201 resp->op_code = MSCHAPV2_OP_RESPONSE;
202 resp->mschapv2_id = req->mschapv2_id;
203 if (data->prev_error) {
205 * TODO: this does not seem to be enough when processing two
206 * or more failure messages. IAS did not increment mschapv2_id
207 * in its own packets, but it seemed to expect the peer to
208 * increment this for all packets(?).
212 ms_len = *respDataLen - 5;
213 WPA_PUT_BE16(resp->ms_length, ms_len);
214 pos = (u8 *) (resp + 1);
215 *pos++ = MSCHAPV2_RESP_LEN; /* Value-Size */
218 peer_challenge = pos;
219 if (data->peer_challenge) {
220 wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: peer_challenge generated "
222 peer_challenge = data->peer_challenge;
223 } else if (hostapd_get_rand(peer_challenge, 16)) {
228 pos += 8; /* Reserved, must be zero */
229 if (data->auth_challenge) {
230 wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: auth_challenge generated "
232 challenge = data->auth_challenge;
234 wpa_hexdump(MSG_DEBUG, "EAP-MSCHAPV2: auth_challenge", challenge, 16);
235 wpa_hexdump(MSG_DEBUG, "EAP-MSCHAPV2: peer_challenge",
237 wpa_hexdump_ascii(MSG_DEBUG, "EAP-MSCHAPV2: username",
238 username, username_len);
239 wpa_hexdump_ascii_key(MSG_DEBUG, "EAP-MSCHAPV2: password",
240 config->password, config->password_len);
241 generate_nt_response(challenge, peer_challenge,
242 username, username_len,
243 config->password, config->password_len,
245 wpa_hexdump(MSG_DEBUG, "EAP-MSCHAPV2: response", pos, 24);
246 /* Authenticator response is not really needed yet, but calculate it
247 * here so that challenges need not be saved. */
248 generate_authenticator_response(config->password, config->password_len,
249 peer_challenge, challenge,
250 username, username_len, pos,
251 data->auth_response);
252 data->auth_response_valid = 1;
254 /* Likewise, generate master_key here since we have the needed data
256 nt_password_hash(config->password, config->password_len,
258 hash_nt_password_hash(password_hash, password_hash_hash);
259 get_master_key(password_hash_hash, pos /* nt_response */,
261 data->master_key_valid = 1;
264 pos++; /* Flag / reserved, must be zero */
266 memcpy(pos, config->identity, config->identity_len);
267 wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: TX identifier %d mschapv2_id %d "
268 "(response)", resp->identifier, resp->mschapv2_id);
273 static u8 * eap_mschapv2_success(struct eap_sm *sm,
274 struct eap_mschapv2_data *data,
275 struct eap_method_ret *ret,
276 const struct eap_mschapv2_hdr *req,
279 struct eap_mschapv2_hdr *resp;
281 u8 recv_response[20];
284 wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: Received success");
285 len = be_to_host16(req->length);
286 pos = (const u8 *) (req + 1);
287 if (!data->auth_response_valid || len < sizeof(*req) + 42 ||
288 pos[0] != 'S' || pos[1] != '=' ||
289 hexstr2bin((char *) (pos + 2), recv_response, 20) ||
290 memcmp(data->auth_response, recv_response, 20) != 0) {
291 wpa_printf(MSG_WARNING, "EAP-MSCHAPV2: Invalid authenticator "
292 "response in success request");
293 ret->methodState = METHOD_DONE;
294 ret->decision = DECISION_FAIL;
298 left = len - sizeof(*req) - 42;
299 while (left > 0 && *pos == ' ') {
303 wpa_hexdump_ascii(MSG_DEBUG, "EAP-MSCHAPV2: Success message",
305 wpa_printf(MSG_INFO, "EAP-MSCHAPV2: Authentication succeeded");
309 wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: Failed to allocate "
310 "buffer for success response");
315 resp->code = EAP_CODE_RESPONSE;
316 resp->identifier = req->identifier;
317 resp->length = host_to_be16(6);
318 resp->type = EAP_TYPE_MSCHAPV2;
319 resp->op_code = MSCHAPV2_OP_SUCCESS;
321 ret->methodState = METHOD_DONE;
322 ret->decision = DECISION_UNCOND_SUCC;
323 ret->allowNotifications = FALSE;
326 if (data->prev_error == ERROR_PASSWD_EXPIRED) {
327 struct wpa_ssid *config = eap_get_config(sm);
328 if (config && config->new_password) {
329 wpa_msg(sm->msg_ctx, MSG_INFO,
330 WPA_EVENT_PASSWORD_CHANGED
331 "EAP-MSCHAPV2: Password changed successfully");
332 data->prev_error = 0;
333 free(config->password);
334 config->password = config->new_password;
335 config->new_password = NULL;
336 config->password_len = config->new_password_len;
337 config->new_password_len = 0;
345 static int eap_mschapv2_failure_txt(struct eap_sm *sm,
346 struct eap_mschapv2_data *data, char *txt)
348 char *pos, *msg = "";
350 struct wpa_ssid *config = eap_get_config(sm);
353 * E=691 R=1 C=<32 octets hex challenge> V=3 M=Authentication Failure
358 if (pos && strncmp(pos, "E=", 2) == 0) {
360 data->prev_error = atoi(pos);
361 wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: error %d",
363 pos = strchr(pos, ' ');
368 if (pos && strncmp(pos, "R=", 2) == 0) {
371 wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: retry is %sallowed",
372 retry == 1 ? "" : "not ");
373 pos = strchr(pos, ' ');
378 if (pos && strncmp(pos, "C=", 2) == 0) {
381 hex_len = strchr(pos, ' ') - (char *) pos;
382 if (hex_len == PASSWD_CHANGE_CHAL_LEN * 2) {
383 if (hexstr2bin(pos, data->passwd_change_challenge,
384 PASSWD_CHANGE_CHAL_LEN)) {
385 wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: invalid "
386 "failure challenge");
388 wpa_hexdump(MSG_DEBUG, "EAP-MSCHAPV2: failure "
390 data->passwd_change_challenge,
391 PASSWD_CHANGE_CHAL_LEN);
392 data->passwd_change_challenge_valid = 1;
395 wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: invalid failure "
396 "challenge len %d", hex_len);
398 pos = strchr(pos, ' ');
402 wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: required challenge field "
403 "was not present in failure message");
406 if (pos && strncmp(pos, "V=", 2) == 0) {
408 data->passwd_change_version = atoi(pos);
409 wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: password changing "
410 "protocol version %d", data->passwd_change_version);
411 pos = strchr(pos, ' ');
416 if (pos && strncmp(pos, "M=", 2) == 0) {
420 wpa_msg(sm->msg_ctx, MSG_WARNING,
421 "EAP-MSCHAPV2: failure message: '%s' (retry %sallowed, error "
423 msg, retry == 1 ? "" : "not ", data->prev_error);
424 if (data->prev_error == ERROR_PASSWD_EXPIRED &&
425 data->passwd_change_version == 3 && config) {
426 if (config->new_password == NULL) {
427 wpa_msg(sm->msg_ctx, MSG_INFO,
428 "EAP-MSCHAPV2: Password expired - password "
430 eap_sm_request_new_password(sm, config);
432 } else if (retry == 1 && config) {
433 /* TODO: could prevent the current password from being used
434 * again at least for some period of time */
435 if (!config->mschapv2_retry)
436 eap_sm_request_identity(sm, config);
437 eap_sm_request_password(sm, config);
438 config->mschapv2_retry = 1;
440 /* TODO: prevent retries using same username/password */
441 config->mschapv2_retry = 0;
448 static u8 * eap_mschapv2_change_password(struct eap_sm *sm,
449 struct eap_mschapv2_data *data,
450 struct eap_method_ret *ret,
451 const struct eap_mschapv2_hdr *req,
454 struct eap_mschapv2_hdr *resp;
456 u8 *peer_challenge, *username, *pos;
458 struct wpa_ssid *config = eap_get_config(sm);
460 if (config == NULL || config->identity == NULL ||
461 config->new_password == NULL || config->password == NULL)
465 * MSCHAPv2 does not include optional domain name in the
466 * challenge-response calculation, so remove domain prefix
469 username = config->identity;
470 username_len = config->identity_len;
471 for (i = 0; i < username_len; i++) {
472 if (username[i] == '\\') {
473 username_len -= i + 1;
480 ret->methodState = METHOD_MAY_CONT;
481 ret->decision = DECISION_COND_SUCC;
482 ret->allowNotifications = TRUE;
485 resp = malloc(*respDataLen);
490 resp->code = EAP_CODE_RESPONSE;
491 resp->identifier = req->identifier;
492 resp->length = host_to_be16((u16) *respDataLen);
493 resp->type = EAP_TYPE_MSCHAPV2;
494 resp->op_code = MSCHAPV2_OP_CHANGE_PASSWORD;
495 resp->mschapv2_id = req->mschapv2_id + 1;
496 ms_len = *respDataLen - 5;
497 WPA_PUT_BE16(resp->ms_length, ms_len);
498 pos = (u8 *) (resp + 1);
500 /* Encrypted-Password */
501 new_password_encrypted_with_old_nt_password_hash(
502 config->new_password, config->new_password_len,
503 config->password, config->password_len, pos);
507 old_nt_password_hash_encrypted_with_new_nt_password_hash(
508 config->new_password, config->new_password_len,
509 config->password, config->password_len, pos);
513 peer_challenge = pos;
514 if (hostapd_get_rand(peer_challenge, 16)) {
520 /* Reserved, must be zero */
525 wpa_hexdump(MSG_DEBUG, "EAP-MSCHAPV2: auth_challenge",
526 data->passwd_change_challenge, PASSWD_CHANGE_CHAL_LEN);
527 wpa_hexdump(MSG_DEBUG, "EAP-MSCHAPV2: peer_challenge",
529 wpa_hexdump_ascii(MSG_DEBUG, "EAP-MSCHAPV2: username",
530 username, username_len);
531 wpa_hexdump_ascii_key(MSG_DEBUG, "EAP-MSCHAPV2: new password",
532 config->new_password, config->new_password_len);
533 generate_nt_response(data->passwd_change_challenge, peer_challenge,
534 username, username_len,
535 config->new_password, config->new_password_len,
537 wpa_hexdump(MSG_DEBUG, "EAP-MSCHAPV2: NT-Response", pos, 24);
539 /* Authenticator response is not really needed yet, but calculate it
540 * here so that challenges need not be saved. */
541 generate_authenticator_response(config->new_password,
542 config->new_password_len,
544 data->passwd_change_challenge,
545 username, username_len, pos,
546 data->auth_response);
547 data->auth_response_valid = 1;
555 wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: TX identifier %d mschapv2_id %d "
556 "(change pw)", resp->identifier, resp->mschapv2_id);
562 static u8 * eap_mschapv2_failure(struct eap_sm *sm,
563 struct eap_mschapv2_data *data,
564 struct eap_method_ret *ret,
565 const struct eap_mschapv2_hdr *req,
568 struct eap_mschapv2_hdr *resp;
569 const u8 *msdata = (const u8 *) (req + 1);
571 int len = be_to_host16(req->length) - sizeof(*req);
574 wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: Received failure");
575 wpa_hexdump_ascii(MSG_DEBUG, "EAP-MSCHAPV2: Failure data",
577 buf = malloc(len + 1);
579 memcpy(buf, msdata, len);
581 retry = eap_mschapv2_failure_txt(sm, data, buf);
586 ret->methodState = METHOD_DONE;
587 ret->decision = DECISION_FAIL;
588 ret->allowNotifications = FALSE;
590 if (data->prev_error == ERROR_PASSWD_EXPIRED &&
591 data->passwd_change_version == 3) {
592 struct wpa_ssid *config = eap_get_config(sm);
593 if (config && config->new_password)
594 return eap_mschapv2_change_password(sm, data, ret, req,
596 if (config && config->pending_req_new_password)
598 } else if (retry && data->prev_error == ERROR_AUTHENTICATION_FAILURE) {
599 /* TODO: could try to retry authentication, e.g, after having
600 * changed the username/password. In this case, EAP MS-CHAP-v2
601 * Failure Response would not be sent here. */
611 resp->code = EAP_CODE_RESPONSE;
612 resp->identifier = req->identifier;
613 resp->length = host_to_be16(6);
614 resp->type = EAP_TYPE_MSCHAPV2;
615 resp->op_code = MSCHAPV2_OP_FAILURE;
621 static u8 * eap_mschapv2_process(struct eap_sm *sm, void *priv,
622 struct eap_method_ret *ret,
623 const u8 *reqData, size_t reqDataLen,
626 struct eap_mschapv2_data *data = priv;
627 struct wpa_ssid *config = eap_get_config(sm);
628 const struct eap_mschapv2_hdr *req;
629 int ms_len, using_prev_challenge = 0;
633 if (config == NULL || config->identity == NULL) {
634 wpa_printf(MSG_INFO, "EAP-MSCHAPV2: Identity not configured");
635 eap_sm_request_identity(sm, config);
640 if (config->password == NULL) {
641 wpa_printf(MSG_INFO, "EAP-MSCHAPV2: Password not configured");
642 eap_sm_request_password(sm, config);
647 if (config->mschapv2_retry && data->prev_challenge &&
648 data->prev_error == ERROR_AUTHENTICATION_FAILURE) {
649 wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: Replacing pending packet "
650 "with the previous challenge");
652 reqData = data->prev_challenge;
653 reqDataLen = data->prev_challenge_len;
654 using_prev_challenge = 1;
655 config->mschapv2_retry = 0;
658 pos = eap_hdr_validate(EAP_TYPE_MSCHAPV2, reqData, reqDataLen, &len);
659 if (pos == NULL || len < 5) {
663 req = (const struct eap_mschapv2_hdr *) reqData;
664 len = be_to_host16(req->length);
665 ms_len = WPA_GET_BE16(req->ms_length);
666 if (ms_len != len - 5) {
667 wpa_printf(MSG_INFO, "EAP-MSCHAPV2: Invalid header: len=%lu "
668 "ms_len=%d", (unsigned long) len, ms_len);
669 if (sm->workaround) {
670 /* Some authentication servers use invalid ms_len,
671 * ignore it for interoperability. */
672 wpa_printf(MSG_INFO, "EAP-MSCHAPV2: workaround, ignore"
680 wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: RX identifier %d mschapv2_id %d",
681 req->identifier, req->mschapv2_id);
683 switch (req->op_code) {
684 case MSCHAPV2_OP_CHALLENGE:
685 if (!using_prev_challenge) {
686 free(data->prev_challenge);
687 data->prev_challenge = malloc(len);
688 if (data->prev_challenge) {
689 data->prev_challenge_len = len;
690 memcpy(data->prev_challenge, reqData, len);
693 return eap_mschapv2_challenge(sm, data, ret, req, respDataLen);
694 case MSCHAPV2_OP_SUCCESS:
695 return eap_mschapv2_success(sm, data, ret, req, respDataLen);
696 case MSCHAPV2_OP_FAILURE:
697 return eap_mschapv2_failure(sm, data, ret, req, respDataLen);
699 wpa_printf(MSG_INFO, "EAP-MSCHAPV2: Unknown op %d - ignored",
707 static Boolean eap_mschapv2_isKeyAvailable(struct eap_sm *sm, void *priv)
709 struct eap_mschapv2_data *data = priv;
710 return data->success && data->master_key_valid;
714 static u8 * eap_mschapv2_getKey(struct eap_sm *sm, void *priv, size_t *len)
716 struct eap_mschapv2_data *data = priv;
720 if (!data->master_key_valid || !data->success)
723 if (data->peer_challenge) {
724 /* EAP-FAST needs both send and receive keys */
725 key_len = 2 * MSCHAPV2_KEY_LEN;
727 key_len = MSCHAPV2_KEY_LEN;
730 key = malloc(key_len);
734 if (data->peer_challenge) {
735 get_asymetric_start_key(data->master_key, key,
736 MSCHAPV2_KEY_LEN, 0, 0);
737 get_asymetric_start_key(data->master_key,
738 key + MSCHAPV2_KEY_LEN,
739 MSCHAPV2_KEY_LEN, 1, 0);
741 get_asymetric_start_key(data->master_key, key,
742 MSCHAPV2_KEY_LEN, 1, 0);
745 wpa_hexdump_key(MSG_DEBUG, "EAP-MSCHAPV2: Derived key",
753 const struct eap_method eap_method_mschapv2 =
755 .method = EAP_TYPE_MSCHAPV2,
757 .init = eap_mschapv2_init,
758 .deinit = eap_mschapv2_deinit,
759 .process = eap_mschapv2_process,
760 .isKeyAvailable = eap_mschapv2_isKeyAvailable,
761 .getKey = eap_mschapv2_getKey,