1189251Ssam/* 2189251Ssam * EAP-IKEv2 peer (RFC 5106) 3189251Ssam * Copyright (c) 2007, Jouni Malinen <j@w1.fi> 4189251Ssam * 5189251Ssam * This program is free software; you can redistribute it and/or modify 6189251Ssam * it under the terms of the GNU General Public License version 2 as 7189251Ssam * published by the Free Software Foundation. 8189251Ssam * 9189251Ssam * Alternatively, this software may be distributed under the terms of BSD 10189251Ssam * license. 11189251Ssam * 12189251Ssam * See README and COPYING for more details. 13189251Ssam */ 14189251Ssam 15189251Ssam#include "includes.h" 16189251Ssam 17189251Ssam#include "common.h" 18189251Ssam#include "eap_i.h" 19189251Ssam#include "eap_common/eap_ikev2_common.h" 20189251Ssam#include "ikev2.h" 21189251Ssam 22189251Ssam 23189251Ssamstruct eap_ikev2_data { 24189251Ssam struct ikev2_responder_data ikev2; 25189251Ssam enum { WAIT_START, PROC_MSG, WAIT_FRAG_ACK, DONE, FAIL } state; 26189251Ssam struct wpabuf *in_buf; 27189251Ssam struct wpabuf *out_buf; 28189251Ssam size_t out_used; 29189251Ssam size_t fragment_size; 30189251Ssam int keys_ready; 31189251Ssam u8 keymat[EAP_MSK_LEN + EAP_EMSK_LEN]; 32189251Ssam int keymat_ok; 33189251Ssam}; 34189251Ssam 35189251Ssam 36189251Ssamstatic const char * eap_ikev2_state_txt(int state) 37189251Ssam{ 38189251Ssam switch (state) { 39189251Ssam case WAIT_START: 40189251Ssam return "WAIT_START"; 41189251Ssam case PROC_MSG: 42189251Ssam return "PROC_MSG"; 43189251Ssam case WAIT_FRAG_ACK: 44189251Ssam return "WAIT_FRAG_ACK"; 45189251Ssam case DONE: 46189251Ssam return "DONE"; 47189251Ssam case FAIL: 48189251Ssam return "FAIL"; 49189251Ssam default: 50189251Ssam return "?"; 51189251Ssam } 52189251Ssam} 53189251Ssam 54189251Ssam 55189251Ssamstatic void eap_ikev2_state(struct eap_ikev2_data *data, int state) 56189251Ssam{ 57189251Ssam wpa_printf(MSG_DEBUG, "EAP-IKEV2: %s -> %s", 58189251Ssam eap_ikev2_state_txt(data->state), 59189251Ssam eap_ikev2_state_txt(state)); 60189251Ssam data->state = state; 61189251Ssam} 62189251Ssam 63189251Ssam 64189251Ssamstatic void * eap_ikev2_init(struct eap_sm *sm) 65189251Ssam{ 66189251Ssam struct eap_ikev2_data *data; 67189251Ssam const u8 *identity, *password; 68189251Ssam size_t identity_len, password_len; 69189251Ssam 70189251Ssam identity = eap_get_config_identity(sm, &identity_len); 71189251Ssam if (identity == NULL) { 72189251Ssam wpa_printf(MSG_INFO, "EAP-IKEV2: No identity available"); 73189251Ssam return NULL; 74189251Ssam } 75189251Ssam 76189251Ssam data = os_zalloc(sizeof(*data)); 77189251Ssam if (data == NULL) 78189251Ssam return NULL; 79189251Ssam data->state = WAIT_START; 80189251Ssam data->fragment_size = IKEV2_FRAGMENT_SIZE; 81189251Ssam data->ikev2.state = SA_INIT; 82189251Ssam data->ikev2.peer_auth = PEER_AUTH_SECRET; 83189251Ssam data->ikev2.key_pad = (u8 *) os_strdup("Key Pad for EAP-IKEv2"); 84189251Ssam if (data->ikev2.key_pad == NULL) 85189251Ssam goto failed; 86189251Ssam data->ikev2.key_pad_len = 21; 87189251Ssam data->ikev2.IDr = os_malloc(identity_len); 88189251Ssam if (data->ikev2.IDr == NULL) 89189251Ssam goto failed; 90189251Ssam os_memcpy(data->ikev2.IDr, identity, identity_len); 91189251Ssam data->ikev2.IDr_len = identity_len; 92189251Ssam 93189251Ssam password = eap_get_config_password(sm, &password_len); 94189251Ssam if (password) { 95189251Ssam data->ikev2.shared_secret = os_malloc(password_len); 96189251Ssam if (data->ikev2.shared_secret == NULL) 97189251Ssam goto failed; 98189251Ssam os_memcpy(data->ikev2.shared_secret, password, password_len); 99189251Ssam data->ikev2.shared_secret_len = password_len; 100189251Ssam } 101189251Ssam 102189251Ssam return data; 103189251Ssam 104189251Ssamfailed: 105189251Ssam ikev2_responder_deinit(&data->ikev2); 106189251Ssam os_free(data); 107189251Ssam return NULL; 108189251Ssam} 109189251Ssam 110189251Ssam 111189251Ssamstatic void eap_ikev2_deinit(struct eap_sm *sm, void *priv) 112189251Ssam{ 113189251Ssam struct eap_ikev2_data *data = priv; 114189251Ssam wpabuf_free(data->in_buf); 115189251Ssam wpabuf_free(data->out_buf); 116189251Ssam ikev2_responder_deinit(&data->ikev2); 117189251Ssam os_free(data); 118189251Ssam} 119189251Ssam 120189251Ssam 121189251Ssamstatic int eap_ikev2_peer_keymat(struct eap_ikev2_data *data) 122189251Ssam{ 123189251Ssam if (eap_ikev2_derive_keymat( 124189251Ssam data->ikev2.proposal.prf, &data->ikev2.keys, 125189251Ssam data->ikev2.i_nonce, data->ikev2.i_nonce_len, 126189251Ssam data->ikev2.r_nonce, data->ikev2.r_nonce_len, 127189251Ssam data->keymat) < 0) { 128189251Ssam wpa_printf(MSG_DEBUG, "EAP-IKEV2: Failed to " 129189251Ssam "derive key material"); 130189251Ssam return -1; 131189251Ssam } 132189251Ssam data->keymat_ok = 1; 133189251Ssam return 0; 134189251Ssam} 135189251Ssam 136189251Ssam 137189251Ssamstatic struct wpabuf * eap_ikev2_build_msg(struct eap_ikev2_data *data, 138189251Ssam struct eap_method_ret *ret, u8 id) 139189251Ssam{ 140189251Ssam struct wpabuf *resp; 141189251Ssam u8 flags; 142189251Ssam size_t send_len, plen, icv_len = 0; 143189251Ssam 144189251Ssam ret->ignore = FALSE; 145189251Ssam wpa_printf(MSG_DEBUG, "EAP-IKEV2: Generating Response"); 146189251Ssam ret->allowNotifications = TRUE; 147189251Ssam 148189251Ssam flags = 0; 149189251Ssam send_len = wpabuf_len(data->out_buf) - data->out_used; 150189251Ssam if (1 + send_len > data->fragment_size) { 151189251Ssam send_len = data->fragment_size - 1; 152189251Ssam flags |= IKEV2_FLAGS_MORE_FRAGMENTS; 153189251Ssam if (data->out_used == 0) { 154189251Ssam flags |= IKEV2_FLAGS_LENGTH_INCLUDED; 155189251Ssam send_len -= 4; 156189251Ssam } 157189251Ssam } 158189251Ssam#ifdef CCNS_PL 159189251Ssam /* Some issues figuring out the length of the message if Message Length 160189251Ssam * field not included?! */ 161189251Ssam if (!(flags & IKEV2_FLAGS_LENGTH_INCLUDED)) 162189251Ssam flags |= IKEV2_FLAGS_LENGTH_INCLUDED; 163189251Ssam#endif /* CCNS_PL */ 164189251Ssam 165189251Ssam plen = 1 + send_len; 166189251Ssam if (flags & IKEV2_FLAGS_LENGTH_INCLUDED) 167189251Ssam plen += 4; 168189251Ssam if (data->keys_ready) { 169189251Ssam const struct ikev2_integ_alg *integ; 170189251Ssam wpa_printf(MSG_DEBUG, "EAP-IKEV2: Add Integrity Checksum " 171189251Ssam "Data"); 172189251Ssam flags |= IKEV2_FLAGS_ICV_INCLUDED; 173189251Ssam integ = ikev2_get_integ(data->ikev2.proposal.integ); 174189251Ssam if (integ == NULL) { 175189251Ssam wpa_printf(MSG_DEBUG, "EAP-IKEV2: Unknown INTEG " 176189251Ssam "transform / cannot generate ICV"); 177189251Ssam return NULL; 178189251Ssam } 179189251Ssam icv_len = integ->hash_len; 180189251Ssam 181189251Ssam plen += icv_len; 182189251Ssam } 183189251Ssam resp = eap_msg_alloc(EAP_VENDOR_IETF, EAP_TYPE_IKEV2, plen, 184189251Ssam EAP_CODE_RESPONSE, id); 185189251Ssam if (resp == NULL) 186189251Ssam return NULL; 187189251Ssam 188189251Ssam wpabuf_put_u8(resp, flags); /* Flags */ 189189251Ssam if (flags & IKEV2_FLAGS_LENGTH_INCLUDED) 190189251Ssam wpabuf_put_be32(resp, wpabuf_len(data->out_buf)); 191189251Ssam 192189251Ssam wpabuf_put_data(resp, wpabuf_head_u8(data->out_buf) + data->out_used, 193189251Ssam send_len); 194189251Ssam data->out_used += send_len; 195189251Ssam 196189251Ssam if (flags & IKEV2_FLAGS_ICV_INCLUDED) { 197189251Ssam const u8 *msg = wpabuf_head(resp); 198189251Ssam size_t len = wpabuf_len(resp); 199189251Ssam ikev2_integ_hash(data->ikev2.proposal.integ, 200189251Ssam data->ikev2.keys.SK_ar, 201189251Ssam data->ikev2.keys.SK_integ_len, 202189251Ssam msg, len, wpabuf_put(resp, icv_len)); 203189251Ssam } 204189251Ssam 205189251Ssam ret->methodState = METHOD_MAY_CONT; 206189251Ssam ret->decision = DECISION_FAIL; 207189251Ssam 208189251Ssam if (data->out_used == wpabuf_len(data->out_buf)) { 209189251Ssam wpa_printf(MSG_DEBUG, "EAP-IKEV2: Sending out %lu bytes " 210189251Ssam "(message sent completely)", 211189251Ssam (unsigned long) send_len); 212189251Ssam wpabuf_free(data->out_buf); 213189251Ssam data->out_buf = NULL; 214189251Ssam data->out_used = 0; 215189251Ssam switch (data->ikev2.state) { 216189251Ssam case SA_AUTH: 217189251Ssam /* SA_INIT was sent out, so message have to be 218189251Ssam * integrity protected from now on. */ 219189251Ssam data->keys_ready = 1; 220189251Ssam break; 221189251Ssam case IKEV2_DONE: 222189251Ssam ret->methodState = METHOD_DONE; 223189251Ssam if (data->state == FAIL) 224189251Ssam break; 225189251Ssam ret->decision = DECISION_COND_SUCC; 226189251Ssam wpa_printf(MSG_DEBUG, "EAP-IKEV2: Authentication " 227189251Ssam "completed successfully"); 228189251Ssam if (eap_ikev2_peer_keymat(data)) 229189251Ssam break; 230189251Ssam eap_ikev2_state(data, DONE); 231189251Ssam break; 232189251Ssam case IKEV2_FAILED: 233189251Ssam wpa_printf(MSG_DEBUG, "EAP-IKEV2: Authentication " 234189251Ssam "failed"); 235189251Ssam ret->methodState = METHOD_DONE; 236189251Ssam ret->decision = DECISION_FAIL; 237189251Ssam break; 238189251Ssam default: 239189251Ssam break; 240189251Ssam } 241189251Ssam } else { 242189251Ssam wpa_printf(MSG_DEBUG, "EAP-IKEV2: Sending out %lu bytes " 243189251Ssam "(%lu more to send)", (unsigned long) send_len, 244189251Ssam (unsigned long) wpabuf_len(data->out_buf) - 245189251Ssam data->out_used); 246189251Ssam eap_ikev2_state(data, WAIT_FRAG_ACK); 247189251Ssam } 248189251Ssam 249189251Ssam return resp; 250189251Ssam} 251189251Ssam 252189251Ssam 253189251Ssamstatic int eap_ikev2_process_icv(struct eap_ikev2_data *data, 254189251Ssam const struct wpabuf *reqData, 255189251Ssam u8 flags, const u8 *pos, const u8 **end) 256189251Ssam{ 257189251Ssam if (flags & IKEV2_FLAGS_ICV_INCLUDED) { 258189251Ssam int icv_len = eap_ikev2_validate_icv( 259189251Ssam data->ikev2.proposal.integ, &data->ikev2.keys, 1, 260189251Ssam reqData, pos, *end); 261189251Ssam if (icv_len < 0) 262189251Ssam return -1; 263189251Ssam /* Hide Integrity Checksum Data from further processing */ 264189251Ssam *end -= icv_len; 265189251Ssam } else if (data->keys_ready) { 266189251Ssam wpa_printf(MSG_INFO, "EAP-IKEV2: The message should have " 267189251Ssam "included integrity checksum"); 268189251Ssam return -1; 269189251Ssam } 270189251Ssam 271189251Ssam return 0; 272189251Ssam} 273189251Ssam 274189251Ssam 275189251Ssamstatic int eap_ikev2_process_cont(struct eap_ikev2_data *data, 276189251Ssam const u8 *buf, size_t len) 277189251Ssam{ 278189251Ssam /* Process continuation of a pending message */ 279189251Ssam if (len > wpabuf_tailroom(data->in_buf)) { 280189251Ssam wpa_printf(MSG_DEBUG, "EAP-IKEV2: Fragment overflow"); 281189251Ssam eap_ikev2_state(data, FAIL); 282189251Ssam return -1; 283189251Ssam } 284189251Ssam 285189251Ssam wpabuf_put_data(data->in_buf, buf, len); 286189251Ssam wpa_printf(MSG_DEBUG, "EAP-IKEV2: Received %lu bytes, waiting " 287189251Ssam "for %lu bytes more", (unsigned long) len, 288189251Ssam (unsigned long) wpabuf_tailroom(data->in_buf)); 289189251Ssam 290189251Ssam return 0; 291189251Ssam} 292189251Ssam 293189251Ssam 294189251Ssamstatic struct wpabuf * eap_ikev2_process_fragment(struct eap_ikev2_data *data, 295189251Ssam struct eap_method_ret *ret, 296189251Ssam u8 id, u8 flags, 297189251Ssam u32 message_length, 298189251Ssam const u8 *buf, size_t len) 299189251Ssam{ 300189251Ssam /* Process a fragment that is not the last one of the message */ 301189251Ssam if (data->in_buf == NULL && !(flags & IKEV2_FLAGS_LENGTH_INCLUDED)) { 302189251Ssam wpa_printf(MSG_DEBUG, "EAP-IKEV2: No Message Length field in " 303189251Ssam "a fragmented packet"); 304189251Ssam ret->ignore = TRUE; 305189251Ssam return NULL; 306189251Ssam } 307189251Ssam 308189251Ssam if (data->in_buf == NULL) { 309189251Ssam /* First fragment of the message */ 310189251Ssam data->in_buf = wpabuf_alloc(message_length); 311189251Ssam if (data->in_buf == NULL) { 312189251Ssam wpa_printf(MSG_DEBUG, "EAP-IKEV2: No memory for " 313189251Ssam "message"); 314189251Ssam ret->ignore = TRUE; 315189251Ssam return NULL; 316189251Ssam } 317189251Ssam wpabuf_put_data(data->in_buf, buf, len); 318189251Ssam wpa_printf(MSG_DEBUG, "EAP-IKEV2: Received %lu bytes in first " 319189251Ssam "fragment, waiting for %lu bytes more", 320189251Ssam (unsigned long) len, 321189251Ssam (unsigned long) wpabuf_tailroom(data->in_buf)); 322189251Ssam } 323189251Ssam 324189251Ssam return eap_ikev2_build_frag_ack(id, EAP_CODE_RESPONSE); 325189251Ssam} 326189251Ssam 327189251Ssam 328189251Ssamstatic struct wpabuf * eap_ikev2_process(struct eap_sm *sm, void *priv, 329189251Ssam struct eap_method_ret *ret, 330189251Ssam const struct wpabuf *reqData) 331189251Ssam{ 332189251Ssam struct eap_ikev2_data *data = priv; 333189251Ssam const u8 *start, *pos, *end; 334189251Ssam size_t len; 335189251Ssam u8 flags, id; 336189251Ssam u32 message_length = 0; 337189251Ssam struct wpabuf tmpbuf; 338189251Ssam 339189251Ssam pos = eap_hdr_validate(EAP_VENDOR_IETF, EAP_TYPE_IKEV2, reqData, &len); 340189251Ssam if (pos == NULL) { 341189251Ssam ret->ignore = TRUE; 342189251Ssam return NULL; 343189251Ssam } 344189251Ssam 345189251Ssam id = eap_get_id(reqData); 346189251Ssam 347189251Ssam start = pos; 348189251Ssam end = start + len; 349189251Ssam 350189251Ssam if (len == 0) 351189251Ssam flags = 0; /* fragment ack */ 352189251Ssam else 353189251Ssam flags = *pos++; 354189251Ssam 355189251Ssam if (eap_ikev2_process_icv(data, reqData, flags, pos, &end) < 0) { 356189251Ssam ret->ignore = TRUE; 357189251Ssam return NULL; 358189251Ssam } 359189251Ssam 360189251Ssam if (flags & IKEV2_FLAGS_LENGTH_INCLUDED) { 361189251Ssam if (end - pos < 4) { 362189251Ssam wpa_printf(MSG_DEBUG, "EAP-IKEV2: Message underflow"); 363189251Ssam ret->ignore = TRUE; 364189251Ssam return NULL; 365189251Ssam } 366189251Ssam message_length = WPA_GET_BE32(pos); 367189251Ssam pos += 4; 368189251Ssam 369189251Ssam if (message_length < (u32) (end - pos)) { 370189251Ssam wpa_printf(MSG_DEBUG, "EAP-IKEV2: Invalid Message " 371189251Ssam "Length (%d; %ld remaining in this msg)", 372189251Ssam message_length, (long) (end - pos)); 373189251Ssam ret->ignore = TRUE; 374189251Ssam return NULL; 375189251Ssam } 376189251Ssam } 377189251Ssam 378189251Ssam wpa_printf(MSG_DEBUG, "EAP-IKEV2: Received packet: Flags 0x%x " 379189251Ssam "Message Length %u", flags, message_length); 380189251Ssam 381189251Ssam if (data->state == WAIT_FRAG_ACK) { 382189251Ssam#ifdef CCNS_PL 383189251Ssam if (len > 1) /* Empty Flags field included in ACK */ 384189251Ssam#else /* CCNS_PL */ 385189251Ssam if (len != 0) 386189251Ssam#endif /* CCNS_PL */ 387189251Ssam { 388189251Ssam wpa_printf(MSG_DEBUG, "EAP-IKEV2: Unexpected payload " 389189251Ssam "in WAIT_FRAG_ACK state"); 390189251Ssam ret->ignore = TRUE; 391189251Ssam return NULL; 392189251Ssam } 393189251Ssam wpa_printf(MSG_DEBUG, "EAP-IKEV2: Fragment acknowledged"); 394189251Ssam eap_ikev2_state(data, PROC_MSG); 395189251Ssam return eap_ikev2_build_msg(data, ret, id); 396189251Ssam } 397189251Ssam 398189251Ssam if (data->in_buf && eap_ikev2_process_cont(data, pos, end - pos) < 0) { 399189251Ssam ret->ignore = TRUE; 400189251Ssam return NULL; 401189251Ssam } 402189251Ssam 403189251Ssam if (flags & IKEV2_FLAGS_MORE_FRAGMENTS) { 404189251Ssam return eap_ikev2_process_fragment(data, ret, id, flags, 405189251Ssam message_length, pos, 406189251Ssam end - pos); 407189251Ssam } 408189251Ssam 409189251Ssam if (data->in_buf == NULL) { 410189251Ssam /* Wrap unfragmented messages as wpabuf without extra copy */ 411189251Ssam wpabuf_set(&tmpbuf, pos, end - pos); 412189251Ssam data->in_buf = &tmpbuf; 413189251Ssam } 414189251Ssam 415189251Ssam if (ikev2_responder_process(&data->ikev2, data->in_buf) < 0) { 416189251Ssam if (data->in_buf == &tmpbuf) 417189251Ssam data->in_buf = NULL; 418189251Ssam eap_ikev2_state(data, FAIL); 419189251Ssam return NULL; 420189251Ssam } 421189251Ssam 422189251Ssam if (data->in_buf != &tmpbuf) 423189251Ssam wpabuf_free(data->in_buf); 424189251Ssam data->in_buf = NULL; 425189251Ssam 426189251Ssam if (data->out_buf == NULL) { 427189251Ssam data->out_buf = ikev2_responder_build(&data->ikev2); 428189251Ssam if (data->out_buf == NULL) { 429189251Ssam wpa_printf(MSG_DEBUG, "EAP-IKEV2: Failed to generate " 430189251Ssam "IKEv2 message"); 431189251Ssam return NULL; 432189251Ssam } 433189251Ssam data->out_used = 0; 434189251Ssam } 435189251Ssam 436189251Ssam eap_ikev2_state(data, PROC_MSG); 437189251Ssam return eap_ikev2_build_msg(data, ret, id); 438189251Ssam} 439189251Ssam 440189251Ssam 441189251Ssamstatic Boolean eap_ikev2_isKeyAvailable(struct eap_sm *sm, void *priv) 442189251Ssam{ 443189251Ssam struct eap_ikev2_data *data = priv; 444189251Ssam return data->state == DONE && data->keymat_ok; 445189251Ssam} 446189251Ssam 447189251Ssam 448189251Ssamstatic u8 * eap_ikev2_getKey(struct eap_sm *sm, void *priv, size_t *len) 449189251Ssam{ 450189251Ssam struct eap_ikev2_data *data = priv; 451189251Ssam u8 *key; 452189251Ssam 453189251Ssam if (data->state != DONE || !data->keymat_ok) 454189251Ssam return NULL; 455189251Ssam 456189251Ssam key = os_malloc(EAP_MSK_LEN); 457189251Ssam if (key) { 458189251Ssam os_memcpy(key, data->keymat, EAP_MSK_LEN); 459189251Ssam *len = EAP_MSK_LEN; 460189251Ssam } 461189251Ssam 462189251Ssam return key; 463189251Ssam} 464189251Ssam 465189251Ssam 466189251Ssamstatic u8 * eap_ikev2_get_emsk(struct eap_sm *sm, void *priv, size_t *len) 467189251Ssam{ 468189251Ssam struct eap_ikev2_data *data = priv; 469189251Ssam u8 *key; 470189251Ssam 471189251Ssam if (data->state != DONE || !data->keymat_ok) 472189251Ssam return NULL; 473189251Ssam 474189251Ssam key = os_malloc(EAP_EMSK_LEN); 475189251Ssam if (key) { 476189251Ssam os_memcpy(key, data->keymat + EAP_MSK_LEN, EAP_EMSK_LEN); 477189251Ssam *len = EAP_EMSK_LEN; 478189251Ssam } 479189251Ssam 480189251Ssam return key; 481189251Ssam} 482189251Ssam 483189251Ssam 484189251Ssamint eap_peer_ikev2_register(void) 485189251Ssam{ 486189251Ssam struct eap_method *eap; 487189251Ssam int ret; 488189251Ssam 489189251Ssam eap = eap_peer_method_alloc(EAP_PEER_METHOD_INTERFACE_VERSION, 490189251Ssam EAP_VENDOR_IETF, EAP_TYPE_IKEV2, 491189251Ssam "IKEV2"); 492189251Ssam if (eap == NULL) 493189251Ssam return -1; 494189251Ssam 495189251Ssam eap->init = eap_ikev2_init; 496189251Ssam eap->deinit = eap_ikev2_deinit; 497189251Ssam eap->process = eap_ikev2_process; 498189251Ssam eap->isKeyAvailable = eap_ikev2_isKeyAvailable; 499189251Ssam eap->getKey = eap_ikev2_getKey; 500189251Ssam eap->get_emsk = eap_ikev2_get_emsk; 501189251Ssam 502189251Ssam ret = eap_peer_method_register(eap); 503189251Ssam if (ret) 504189251Ssam eap_peer_method_free(eap); 505189251Ssam return ret; 506189251Ssam} 507