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"
26 struct eap_mschapv2_hdr {
29 u16 length; /* including code, identifier, and length */
30 u8 type; /* EAP_TYPE_MSCHAPV2 */
31 u8 op_code; /* MSCHAPV2_OP_* */
32 u8 mschapv2_id; /* usually same as identifier */
33 u8 ms_length[2]; /* Note: misaligned; length - 5 */
34 /* followed by data */
35 } __attribute__ ((packed));
37 #define MSCHAPV2_OP_CHALLENGE 1
38 #define MSCHAPV2_OP_RESPONSE 2
39 #define MSCHAPV2_OP_SUCCESS 3
40 #define MSCHAPV2_OP_FAILURE 4
41 #define MSCHAPV2_OP_CHANGE_PASSWORD 7
43 #define MSCHAPV2_RESP_LEN 49
45 #define ERROR_RESTRICTED_LOGON_HOURS 646
46 #define ERROR_ACCT_DISABLED 647
47 #define ERROR_PASSWD_EXPIRED 648
48 #define ERROR_NO_DIALIN_PERMISSION 649
49 #define ERROR_AUTHENTICATION_FAILURE 691
50 #define ERROR_CHANGING_PASSWORD 709
52 #define PASSWD_CHANGE_CHAL_LEN 16
53 #define MSCHAPV2_KEY_LEN 16
56 struct eap_mschapv2_data {
58 int auth_response_valid;
61 u8 passwd_change_challenge[PASSWD_CHANGE_CHAL_LEN];
62 int passwd_change_challenge_valid;
63 int passwd_change_version;
65 /* Optional challenge values generated in EAP-FAST Phase 1 negotiation
77 static void eap_mschapv2_deinit(struct eap_sm *sm, void *priv);
80 static void * eap_mschapv2_init(struct eap_sm *sm)
82 struct eap_mschapv2_data *data;
83 data = malloc(sizeof(*data));
86 memset(data, 0, sizeof(*data));
88 if (sm->peer_challenge) {
89 data->peer_challenge = malloc(16);
90 if (data->peer_challenge == NULL) {
91 eap_mschapv2_deinit(sm, data);
94 memcpy(data->peer_challenge, sm->peer_challenge, 16);
97 if (sm->auth_challenge) {
98 data->auth_challenge = malloc(16);
99 if (data->auth_challenge == NULL) {
100 eap_mschapv2_deinit(sm, data);
103 memcpy(data->auth_challenge, sm->auth_challenge, 16);
106 data->phase2 = sm->init_phase2;
112 static void eap_mschapv2_deinit(struct eap_sm *sm, void *priv)
114 struct eap_mschapv2_data *data = priv;
115 free(data->peer_challenge);
116 free(data->auth_challenge);
121 static u8 * eap_mschapv2_challenge(struct eap_sm *sm,
122 struct eap_mschapv2_data *data,
123 struct eap_method_ret *ret,
124 struct eap_mschapv2_hdr *req,
127 struct wpa_ssid *config = eap_get_config(sm);
128 u8 *challenge, *peer_challenge, *username, *pos;
130 size_t len, challenge_len, username_len;
131 struct eap_mschapv2_hdr *resp;
132 u8 password_hash[16], password_hash_hash[16];
134 /* MSCHAPv2 does not include optional domain name in the
135 * challenge-response calculation, so remove domain prefix
137 username = config->identity;
138 username_len = config->identity_len;
139 for (i = 0; i < username_len; i++) {
140 if (username[i] == '\\') {
141 username_len -= i + 1;
147 wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: Received challenge");
148 len = be_to_host16(req->length);
149 pos = (u8 *) (req + 1);
150 challenge_len = *pos++;
151 if (challenge_len != 16) {
152 wpa_printf(MSG_INFO, "EAP-MSCHAPV2: Invalid challenge length "
153 "%d", challenge_len);
158 if (len < 10 || len - 10 < challenge_len) {
159 wpa_printf(MSG_INFO, "EAP-MSCHAPV2: Too short challenge"
160 " packet: len=%lu challenge_len=%d",
161 (unsigned long) len, challenge_len);
167 pos += challenge_len;
168 wpa_hexdump_ascii(MSG_DEBUG, "EAP-MSCHAPV2: Authentication Servername",
169 pos, len - challenge_len - 10);
172 ret->methodState = METHOD_CONT;
173 ret->decision = DECISION_FAIL;
174 ret->allowNotifications = TRUE;
176 wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: Generating Challenge Response");
178 *respDataLen = sizeof(*resp) + 1 + MSCHAPV2_RESP_LEN +
179 config->identity_len;
180 resp = malloc(*respDataLen);
183 memset(resp, 0, *respDataLen);
184 resp->code = EAP_CODE_RESPONSE;
185 resp->identifier = req->identifier;
186 resp->length = host_to_be16(*respDataLen);
187 resp->type = EAP_TYPE_MSCHAPV2;
188 resp->op_code = MSCHAPV2_OP_RESPONSE;
189 resp->mschapv2_id = req->mschapv2_id;
190 ms_len = *respDataLen - 5;
191 resp->ms_length[0] = ms_len >> 8;
192 resp->ms_length[1] = ms_len & 0xff;
193 pos = (u8 *) (resp + 1);
194 *pos++ = MSCHAPV2_RESP_LEN; /* Value-Size */
197 peer_challenge = pos;
198 if (data->peer_challenge) {
199 wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: peer_challenge generated "
201 peer_challenge = data->peer_challenge;
202 } else if (hostapd_get_rand(peer_challenge, 16)) {
207 pos += 8; /* Reserved, must be zero */
208 if (data->auth_challenge) {
209 wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: auth_challenge generated "
211 challenge = data->auth_challenge;
213 wpa_hexdump(MSG_DEBUG, "EAP-MSCHAPV2: auth_challenge", challenge, 16);
214 wpa_hexdump(MSG_DEBUG, "EAP-MSCHAPV2: peer_challenge",
216 wpa_hexdump_ascii(MSG_DEBUG, "EAP-MSCHAPV2: username",
217 username, username_len);
218 wpa_hexdump_ascii_key(MSG_DEBUG, "EAP-MSCHAPV2: password",
219 config->password, config->password_len);
220 generate_nt_response(challenge, peer_challenge,
221 username, username_len,
222 config->password, config->password_len,
224 wpa_hexdump(MSG_DEBUG, "EAP-MSCHAPV2: response", pos, 24);
225 /* Authenticator response is not really needed yet, but calculate it
226 * here so that challenges need not be saved. */
227 generate_authenticator_response(config->password, config->password_len,
228 peer_challenge, challenge,
229 username, username_len, pos,
230 data->auth_response);
231 data->auth_response_valid = 1;
233 /* Likewise, generate master_key here since we have the needed data
235 nt_password_hash(config->password, config->password_len,
237 hash_nt_password_hash(password_hash, password_hash_hash);
238 get_master_key(password_hash_hash, pos /* nt_response */,
240 data->master_key_valid = 1;
243 pos++; /* Flag / reserved, must be zero */
245 memcpy(pos, config->identity, config->identity_len);
250 static u8 * eap_mschapv2_success(struct eap_sm *sm,
251 struct eap_mschapv2_data *data,
252 struct eap_method_ret *ret,
253 struct eap_mschapv2_hdr *req,
256 struct eap_mschapv2_hdr *resp;
257 u8 *pos, recv_response[20];
260 wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: Received success");
261 len = be_to_host16(req->length);
262 pos = (u8 *) (req + 1);
263 if (!data->auth_response_valid || len < sizeof(*req) + 42 ||
264 pos[0] != 'S' || pos[1] != '=' ||
265 hexstr2bin((char *) (pos + 2), recv_response, 20) ||
266 memcmp(data->auth_response, recv_response, 20) != 0) {
267 wpa_printf(MSG_WARNING, "EAP-MSCHAPV2: Invalid authenticator "
268 "response in success request");
269 ret->methodState = METHOD_DONE;
270 ret->decision = DECISION_FAIL;
274 left = len - sizeof(*req) - 42;
275 while (left > 0 && *pos == ' ') {
279 wpa_hexdump_ascii(MSG_DEBUG, "EAP-MSCHAPV2: Success message",
281 wpa_printf(MSG_INFO, "EAP-MSCHAPV2: Authentication succeeded");
285 wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: Failed to allocate "
286 "buffer for success response");
291 resp->code = EAP_CODE_RESPONSE;
292 resp->identifier = req->identifier;
293 resp->length = host_to_be16(6);
294 resp->type = EAP_TYPE_MSCHAPV2;
295 resp->op_code = MSCHAPV2_OP_SUCCESS;
297 ret->methodState = METHOD_DONE;
298 ret->decision = DECISION_UNCOND_SUCC;
299 ret->allowNotifications = FALSE;
306 static int eap_mschapv2_failure_txt(struct eap_sm *sm,
307 struct eap_mschapv2_data *data, char *txt)
309 char *pos, *msg = "";
311 struct wpa_ssid *config = eap_get_config(sm);
314 * E=691 R=1 C=<32 octets hex challenge> V=3 M=Authentication Failure
319 if (pos && strncmp(pos, "E=", 2) == 0) {
321 data->prev_error = atoi(pos);
322 wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: error %d",
324 pos = strchr(pos, ' ');
329 if (pos && strncmp(pos, "R=", 2) == 0) {
332 wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: retry is %sallowed",
333 retry == 1 ? "" : "not ");
334 pos = strchr(pos, ' ');
339 if (pos && strncmp(pos, "C=", 2) == 0) {
342 hex_len = strchr(pos, ' ') - (char *) pos;
343 if (hex_len == PASSWD_CHANGE_CHAL_LEN * 2) {
344 if (hexstr2bin(pos, data->passwd_change_challenge,
345 PASSWD_CHANGE_CHAL_LEN)) {
346 wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: invalid "
347 "failure challenge");
349 wpa_hexdump(MSG_DEBUG, "EAP-MSCHAPV2: failure "
351 data->passwd_change_challenge,
352 PASSWD_CHANGE_CHAL_LEN);
353 data->passwd_change_challenge_valid = 1;
356 wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: invalid failure "
357 "challenge len %d", hex_len);
359 pos = strchr(pos, ' ');
363 wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: required challenge field "
364 "was not present in failure message");
367 if (pos && strncmp(pos, "V=", 2) == 0) {
369 data->passwd_change_version = atoi(pos);
370 wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: password changing "
371 "protocol version %d", data->passwd_change_version);
372 pos = strchr(pos, ' ');
377 if (pos && strncmp(pos, "M=", 2) == 0) {
381 wpa_msg(sm->msg_ctx, MSG_WARNING,
382 "EAP-MSCHAPV2: failure message: '%s' (retry %sallowed, error "
384 msg, retry == 1 ? "" : "not ", data->prev_error);
385 if (retry == 1 && config) {
386 /* TODO: could prevent the current password from being used
387 * again at least for some period of time */
388 eap_sm_request_identity(sm, config);
389 eap_sm_request_password(sm, config);
391 /* TODO: prevent retries using same username/password */
398 static u8 * eap_mschapv2_failure(struct eap_sm *sm,
399 struct eap_mschapv2_data *data,
400 struct eap_method_ret *ret,
401 struct eap_mschapv2_hdr *req,
404 struct eap_mschapv2_hdr *resp;
405 u8 *msdata = (u8 *) (req + 1);
407 int len = be_to_host16(req->length) - sizeof(*req);
410 wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: Received failure");
411 wpa_hexdump_ascii(MSG_DEBUG, "EAP-MSCHAPV2: Failure data",
413 buf = malloc(len + 1);
415 memcpy(buf, msdata, len);
417 retry = eap_mschapv2_failure_txt(sm, data, buf);
422 ret->methodState = METHOD_DONE;
423 ret->decision = DECISION_FAIL;
424 ret->allowNotifications = FALSE;
427 /* TODO: could try to retry authentication, e.g, after having
428 * changed the username/password. In this case, EAP MS-CHAP-v2
429 * Failure Response would not be sent here. */
438 resp->code = EAP_CODE_RESPONSE;
439 resp->identifier = req->identifier;
440 resp->length = host_to_be16(6);
441 resp->type = EAP_TYPE_MSCHAPV2;
442 resp->op_code = MSCHAPV2_OP_FAILURE;
448 static u8 * eap_mschapv2_process(struct eap_sm *sm, void *priv,
449 struct eap_method_ret *ret,
450 u8 *reqData, size_t reqDataLen,
453 struct eap_mschapv2_data *data = priv;
454 struct wpa_ssid *config = eap_get_config(sm);
455 struct eap_mschapv2_hdr *req;
458 if (config == NULL || config->identity == NULL) {
459 wpa_printf(MSG_INFO, "EAP-MSCHAPV2: Identity not configured");
460 eap_sm_request_identity(sm, config);
465 if (config->password == NULL) {
466 wpa_printf(MSG_INFO, "EAP-MSCHAPV2: Password not configured");
467 eap_sm_request_password(sm, config);
472 req = (struct eap_mschapv2_hdr *) reqData;
473 len = be_to_host16(req->length);
474 if (len < sizeof(*req) + 2 || req->type != EAP_TYPE_MSCHAPV2 ||
476 wpa_printf(MSG_INFO, "EAP-MSCHAPV2: Invalid frame");
481 ms_len = ((int) req->ms_length[0] << 8) | req->ms_length[1];
482 if (ms_len != len - 5) {
483 wpa_printf(MSG_INFO, "EAP-MSCHAPV2: Invalid header: len=%d "
484 "ms_len=%d", len, ms_len);
485 if (sm->workaround) {
486 /* Some authentication servers use invalid ms_len,
487 * ignore it for interoperability. */
488 wpa_printf(MSG_INFO, "EAP-MSCHAPV2: workaround, ignore"
496 switch (req->op_code) {
497 case MSCHAPV2_OP_CHALLENGE:
498 return eap_mschapv2_challenge(sm, data, ret, req, respDataLen);
499 case MSCHAPV2_OP_SUCCESS:
500 return eap_mschapv2_success(sm, data, ret, req, respDataLen);
501 case MSCHAPV2_OP_FAILURE:
502 return eap_mschapv2_failure(sm, data, ret, req, respDataLen);
504 wpa_printf(MSG_INFO, "EAP-MSCHAPV2: Unknown op %d - ignored",
512 static Boolean eap_mschapv2_isKeyAvailable(struct eap_sm *sm, void *priv)
514 struct eap_mschapv2_data *data = priv;
515 return data->success && data->master_key_valid;
519 static u8 * eap_mschapv2_getKey(struct eap_sm *sm, void *priv, size_t *len)
521 struct eap_mschapv2_data *data = priv;
525 if (!data->master_key_valid || !data->success)
528 if (data->peer_challenge) {
529 /* EAP-FAST needs both send and receive keys */
530 key_len = 2 * MSCHAPV2_KEY_LEN;
532 key_len = MSCHAPV2_KEY_LEN;
535 key = malloc(key_len);
539 if (data->peer_challenge) {
540 get_asymetric_start_key(data->master_key, key,
541 MSCHAPV2_KEY_LEN, 0, 0);
542 get_asymetric_start_key(data->master_key,
543 key + MSCHAPV2_KEY_LEN,
544 MSCHAPV2_KEY_LEN, 1, 0);
546 get_asymetric_start_key(data->master_key, key,
547 MSCHAPV2_KEY_LEN, 1, 0);
550 wpa_hexdump_key(MSG_DEBUG, "EAP-MSCHAPV2: Derived key",
558 const struct eap_method eap_method_mschapv2 =
560 .method = EAP_TYPE_MSCHAPV2,
562 .init = eap_mschapv2_init,
563 .deinit = eap_mschapv2_deinit,
564 .process = eap_mschapv2_process,
565 .isKeyAvailable = eap_mschapv2_isKeyAvailable,
566 .getKey = eap_mschapv2_getKey,