diff options
Diffstat (limited to 'contrib/wpa_supplicant/eap_mschapv2.c')
-rw-r--r-- | contrib/wpa_supplicant/eap_mschapv2.c | 796 |
1 files changed, 489 insertions, 307 deletions
diff --git a/contrib/wpa_supplicant/eap_mschapv2.c b/contrib/wpa_supplicant/eap_mschapv2.c index 9fcd480..26c8351 100644 --- a/contrib/wpa_supplicant/eap_mschapv2.c +++ b/contrib/wpa_supplicant/eap_mschapv2.c @@ -1,6 +1,6 @@ /* - * WPA Supplicant / EAP-MSCHAPV2 (draft-kamath-pppext-eap-mschapv2-00.txt) - * Copyright (c) 2004-2005, Jouni Malinen <jkmaline@cc.hut.fi> + * EAP peer method: EAP-MSCHAPV2 (draft-kamath-pppext-eap-mschapv2-00.txt) + * Copyright (c) 2004-2006, Jouni Malinen <j@w1.fi> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -10,30 +10,60 @@ * license. * * See README and COPYING for more details. + * + * This file implements EAP peer part of EAP-MSCHAPV2 method (EAP type 26). + * draft-kamath-pppext-eap-mschapv2-00.txt defines the Microsoft EAP CHAP + * Extensions Protocol, Version 2, for mutual authentication and key + * derivation. This encapsulates MS-CHAP-v2 protocol which is defined in + * RFC 2759. Use of EAP-MSCHAPV2 derived keys with MPPE cipher is described in + * RFC 3079. */ -#include <stdlib.h> -#include <stdio.h> -#include <string.h> +#include "includes.h" #include "common.h" #include "eap_i.h" -#include "wpa_supplicant.h" #include "config_ssid.h" #include "ms_funcs.h" #include "wpa_ctrl.h" +#define MSCHAPV2_CHAL_LEN 16 +#define MSCHAPV2_NT_RESPONSE_LEN 24 + +#ifdef _MSC_VER +#pragma pack(push, 1) +#endif /* _MSC_VER */ + struct eap_mschapv2_hdr { - u8 code; - u8 identifier; - u16 length; /* including code, identifier, and length */ - u8 type; /* EAP_TYPE_MSCHAPV2 */ u8 op_code; /* MSCHAPV2_OP_* */ - u8 mschapv2_id; /* usually same as identifier */ + u8 mschapv2_id; /* usually same as EAP identifier; must be changed + * for challenges, but not for success/failure */ u8 ms_length[2]; /* Note: misaligned; length - 5 */ /* followed by data */ -} __attribute__ ((packed)); +} STRUCT_PACKED; + +/* Response Data field */ +struct ms_response { + u8 peer_challenge[MSCHAPV2_CHAL_LEN]; + u8 reserved[8]; + u8 nt_response[MSCHAPV2_NT_RESPONSE_LEN]; + u8 flags; +} STRUCT_PACKED; + +/* Change-Password Data field */ +struct ms_change_password { + u8 encr_password[516]; + u8 encr_hash[16]; + u8 peer_challenge[MSCHAPV2_CHAL_LEN]; + u8 reserved[8]; + u8 nt_response[MSCHAPV2_NT_RESPONSE_LEN]; + u8 flags[2]; +} STRUCT_PACKED; + +#ifdef _MSC_VER +#pragma pack(pop) +#endif /* _MSC_VER */ #define MSCHAPV2_OP_CHALLENGE 1 #define MSCHAPV2_OP_RESPONSE 2 @@ -41,8 +71,6 @@ struct eap_mschapv2_hdr { #define MSCHAPV2_OP_FAILURE 4 #define MSCHAPV2_OP_CHANGE_PASSWORD 7 -#define MSCHAPV2_RESP_LEN 49 - #define ERROR_RESTRICTED_LOGON_HOURS 646 #define ERROR_ACCT_DISABLED 647 #define ERROR_PASSWD_EXPIRED 648 @@ -67,6 +95,7 @@ struct eap_mschapv2_data { */ u8 *peer_challenge; u8 *auth_challenge; + int full_key; int phase2; u8 master_key[16]; @@ -84,27 +113,31 @@ static void eap_mschapv2_deinit(struct eap_sm *sm, void *priv); static void * eap_mschapv2_init(struct eap_sm *sm) { struct eap_mschapv2_data *data; - data = malloc(sizeof(*data)); + data = os_zalloc(sizeof(*data)); if (data == NULL) return NULL; - memset(data, 0, sizeof(*data)); + + data->full_key = sm->mschapv2_full_key; if (sm->peer_challenge) { - data->peer_challenge = malloc(16); + data->full_key = 1; + data->peer_challenge = os_malloc(MSCHAPV2_CHAL_LEN); if (data->peer_challenge == NULL) { eap_mschapv2_deinit(sm, data); return NULL; } - memcpy(data->peer_challenge, sm->peer_challenge, 16); + os_memcpy(data->peer_challenge, sm->peer_challenge, + MSCHAPV2_CHAL_LEN); } if (sm->auth_challenge) { - data->auth_challenge = malloc(16); + data->auth_challenge = os_malloc(MSCHAPV2_CHAL_LEN); if (data->auth_challenge == NULL) { eap_mschapv2_deinit(sm, data); return NULL; } - memcpy(data->auth_challenge, sm->auth_challenge, 16); + os_memcpy(data->auth_challenge, sm->auth_challenge, + MSCHAPV2_CHAL_LEN); } data->phase2 = sm->init_phase2; @@ -116,54 +149,194 @@ static void * eap_mschapv2_init(struct eap_sm *sm) static void eap_mschapv2_deinit(struct eap_sm *sm, void *priv) { struct eap_mschapv2_data *data = priv; - free(data->peer_challenge); - free(data->auth_challenge); - free(data->prev_challenge); - free(data); + os_free(data->peer_challenge); + os_free(data->auth_challenge); + os_free(data->prev_challenge); + os_free(data); +} + + +static const u8 * eap_mschapv2_remove_domain(const u8 *username, size_t *len) +{ + size_t i; + + /* + * MSCHAPv2 does not include optional domain name in the + * challenge-response calculation, so remove domain prefix + * (if present). + */ + + for (i = 0; i < *len; i++) { + if (username[i] == '\\') { + *len -= i + 1; + return username + i + 1; + } + } + + return username; +} + + +static void eap_mschapv2_derive_response( + struct eap_mschapv2_data *data, + const u8 *username, size_t username_len, + const u8 *password, size_t password_len, + const u8 *auth_challenge, const u8 *peer_challenge, + u8 *nt_response) +{ + u8 password_hash[16], password_hash_hash[16]; + + wpa_hexdump(MSG_DEBUG, "EAP-MSCHAPV2: auth_challenge", + auth_challenge, MSCHAPV2_CHAL_LEN); + wpa_hexdump(MSG_DEBUG, "EAP-MSCHAPV2: peer_challenge", + peer_challenge, MSCHAPV2_CHAL_LEN); + wpa_hexdump_ascii(MSG_DEBUG, "EAP-MSCHAPV2: username", + username, username_len); + wpa_hexdump_ascii_key(MSG_DEBUG, "EAP-MSCHAPV2: password", + password, password_len); + generate_nt_response(auth_challenge, peer_challenge, + username, username_len, + password, password_len, nt_response); + wpa_hexdump(MSG_DEBUG, "EAP-MSCHAPV2: response", nt_response, + MSCHAPV2_NT_RESPONSE_LEN); + /* Authenticator response is not really needed yet, but calculate it + * here so that challenges need not be saved. */ + generate_authenticator_response(password, password_len, + peer_challenge, auth_challenge, + username, username_len, nt_response, + data->auth_response); + data->auth_response_valid = 1; + + /* Likewise, generate master_key here since we have the needed data + * available. */ + nt_password_hash(password, password_len, password_hash); + hash_nt_password_hash(password_hash, password_hash_hash); + get_master_key(password_hash_hash, nt_response, data->master_key); + data->master_key_valid = 1; +} + + +static u8 * eap_mschapv2_challenge_reply(struct eap_sm *sm, + struct eap_mschapv2_data *data, u8 id, + u8 mschapv2_id, + const u8 *auth_challenge, + size_t *respDataLen) +{ + struct eap_hdr *resp; + struct eap_mschapv2_hdr *ms; + u8 *rpos, *peer_challenge; + int ms_len; + struct ms_response *r; + size_t username_len, identity_len, password_len; + const u8 *username, *identity, *password; + + wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: Generating Challenge Response"); + + identity = eap_get_config_identity(sm, &identity_len); + password = eap_get_config_password(sm, &password_len); + if (identity == NULL || password == NULL) + return NULL; + + username_len = identity_len; + username = eap_mschapv2_remove_domain(identity, &username_len); + + ms_len = sizeof(*ms) + 1 + sizeof(*r) + identity_len; + resp = eap_msg_alloc(EAP_VENDOR_IETF, EAP_TYPE_MSCHAPV2, respDataLen, + ms_len, EAP_CODE_RESPONSE, id, &rpos); + if (resp == NULL) + return NULL; + + ms = (struct eap_mschapv2_hdr *) rpos; + ms->op_code = MSCHAPV2_OP_RESPONSE; + ms->mschapv2_id = mschapv2_id; + if (data->prev_error) { + /* + * TODO: this does not seem to be enough when processing two + * or more failure messages. IAS did not increment mschapv2_id + * in its own packets, but it seemed to expect the peer to + * increment this for all packets(?). + */ + ms->mschapv2_id++; + } + WPA_PUT_BE16(ms->ms_length, ms_len); + rpos = (u8 *) (ms + 1); + *rpos++ = sizeof(*r); /* Value-Size */ + + /* Response */ + r = (struct ms_response *) rpos; + peer_challenge = r->peer_challenge; + if (data->peer_challenge) { + wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: peer_challenge generated " + "in Phase 1"); + peer_challenge = data->peer_challenge; + os_memset(r->peer_challenge, 0, MSCHAPV2_CHAL_LEN); + } else if (os_get_random(peer_challenge, MSCHAPV2_CHAL_LEN)) { + os_free(resp); + return NULL; + } + os_memset(r->reserved, 0, 8); + if (data->auth_challenge) { + wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: auth_challenge generated " + "in Phase 1"); + auth_challenge = data->auth_challenge; + } + eap_mschapv2_derive_response(data, username, username_len, + password, password_len, + auth_challenge, peer_challenge, + r->nt_response); + + r->flags = 0; /* reserved, must be zero */ + + os_memcpy((u8 *) (r + 1), identity, identity_len); + wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: TX identifier %d mschapv2_id %d " + "(response)", resp->identifier, ms->mschapv2_id); + return (u8 *) resp; } +/** + * eap_mschapv2_process - Process an EAP-MSCHAPv2 challenge message + * @sm: Pointer to EAP state machine allocated with eap_sm_init() + * @data: Pointer to private EAP method data from eap_mschapv2_init() + * @ret: Return values from EAP request validation and processing + * @req: Pointer to EAP-MSCHAPv2 header from the request + * @req_len: Length of the EAP-MSCHAPv2 data + * @id: EAP identifier used in th erequest + * @respDataLen: Length of the returned EAP response + * Returns: Pointer to allocated EAP response packet (eapRespData) or %NULL if + * no reply available + */ static u8 * eap_mschapv2_challenge(struct eap_sm *sm, struct eap_mschapv2_data *data, struct eap_method_ret *ret, const struct eap_mschapv2_hdr *req, - size_t *respDataLen) + size_t req_len, u8 id, size_t *respDataLen) { - struct wpa_ssid *config = eap_get_config(sm); - u8 *challenge, *peer_challenge, *username, *pos; - int i, ms_len; - size_t len, challenge_len, username_len; - struct eap_mschapv2_hdr *resp; - u8 password_hash[16], password_hash_hash[16]; + size_t len, challenge_len; + const u8 *pos, *challenge; - if (config == NULL) + if (eap_get_config_identity(sm, &len) == NULL || + eap_get_config_password(sm, &len) == NULL) return NULL; - /* MSCHAPv2 does not include optional domain name in the - * challenge-response calculation, so remove domain prefix - * (if present). */ - username = config->identity; - username_len = config->identity_len; - for (i = 0; i < username_len; i++) { - if (username[i] == '\\') { - username_len -= i + 1; - username += i + 1; - break; - } - } - wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: Received challenge"); - len = be_to_host16(req->length); - pos = (u8 *) (req + 1); + if (req_len < sizeof(*req) + 1) { + wpa_printf(MSG_INFO, "EAP-MSCHAPV2: Too short challenge data " + "(len %lu)", (unsigned long) req_len); + ret->ignore = TRUE; + return NULL; + } + pos = (const u8 *) (req + 1); challenge_len = *pos++; - if (challenge_len != 16) { + len = req_len - sizeof(*req) - 1; + if (challenge_len != MSCHAPV2_CHAL_LEN) { wpa_printf(MSG_INFO, "EAP-MSCHAPV2: Invalid challenge length " "%lu", (unsigned long) challenge_len); ret->ignore = TRUE; return NULL; } - if (len < 10 || len - 10 < challenge_len) { + if (len < challenge_len) { wpa_printf(MSG_INFO, "EAP-MSCHAPV2: Too short challenge" " packet: len=%lu challenge_len=%lu", (unsigned long) len, (unsigned long) challenge_len); @@ -178,116 +351,70 @@ static u8 * eap_mschapv2_challenge(struct eap_sm *sm, } else challenge = pos; pos += challenge_len; + len -= challenge_len; wpa_hexdump_ascii(MSG_DEBUG, "EAP-MSCHAPV2: Authentication Servername", - pos, len - challenge_len - 10); + pos, len); ret->ignore = FALSE; ret->methodState = METHOD_MAY_CONT; ret->decision = DECISION_FAIL; ret->allowNotifications = TRUE; - wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: Generating Challenge Response"); + return eap_mschapv2_challenge_reply(sm, data, id, req->mschapv2_id, + challenge, respDataLen); +} - *respDataLen = sizeof(*resp) + 1 + MSCHAPV2_RESP_LEN + - config->identity_len; - resp = malloc(*respDataLen); - if (resp == NULL) - return NULL; - memset(resp, 0, *respDataLen); - resp->code = EAP_CODE_RESPONSE; - resp->identifier = req->identifier; - resp->length = host_to_be16(*respDataLen); - resp->type = EAP_TYPE_MSCHAPV2; - resp->op_code = MSCHAPV2_OP_RESPONSE; - resp->mschapv2_id = req->mschapv2_id; - if (data->prev_error) { - /* - * TODO: this does not seem to be enough when processing two - * or more failure messages. IAS did not increment mschapv2_id - * in its own packets, but it seemed to expect the peer to - * increment this for all packets(?). - */ - resp->mschapv2_id++; - } - ms_len = *respDataLen - 5; - WPA_PUT_BE16(resp->ms_length, ms_len); - pos = (u8 *) (resp + 1); - *pos++ = MSCHAPV2_RESP_LEN; /* Value-Size */ - /* Response */ - peer_challenge = pos; - if (data->peer_challenge) { - wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: peer_challenge generated " - "in Phase 1"); - peer_challenge = data->peer_challenge; - } else if (hostapd_get_rand(peer_challenge, 16)) { - free(resp); - return NULL; - } - pos += 16; - pos += 8; /* Reserved, must be zero */ - if (data->auth_challenge) { - wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: auth_challenge generated " - "in Phase 1"); - challenge = data->auth_challenge; +static void eap_mschapv2_password_changed(struct eap_sm *sm, + struct eap_mschapv2_data *data) +{ + struct wpa_ssid *config = eap_get_config(sm); + if (config && config->new_password) { + wpa_msg(sm->msg_ctx, MSG_INFO, + WPA_EVENT_PASSWORD_CHANGED + "EAP-MSCHAPV2: Password changed successfully"); + data->prev_error = 0; + os_free(config->password); + config->password = config->new_password; + config->new_password = NULL; + config->password_len = config->new_password_len; + config->new_password_len = 0; } - wpa_hexdump(MSG_DEBUG, "EAP-MSCHAPV2: auth_challenge", challenge, 16); - wpa_hexdump(MSG_DEBUG, "EAP-MSCHAPV2: peer_challenge", - peer_challenge, 16); - wpa_hexdump_ascii(MSG_DEBUG, "EAP-MSCHAPV2: username", - username, username_len); - wpa_hexdump_ascii_key(MSG_DEBUG, "EAP-MSCHAPV2: password", - config->password, config->password_len); - generate_nt_response(challenge, peer_challenge, - username, username_len, - config->password, config->password_len, - pos); - wpa_hexdump(MSG_DEBUG, "EAP-MSCHAPV2: response", pos, 24); - /* Authenticator response is not really needed yet, but calculate it - * here so that challenges need not be saved. */ - generate_authenticator_response(config->password, config->password_len, - peer_challenge, challenge, - username, username_len, pos, - data->auth_response); - data->auth_response_valid = 1; - - /* Likewise, generate master_key here since we have the needed data - * available. */ - nt_password_hash(config->password, config->password_len, - password_hash); - hash_nt_password_hash(password_hash, password_hash_hash); - get_master_key(password_hash_hash, pos /* nt_response */, - data->master_key); - data->master_key_valid = 1; - - pos += 24; - pos++; /* Flag / reserved, must be zero */ - - memcpy(pos, config->identity, config->identity_len); - wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: TX identifier %d mschapv2_id %d " - "(response)", resp->identifier, resp->mschapv2_id); - return (u8 *) resp; } +/** + * eap_mschapv2_process - Process an EAP-MSCHAPv2 success message + * @sm: Pointer to EAP state machine allocated with eap_sm_init() + * @data: Pointer to private EAP method data from eap_mschapv2_init() + * @ret: Return values from EAP request validation and processing + * @req: Pointer to EAP-MSCHAPv2 header from the request + * @req_len: Length of the EAP-MSCHAPv2 data + * @id: EAP identifier used in th erequest + * @respDataLen: Length of the returned EAP response + * Returns: Pointer to allocated EAP response packet (eapRespData) or %NULL if + * no reply available + */ static u8 * eap_mschapv2_success(struct eap_sm *sm, struct eap_mschapv2_data *data, struct eap_method_ret *ret, const struct eap_mschapv2_hdr *req, - size_t *respDataLen) + size_t req_len, u8 id, size_t *respDataLen) { - struct eap_mschapv2_hdr *resp; + struct eap_hdr *resp; + struct eap_mschapv2_hdr *ms; const u8 *pos; u8 recv_response[20]; - int len, left; + size_t len; + u8 *rpos; wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: Received success"); - len = be_to_host16(req->length); + len = req_len - sizeof(*req); pos = (const u8 *) (req + 1); - if (!data->auth_response_valid || len < sizeof(*req) + 42 || + if (!data->auth_response_valid || len < 42 || pos[0] != 'S' || pos[1] != '=' || hexstr2bin((char *) (pos + 2), recv_response, 20) || - memcmp(data->auth_response, recv_response, 20) != 0) { + os_memcmp(data->auth_response, recv_response, 20) != 0) { wpa_printf(MSG_WARNING, "EAP-MSCHAPV2: Invalid authenticator " "response in success request"); ret->methodState = METHOD_DONE; @@ -295,16 +422,19 @@ static u8 * eap_mschapv2_success(struct eap_sm *sm, return NULL; } pos += 42; - left = len - sizeof(*req) - 42; - while (left > 0 && *pos == ' ') { + len -= 42; + while (len > 0 && *pos == ' ') { pos++; - left--; + len--; } wpa_hexdump_ascii(MSG_DEBUG, "EAP-MSCHAPV2: Success message", - pos, left); + pos, len); wpa_printf(MSG_INFO, "EAP-MSCHAPV2: Authentication succeeded"); - *respDataLen = 6; - resp = malloc(6); + + /* Note: Only op_code of the EAP-MSCHAPV2 header is included in success + * message. */ + resp = eap_msg_alloc(EAP_VENDOR_IETF, EAP_TYPE_MSCHAPV2, respDataLen, + 1, EAP_CODE_RESPONSE, id, &rpos); if (resp == NULL) { wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: Failed to allocate " "buffer for success response"); @@ -312,31 +442,16 @@ static u8 * eap_mschapv2_success(struct eap_sm *sm, return NULL; } - resp->code = EAP_CODE_RESPONSE; - resp->identifier = req->identifier; - resp->length = host_to_be16(6); - resp->type = EAP_TYPE_MSCHAPV2; - resp->op_code = MSCHAPV2_OP_SUCCESS; + ms = (struct eap_mschapv2_hdr *) rpos; + ms->op_code = MSCHAPV2_OP_SUCCESS; ret->methodState = METHOD_DONE; ret->decision = DECISION_UNCOND_SUCC; ret->allowNotifications = FALSE; data->success = 1; - if (data->prev_error == ERROR_PASSWD_EXPIRED) { - struct wpa_ssid *config = eap_get_config(sm); - if (config && config->new_password) { - wpa_msg(sm->msg_ctx, MSG_INFO, - WPA_EVENT_PASSWORD_CHANGED - "EAP-MSCHAPV2: Password changed successfully"); - data->prev_error = 0; - free(config->password); - config->password = config->new_password; - config->new_password = NULL; - config->password_len = config->new_password_len; - config->new_password_len = 0; - } - } + if (data->prev_error == ERROR_PASSWD_EXPIRED) + eap_mschapv2_password_changed(sm, data); return (u8 *) resp; } @@ -355,30 +470,30 @@ static int eap_mschapv2_failure_txt(struct eap_sm *sm, pos = txt; - if (pos && strncmp(pos, "E=", 2) == 0) { + if (pos && os_strncmp(pos, "E=", 2) == 0) { pos += 2; data->prev_error = atoi(pos); wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: error %d", data->prev_error); - pos = strchr(pos, ' '); + pos = os_strchr(pos, ' '); if (pos) pos++; } - if (pos && strncmp(pos, "R=", 2) == 0) { + if (pos && os_strncmp(pos, "R=", 2) == 0) { pos += 2; retry = atoi(pos); wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: retry is %sallowed", retry == 1 ? "" : "not "); - pos = strchr(pos, ' '); + pos = os_strchr(pos, ' '); if (pos) pos++; } - if (pos && strncmp(pos, "C=", 2) == 0) { + if (pos && os_strncmp(pos, "C=", 2) == 0) { int hex_len; pos += 2; - hex_len = strchr(pos, ' ') - (char *) pos; + hex_len = os_strchr(pos, ' ') - (char *) pos; if (hex_len == PASSWD_CHANGE_CHAL_LEN * 2) { if (hexstr2bin(pos, data->passwd_change_challenge, PASSWD_CHANGE_CHAL_LEN)) { @@ -395,7 +510,7 @@ static int eap_mschapv2_failure_txt(struct eap_sm *sm, wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: invalid failure " "challenge len %d", hex_len); } - pos = strchr(pos, ' '); + pos = os_strchr(pos, ' '); if (pos) pos++; } else { @@ -403,17 +518,17 @@ static int eap_mschapv2_failure_txt(struct eap_sm *sm, "was not present in failure message"); } - if (pos && strncmp(pos, "V=", 2) == 0) { + if (pos && os_strncmp(pos, "V=", 2) == 0) { pos += 2; data->passwd_change_version = atoi(pos); wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: password changing " "protocol version %d", data->passwd_change_version); - pos = strchr(pos, ' '); + pos = os_strchr(pos, ' '); if (pos) pos++; } - if (pos && strncmp(pos, "M=", 2) == 0) { + if (pos && os_strncmp(pos, "M=", 2) == 0) { pos += 2; msg = pos; } @@ -427,14 +542,14 @@ static int eap_mschapv2_failure_txt(struct eap_sm *sm, wpa_msg(sm->msg_ctx, MSG_INFO, "EAP-MSCHAPV2: Password expired - password " "change required"); - eap_sm_request_new_password(sm, config); + eap_sm_request_new_password(sm); } } else if (retry == 1 && config) { /* TODO: could prevent the current password from being used * again at least for some period of time */ if (!config->mschapv2_retry) - eap_sm_request_identity(sm, config); - eap_sm_request_password(sm, config); + eap_sm_request_identity(sm); + eap_sm_request_password(sm); config->mschapv2_retry = 1; } else if (config) { /* TODO: prevent retries using same username/password */ @@ -449,137 +564,135 @@ static u8 * eap_mschapv2_change_password(struct eap_sm *sm, struct eap_mschapv2_data *data, struct eap_method_ret *ret, const struct eap_mschapv2_hdr *req, - size_t *respDataLen) + u8 id, size_t *respDataLen) { - struct eap_mschapv2_hdr *resp; - int ms_len, i; - u8 *peer_challenge, *username, *pos; - size_t username_len; - struct wpa_ssid *config = eap_get_config(sm); - - if (config == NULL || config->identity == NULL || - config->new_password == NULL || config->password == NULL) + struct eap_hdr *resp; + int ms_len; + const u8 *username, *password, *new_password; + u8 *pos; + size_t username_len, password_len, new_password_len; + struct eap_mschapv2_hdr *ms; + struct ms_change_password *cp; + + username = eap_get_config_identity(sm, &username_len); + password = eap_get_config_password(sm, &password_len); + new_password = eap_get_config_new_password(sm, &new_password_len); + if (username == NULL || password == NULL || new_password == NULL) return NULL; - /* - * MSCHAPv2 does not include optional domain name in the - * challenge-response calculation, so remove domain prefix - * (if present). - */ - username = config->identity; - username_len = config->identity_len; - for (i = 0; i < username_len; i++) { - if (username[i] == '\\') { - username_len -= i + 1; - username += i + 1; - break; - } - } + username = eap_mschapv2_remove_domain(username, &username_len); ret->ignore = FALSE; ret->methodState = METHOD_MAY_CONT; ret->decision = DECISION_COND_SUCC; ret->allowNotifications = TRUE; - *respDataLen = 591; - resp = malloc(*respDataLen); - if (resp == NULL) { + ms_len = sizeof(*ms) + sizeof(*cp); + resp = eap_msg_alloc(EAP_VENDOR_IETF, EAP_TYPE_MSCHAPV2, respDataLen, + ms_len, EAP_CODE_RESPONSE, id, &pos); + if (resp == NULL) return NULL; - } - resp->code = EAP_CODE_RESPONSE; - resp->identifier = req->identifier; - resp->length = host_to_be16((u16) *respDataLen); - resp->type = EAP_TYPE_MSCHAPV2; - resp->op_code = MSCHAPV2_OP_CHANGE_PASSWORD; - resp->mschapv2_id = req->mschapv2_id + 1; - ms_len = *respDataLen - 5; - WPA_PUT_BE16(resp->ms_length, ms_len); - pos = (u8 *) (resp + 1); + ms = (struct eap_mschapv2_hdr *) pos; + ms->op_code = MSCHAPV2_OP_CHANGE_PASSWORD; + ms->mschapv2_id = req->mschapv2_id + 1; + WPA_PUT_BE16(ms->ms_length, ms_len); + cp = (struct ms_change_password *) (ms + 1); /* Encrypted-Password */ new_password_encrypted_with_old_nt_password_hash( - config->new_password, config->new_password_len, - config->password, config->password_len, pos); - pos += 516; + new_password, new_password_len, + password, password_len, cp->encr_password); /* Encrypted-Hash */ old_nt_password_hash_encrypted_with_new_nt_password_hash( - config->new_password, config->new_password_len, - config->password, config->password_len, pos); - pos += 16; + new_password, new_password_len, + password, password_len, cp->encr_hash); /* Peer-Challenge */ - peer_challenge = pos; - if (hostapd_get_rand(peer_challenge, 16)) { - free(resp); + if (os_get_random(cp->peer_challenge, MSCHAPV2_CHAL_LEN)) { + os_free(resp); return NULL; } - pos += 16; /* Reserved, must be zero */ - memset(pos, 0, 8); - pos += 8; + os_memset(cp->reserved, 0, 8); /* NT-Response */ wpa_hexdump(MSG_DEBUG, "EAP-MSCHAPV2: auth_challenge", data->passwd_change_challenge, PASSWD_CHANGE_CHAL_LEN); wpa_hexdump(MSG_DEBUG, "EAP-MSCHAPV2: peer_challenge", - peer_challenge, 16); + cp->peer_challenge, MSCHAPV2_CHAL_LEN); wpa_hexdump_ascii(MSG_DEBUG, "EAP-MSCHAPV2: username", username, username_len); wpa_hexdump_ascii_key(MSG_DEBUG, "EAP-MSCHAPV2: new password", - config->new_password, config->new_password_len); - generate_nt_response(data->passwd_change_challenge, peer_challenge, + new_password, new_password_len); + generate_nt_response(data->passwd_change_challenge, cp->peer_challenge, username, username_len, - config->new_password, config->new_password_len, - pos); - wpa_hexdump(MSG_DEBUG, "EAP-MSCHAPV2: NT-Response", pos, 24); + new_password, new_password_len, + cp->nt_response); + wpa_hexdump(MSG_DEBUG, "EAP-MSCHAPV2: NT-Response", + cp->nt_response, MSCHAPV2_NT_RESPONSE_LEN); /* Authenticator response is not really needed yet, but calculate it * here so that challenges need not be saved. */ - generate_authenticator_response(config->new_password, - config->new_password_len, - peer_challenge, + generate_authenticator_response(new_password, new_password_len, + cp->peer_challenge, data->passwd_change_challenge, - username, username_len, pos, - data->auth_response); + username, username_len, + cp->nt_response, data->auth_response); data->auth_response_valid = 1; - pos += 24; - /* Flags */ - *pos++ = 0; - *pos++ = 0; + os_memset(cp->flags, 0, 2); wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: TX identifier %d mschapv2_id %d " - "(change pw)", resp->identifier, resp->mschapv2_id); + "(change pw)", resp->identifier, ms->mschapv2_id); return (u8 *) resp; } +/** + * eap_mschapv2_process - Process an EAP-MSCHAPv2 failure message + * @sm: Pointer to EAP state machine allocated with eap_sm_init() + * @data: Pointer to private EAP method data from eap_mschapv2_init() + * @ret: Return values from EAP request validation and processing + * @req: Pointer to EAP-MSCHAPv2 header from the request + * @req_len: Length of the EAP-MSCHAPv2 data + * @id: EAP identifier used in th erequest + * @respDataLen: Length of the returned EAP response + * Returns: Pointer to allocated EAP response packet (eapRespData) or %NULL if + * no reply available + */ static u8 * eap_mschapv2_failure(struct eap_sm *sm, struct eap_mschapv2_data *data, struct eap_method_ret *ret, const struct eap_mschapv2_hdr *req, - size_t *respDataLen) + size_t req_len, u8 id, size_t *respDataLen) { - struct eap_mschapv2_hdr *resp; + struct eap_hdr *resp; const u8 *msdata = (const u8 *) (req + 1); char *buf; - int len = be_to_host16(req->length) - sizeof(*req); + size_t len = req_len - sizeof(*req); int retry = 0; + struct eap_mschapv2_hdr *ms; + u8 *pos; wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: Received failure"); wpa_hexdump_ascii(MSG_DEBUG, "EAP-MSCHAPV2: Failure data", msdata, len); - buf = malloc(len + 1); + /* + * eap_mschapv2_failure_txt() expects a nul terminated string, so we + * must allocate a large enough temporary buffer to create that since + * the received message does not include nul termination. + */ + buf = os_malloc(len + 1); if (buf) { - memcpy(buf, msdata, len); + os_memcpy(buf, msdata, len); buf[len] = '\0'; retry = eap_mschapv2_failure_txt(sm, data, buf); - free(buf); + os_free(buf); } ret->ignore = FALSE; @@ -592,7 +705,7 @@ static u8 * eap_mschapv2_failure(struct eap_sm *sm, struct wpa_ssid *config = eap_get_config(sm); if (config && config->new_password) return eap_mschapv2_change_password(sm, data, ret, req, - respDataLen); + id, respDataLen); if (config && config->pending_req_new_password) return NULL; } else if (retry && data->prev_error == ERROR_AUTHENTICATION_FAILURE) { @@ -602,22 +715,91 @@ static u8 * eap_mschapv2_failure(struct eap_sm *sm, return NULL; } - *respDataLen = 6; - resp = malloc(6); - if (resp == NULL) { + /* Note: Only op_code of the EAP-MSCHAPV2 header is included in failure + * message. */ + resp = eap_msg_alloc(EAP_VENDOR_IETF, EAP_TYPE_MSCHAPV2, respDataLen, + 1, EAP_CODE_RESPONSE, id, &pos); + if (resp == NULL) return NULL; - } - resp->code = EAP_CODE_RESPONSE; - resp->identifier = req->identifier; - resp->length = host_to_be16(6); - resp->type = EAP_TYPE_MSCHAPV2; - resp->op_code = MSCHAPV2_OP_FAILURE; + ms = (struct eap_mschapv2_hdr *) pos; + ms->op_code = MSCHAPV2_OP_FAILURE; return (u8 *) resp; } +static int eap_mschapv2_check_config(struct eap_sm *sm) +{ + size_t len; + + if (eap_get_config_identity(sm, &len) == NULL) { + wpa_printf(MSG_INFO, "EAP-MSCHAPV2: Identity not configured"); + eap_sm_request_identity(sm); + return -1; + } + + if (eap_get_config_password(sm, &len) == NULL) { + wpa_printf(MSG_INFO, "EAP-MSCHAPV2: Password not configured"); + eap_sm_request_password(sm); + return -1; + } + + return 0; +} + + +static int eap_mschapv2_check_mslen(struct eap_sm *sm, size_t len, + const struct eap_mschapv2_hdr *ms) +{ + size_t ms_len = WPA_GET_BE16(ms->ms_length); + + if (ms_len == len) + return 0; + + wpa_printf(MSG_INFO, "EAP-MSCHAPV2: Invalid header: len=%lu " + "ms_len=%lu", (unsigned long) len, (unsigned long) ms_len); + if (sm->workaround) { + /* Some authentication servers use invalid ms_len, + * ignore it for interoperability. */ + wpa_printf(MSG_INFO, "EAP-MSCHAPV2: workaround, ignore" + " invalid ms_len %lu (len %lu)", + (unsigned long) ms_len, + (unsigned long) len); + return 0; + } + + return -1; +} + + +static void eap_mschapv2_copy_challenge(struct eap_mschapv2_data *data, + const u8 *reqData, size_t reqDataLen) +{ + /* + * Store a copy of the challenge message, so that it can be processed + * again in case retry is allowed after a possible failure. + */ + os_free(data->prev_challenge); + data->prev_challenge = os_malloc(reqDataLen); + if (data->prev_challenge) { + data->prev_challenge_len = reqDataLen; + os_memcpy(data->prev_challenge, reqData, reqDataLen); + } +} + + +/** + * eap_mschapv2_process - Process an EAP-MSCHAPv2 request + * @sm: Pointer to EAP state machine allocated with eap_sm_init() + * @priv: Pointer to private EAP method data from eap_mschapv2_init() + * @ret: Return values from EAP request validation and processing + * @reqData: EAP request to be processed (eapReqData) + * @reqDataLen: Length of the EAP request + * @respDataLen: Length of the returned EAP response + * Returns: Pointer to allocated EAP response packet (eapRespData) or %NULL if + * no reply available + */ static u8 * eap_mschapv2_process(struct eap_sm *sm, void *priv, struct eap_method_ret *ret, const u8 *reqData, size_t reqDataLen, @@ -625,21 +807,13 @@ static u8 * eap_mschapv2_process(struct eap_sm *sm, void *priv, { struct eap_mschapv2_data *data = priv; struct wpa_ssid *config = eap_get_config(sm); - const struct eap_mschapv2_hdr *req; - int ms_len, using_prev_challenge = 0; + const struct eap_hdr *req; + const struct eap_mschapv2_hdr *ms; + int using_prev_challenge = 0; const u8 *pos; size_t len; - if (config == NULL || config->identity == NULL) { - wpa_printf(MSG_INFO, "EAP-MSCHAPV2: Identity not configured"); - eap_sm_request_identity(sm, config); - ret->ignore = TRUE; - return NULL; - } - - if (config->password == NULL) { - wpa_printf(MSG_INFO, "EAP-MSCHAPV2: Password not configured"); - eap_sm_request_password(sm, config); + if (eap_mschapv2_check_config(sm)) { ret->ignore = TRUE; return NULL; } @@ -655,49 +829,38 @@ static u8 * eap_mschapv2_process(struct eap_sm *sm, void *priv, config->mschapv2_retry = 0; } - pos = eap_hdr_validate(EAP_TYPE_MSCHAPV2, reqData, reqDataLen, &len); - if (pos == NULL || len < 5) { + pos = eap_hdr_validate(EAP_VENDOR_IETF, EAP_TYPE_MSCHAPV2, + reqData, reqDataLen, &len); + if (pos == NULL || len < sizeof(*ms) + 1) { ret->ignore = TRUE; return NULL; } - req = (const struct eap_mschapv2_hdr *) reqData; - len = be_to_host16(req->length); - ms_len = WPA_GET_BE16(req->ms_length); - if (ms_len != len - 5) { - wpa_printf(MSG_INFO, "EAP-MSCHAPV2: Invalid header: len=%lu " - "ms_len=%d", (unsigned long) len, ms_len); - if (sm->workaround) { - /* Some authentication servers use invalid ms_len, - * ignore it for interoperability. */ - wpa_printf(MSG_INFO, "EAP-MSCHAPV2: workaround, ignore" - " invalid ms_len"); - } else { - ret->ignore = TRUE; - return NULL; - } + + ms = (const struct eap_mschapv2_hdr *) pos; + if (eap_mschapv2_check_mslen(sm, len, ms)) { + ret->ignore = TRUE; + return NULL; } + req = (const struct eap_hdr *) reqData; wpa_printf(MSG_DEBUG, "EAP-MSCHAPV2: RX identifier %d mschapv2_id %d", - req->identifier, req->mschapv2_id); + req->identifier, ms->mschapv2_id); - switch (req->op_code) { + switch (ms->op_code) { case MSCHAPV2_OP_CHALLENGE: - if (!using_prev_challenge) { - free(data->prev_challenge); - data->prev_challenge = malloc(len); - if (data->prev_challenge) { - data->prev_challenge_len = len; - memcpy(data->prev_challenge, reqData, len); - } - } - return eap_mschapv2_challenge(sm, data, ret, req, respDataLen); + if (!using_prev_challenge) + eap_mschapv2_copy_challenge(data, reqData, reqDataLen); + return eap_mschapv2_challenge(sm, data, ret, ms, len, + req->identifier, respDataLen); case MSCHAPV2_OP_SUCCESS: - return eap_mschapv2_success(sm, data, ret, req, respDataLen); + return eap_mschapv2_success(sm, data, ret, ms, len, + req->identifier, respDataLen); case MSCHAPV2_OP_FAILURE: - return eap_mschapv2_failure(sm, data, ret, req, respDataLen); + return eap_mschapv2_failure(sm, data, ret, ms, len, + req->identifier, respDataLen); default: wpa_printf(MSG_INFO, "EAP-MSCHAPV2: Unknown op %d - ignored", - req->op_code); + ms->op_code); ret->ignore = TRUE; return NULL; } @@ -720,18 +883,18 @@ static u8 * eap_mschapv2_getKey(struct eap_sm *sm, void *priv, size_t *len) if (!data->master_key_valid || !data->success) return NULL; - if (data->peer_challenge) { + if (data->full_key) { /* EAP-FAST needs both send and receive keys */ key_len = 2 * MSCHAPV2_KEY_LEN; } else { key_len = MSCHAPV2_KEY_LEN; } - key = malloc(key_len); + key = os_malloc(key_len); if (key == NULL) return NULL; - if (data->peer_challenge) { + if (data->full_key) { get_asymetric_start_key(data->master_key, key, MSCHAPV2_KEY_LEN, 0, 0); get_asymetric_start_key(data->master_key, @@ -750,13 +913,32 @@ static u8 * eap_mschapv2_getKey(struct eap_sm *sm, void *priv, size_t *len) } -const struct eap_method eap_method_mschapv2 = +/** + * eap_peer_mschapv2_register - Register EAP-MSCHAPv2 peer method + * Returns: 0 on success, -1 on failure + * + * This function is used to register EAP-MSCHAPv2 peer method into the EAP + * method list. + */ +int eap_peer_mschapv2_register(void) { - .method = EAP_TYPE_MSCHAPV2, - .name = "MSCHAPV2", - .init = eap_mschapv2_init, - .deinit = eap_mschapv2_deinit, - .process = eap_mschapv2_process, - .isKeyAvailable = eap_mschapv2_isKeyAvailable, - .getKey = eap_mschapv2_getKey, -}; + struct eap_method *eap; + int ret; + + eap = eap_peer_method_alloc(EAP_PEER_METHOD_INTERFACE_VERSION, + EAP_VENDOR_IETF, EAP_TYPE_MSCHAPV2, + "MSCHAPV2"); + if (eap == NULL) + return -1; + + eap->init = eap_mschapv2_init; + eap->deinit = eap_mschapv2_deinit; + eap->process = eap_mschapv2_process; + eap->isKeyAvailable = eap_mschapv2_isKeyAvailable; + eap->getKey = eap_mschapv2_getKey; + + ret = eap_peer_method_register(eap); + if (ret) + eap_peer_method_free(eap); + return ret; +} |