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