1189251Ssam/* 2189251Ssam * EAP peer method: LEAP 3189251Ssam * Copyright (c) 2004-2007, Jouni Malinen <j@w1.fi> 4189251Ssam * 5252726Srpaulo * This software may be distributed under the terms of the BSD license. 6252726Srpaulo * See README for more details. 7189251Ssam */ 8189251Ssam 9189251Ssam#include "includes.h" 10189251Ssam 11189251Ssam#include "common.h" 12214734Srpaulo#include "crypto/ms_funcs.h" 13214734Srpaulo#include "crypto/crypto.h" 14252726Srpaulo#include "crypto/random.h" 15189251Ssam#include "eap_i.h" 16189251Ssam 17189251Ssam#define LEAP_VERSION 1 18189251Ssam#define LEAP_CHALLENGE_LEN 8 19189251Ssam#define LEAP_RESPONSE_LEN 24 20189251Ssam#define LEAP_KEY_LEN 16 21189251Ssam 22189251Ssam 23189251Ssamstruct eap_leap_data { 24189251Ssam enum { 25189251Ssam LEAP_WAIT_CHALLENGE, 26189251Ssam LEAP_WAIT_SUCCESS, 27189251Ssam LEAP_WAIT_RESPONSE, 28189251Ssam LEAP_DONE 29189251Ssam } state; 30189251Ssam 31189251Ssam u8 peer_challenge[LEAP_CHALLENGE_LEN]; 32189251Ssam u8 peer_response[LEAP_RESPONSE_LEN]; 33189251Ssam 34189251Ssam u8 ap_challenge[LEAP_CHALLENGE_LEN]; 35189251Ssam u8 ap_response[LEAP_RESPONSE_LEN]; 36189251Ssam}; 37189251Ssam 38189251Ssam 39189251Ssamstatic void * eap_leap_init(struct eap_sm *sm) 40189251Ssam{ 41189251Ssam struct eap_leap_data *data; 42189251Ssam 43189251Ssam data = os_zalloc(sizeof(*data)); 44189251Ssam if (data == NULL) 45189251Ssam return NULL; 46189251Ssam data->state = LEAP_WAIT_CHALLENGE; 47189251Ssam 48189251Ssam sm->leap_done = FALSE; 49189251Ssam return data; 50189251Ssam} 51189251Ssam 52189251Ssam 53189251Ssamstatic void eap_leap_deinit(struct eap_sm *sm, void *priv) 54189251Ssam{ 55189251Ssam os_free(priv); 56189251Ssam} 57189251Ssam 58189251Ssam 59189251Ssamstatic struct wpabuf * eap_leap_process_request(struct eap_sm *sm, void *priv, 60189251Ssam struct eap_method_ret *ret, 61189251Ssam const struct wpabuf *reqData) 62189251Ssam{ 63189251Ssam struct eap_leap_data *data = priv; 64189251Ssam struct wpabuf *resp; 65189251Ssam const u8 *pos, *challenge, *identity, *password; 66189251Ssam u8 challenge_len, *rpos; 67189251Ssam size_t identity_len, password_len, len; 68189251Ssam int pwhash; 69189251Ssam 70189251Ssam wpa_printf(MSG_DEBUG, "EAP-LEAP: Processing EAP-Request"); 71189251Ssam 72189251Ssam identity = eap_get_config_identity(sm, &identity_len); 73189251Ssam password = eap_get_config_password2(sm, &password_len, &pwhash); 74189251Ssam if (identity == NULL || password == NULL) 75189251Ssam return NULL; 76189251Ssam 77189251Ssam pos = eap_hdr_validate(EAP_VENDOR_IETF, EAP_TYPE_LEAP, reqData, &len); 78189251Ssam if (pos == NULL || len < 3) { 79189251Ssam wpa_printf(MSG_INFO, "EAP-LEAP: Invalid EAP-Request frame"); 80189251Ssam ret->ignore = TRUE; 81189251Ssam return NULL; 82189251Ssam } 83189251Ssam 84189251Ssam if (*pos != LEAP_VERSION) { 85189251Ssam wpa_printf(MSG_WARNING, "EAP-LEAP: Unsupported LEAP version " 86189251Ssam "%d", *pos); 87189251Ssam ret->ignore = TRUE; 88189251Ssam return NULL; 89189251Ssam } 90189251Ssam pos++; 91189251Ssam 92189251Ssam pos++; /* skip unused byte */ 93189251Ssam 94189251Ssam challenge_len = *pos++; 95189251Ssam if (challenge_len != LEAP_CHALLENGE_LEN || challenge_len > len - 3) { 96189251Ssam wpa_printf(MSG_INFO, "EAP-LEAP: Invalid challenge " 97189251Ssam "(challenge_len=%d reqDataLen=%lu)", 98189251Ssam challenge_len, (unsigned long) wpabuf_len(reqData)); 99189251Ssam ret->ignore = TRUE; 100189251Ssam return NULL; 101189251Ssam } 102189251Ssam challenge = pos; 103189251Ssam os_memcpy(data->peer_challenge, challenge, LEAP_CHALLENGE_LEN); 104189251Ssam wpa_hexdump(MSG_MSGDUMP, "EAP-LEAP: Challenge from AP", 105189251Ssam challenge, LEAP_CHALLENGE_LEN); 106189251Ssam 107189251Ssam wpa_printf(MSG_DEBUG, "EAP-LEAP: Generating Challenge Response"); 108189251Ssam 109189251Ssam resp = eap_msg_alloc(EAP_VENDOR_IETF, EAP_TYPE_LEAP, 110189251Ssam 3 + LEAP_RESPONSE_LEN + identity_len, 111189251Ssam EAP_CODE_RESPONSE, eap_get_id(reqData)); 112189251Ssam if (resp == NULL) 113189251Ssam return NULL; 114189251Ssam wpabuf_put_u8(resp, LEAP_VERSION); 115189251Ssam wpabuf_put_u8(resp, 0); /* unused */ 116189251Ssam wpabuf_put_u8(resp, LEAP_RESPONSE_LEN); 117189251Ssam rpos = wpabuf_put(resp, LEAP_RESPONSE_LEN); 118189251Ssam if (pwhash) 119189251Ssam challenge_response(challenge, password, rpos); 120189251Ssam else 121189251Ssam nt_challenge_response(challenge, password, password_len, rpos); 122189251Ssam os_memcpy(data->peer_response, rpos, LEAP_RESPONSE_LEN); 123189251Ssam wpa_hexdump(MSG_MSGDUMP, "EAP-LEAP: Response", 124189251Ssam rpos, LEAP_RESPONSE_LEN); 125189251Ssam wpabuf_put_data(resp, identity, identity_len); 126189251Ssam 127189251Ssam data->state = LEAP_WAIT_SUCCESS; 128189251Ssam 129189251Ssam return resp; 130189251Ssam} 131189251Ssam 132189251Ssam 133189251Ssamstatic struct wpabuf * eap_leap_process_success(struct eap_sm *sm, void *priv, 134189251Ssam struct eap_method_ret *ret, 135189251Ssam const struct wpabuf *reqData) 136189251Ssam{ 137189251Ssam struct eap_leap_data *data = priv; 138189251Ssam struct wpabuf *resp; 139189251Ssam u8 *pos; 140189251Ssam const u8 *identity; 141189251Ssam size_t identity_len; 142189251Ssam 143189251Ssam wpa_printf(MSG_DEBUG, "EAP-LEAP: Processing EAP-Success"); 144189251Ssam 145189251Ssam identity = eap_get_config_identity(sm, &identity_len); 146189251Ssam if (identity == NULL) 147189251Ssam return NULL; 148189251Ssam 149189251Ssam if (data->state != LEAP_WAIT_SUCCESS) { 150189251Ssam wpa_printf(MSG_INFO, "EAP-LEAP: EAP-Success received in " 151189251Ssam "unexpected state (%d) - ignored", data->state); 152189251Ssam ret->ignore = TRUE; 153189251Ssam return NULL; 154189251Ssam } 155189251Ssam 156189251Ssam resp = eap_msg_alloc(EAP_VENDOR_IETF, EAP_TYPE_LEAP, 157189251Ssam 3 + LEAP_CHALLENGE_LEN + identity_len, 158189251Ssam EAP_CODE_REQUEST, eap_get_id(reqData)); 159189251Ssam if (resp == NULL) 160189251Ssam return NULL; 161189251Ssam wpabuf_put_u8(resp, LEAP_VERSION); 162189251Ssam wpabuf_put_u8(resp, 0); /* unused */ 163189251Ssam wpabuf_put_u8(resp, LEAP_CHALLENGE_LEN); 164189251Ssam pos = wpabuf_put(resp, LEAP_CHALLENGE_LEN); 165252726Srpaulo if (random_get_bytes(pos, LEAP_CHALLENGE_LEN)) { 166189251Ssam wpa_printf(MSG_WARNING, "EAP-LEAP: Failed to read random data " 167189251Ssam "for challenge"); 168189251Ssam wpabuf_free(resp); 169189251Ssam ret->ignore = TRUE; 170189251Ssam return NULL; 171189251Ssam } 172189251Ssam os_memcpy(data->ap_challenge, pos, LEAP_CHALLENGE_LEN); 173189251Ssam wpa_hexdump(MSG_MSGDUMP, "EAP-LEAP: Challenge to AP/AS", pos, 174189251Ssam LEAP_CHALLENGE_LEN); 175189251Ssam wpabuf_put_data(resp, identity, identity_len); 176189251Ssam 177189251Ssam data->state = LEAP_WAIT_RESPONSE; 178189251Ssam 179189251Ssam return resp; 180189251Ssam} 181189251Ssam 182189251Ssam 183189251Ssamstatic struct wpabuf * eap_leap_process_response(struct eap_sm *sm, void *priv, 184189251Ssam struct eap_method_ret *ret, 185189251Ssam const struct wpabuf *reqData) 186189251Ssam{ 187189251Ssam struct eap_leap_data *data = priv; 188189251Ssam const u8 *pos, *password; 189189251Ssam u8 response_len, pw_hash[16], pw_hash_hash[16], 190189251Ssam expected[LEAP_RESPONSE_LEN]; 191189251Ssam size_t password_len, len; 192189251Ssam int pwhash; 193189251Ssam 194189251Ssam wpa_printf(MSG_DEBUG, "EAP-LEAP: Processing EAP-Response"); 195189251Ssam 196189251Ssam password = eap_get_config_password2(sm, &password_len, &pwhash); 197189251Ssam if (password == NULL) 198189251Ssam return NULL; 199189251Ssam 200189251Ssam pos = eap_hdr_validate(EAP_VENDOR_IETF, EAP_TYPE_LEAP, reqData, &len); 201189251Ssam if (pos == NULL || len < 3) { 202189251Ssam wpa_printf(MSG_INFO, "EAP-LEAP: Invalid EAP-Response frame"); 203189251Ssam ret->ignore = TRUE; 204189251Ssam return NULL; 205189251Ssam } 206189251Ssam 207189251Ssam if (*pos != LEAP_VERSION) { 208189251Ssam wpa_printf(MSG_WARNING, "EAP-LEAP: Unsupported LEAP version " 209189251Ssam "%d", *pos); 210189251Ssam ret->ignore = TRUE; 211189251Ssam return NULL; 212189251Ssam } 213189251Ssam pos++; 214189251Ssam 215189251Ssam pos++; /* skip unused byte */ 216189251Ssam 217189251Ssam response_len = *pos++; 218189251Ssam if (response_len != LEAP_RESPONSE_LEN || response_len > len - 3) { 219189251Ssam wpa_printf(MSG_INFO, "EAP-LEAP: Invalid response " 220189251Ssam "(response_len=%d reqDataLen=%lu)", 221189251Ssam response_len, (unsigned long) wpabuf_len(reqData)); 222189251Ssam ret->ignore = TRUE; 223189251Ssam return NULL; 224189251Ssam } 225189251Ssam 226189251Ssam wpa_hexdump(MSG_DEBUG, "EAP-LEAP: Response from AP", 227189251Ssam pos, LEAP_RESPONSE_LEN); 228189251Ssam os_memcpy(data->ap_response, pos, LEAP_RESPONSE_LEN); 229189251Ssam 230189251Ssam if (pwhash) { 231214734Srpaulo if (hash_nt_password_hash(password, pw_hash_hash)) { 232214734Srpaulo ret->ignore = TRUE; 233214734Srpaulo return NULL; 234214734Srpaulo } 235189251Ssam } else { 236214734Srpaulo if (nt_password_hash(password, password_len, pw_hash) || 237214734Srpaulo hash_nt_password_hash(pw_hash, pw_hash_hash)) { 238214734Srpaulo ret->ignore = TRUE; 239214734Srpaulo return NULL; 240214734Srpaulo } 241189251Ssam } 242189251Ssam challenge_response(data->ap_challenge, pw_hash_hash, expected); 243189251Ssam 244189251Ssam ret->methodState = METHOD_DONE; 245189251Ssam ret->allowNotifications = FALSE; 246189251Ssam 247189251Ssam if (os_memcmp(pos, expected, LEAP_RESPONSE_LEN) != 0) { 248189251Ssam wpa_printf(MSG_WARNING, "EAP-LEAP: AP sent an invalid " 249189251Ssam "response - authentication failed"); 250189251Ssam wpa_hexdump(MSG_DEBUG, "EAP-LEAP: Expected response from AP", 251189251Ssam expected, LEAP_RESPONSE_LEN); 252189251Ssam ret->decision = DECISION_FAIL; 253189251Ssam return NULL; 254189251Ssam } 255189251Ssam 256189251Ssam ret->decision = DECISION_UNCOND_SUCC; 257189251Ssam 258189251Ssam /* LEAP is somewhat odd method since it sends EAP-Success in the middle 259189251Ssam * of the authentication. Use special variable to transit EAP state 260189251Ssam * machine to SUCCESS state. */ 261189251Ssam sm->leap_done = TRUE; 262189251Ssam data->state = LEAP_DONE; 263189251Ssam 264189251Ssam /* No more authentication messages expected; AP will send EAPOL-Key 265189251Ssam * frames if encryption is enabled. */ 266189251Ssam return NULL; 267189251Ssam} 268189251Ssam 269189251Ssam 270189251Ssamstatic struct wpabuf * eap_leap_process(struct eap_sm *sm, void *priv, 271189251Ssam struct eap_method_ret *ret, 272189251Ssam const struct wpabuf *reqData) 273189251Ssam{ 274189251Ssam const struct eap_hdr *eap; 275189251Ssam size_t password_len; 276189251Ssam const u8 *password; 277189251Ssam 278189251Ssam password = eap_get_config_password(sm, &password_len); 279189251Ssam if (password == NULL) { 280189251Ssam wpa_printf(MSG_INFO, "EAP-LEAP: Password not configured"); 281189251Ssam eap_sm_request_password(sm); 282189251Ssam ret->ignore = TRUE; 283189251Ssam return NULL; 284189251Ssam } 285189251Ssam 286189251Ssam /* 287189251Ssam * LEAP needs to be able to handle EAP-Success frame which does not 288189251Ssam * include Type field. Consequently, eap_hdr_validate() cannot be used 289189251Ssam * here. This validation will be done separately for EAP-Request and 290189251Ssam * EAP-Response frames. 291189251Ssam */ 292189251Ssam eap = wpabuf_head(reqData); 293189251Ssam if (wpabuf_len(reqData) < sizeof(*eap) || 294189251Ssam be_to_host16(eap->length) > wpabuf_len(reqData)) { 295189251Ssam wpa_printf(MSG_INFO, "EAP-LEAP: Invalid frame"); 296189251Ssam ret->ignore = TRUE; 297189251Ssam return NULL; 298189251Ssam } 299189251Ssam 300189251Ssam ret->ignore = FALSE; 301189251Ssam ret->allowNotifications = TRUE; 302189251Ssam ret->methodState = METHOD_MAY_CONT; 303189251Ssam ret->decision = DECISION_FAIL; 304189251Ssam 305189251Ssam sm->leap_done = FALSE; 306189251Ssam 307189251Ssam switch (eap->code) { 308189251Ssam case EAP_CODE_REQUEST: 309189251Ssam return eap_leap_process_request(sm, priv, ret, reqData); 310189251Ssam case EAP_CODE_SUCCESS: 311189251Ssam return eap_leap_process_success(sm, priv, ret, reqData); 312189251Ssam case EAP_CODE_RESPONSE: 313189251Ssam return eap_leap_process_response(sm, priv, ret, reqData); 314189251Ssam default: 315189251Ssam wpa_printf(MSG_INFO, "EAP-LEAP: Unexpected EAP code (%d) - " 316189251Ssam "ignored", eap->code); 317189251Ssam ret->ignore = TRUE; 318189251Ssam return NULL; 319189251Ssam } 320189251Ssam} 321189251Ssam 322189251Ssam 323189251Ssamstatic Boolean eap_leap_isKeyAvailable(struct eap_sm *sm, void *priv) 324189251Ssam{ 325189251Ssam struct eap_leap_data *data = priv; 326189251Ssam return data->state == LEAP_DONE; 327189251Ssam} 328189251Ssam 329189251Ssam 330189251Ssamstatic u8 * eap_leap_getKey(struct eap_sm *sm, void *priv, size_t *len) 331189251Ssam{ 332189251Ssam struct eap_leap_data *data = priv; 333189251Ssam u8 *key, pw_hash_hash[16], pw_hash[16]; 334189251Ssam const u8 *addr[5], *password; 335189251Ssam size_t elen[5], password_len; 336189251Ssam int pwhash; 337189251Ssam 338189251Ssam if (data->state != LEAP_DONE) 339189251Ssam return NULL; 340189251Ssam 341189251Ssam password = eap_get_config_password2(sm, &password_len, &pwhash); 342189251Ssam if (password == NULL) 343189251Ssam return NULL; 344189251Ssam 345189251Ssam key = os_malloc(LEAP_KEY_LEN); 346189251Ssam if (key == NULL) 347189251Ssam return NULL; 348189251Ssam 349214734Srpaulo if (pwhash) { 350214734Srpaulo if (hash_nt_password_hash(password, pw_hash_hash)) { 351214734Srpaulo os_free(key); 352214734Srpaulo return NULL; 353214734Srpaulo } 354214734Srpaulo } else { 355214734Srpaulo if (nt_password_hash(password, password_len, pw_hash) || 356214734Srpaulo hash_nt_password_hash(pw_hash, pw_hash_hash)) { 357214734Srpaulo os_free(key); 358214734Srpaulo return NULL; 359214734Srpaulo } 360189251Ssam } 361189251Ssam wpa_hexdump_key(MSG_DEBUG, "EAP-LEAP: pw_hash_hash", 362189251Ssam pw_hash_hash, 16); 363189251Ssam wpa_hexdump(MSG_DEBUG, "EAP-LEAP: peer_challenge", 364189251Ssam data->peer_challenge, LEAP_CHALLENGE_LEN); 365189251Ssam wpa_hexdump(MSG_DEBUG, "EAP-LEAP: peer_response", 366189251Ssam data->peer_response, LEAP_RESPONSE_LEN); 367189251Ssam wpa_hexdump(MSG_DEBUG, "EAP-LEAP: ap_challenge", 368189251Ssam data->ap_challenge, LEAP_CHALLENGE_LEN); 369189251Ssam wpa_hexdump(MSG_DEBUG, "EAP-LEAP: ap_response", 370189251Ssam data->ap_response, LEAP_RESPONSE_LEN); 371189251Ssam 372189251Ssam addr[0] = pw_hash_hash; 373189251Ssam elen[0] = 16; 374189251Ssam addr[1] = data->ap_challenge; 375189251Ssam elen[1] = LEAP_CHALLENGE_LEN; 376189251Ssam addr[2] = data->ap_response; 377189251Ssam elen[2] = LEAP_RESPONSE_LEN; 378189251Ssam addr[3] = data->peer_challenge; 379189251Ssam elen[3] = LEAP_CHALLENGE_LEN; 380189251Ssam addr[4] = data->peer_response; 381189251Ssam elen[4] = LEAP_RESPONSE_LEN; 382189251Ssam md5_vector(5, addr, elen, key); 383189251Ssam wpa_hexdump_key(MSG_DEBUG, "EAP-LEAP: master key", key, LEAP_KEY_LEN); 384189251Ssam *len = LEAP_KEY_LEN; 385189251Ssam 386189251Ssam return key; 387189251Ssam} 388189251Ssam 389189251Ssam 390189251Ssamint eap_peer_leap_register(void) 391189251Ssam{ 392189251Ssam struct eap_method *eap; 393189251Ssam int ret; 394189251Ssam 395189251Ssam eap = eap_peer_method_alloc(EAP_PEER_METHOD_INTERFACE_VERSION, 396189251Ssam EAP_VENDOR_IETF, EAP_TYPE_LEAP, "LEAP"); 397189251Ssam if (eap == NULL) 398189251Ssam return -1; 399189251Ssam 400189251Ssam eap->init = eap_leap_init; 401189251Ssam eap->deinit = eap_leap_deinit; 402189251Ssam eap->process = eap_leap_process; 403189251Ssam eap->isKeyAvailable = eap_leap_isKeyAvailable; 404189251Ssam eap->getKey = eap_leap_getKey; 405189251Ssam 406189251Ssam ret = eap_peer_method_register(eap); 407189251Ssam if (ret) 408189251Ssam eap_peer_method_free(eap); 409189251Ssam return ret; 410189251Ssam} 411