| /* |
| * hostapd / EAP-EKE (RFC 6124) server |
| * Copyright (c) 2013, Jouni Malinen <j@w1.fi> |
| * |
| * This software may be distributed under the terms of the BSD license. |
| * See README for more details. |
| */ |
| |
| #include "includes.h" |
| |
| #include "common.h" |
| #include "crypto/random.h" |
| #include "eap_server/eap_i.h" |
| #include "eap_common/eap_eke_common.h" |
| |
| |
| struct eap_eke_data { |
| enum { |
| IDENTITY, COMMIT, CONFIRM, FAILURE_REPORT, SUCCESS, FAILURE |
| } state; |
| u8 msk[EAP_MSK_LEN]; |
| u8 emsk[EAP_EMSK_LEN]; |
| u8 *peerid; |
| size_t peerid_len; |
| u8 peerid_type; |
| u8 serverid_type; |
| u8 dh_priv[EAP_EKE_MAX_DH_LEN]; |
| u8 key[EAP_EKE_MAX_KEY_LEN]; |
| struct eap_eke_session sess; |
| u8 nonce_p[EAP_EKE_MAX_NONCE_LEN]; |
| u8 nonce_s[EAP_EKE_MAX_NONCE_LEN]; |
| struct wpabuf *msgs; |
| int phase2; |
| u32 failure_code; |
| }; |
| |
| |
| static const char * eap_eke_state_txt(int state) |
| { |
| switch (state) { |
| case IDENTITY: |
| return "IDENTITY"; |
| case COMMIT: |
| return "COMMIT"; |
| case CONFIRM: |
| return "CONFIRM"; |
| case FAILURE_REPORT: |
| return "FAILURE_REPORT"; |
| case SUCCESS: |
| return "SUCCESS"; |
| case FAILURE: |
| return "FAILURE"; |
| default: |
| return "?"; |
| } |
| } |
| |
| |
| static void eap_eke_state(struct eap_eke_data *data, int state) |
| { |
| wpa_printf(MSG_DEBUG, "EAP-EKE: %s -> %s", |
| eap_eke_state_txt(data->state), |
| eap_eke_state_txt(state)); |
| data->state = state; |
| } |
| |
| |
| static void eap_eke_fail(struct eap_eke_data *data, u32 code) |
| { |
| wpa_printf(MSG_DEBUG, "EAP-EKE: Failure - code 0x%x", code); |
| data->failure_code = code; |
| eap_eke_state(data, FAILURE_REPORT); |
| } |
| |
| |
| static void * eap_eke_init(struct eap_sm *sm) |
| { |
| struct eap_eke_data *data; |
| size_t i; |
| |
| data = os_zalloc(sizeof(*data)); |
| if (data == NULL) |
| return NULL; |
| eap_eke_state(data, IDENTITY); |
| |
| data->serverid_type = EAP_EKE_ID_OPAQUE; |
| for (i = 0; i < sm->server_id_len; i++) { |
| if (sm->server_id[i] == '.' && |
| data->serverid_type == EAP_EKE_ID_OPAQUE) |
| data->serverid_type = EAP_EKE_ID_FQDN; |
| if (sm->server_id[i] == '@') |
| data->serverid_type = EAP_EKE_ID_NAI; |
| } |
| |
| data->phase2 = sm->init_phase2; |
| |
| return data; |
| } |
| |
| |
| static void eap_eke_reset(struct eap_sm *sm, void *priv) |
| { |
| struct eap_eke_data *data = priv; |
| eap_eke_session_clean(&data->sess); |
| os_free(data->peerid); |
| wpabuf_free(data->msgs); |
| bin_clear_free(data, sizeof(*data)); |
| } |
| |
| |
| static struct wpabuf * eap_eke_build_msg(struct eap_eke_data *data, |
| u8 id, size_t length, u8 eke_exch) |
| { |
| struct wpabuf *msg; |
| size_t plen; |
| |
| plen = 1 + length; |
| |
| msg = eap_msg_alloc(EAP_VENDOR_IETF, EAP_TYPE_EKE, plen, |
| EAP_CODE_REQUEST, id); |
| if (msg == NULL) { |
| wpa_printf(MSG_ERROR, "EAP-EKE: Failed to allocate memory"); |
| return NULL; |
| } |
| |
| wpabuf_put_u8(msg, eke_exch); |
| |
| return msg; |
| } |
| |
| |
| static int supported_proposal(const u8 *pos) |
| { |
| if (pos[0] == EAP_EKE_DHGROUP_EKE_16 && |
| pos[1] == EAP_EKE_ENCR_AES128_CBC && |
| pos[2] == EAP_EKE_PRF_HMAC_SHA2_256 && |
| pos[3] == EAP_EKE_MAC_HMAC_SHA2_256) |
| return 1; |
| |
| if (pos[0] == EAP_EKE_DHGROUP_EKE_15 && |
| pos[1] == EAP_EKE_ENCR_AES128_CBC && |
| pos[2] == EAP_EKE_PRF_HMAC_SHA2_256 && |
| pos[3] == EAP_EKE_MAC_HMAC_SHA2_256) |
| return 1; |
| |
| if (pos[0] == EAP_EKE_DHGROUP_EKE_14 && |
| pos[1] == EAP_EKE_ENCR_AES128_CBC && |
| pos[2] == EAP_EKE_PRF_HMAC_SHA2_256 && |
| pos[3] == EAP_EKE_MAC_HMAC_SHA2_256) |
| return 1; |
| |
| if (pos[0] == EAP_EKE_DHGROUP_EKE_14 && |
| pos[1] == EAP_EKE_ENCR_AES128_CBC && |
| pos[2] == EAP_EKE_PRF_HMAC_SHA1 && |
| pos[3] == EAP_EKE_MAC_HMAC_SHA1) |
| return 1; |
| |
| return 0; |
| } |
| |
| |
| static struct wpabuf * eap_eke_build_failure(struct eap_eke_data *data, u8 id) |
| { |
| struct wpabuf *msg; |
| |
| wpa_printf(MSG_DEBUG, "EAP-EKE: Request/Failure: Failure-Code=0x%x", |
| data->failure_code); |
| |
| msg = eap_eke_build_msg(data, id, 4, EAP_EKE_FAILURE); |
| if (msg == NULL) { |
| eap_eke_state(data, FAILURE); |
| return NULL; |
| } |
| wpabuf_put_be32(msg, data->failure_code); |
| |
| return msg; |
| } |
| |
| |
| static struct wpabuf * eap_eke_build_identity(struct eap_sm *sm, |
| struct eap_eke_data *data, |
| u8 id) |
| { |
| struct wpabuf *msg; |
| size_t plen; |
| |
| wpa_printf(MSG_DEBUG, "EAP-EKE: Request/Identity"); |
| |
| plen = 2 + 4 * 4 + 1 + sm->server_id_len; |
| msg = eap_eke_build_msg(data, id, plen, EAP_EKE_ID); |
| if (msg == NULL) |
| return NULL; |
| |
| wpabuf_put_u8(msg, 4); /* NumProposals */ |
| wpabuf_put_u8(msg, 0); /* Reserved */ |
| |
| /* Proposal - DH Group 16 with AES128-CBC and SHA256 */ |
| wpabuf_put_u8(msg, EAP_EKE_DHGROUP_EKE_16); /* Group Description */ |
| wpabuf_put_u8(msg, EAP_EKE_ENCR_AES128_CBC); /* Encryption */ |
| wpabuf_put_u8(msg, EAP_EKE_PRF_HMAC_SHA2_256); /* PRF */ |
| wpabuf_put_u8(msg, EAP_EKE_MAC_HMAC_SHA2_256); /* MAC */ |
| |
| /* Proposal - DH Group 15 with AES128-CBC and SHA256 */ |
| wpabuf_put_u8(msg, EAP_EKE_DHGROUP_EKE_15); /* Group Description */ |
| wpabuf_put_u8(msg, EAP_EKE_ENCR_AES128_CBC); /* Encryption */ |
| wpabuf_put_u8(msg, EAP_EKE_PRF_HMAC_SHA2_256); /* PRF */ |
| wpabuf_put_u8(msg, EAP_EKE_MAC_HMAC_SHA2_256); /* MAC */ |
| |
| /* Proposal - DH Group 14 with AES128-CBC and SHA256 */ |
| wpabuf_put_u8(msg, EAP_EKE_DHGROUP_EKE_14); /* Group Description */ |
| wpabuf_put_u8(msg, EAP_EKE_ENCR_AES128_CBC); /* Encryption */ |
| wpabuf_put_u8(msg, EAP_EKE_PRF_HMAC_SHA2_256); /* PRF */ |
| wpabuf_put_u8(msg, EAP_EKE_MAC_HMAC_SHA2_256); /* MAC */ |
| |
| /* |
| * Proposal - DH Group 14 with AES128-CBC and SHA1 |
| * (mandatory to implement algorithms) |
| */ |
| wpabuf_put_u8(msg, EAP_EKE_DHGROUP_EKE_14); /* Group Description */ |
| wpabuf_put_u8(msg, EAP_EKE_ENCR_AES128_CBC); /* Encryption */ |
| wpabuf_put_u8(msg, EAP_EKE_PRF_HMAC_SHA1); /* PRF */ |
| wpabuf_put_u8(msg, EAP_EKE_MAC_HMAC_SHA1); /* MAC */ |
| |
| /* Server IDType + Identity */ |
| wpabuf_put_u8(msg, data->serverid_type); |
| wpabuf_put_data(msg, sm->server_id, sm->server_id_len); |
| |
| wpabuf_free(data->msgs); |
| data->msgs = wpabuf_dup(msg); |
| if (data->msgs == NULL) { |
| wpabuf_free(msg); |
| return NULL; |
| } |
| |
| return msg; |
| } |
| |
| |
| static struct wpabuf * eap_eke_build_commit(struct eap_sm *sm, |
| struct eap_eke_data *data, u8 id) |
| { |
| struct wpabuf *msg; |
| u8 pub[EAP_EKE_MAX_DH_LEN]; |
| |
| wpa_printf(MSG_DEBUG, "EAP-EKE: Request/Commit"); |
| |
| if (sm->user == NULL || sm->user->password == NULL) { |
| wpa_printf(MSG_INFO, "EAP-EKE: Password with not configured"); |
| eap_eke_fail(data, EAP_EKE_FAIL_PASSWD_NOT_FOUND); |
| return eap_eke_build_failure(data, id); |
| } |
| |
| if (eap_eke_derive_key(&data->sess, sm->user->password, |
| sm->user->password_len, |
| sm->server_id, sm->server_id_len, |
| data->peerid, data->peerid_len, data->key) < 0) { |
| wpa_printf(MSG_INFO, "EAP-EKE: Failed to derive key"); |
| eap_eke_fail(data, EAP_EKE_FAIL_PRIVATE_INTERNAL_ERROR); |
| return eap_eke_build_failure(data, id); |
| } |
| |
| msg = eap_eke_build_msg(data, id, data->sess.dhcomp_len, |
| EAP_EKE_COMMIT); |
| if (msg == NULL) { |
| eap_eke_fail(data, EAP_EKE_FAIL_PRIVATE_INTERNAL_ERROR); |
| return eap_eke_build_failure(data, id); |
| } |
| |
| /* |
| * y_s = g ^ x_s (mod p) |
| * x_s = random number 2 .. p-1 |
| * temp = prf(0+, password) |
| * key = prf+(temp, ID_S | ID_P) |
| * DHComponent_S = Encr(key, y_s) |
| */ |
| |
| if (eap_eke_dh_init(data->sess.dhgroup, data->dh_priv, pub) < 0) { |
| wpa_printf(MSG_INFO, "EAP-EKE: Failed to initialize DH"); |
| eap_eke_fail(data, EAP_EKE_FAIL_PRIVATE_INTERNAL_ERROR); |
| return eap_eke_build_failure(data, id); |
| } |
| |
| if (eap_eke_dhcomp(&data->sess, data->key, pub, |
| wpabuf_put(msg, data->sess.dhcomp_len)) |
| < 0) { |
| wpa_printf(MSG_INFO, "EAP-EKE: Failed to build DHComponent_S"); |
| wpabuf_free(msg); |
| eap_eke_fail(data, EAP_EKE_FAIL_PRIVATE_INTERNAL_ERROR); |
| return eap_eke_build_failure(data, id); |
| } |
| |
| if (wpabuf_resize(&data->msgs, wpabuf_len(msg)) < 0) { |
| wpabuf_free(msg); |
| eap_eke_fail(data, EAP_EKE_FAIL_PRIVATE_INTERNAL_ERROR); |
| return eap_eke_build_failure(data, id); |
| } |
| wpabuf_put_buf(data->msgs, msg); |
| |
| return msg; |
| } |
| |
| |
| static struct wpabuf * eap_eke_build_confirm(struct eap_sm *sm, |
| struct eap_eke_data *data, u8 id) |
| { |
| struct wpabuf *msg; |
| size_t plen, prot_len; |
| u8 nonces[2 * EAP_EKE_MAX_NONCE_LEN]; |
| u8 *auth; |
| |
| wpa_printf(MSG_DEBUG, "EAP-EKE: Request/Confirm"); |
| |
| plen = data->sess.pnonce_ps_len + data->sess.prf_len; |
| msg = eap_eke_build_msg(data, id, plen, EAP_EKE_CONFIRM); |
| if (msg == NULL) { |
| eap_eke_fail(data, EAP_EKE_FAIL_PRIVATE_INTERNAL_ERROR); |
| return eap_eke_build_failure(data, id); |
| } |
| |
| if (random_get_bytes(data->nonce_s, data->sess.nonce_len)) { |
| wpabuf_free(msg); |
| eap_eke_fail(data, EAP_EKE_FAIL_PRIVATE_INTERNAL_ERROR); |
| return eap_eke_build_failure(data, id); |
| } |
| wpa_hexdump_key(MSG_DEBUG, "EAP-EKE: Nonce_S", |
| data->nonce_s, data->sess.nonce_len); |
| |
| os_memcpy(nonces, data->nonce_p, data->sess.nonce_len); |
| os_memcpy(nonces + data->sess.nonce_len, data->nonce_s, |
| data->sess.nonce_len); |
| prot_len = wpabuf_tailroom(msg); |
| if (eap_eke_prot(&data->sess, nonces, 2 * data->sess.nonce_len, |
| wpabuf_put(msg, 0), &prot_len) < 0) { |
| wpabuf_free(msg); |
| eap_eke_fail(data, EAP_EKE_FAIL_PRIVATE_INTERNAL_ERROR); |
| return eap_eke_build_failure(data, id); |
| } |
| wpabuf_put(msg, prot_len); |
| |
| if (eap_eke_derive_ka(&data->sess, |
| sm->server_id, sm->server_id_len, |
| data->peerid, data->peerid_len, |
| data->nonce_p, data->nonce_s) < 0) { |
| wpabuf_free(msg); |
| eap_eke_fail(data, EAP_EKE_FAIL_PRIVATE_INTERNAL_ERROR); |
| return eap_eke_build_failure(data, id); |
| } |
| |
| auth = wpabuf_put(msg, data->sess.prf_len); |
| if (eap_eke_auth(&data->sess, "EAP-EKE server", data->msgs, auth) < 0) { |
| wpabuf_free(msg); |
| eap_eke_fail(data, EAP_EKE_FAIL_PRIVATE_INTERNAL_ERROR); |
| return eap_eke_build_failure(data, id); |
| } |
| wpa_hexdump(MSG_DEBUG, "EAP-EKE: Auth_S", auth, data->sess.prf_len); |
| |
| return msg; |
| } |
| |
| |
| static struct wpabuf * eap_eke_buildReq(struct eap_sm *sm, void *priv, u8 id) |
| { |
| struct eap_eke_data *data = priv; |
| |
| switch (data->state) { |
| case IDENTITY: |
| return eap_eke_build_identity(sm, data, id); |
| case COMMIT: |
| return eap_eke_build_commit(sm, data, id); |
| case CONFIRM: |
| return eap_eke_build_confirm(sm, data, id); |
| case FAILURE_REPORT: |
| return eap_eke_build_failure(data, id); |
| default: |
| wpa_printf(MSG_DEBUG, "EAP-EKE: Unknown state %d in buildReq", |
| data->state); |
| break; |
| } |
| return NULL; |
| } |
| |
| |
| static Boolean eap_eke_check(struct eap_sm *sm, void *priv, |
| struct wpabuf *respData) |
| { |
| struct eap_eke_data *data = priv; |
| size_t len; |
| const u8 *pos; |
| u8 eke_exch; |
| |
| pos = eap_hdr_validate(EAP_VENDOR_IETF, EAP_TYPE_EKE, respData, &len); |
| if (pos == NULL || len < 1) { |
| wpa_printf(MSG_INFO, "EAP-EKE: Invalid frame"); |
| return TRUE; |
| } |
| |
| eke_exch = *pos; |
| wpa_printf(MSG_DEBUG, "EAP-EKE: Received frame: EKE-Exch=%d", eke_exch); |
| |
| if (data->state == IDENTITY && eke_exch == EAP_EKE_ID) |
| return FALSE; |
| |
| if (data->state == COMMIT && eke_exch == EAP_EKE_COMMIT) |
| return FALSE; |
| |
| if (data->state == CONFIRM && eke_exch == EAP_EKE_CONFIRM) |
| return FALSE; |
| |
| if (eke_exch == EAP_EKE_FAILURE) |
| return FALSE; |
| |
| wpa_printf(MSG_INFO, "EAP-EKE: Unexpected EKE-Exch=%d in state=%d", |
| eke_exch, data->state); |
| |
| return TRUE; |
| } |
| |
| |
| static void eap_eke_process_identity(struct eap_sm *sm, |
| struct eap_eke_data *data, |
| const struct wpabuf *respData, |
| const u8 *payload, size_t payloadlen) |
| { |
| const u8 *pos, *end; |
| int i; |
| |
| wpa_printf(MSG_DEBUG, "EAP-EKE: Received Response/Identity"); |
| |
| if (data->state != IDENTITY) { |
| eap_eke_fail(data, EAP_EKE_FAIL_PROTO_ERROR); |
| return; |
| } |
| |
| pos = payload; |
| end = payload + payloadlen; |
| |
| if (pos + 2 + 4 + 1 > end) { |
| wpa_printf(MSG_INFO, "EAP-EKE: Too short EAP-EKE-ID payload"); |
| eap_eke_fail(data, EAP_EKE_FAIL_PROTO_ERROR); |
| return; |
| } |
| |
| if (*pos != 1) { |
| wpa_printf(MSG_INFO, "EAP-EKE: Unexpected NumProposals %d (expected 1)", |
| *pos); |
| eap_eke_fail(data, EAP_EKE_FAIL_PROTO_ERROR); |
| return; |
| } |
| |
| pos += 2; |
| |
| if (!supported_proposal(pos)) { |
| wpa_printf(MSG_INFO, "EAP-EKE: Unexpected Proposal (%u:%u:%u:%u)", |
| pos[0], pos[1], pos[2], pos[3]); |
| eap_eke_fail(data, EAP_EKE_FAIL_PROTO_ERROR); |
| return; |
| } |
| |
| wpa_printf(MSG_DEBUG, "EAP-EKE: Selected Proposal (%u:%u:%u:%u)", |
| pos[0], pos[1], pos[2], pos[3]); |
| if (eap_eke_session_init(&data->sess, pos[0], pos[1], pos[2], pos[3]) < |
| 0) { |
| eap_eke_fail(data, EAP_EKE_FAIL_PRIVATE_INTERNAL_ERROR); |
| return; |
| } |
| pos += 4; |
| |
| data->peerid_type = *pos++; |
| os_free(data->peerid); |
| data->peerid = os_malloc(end - pos); |
| if (data->peerid == NULL) { |
| wpa_printf(MSG_INFO, "EAP-EKE: Failed to allocate memory for peerid"); |
| eap_eke_fail(data, EAP_EKE_FAIL_PRIVATE_INTERNAL_ERROR); |
| return; |
| } |
| os_memcpy(data->peerid, pos, end - pos); |
| data->peerid_len = end - pos; |
| wpa_printf(MSG_DEBUG, "EAP-EKE: Peer IDType %u", data->peerid_type); |
| wpa_hexdump_ascii(MSG_DEBUG, "EAP-EKE: Peer Identity", |
| data->peerid, data->peerid_len); |
| |
| if (eap_user_get(sm, data->peerid, data->peerid_len, data->phase2)) { |
| wpa_printf(MSG_INFO, "EAP-EKE: Peer Identity not found from user database"); |
| eap_eke_fail(data, EAP_EKE_FAIL_PASSWD_NOT_FOUND); |
| return; |
| } |
| |
| for (i = 0; i < EAP_MAX_METHODS; i++) { |
| if (sm->user->methods[i].vendor == EAP_VENDOR_IETF && |
| sm->user->methods[i].method == EAP_TYPE_EKE) |
| break; |
| } |
| if (i == EAP_MAX_METHODS) { |
| wpa_printf(MSG_INFO, "EAP-EKE: Matching user entry does not allow EAP-EKE"); |
| eap_eke_fail(data, EAP_EKE_FAIL_PASSWD_NOT_FOUND); |
| return; |
| } |
| |
| if (sm->user->password == NULL || sm->user->password_len == 0) { |
| wpa_printf(MSG_INFO, "EAP-EKE: No password configured for peer"); |
| eap_eke_fail(data, EAP_EKE_FAIL_PASSWD_NOT_FOUND); |
| return; |
| } |
| |
| if (wpabuf_resize(&data->msgs, wpabuf_len(respData)) < 0) { |
| eap_eke_fail(data, EAP_EKE_FAIL_PRIVATE_INTERNAL_ERROR); |
| return; |
| } |
| wpabuf_put_buf(data->msgs, respData); |
| |
| eap_eke_state(data, COMMIT); |
| } |
| |
| |
| static void eap_eke_process_commit(struct eap_sm *sm, |
| struct eap_eke_data *data, |
| const struct wpabuf *respData, |
| const u8 *payload, size_t payloadlen) |
| { |
| const u8 *pos, *end, *dhcomp, *pnonce; |
| size_t decrypt_len; |
| |
| wpa_printf(MSG_DEBUG, "EAP-EKE: Received Response/Commit"); |
| |
| if (data->state != COMMIT) { |
| eap_eke_fail(data, EAP_EKE_FAIL_PROTO_ERROR); |
| return; |
| } |
| |
| pos = payload; |
| end = payload + payloadlen; |
| |
| if (pos + data->sess.dhcomp_len + data->sess.pnonce_len > end) { |
| wpa_printf(MSG_DEBUG, "EAP-EKE: Too short EAP-EKE-Commit"); |
| eap_eke_fail(data, EAP_EKE_FAIL_PROTO_ERROR); |
| return; |
| } |
| |
| wpa_hexdump(MSG_DEBUG, "EAP-EKE: DHComponent_P", |
| pos, data->sess.dhcomp_len); |
| dhcomp = pos; |
| pos += data->sess.dhcomp_len; |
| wpa_hexdump(MSG_DEBUG, "EAP-EKE: PNonce_P", pos, data->sess.pnonce_len); |
| pnonce = pos; |
| pos += data->sess.pnonce_len; |
| wpa_hexdump(MSG_DEBUG, "EAP-EKE: CBValue", pos, end - pos); |
| |
| if (eap_eke_shared_secret(&data->sess, data->key, data->dh_priv, dhcomp) |
| < 0) { |
| wpa_printf(MSG_INFO, "EAP-EKE: Failed to derive shared secret"); |
| eap_eke_fail(data, EAP_EKE_FAIL_PRIVATE_INTERNAL_ERROR); |
| return; |
| } |
| |
| if (eap_eke_derive_ke_ki(&data->sess, |
| sm->server_id, sm->server_id_len, |
| data->peerid, data->peerid_len) < 0) { |
| wpa_printf(MSG_INFO, "EAP-EKE: Failed to derive Ke/Ki"); |
| eap_eke_fail(data, EAP_EKE_FAIL_PRIVATE_INTERNAL_ERROR); |
| return; |
| } |
| |
| decrypt_len = sizeof(data->nonce_p); |
| if (eap_eke_decrypt_prot(&data->sess, pnonce, data->sess.pnonce_len, |
| data->nonce_p, &decrypt_len) < 0) { |
| wpa_printf(MSG_INFO, "EAP-EKE: Failed to decrypt PNonce_P"); |
| eap_eke_fail(data, EAP_EKE_FAIL_AUTHENTICATION_FAIL); |
| return; |
| } |
| if (decrypt_len < (size_t) data->sess.nonce_len) { |
| wpa_printf(MSG_INFO, "EAP-EKE: PNonce_P protected data too short to include Nonce_P"); |
| eap_eke_fail(data, EAP_EKE_FAIL_AUTHENTICATION_FAIL); |
| return; |
| } |
| wpa_hexdump_key(MSG_DEBUG, "EAP-EKE: Nonce_P", |
| data->nonce_p, data->sess.nonce_len); |
| |
| if (wpabuf_resize(&data->msgs, wpabuf_len(respData)) < 0) { |
| eap_eke_fail(data, EAP_EKE_FAIL_PRIVATE_INTERNAL_ERROR); |
| return; |
| } |
| wpabuf_put_buf(data->msgs, respData); |
| |
| eap_eke_state(data, CONFIRM); |
| } |
| |
| |
| static void eap_eke_process_confirm(struct eap_sm *sm, |
| struct eap_eke_data *data, |
| const struct wpabuf *respData, |
| const u8 *payload, size_t payloadlen) |
| { |
| size_t decrypt_len; |
| u8 nonce[EAP_EKE_MAX_NONCE_LEN]; |
| u8 auth_p[EAP_EKE_MAX_HASH_LEN]; |
| |
| wpa_printf(MSG_DEBUG, "EAP-EKE: Received Response/Confirm"); |
| |
| if (data->state != CONFIRM) { |
| eap_eke_fail(data, EAP_EKE_FAIL_PROTO_ERROR); |
| return; |
| } |
| |
| wpa_printf(MSG_DEBUG, "EAP-EKE: Received Response/Confirm"); |
| |
| if (payloadlen < (size_t) data->sess.pnonce_len + data->sess.prf_len) { |
| wpa_printf(MSG_DEBUG, "EAP-EKE: Too short EAP-EKE-Confirm"); |
| eap_eke_fail(data, EAP_EKE_FAIL_PROTO_ERROR); |
| return; |
| } |
| |
| decrypt_len = sizeof(nonce); |
| if (eap_eke_decrypt_prot(&data->sess, payload, data->sess.pnonce_len, |
| nonce, &decrypt_len) < 0) { |
| wpa_printf(MSG_INFO, "EAP-EKE: Failed to decrypt PNonce_S"); |
| eap_eke_fail(data, EAP_EKE_FAIL_AUTHENTICATION_FAIL); |
| return; |
| } |
| if (decrypt_len < (size_t) data->sess.nonce_len) { |
| wpa_printf(MSG_INFO, "EAP-EKE: PNonce_S protected data too short to include Nonce_S"); |
| eap_eke_fail(data, EAP_EKE_FAIL_AUTHENTICATION_FAIL); |
| return; |
| } |
| wpa_hexdump_key(MSG_DEBUG, "EAP-EKE: Received Nonce_S", |
| nonce, data->sess.nonce_len); |
| if (os_memcmp(nonce, data->nonce_s, data->sess.nonce_len) != 0) { |
| wpa_printf(MSG_INFO, "EAP-EKE: Received Nonce_S does not match previously sent Nonce_S"); |
| eap_eke_fail(data, EAP_EKE_FAIL_AUTHENTICATION_FAIL); |
| return; |
| } |
| |
| if (eap_eke_auth(&data->sess, "EAP-EKE peer", data->msgs, auth_p) < 0) { |
| wpa_printf(MSG_INFO, "EAP-EKE: Could not derive Auth_P"); |
| eap_eke_fail(data, EAP_EKE_FAIL_PRIVATE_INTERNAL_ERROR); |
| return; |
| } |
| wpa_hexdump(MSG_DEBUG, "EAP-EKE: Auth_P", auth_p, data->sess.prf_len); |
| if (os_memcmp_const(auth_p, payload + data->sess.pnonce_len, |
| data->sess.prf_len) != 0) { |
| wpa_printf(MSG_INFO, "EAP-EKE: Auth_P does not match"); |
| eap_eke_fail(data, EAP_EKE_FAIL_AUTHENTICATION_FAIL); |
| return; |
| } |
| |
| if (eap_eke_derive_msk(&data->sess, sm->server_id, sm->server_id_len, |
| data->peerid, data->peerid_len, |
| data->nonce_s, data->nonce_p, |
| data->msk, data->emsk) < 0) { |
| wpa_printf(MSG_INFO, "EAP-EKE: Failed to derive MSK/EMSK"); |
| eap_eke_fail(data, EAP_EKE_FAIL_PRIVATE_INTERNAL_ERROR); |
| return; |
| } |
| |
| os_memset(data->dh_priv, 0, sizeof(data->dh_priv)); |
| os_memset(data->key, 0, sizeof(data->key)); |
| eap_eke_session_clean(&data->sess); |
| |
| eap_eke_state(data, SUCCESS); |
| } |
| |
| |
| static void eap_eke_process_failure(struct eap_sm *sm, |
| struct eap_eke_data *data, |
| const struct wpabuf *respData, |
| const u8 *payload, size_t payloadlen) |
| { |
| u32 code; |
| |
| wpa_printf(MSG_DEBUG, "EAP-EKE: Received Response/Failure"); |
| |
| if (payloadlen < 4) { |
| wpa_printf(MSG_DEBUG, "EAP-EKE: Too short EAP-EKE-Failure"); |
| eap_eke_state(data, FAILURE); |
| return; |
| } |
| |
| code = WPA_GET_BE32(payload); |
| wpa_printf(MSG_DEBUG, "EAP-EKE: Peer reported failure code 0x%x", code); |
| |
| eap_eke_state(data, FAILURE); |
| } |
| |
| |
| static void eap_eke_process(struct eap_sm *sm, void *priv, |
| struct wpabuf *respData) |
| { |
| struct eap_eke_data *data = priv; |
| u8 eke_exch; |
| size_t len; |
| const u8 *pos, *end; |
| |
| pos = eap_hdr_validate(EAP_VENDOR_IETF, EAP_TYPE_EKE, respData, &len); |
| if (pos == NULL || len < 1) |
| return; |
| |
| eke_exch = *pos; |
| end = pos + len; |
| pos++; |
| |
| wpa_hexdump(MSG_DEBUG, "EAP-EKE: Received payload", pos, end - pos); |
| |
| switch (eke_exch) { |
| case EAP_EKE_ID: |
| eap_eke_process_identity(sm, data, respData, pos, end - pos); |
| break; |
| case EAP_EKE_COMMIT: |
| eap_eke_process_commit(sm, data, respData, pos, end - pos); |
| break; |
| case EAP_EKE_CONFIRM: |
| eap_eke_process_confirm(sm, data, respData, pos, end - pos); |
| break; |
| case EAP_EKE_FAILURE: |
| eap_eke_process_failure(sm, data, respData, pos, end - pos); |
| break; |
| } |
| } |
| |
| |
| static Boolean eap_eke_isDone(struct eap_sm *sm, void *priv) |
| { |
| struct eap_eke_data *data = priv; |
| return data->state == SUCCESS || data->state == FAILURE; |
| } |
| |
| |
| static u8 * eap_eke_getKey(struct eap_sm *sm, void *priv, size_t *len) |
| { |
| struct eap_eke_data *data = priv; |
| u8 *key; |
| |
| if (data->state != SUCCESS) |
| return NULL; |
| |
| key = os_malloc(EAP_MSK_LEN); |
| if (key == NULL) |
| return NULL; |
| os_memcpy(key, data->msk, EAP_MSK_LEN); |
| *len = EAP_MSK_LEN; |
| |
| return key; |
| } |
| |
| |
| static u8 * eap_eke_get_emsk(struct eap_sm *sm, void *priv, size_t *len) |
| { |
| struct eap_eke_data *data = priv; |
| u8 *key; |
| |
| if (data->state != SUCCESS) |
| return NULL; |
| |
| key = os_malloc(EAP_EMSK_LEN); |
| if (key == NULL) |
| return NULL; |
| os_memcpy(key, data->emsk, EAP_EMSK_LEN); |
| *len = EAP_EMSK_LEN; |
| |
| return key; |
| } |
| |
| |
| static Boolean eap_eke_isSuccess(struct eap_sm *sm, void *priv) |
| { |
| struct eap_eke_data *data = priv; |
| return data->state == SUCCESS; |
| } |
| |
| |
| int eap_server_eke_register(void) |
| { |
| struct eap_method *eap; |
| int ret; |
| |
| eap = eap_server_method_alloc(EAP_SERVER_METHOD_INTERFACE_VERSION, |
| EAP_VENDOR_IETF, EAP_TYPE_EKE, "EKE"); |
| if (eap == NULL) |
| return -1; |
| |
| eap->init = eap_eke_init; |
| eap->reset = eap_eke_reset; |
| eap->buildReq = eap_eke_buildReq; |
| eap->check = eap_eke_check; |
| eap->process = eap_eke_process; |
| eap->isDone = eap_eke_isDone; |
| eap->getKey = eap_eke_getKey; |
| eap->isSuccess = eap_eke_isSuccess; |
| eap->get_emsk = eap_eke_get_emsk; |
| |
| ret = eap_server_method_register(eap); |
| if (ret) |
| eap_server_method_free(eap); |
| return ret; |
| } |