diff --git a/src/eap_common/eap_defs.h b/src/eap_common/eap_defs.h index 0d247c4d1..f5890bec2 100644 --- a/src/eap_common/eap_defs.h +++ b/src/eap_common/eap_defs.h @@ -63,6 +63,7 @@ typedef enum { EAP_TYPE_AKA_PRIME = 50 /* RFC 5448 */, EAP_TYPE_GPSK = 51 /* RFC 5433 */, EAP_TYPE_PWD = 52 /* RFC 5931 */, + EAP_TYPE_EKE = 53 /* RFC 6124 */, EAP_TYPE_EXPANDED = 254 /* RFC 3748 */ } EapType; diff --git a/src/eap_common/eap_eke_common.c b/src/eap_common/eap_eke_common.c new file mode 100644 index 000000000..a62ac8e04 --- /dev/null +++ b/src/eap_common/eap_eke_common.c @@ -0,0 +1,768 @@ +/* + * EAP server/peer: EAP-EKE shared routines + * Copyright (c) 2011-2013, Jouni Malinen + * + * 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/aes.h" +#include "crypto/aes_wrap.h" +#include "crypto/crypto.h" +#include "crypto/dh_groups.h" +#include "crypto/random.h" +#include "crypto/sha1.h" +#include "crypto/sha256.h" +#include "eap_common/eap_defs.h" +#include "eap_eke_common.h" + + +static int eap_eke_dh_len(u8 group) +{ + switch (group) { + case EAP_EKE_DHGROUP_EKE_2: + return 128; + case EAP_EKE_DHGROUP_EKE_5: + return 192; + case EAP_EKE_DHGROUP_EKE_14: + return 256; + case EAP_EKE_DHGROUP_EKE_15: + return 384; + case EAP_EKE_DHGROUP_EKE_16: + return 512; + } + + return -1; +} + + +static int eap_eke_dhcomp_len(u8 dhgroup, u8 encr) +{ + int dhlen; + + dhlen = eap_eke_dh_len(dhgroup); + if (dhlen < 0) + return -1; + if (encr != EAP_EKE_ENCR_AES128_CBC) + return -1; + return AES_BLOCK_SIZE + dhlen; +} + + +static const struct dh_group * eap_eke_dh_group(u8 group) +{ + switch (group) { + case EAP_EKE_DHGROUP_EKE_2: + return dh_groups_get(2); + case EAP_EKE_DHGROUP_EKE_5: + return dh_groups_get(5); + case EAP_EKE_DHGROUP_EKE_14: + return dh_groups_get(14); + case EAP_EKE_DHGROUP_EKE_15: + return dh_groups_get(15); + case EAP_EKE_DHGROUP_EKE_16: + return dh_groups_get(16); + } + + return NULL; +} + + +static int eap_eke_dh_generator(u8 group) +{ + switch (group) { + case EAP_EKE_DHGROUP_EKE_2: + return 5; + case EAP_EKE_DHGROUP_EKE_5: + return 31; + case EAP_EKE_DHGROUP_EKE_14: + return 11; + case EAP_EKE_DHGROUP_EKE_15: + return 5; + case EAP_EKE_DHGROUP_EKE_16: + return 5; + } + + return -1; +} + + +static int eap_eke_pnonce_len(u8 mac) +{ + int mac_len; + + if (mac == EAP_EKE_MAC_HMAC_SHA1) + mac_len = SHA1_MAC_LEN; + else if (mac == EAP_EKE_MAC_HMAC_SHA2_256) + mac_len = SHA256_MAC_LEN; + else + return -1; + + return AES_BLOCK_SIZE + 16 + mac_len; +} + + +static int eap_eke_pnonce_ps_len(u8 mac) +{ + int mac_len; + + if (mac == EAP_EKE_MAC_HMAC_SHA1) + mac_len = SHA1_MAC_LEN; + else if (mac == EAP_EKE_MAC_HMAC_SHA2_256) + mac_len = SHA256_MAC_LEN; + else + return -1; + + return AES_BLOCK_SIZE + 2 * 16 + mac_len; +} + + +static int eap_eke_prf_len(u8 prf) +{ + if (prf == EAP_EKE_PRF_HMAC_SHA1) + return 20; + if (prf == EAP_EKE_PRF_HMAC_SHA2_256) + return 32; + return -1; +} + + +static int eap_eke_nonce_len(u8 prf) +{ + int prf_len; + + prf_len = eap_eke_prf_len(prf); + if (prf_len < 0) + return -1; + + if (prf_len > 2 * 16) + return (prf_len + 1) / 2; + + return 16; +} + + +static int eap_eke_auth_len(u8 prf) +{ + switch (prf) { + case EAP_EKE_PRF_HMAC_SHA1: + return SHA1_MAC_LEN; + case EAP_EKE_PRF_HMAC_SHA2_256: + return SHA256_MAC_LEN; + } + + return -1; +} + + +int eap_eke_dh_init(u8 group, u8 *ret_priv, u8 *ret_pub) +{ + int generator; + u8 gen; + const struct dh_group *dh; + size_t pub_len, i; + + generator = eap_eke_dh_generator(group); + if (generator < 0 || generator > 255) + return -1; + gen = generator; + + dh = eap_eke_dh_group(group); + if (dh == NULL) + return -1; + + /* x = random number 2 .. p-1 */ + if (random_get_bytes(ret_priv, dh->prime_len)) + return -1; + if (os_memcmp(ret_priv, dh->prime, dh->prime_len) > 0) { + /* Make sure private value is smaller than prime */ + ret_priv[0] = 0; + } + for (i = 0; i < dh->prime_len - 1; i++) { + if (ret_priv[i]) + break; + } + if (i == dh->prime_len - 1 && (ret_priv[i] == 0 || ret_priv[i] == 1)) + return -1; + wpa_hexdump_key(MSG_DEBUG, "EAP-EKE: DH private value", + ret_priv, dh->prime_len); + + /* y = g ^ x (mod p) */ + pub_len = dh->prime_len; + if (crypto_mod_exp(&gen, 1, ret_priv, dh->prime_len, + dh->prime, dh->prime_len, ret_pub, &pub_len) < 0) + return -1; + if (pub_len < dh->prime_len) { + size_t pad = dh->prime_len - pub_len; + os_memmove(ret_pub + pad, ret_pub, pub_len); + os_memset(ret_pub, 0, pad); + } + + wpa_hexdump(MSG_DEBUG, "EAP-EKE: DH public value", + ret_pub, dh->prime_len); + + return 0; +} + + +static int eap_eke_prf(u8 prf, const u8 *key, size_t key_len, const u8 *data, + size_t data_len, const u8 *data2, size_t data2_len, + u8 *res) +{ + const u8 *addr[2]; + size_t len[2]; + size_t num_elem = 1; + + addr[0] = data; + len[0] = data_len; + if (data2) { + num_elem++; + addr[1] = data2; + len[1] = data2_len; + } + + if (prf == EAP_EKE_PRF_HMAC_SHA1) + return hmac_sha1_vector(key, key_len, num_elem, addr, len, res); + if (prf == EAP_EKE_PRF_HMAC_SHA2_256) + return hmac_sha256_vector(key, key_len, num_elem, addr, len, + res); + return -1; +} + + +static int eap_eke_prf_hmac_sha1(const u8 *key, size_t key_len, const u8 *data, + size_t data_len, u8 *res, size_t len) +{ + u8 hash[SHA1_MAC_LEN]; + u8 idx; + const u8 *addr[3]; + size_t vlen[3]; + int ret; + + idx = 0; + addr[0] = hash; + vlen[0] = SHA1_MAC_LEN; + addr[1] = data; + vlen[1] = data_len; + addr[2] = &idx; + vlen[2] = 1; + + while (len > 0) { + idx++; + if (idx == 1) + ret = hmac_sha1_vector(key, key_len, 2, &addr[1], + &vlen[1], hash); + else + ret = hmac_sha1_vector(key, key_len, 3, addr, vlen, + hash); + if (ret < 0) + return -1; + if (len > SHA1_MAC_LEN) { + os_memcpy(res, hash, SHA1_MAC_LEN); + res += SHA1_MAC_LEN; + len -= SHA1_MAC_LEN; + } else { + os_memcpy(res, hash, len); + len = 0; + } + } + + return 0; +} + + +static int eap_eke_prf_hmac_sha256(const u8 *key, size_t key_len, const u8 *data, + size_t data_len, u8 *res, size_t len) +{ + u8 hash[SHA256_MAC_LEN]; + u8 idx; + const u8 *addr[3]; + size_t vlen[3]; + int ret; + + idx = 0; + addr[0] = hash; + vlen[0] = SHA256_MAC_LEN; + addr[1] = data; + vlen[1] = data_len; + addr[2] = &idx; + vlen[2] = 1; + + while (len > 0) { + idx++; + if (idx == 1) + ret = hmac_sha256_vector(key, key_len, 2, &addr[1], + &vlen[1], hash); + else + ret = hmac_sha256_vector(key, key_len, 3, addr, vlen, + hash); + if (ret < 0) + return -1; + if (len > SHA256_MAC_LEN) { + os_memcpy(res, hash, SHA256_MAC_LEN); + res += SHA256_MAC_LEN; + len -= SHA256_MAC_LEN; + } else { + os_memcpy(res, hash, len); + len = 0; + } + } + + return 0; +} + + +static int eap_eke_prfplus(u8 prf, const u8 *key, size_t key_len, + const u8 *data, size_t data_len, u8 *res, size_t len) +{ + if (prf == EAP_EKE_PRF_HMAC_SHA1) + return eap_eke_prf_hmac_sha1(key, key_len, data, data_len, res, + len); + if (prf == EAP_EKE_PRF_HMAC_SHA2_256) + return eap_eke_prf_hmac_sha256(key, key_len, data, data_len, + res, len); + return -1; +} + + +int eap_eke_derive_key(struct eap_eke_session *sess, + const u8 *password, size_t password_len, + const u8 *id_s, size_t id_s_len, const u8 *id_p, + size_t id_p_len, u8 *key) +{ + u8 zeros[EAP_EKE_MAX_HASH_LEN]; + u8 temp[EAP_EKE_MAX_HASH_LEN]; + size_t key_len = 16; /* Only AES-128-CBC is used here */ + u8 *id; + + /* temp = prf(0+, password) */ + os_memset(zeros, 0, sess->prf_len); + if (eap_eke_prf(sess->prf, zeros, sess->prf_len, + password, password_len, NULL, 0, temp) < 0) + return -1; + wpa_hexdump_key(MSG_DEBUG, "EAP-EKE: temp = prf(0+, password)", + temp, sess->prf_len); + + /* key = prf+(temp, ID_S | ID_P) */ + id = os_malloc(id_s_len + id_p_len); + if (id == NULL) + return -1; + os_memcpy(id, id_s, id_s_len); + os_memcpy(id + id_s_len, id_p, id_p_len); + wpa_hexdump_ascii(MSG_DEBUG, "EAP-EKE: ID_S | ID_P", + id, id_s_len + id_p_len); + if (eap_eke_prfplus(sess->prf, temp, sess->prf_len, + id, id_s_len + id_p_len, key, key_len) < 0) { + os_free(id); + return -1; + } + os_free(id); + wpa_hexdump_key(MSG_DEBUG, "EAP-EKE: key = prf+(temp, ID_S | ID_P)", + key, key_len); + + return 0; +} + + +int eap_eke_dhcomp(struct eap_eke_session *sess, const u8 *key, const u8 *dhpub, + u8 *ret_dhcomp) +{ + u8 pub[EAP_EKE_MAX_DH_LEN]; + int dh_len; + u8 iv[AES_BLOCK_SIZE]; + + dh_len = eap_eke_dh_len(sess->dhgroup); + if (dh_len < 0) + return -1; + + /* + * DHComponent = Encr(key, y) + * + * All defined DH groups use primes that have length devisible by 16, so + * no need to do extra padding for y (= pub). + */ + if (sess->encr != EAP_EKE_ENCR_AES128_CBC) + return -1; + if (random_get_bytes(iv, AES_BLOCK_SIZE)) + return -1; + wpa_hexdump(MSG_DEBUG, "EAP-EKE: IV for Encr(key, y)", + iv, AES_BLOCK_SIZE); + os_memcpy(pub, dhpub, dh_len); + if (aes_128_cbc_encrypt(key, iv, pub, dh_len) < 0) + return -1; + os_memcpy(ret_dhcomp, iv, AES_BLOCK_SIZE); + os_memcpy(ret_dhcomp + AES_BLOCK_SIZE, pub, dh_len); + wpa_hexdump(MSG_DEBUG, "EAP-EKE: DHComponent = Encr(key, y)", + ret_dhcomp, AES_BLOCK_SIZE + dh_len); + + return 0; +} + + +int eap_eke_shared_secret(struct eap_eke_session *sess, const u8 *key, + const u8 *dhpriv, const u8 *peer_dhcomp) +{ + u8 zeros[EAP_EKE_MAX_HASH_LEN]; + u8 peer_pub[EAP_EKE_MAX_DH_LEN]; + u8 modexp[EAP_EKE_MAX_DH_LEN]; + size_t len; + const struct dh_group *dh; + + if (sess->encr != EAP_EKE_ENCR_AES128_CBC) + return -1; + + dh = eap_eke_dh_group(sess->dhgroup); + if (dh == NULL) + return -1; + + /* Decrypt peer DHComponent */ + os_memcpy(peer_pub, peer_dhcomp + AES_BLOCK_SIZE, dh->prime_len); + if (aes_128_cbc_decrypt(key, peer_dhcomp, peer_pub, dh->prime_len) < 0) { + wpa_printf(MSG_INFO, "EAP-EKE: Failed to decrypt DHComponent"); + return -1; + } + wpa_hexdump_key(MSG_DEBUG, "EAP-EKE: Decrypted peer DH pubkey", + peer_pub, dh->prime_len); + + /* SharedSecret = prf(0+, g ^ (x_s * x_p) (mod p)) */ + len = dh->prime_len; + if (crypto_mod_exp(peer_pub, dh->prime_len, dhpriv, dh->prime_len, + dh->prime, dh->prime_len, modexp, &len) < 0) + return -1; + if (len < dh->prime_len) { + size_t pad = dh->prime_len - len; + os_memmove(modexp + pad, modexp, len); + os_memset(modexp, 0, pad); + } + + os_memset(zeros, 0, sess->auth_len); + if (eap_eke_prf(sess->prf, zeros, sess->auth_len, modexp, dh->prime_len, + NULL, 0, sess->shared_secret) < 0) + return -1; + wpa_hexdump_key(MSG_DEBUG, "EAP-EKE: SharedSecret", + sess->shared_secret, sess->auth_len); + + return 0; +} + + +int eap_eke_derive_ke_ki(struct eap_eke_session *sess, + const u8 *id_s, size_t id_s_len, + const u8 *id_p, size_t id_p_len) +{ + u8 buf[EAP_EKE_MAX_KE_LEN + EAP_EKE_MAX_KI_LEN]; + size_t ke_len, ki_len; + u8 *data; + size_t data_len; + const char *label = "EAP-EKE Keys"; + size_t label_len; + + /* + * Ke | Ki = prf+(SharedSecret, "EAP-EKE Keys" | ID_S | ID_P) + * Ke = encryption key + * Ki = integrity protection key + * Length of each key depends on the selected algorithms. + */ + + if (sess->encr == EAP_EKE_ENCR_AES128_CBC) + ke_len = 16; + else + return -1; + + if (sess->mac == EAP_EKE_PRF_HMAC_SHA1) + ki_len = 20; + else if (sess->mac == EAP_EKE_PRF_HMAC_SHA2_256) + ki_len = 32; + else + return -1; + + label_len = os_strlen(label); + data_len = label_len + id_s_len + id_p_len; + data = os_malloc(data_len); + if (data == NULL) + return -1; + os_memcpy(data, label, label_len); + os_memcpy(data + label_len, id_s, id_s_len); + os_memcpy(data + label_len + id_s_len, id_p, id_p_len); + if (eap_eke_prfplus(sess->prf, sess->shared_secret, sess->prf_len, + data, data_len, buf, ke_len + ki_len) < 0) { + os_free(data); + return -1; + } + + os_memcpy(sess->ke, buf, ke_len); + wpa_hexdump_key(MSG_DEBUG, "EAP-EKE: Ke", sess->ke, ke_len); + os_memcpy(sess->ki, buf + ke_len, ki_len); + wpa_hexdump_key(MSG_DEBUG, "EAP-EKE: Ki", sess->ki, ki_len); + + os_free(data); + return 0; +} + + +int eap_eke_derive_ka(struct eap_eke_session *sess, + const u8 *id_s, size_t id_s_len, + const u8 *id_p, size_t id_p_len, + const u8 *nonce_p, const u8 *nonce_s) +{ + u8 *data, *pos; + size_t data_len; + const char *label = "EAP-EKE Ka"; + size_t label_len; + + /* + * Ka = prf+(SharedSecret, "EAP-EKE Ka" | ID_S | ID_P | Nonce_P | + * Nonce_S) + * Ka = authentication key + * Length of the key depends on the selected algorithms. + */ + + label_len = os_strlen(label); + data_len = label_len + id_s_len + id_p_len + 2 * sess->nonce_len; + data = os_malloc(data_len); + if (data == NULL) + return -1; + pos = data; + os_memcpy(pos, label, label_len); + pos += label_len; + os_memcpy(pos, id_s, id_s_len); + pos += id_s_len; + os_memcpy(pos, id_p, id_p_len); + pos += id_p_len; + os_memcpy(pos, nonce_p, sess->nonce_len); + pos += sess->nonce_len; + os_memcpy(pos, nonce_s, sess->nonce_len); + if (eap_eke_prfplus(sess->prf, sess->shared_secret, sess->prf_len, + data, data_len, sess->ka, sess->prf_len) < 0) { + os_free(data); + return -1; + } + os_free(data); + + wpa_hexdump_key(MSG_DEBUG, "EAP-EKE: Ka", sess->ka, sess->prf_len); + + return 0; +} + + +int eap_eke_derive_msk(struct eap_eke_session *sess, + const u8 *id_s, size_t id_s_len, + const u8 *id_p, size_t id_p_len, + const u8 *nonce_p, const u8 *nonce_s, + u8 *msk, u8 *emsk) +{ + u8 *data, *pos; + size_t data_len; + const char *label = "EAP-EKE Exported Keys"; + size_t label_len; + u8 buf[EAP_MSK_LEN + EAP_EMSK_LEN]; + + /* + * MSK | EMSK = prf+(SharedSecret, "EAP-EKE Exported Keys" | ID_S | + * ID_P | Nonce_P | Nonce_S) + */ + + label_len = os_strlen(label); + data_len = label_len + id_s_len + id_p_len + 2 * sess->nonce_len; + data = os_malloc(data_len); + if (data == NULL) + return -1; + pos = data; + os_memcpy(pos, label, label_len); + pos += label_len; + os_memcpy(pos, id_s, id_s_len); + pos += id_s_len; + os_memcpy(pos, id_p, id_p_len); + pos += id_p_len; + os_memcpy(pos, nonce_p, sess->nonce_len); + pos += sess->nonce_len; + os_memcpy(pos, nonce_s, sess->nonce_len); + if (eap_eke_prfplus(sess->prf, sess->shared_secret, sess->prf_len, + data, data_len, buf, EAP_MSK_LEN + EAP_EMSK_LEN) < + 0) { + os_free(data); + return -1; + } + os_free(data); + + os_memcpy(msk, buf, EAP_MSK_LEN); + os_memcpy(emsk, buf + EAP_MSK_LEN, EAP_EMSK_LEN); + os_memset(buf, 0, sizeof(buf)); + + wpa_hexdump_key(MSG_DEBUG, "EAP-EKE: MSK", msk, EAP_MSK_LEN); + wpa_hexdump_key(MSG_DEBUG, "EAP-EKE: EMSK", msk, EAP_EMSK_LEN); + + return 0; +} + + +static int eap_eke_mac(u8 mac, const u8 *key, const u8 *data, size_t data_len, + u8 *res) +{ + if (mac == EAP_EKE_MAC_HMAC_SHA1) + return hmac_sha1(key, SHA1_MAC_LEN, data, data_len, res); + if (mac == EAP_EKE_MAC_HMAC_SHA2_256) + return hmac_sha256(key, SHA256_MAC_LEN, data, data_len, res); + return -1; +} + + +int eap_eke_prot(struct eap_eke_session *sess, + const u8 *data, size_t data_len, + u8 *prot, size_t *prot_len) +{ + size_t block_size, icv_len, pad; + u8 *pos, *iv, *e; + + if (sess->encr == EAP_EKE_ENCR_AES128_CBC) + block_size = AES_BLOCK_SIZE; + else + return -1; + + if (sess->mac == EAP_EKE_PRF_HMAC_SHA1) + icv_len = SHA1_MAC_LEN; + else if (sess->mac == EAP_EKE_PRF_HMAC_SHA2_256) + icv_len = SHA256_MAC_LEN; + else + return -1; + + pad = data_len % block_size; + if (pad) + pad = block_size - pad; + + if (*prot_len < block_size + data_len + pad + icv_len) { + wpa_printf(MSG_INFO, "EAP-EKE: Not enough room for Prot() data"); + } + pos = prot; + + if (random_get_bytes(pos, block_size)) + return -1; + iv = pos; + wpa_hexdump(MSG_DEBUG, "EAP-EKE: IV for Prot()", iv, block_size); + pos += block_size; + + e = pos; + os_memcpy(pos, data, data_len); + pos += data_len; + if (pad) { + if (random_get_bytes(pos, pad)) + return -1; + pos += pad; + } + + if (aes_128_cbc_encrypt(sess->ke, iv, e, data_len + pad) < 0) + return -1; + + if (eap_eke_mac(sess->mac, sess->ki, e, data_len + pad, pos) < 0) + return -1; + pos += icv_len; + + *prot_len = pos - prot; + return 0; +} + + +int eap_eke_decrypt_prot(struct eap_eke_session *sess, + const u8 *prot, size_t prot_len, + u8 *data, size_t *data_len) +{ + size_t block_size, icv_len; + u8 icv[EAP_EKE_MAX_HASH_LEN]; + + if (sess->encr == EAP_EKE_ENCR_AES128_CBC) + block_size = AES_BLOCK_SIZE; + else + return -1; + + if (sess->mac == EAP_EKE_PRF_HMAC_SHA1) + icv_len = SHA1_MAC_LEN; + else if (sess->mac == EAP_EKE_PRF_HMAC_SHA2_256) + icv_len = SHA256_MAC_LEN; + else + return -1; + + if (prot_len < 2 * block_size + icv_len) + return -1; + if ((prot_len - icv_len) % block_size) + return -1; + + if (eap_eke_mac(sess->mac, sess->ki, prot + block_size, + prot_len - block_size - icv_len, icv) < 0) + return -1; + if (os_memcmp(icv, prot + prot_len - icv_len, icv_len) != 0) { + wpa_printf(MSG_INFO, "EAP-EKE: ICV mismatch in Prot() data"); + return -1; + } + + if (*data_len < prot_len - block_size - icv_len) { + wpa_printf(MSG_INFO, "EAP-EKE: Not enough room for decrypted Prot() data"); + return -1; + } + + *data_len = prot_len - block_size - icv_len; + os_memcpy(data, prot + block_size, *data_len); + if (aes_128_cbc_decrypt(sess->ke, prot, data, *data_len) < 0) { + wpa_printf(MSG_INFO, "EAP-EKE: Failed to decrypt Prot() data"); + return -1; + } + wpa_hexdump_key(MSG_DEBUG, "EAP-EKE: Decrypted Prot() data", + data, *data_len); + + return 0; +} + + +int eap_eke_auth(struct eap_eke_session *sess, const char *label, + const struct wpabuf *msgs, u8 *auth) +{ + wpa_printf(MSG_DEBUG, "EAP-EKE: Auth(%s)", label); + wpa_hexdump_key(MSG_DEBUG, "EAP-EKE: Ka for Auth", + sess->ka, sess->auth_len); + wpa_hexdump_buf(MSG_MSGDUMP, "EAP-EKE: Messages for Auth", msgs); + return eap_eke_prf(sess->prf, sess->ka, sess->auth_len, + (const u8 *) label, os_strlen(label), + wpabuf_head(msgs), wpabuf_len(msgs), auth); +} + + +int eap_eke_session_init(struct eap_eke_session *sess, u8 dhgroup, u8 encr, + u8 prf, u8 mac) +{ + sess->dhgroup = dhgroup; + sess->encr = encr; + sess->prf = prf; + sess->mac = mac; + + sess->prf_len = eap_eke_prf_len(prf); + if (sess->prf_len < 0) + return -1; + sess->nonce_len = eap_eke_nonce_len(prf); + if (sess->nonce_len < 0) + return -1; + sess->auth_len = eap_eke_auth_len(prf); + if (sess->auth_len < 0) + return -1; + sess->dhcomp_len = eap_eke_dhcomp_len(sess->dhgroup, sess->encr); + if (sess->dhcomp_len < 0) + return -1; + sess->pnonce_len = eap_eke_pnonce_len(sess->mac); + if (sess->pnonce_len < 0) + return -1; + sess->pnonce_ps_len = eap_eke_pnonce_ps_len(sess->mac); + if (sess->pnonce_ps_len < 0) + return -1; + + return 0; +} + + +void eap_eke_session_clean(struct eap_eke_session *sess) +{ + os_memset(sess->shared_secret, 0, EAP_EKE_MAX_HASH_LEN); + os_memset(sess->ke, 0, EAP_EKE_MAX_KE_LEN); + os_memset(sess->ki, 0, EAP_EKE_MAX_KI_LEN); + os_memset(sess->ka, 0, EAP_EKE_MAX_KA_LEN); +} diff --git a/src/eap_common/eap_eke_common.h b/src/eap_common/eap_eke_common.h new file mode 100644 index 000000000..a4c042225 --- /dev/null +++ b/src/eap_common/eap_eke_common.h @@ -0,0 +1,114 @@ +/* + * EAP server/peer: EAP-EKE shared routines + * Copyright (c) 2011-2013, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef EAP_EKE_COMMON_H +#define EAP_EKE_COMMON_H + +/* EKE Exchange */ +#define EAP_EKE_ID 1 +#define EAP_EKE_COMMIT 2 +#define EAP_EKE_CONFIRM 3 +#define EAP_EKE_FAILURE 4 + +/* Diffie-Hellman Group Registry */ +#define EAP_EKE_DHGROUP_EKE_2 1 +#define EAP_EKE_DHGROUP_EKE_5 2 +#define EAP_EKE_DHGROUP_EKE_14 3 /* mandatory to implement */ +#define EAP_EKE_DHGROUP_EKE_15 4 +#define EAP_EKE_DHGROUP_EKE_16 5 + +/* Encryption Algorithm Registry */ +#define EAP_EKE_ENCR_AES128_CBC 1 /* mandatory to implement */ + +/* Pseudo Random Function Registry */ +#define EAP_EKE_PRF_HMAC_SHA1 1 /* mandatory to implement */ +#define EAP_EKE_PRF_HMAC_SHA2_256 2 + +/* Keyed Message Digest (MAC) Registry */ +#define EAP_EKE_MAC_HMAC_SHA1 1 /* mandatory to implement */ +#define EAP_EKE_MAC_HMAC_SHA2_256 2 + +/* Identity Type Registry */ +#define EAP_EKE_ID_OPAQUE 1 +#define EAP_EKE_ID_NAI 2 +#define EAP_EKE_ID_IPv4 3 +#define EAP_EKE_ID_IPv6 4 +#define EAP_EKE_ID_FQDN 5 +#define EAP_EKE_ID_DN 6 + +/* Failure-Code */ +#define EAP_EKE_FAIL_NO_ERROR 1 +#define EAP_EKE_FAIL_PROTO_ERROR 2 +#define EAP_EKE_FAIL_PASSWD_NOT_FOUND 3 +#define EAP_EKE_FAIL_AUTHENTICATION_FAIL 4 +#define EAP_EKE_FAIL_AUTHORIZATION_FAIL 5 +#define EAP_EKE_FAIL_NO_PROPOSAL_CHOSEN 6 +#define EAP_EKE_FAIL_PRIVATE_INTERNAL_ERROR 0xffffffff + +#define EAP_EKE_MAX_DH_LEN 512 +#define EAP_EKE_MAX_HASH_LEN 32 +#define EAP_EKE_MAX_KEY_LEN 16 +#define EAP_EKE_MAX_KE_LEN 16 +#define EAP_EKE_MAX_KI_LEN 32 +#define EAP_EKE_MAX_KA_LEN 32 +#define EAP_EKE_MAX_NONCE_LEN 16 + +struct eap_eke_session { + /* Selected proposal */ + u8 dhgroup; + u8 encr; + u8 prf; + u8 mac; + + u8 shared_secret[EAP_EKE_MAX_HASH_LEN]; + u8 ke[EAP_EKE_MAX_KE_LEN]; + u8 ki[EAP_EKE_MAX_KI_LEN]; + u8 ka[EAP_EKE_MAX_KA_LEN]; + + int prf_len; + int nonce_len; + int auth_len; + int dhcomp_len; + int pnonce_len; + int pnonce_ps_len; +}; + +int eap_eke_session_init(struct eap_eke_session *sess, u8 dhgroup, u8 encr, + u8 prf, u8 mac); +void eap_eke_session_clean(struct eap_eke_session *sess); +int eap_eke_dh_init(u8 group, u8 *ret_priv, u8 *ret_pub); +int eap_eke_derive_key(struct eap_eke_session *sess, + const u8 *password, size_t password_len, + const u8 *id_s, size_t id_s_len, const u8 *id_p, + size_t id_p_len, u8 *key); +int eap_eke_dhcomp(struct eap_eke_session *sess, const u8 *key, const u8 *dhpub, + u8 *ret_dhcomp); +int eap_eke_shared_secret(struct eap_eke_session *sess, const u8 *key, + const u8 *dhpriv, const u8 *peer_dhcomp); +int eap_eke_derive_ke_ki(struct eap_eke_session *sess, + const u8 *id_s, size_t id_s_len, + const u8 *id_p, size_t id_p_len); +int eap_eke_derive_ka(struct eap_eke_session *sess, + const u8 *id_s, size_t id_s_len, + const u8 *id_p, size_t id_p_len, + const u8 *nonce_p, const u8 *nonce_s); +int eap_eke_derive_msk(struct eap_eke_session *sess, + const u8 *id_s, size_t id_s_len, + const u8 *id_p, size_t id_p_len, + const u8 *nonce_p, const u8 *nonce_s, + u8 *msk, u8 *emsk); +int eap_eke_prot(struct eap_eke_session *sess, + const u8 *data, size_t data_len, + u8 *prot, size_t *prot_len); +int eap_eke_decrypt_prot(struct eap_eke_session *sess, + const u8 *prot, size_t prot_len, + u8 *data, size_t *data_len); +int eap_eke_auth(struct eap_eke_session *sess, const char *label, + const struct wpabuf *msgs, u8 *auth); + +#endif /* EAP_EKE_COMMON_H */ diff --git a/src/eap_peer/eap_eke.c b/src/eap_peer/eap_eke.c new file mode 100644 index 000000000..c71db5fd7 --- /dev/null +++ b/src/eap_peer/eap_eke.c @@ -0,0 +1,723 @@ +/* + * EAP peer method: EAP-EKE (RFC 6124) + * Copyright (c) 2013, Jouni Malinen + * + * 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_peer/eap_i.h" +#include "eap_common/eap_eke_common.h" + +struct eap_eke_data { + enum { + IDENTITY, COMMIT, CONFIRM, SUCCESS, FAILURE + } state; + u8 msk[EAP_MSK_LEN]; + u8 emsk[EAP_EMSK_LEN]; + u8 *peerid; + size_t peerid_len; + u8 *serverid; + size_t serverid_len; + u8 dh_priv[EAP_EKE_MAX_DH_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; +}; + + +static const char * eap_eke_state_txt(int state) +{ + switch (state) { + case IDENTITY: + return "IDENTITY"; + case COMMIT: + return "COMMIT"; + case CONFIRM: + return "CONFIRM"; + 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_deinit(struct eap_sm *sm, void *priv); + + +static void * eap_eke_init(struct eap_sm *sm) +{ + struct eap_eke_data *data; + const u8 *identity, *password; + size_t identity_len, password_len; + + password = eap_get_config_password(sm, &password_len); + if (!password) { + wpa_printf(MSG_INFO, "EAP-EKE: No password configured"); + return NULL; + } + + data = os_zalloc(sizeof(*data)); + if (data == NULL) + return NULL; + eap_eke_state(data, IDENTITY); + + identity = eap_get_config_identity(sm, &identity_len); + if (identity) { + data->peerid = os_malloc(identity_len); + if (data->peerid == NULL) { + eap_eke_deinit(sm, data); + return NULL; + } + os_memcpy(data->peerid, identity, identity_len); + data->peerid_len = identity_len; + } + + return data; +} + + +static void eap_eke_deinit(struct eap_sm *sm, void *priv) +{ + struct eap_eke_data *data = priv; + eap_eke_session_clean(&data->sess); + os_free(data->serverid); + os_free(data->peerid); + wpabuf_free(data->msgs); + os_free(data); +} + + +static struct wpabuf * eap_eke_build_msg(struct eap_eke_data *data, int 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_RESPONSE, 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 eap_eke_supp_dhgroup(u8 dhgroup) +{ + return dhgroup == EAP_EKE_DHGROUP_EKE_2 || + dhgroup == EAP_EKE_DHGROUP_EKE_5 || + dhgroup == EAP_EKE_DHGROUP_EKE_14 || + dhgroup == EAP_EKE_DHGROUP_EKE_15 || + dhgroup == EAP_EKE_DHGROUP_EKE_16; +} + + +static int eap_eke_supp_encr(u8 encr) +{ + return encr == EAP_EKE_ENCR_AES128_CBC; +} + + +static int eap_eke_supp_prf(u8 prf) +{ + return prf == EAP_EKE_PRF_HMAC_SHA1 || + prf == EAP_EKE_PRF_HMAC_SHA2_256; +} + + +static int eap_eke_supp_mac(u8 mac) +{ + return mac == EAP_EKE_MAC_HMAC_SHA1 || + mac == EAP_EKE_MAC_HMAC_SHA2_256; +} + + +static struct wpabuf * eap_eke_build_fail(struct eap_eke_data *data, + struct eap_method_ret *ret, + const struct wpabuf *reqData, + u32 failure_code) +{ + struct wpabuf *resp; + + wpa_printf(MSG_DEBUG, "EAP-EKE: Sending EAP-EKE-Failure/Response - code=0x%x", + failure_code); + + resp = eap_eke_build_msg(data, eap_get_id(reqData), 4, EAP_EKE_FAILURE); + if (resp) + wpabuf_put_be32(resp, failure_code); + + os_memset(data->dh_priv, 0, sizeof(data->dh_priv)); + eap_eke_session_clean(&data->sess); + + eap_eke_state(data, FAILURE); + ret->methodState = METHOD_DONE; + ret->decision = DECISION_FAIL; + ret->allowNotifications = FALSE; + + return resp; +} + + +static struct wpabuf * eap_eke_process_id(struct eap_eke_data *data, + struct eap_method_ret *ret, + const struct wpabuf *reqData, + const u8 *payload, + size_t payload_len) +{ + struct wpabuf *resp; + unsigned num_prop, i; + const u8 *pos, *end; + const u8 *prop = NULL; + u8 idtype; + + if (data->state != IDENTITY) { + return eap_eke_build_fail(data, ret, reqData, + EAP_EKE_FAIL_PROTO_ERROR); + } + + wpa_printf(MSG_DEBUG, "EAP-EKE: Received EAP-EKE-ID/Request"); + + if (payload_len < 2 + 4) { + wpa_printf(MSG_DEBUG, "EAP-EKE: Too short ID/Request Data"); + return eap_eke_build_fail(data, ret, reqData, + EAP_EKE_FAIL_PROTO_ERROR); + } + + pos = payload; + end = payload + payload_len; + + num_prop = *pos++; + pos++; /* Ignore Reserved field */ + + if (pos + num_prop * 4 > end) { + wpa_printf(MSG_DEBUG, "EAP-EKE: Too short ID/Request Data (num_prop=%u)", + num_prop); + return eap_eke_build_fail(data, ret, reqData, + EAP_EKE_FAIL_PROTO_ERROR); + } + + for (i = 0; i < num_prop; i++) { + const u8 *tmp = pos; + + wpa_printf(MSG_DEBUG, "EAP-EKE: Proposal #%u: dh=%u encr=%u prf=%u mac=%u", + i, pos[0], pos[1], pos[2], pos[3]); + pos += 4; + + if (!eap_eke_supp_dhgroup(*tmp)) + continue; + tmp++; + if (!eap_eke_supp_encr(*tmp)) + continue; + tmp++; + if (!eap_eke_supp_prf(*tmp)) + continue; + tmp++; + if (!eap_eke_supp_mac(*tmp)) + continue; + + prop = tmp - 3; + if (eap_eke_session_init(&data->sess, prop[0], prop[1], prop[2], + prop[3]) < 0) { + prop = NULL; + continue; + } + + wpa_printf(MSG_DEBUG, "EAP-EKE: Selected proposal"); + break; + } + + if (prop == NULL) { + wpa_printf(MSG_DEBUG, "EAP-EKE: No acceptable proposal found"); + return eap_eke_build_fail(data, ret, reqData, + EAP_EKE_FAIL_NO_PROPOSAL_CHOSEN); + } + + pos += (num_prop - i - 1) * 4; + + if (pos == end) { + wpa_printf(MSG_DEBUG, "EAP-EKE: Too short ID/Request Data to include IDType/Identity"); + return eap_eke_build_fail(data, ret, reqData, + EAP_EKE_FAIL_PROTO_ERROR); + } + + idtype = *pos++; + wpa_printf(MSG_DEBUG, "EAP-EKE: Server IDType %u", idtype); + wpa_hexdump_ascii(MSG_DEBUG, "EAP-EKE: Server Identity", + pos, end - pos); + os_free(data->serverid); + data->serverid = os_malloc(end - pos); + if (data->serverid == NULL) { + return eap_eke_build_fail(data, ret, reqData, + EAP_EKE_FAIL_PRIVATE_INTERNAL_ERROR); + } + os_memcpy(data->serverid, pos, end - pos); + data->serverid_len = end - pos; + + wpa_printf(MSG_DEBUG, "EAP-EKE: Sending EAP-EKE-ID/Response"); + + resp = eap_eke_build_msg(data, eap_get_id(reqData), + 2 + 4 + 1 + data->peerid_len, + EAP_EKE_ID); + if (resp == NULL) { + return eap_eke_build_fail(data, ret, reqData, + EAP_EKE_FAIL_PRIVATE_INTERNAL_ERROR); + } + + wpabuf_put_u8(resp, 1); /* NumProposals */ + wpabuf_put_u8(resp, 0); /* Reserved */ + wpabuf_put_data(resp, prop, 4); /* Selected Proposal */ + wpabuf_put_u8(resp, EAP_EKE_ID_NAI); + if (data->peerid) + wpabuf_put_data(resp, data->peerid, data->peerid_len); + + wpabuf_free(data->msgs); + data->msgs = wpabuf_alloc(wpabuf_len(reqData) + wpabuf_len(resp)); + if (data->msgs == NULL) { + wpabuf_free(resp); + return eap_eke_build_fail(data, ret, reqData, + EAP_EKE_FAIL_PRIVATE_INTERNAL_ERROR); + } + wpabuf_put_buf(data->msgs, reqData); + wpabuf_put_buf(data->msgs, resp); + + eap_eke_state(data, COMMIT); + + return resp; +} + + +static struct wpabuf * eap_eke_process_commit(struct eap_sm *sm, + struct eap_eke_data *data, + struct eap_method_ret *ret, + const struct wpabuf *reqData, + const u8 *payload, + size_t payload_len) +{ + struct wpabuf *resp; + const u8 *pos, *end, *dhcomp; + size_t prot_len; + u8 *rpos; + u8 key[EAP_EKE_MAX_KEY_LEN]; + u8 pub[EAP_EKE_MAX_DH_LEN]; + const u8 *password; + size_t password_len; + + if (data->state != COMMIT) { + wpa_printf(MSG_DEBUG, "EAP-EKE: EAP-EKE-Commit/Request received in unexpected state (%d)", data->state); + return eap_eke_build_fail(data, ret, reqData, + EAP_EKE_FAIL_PROTO_ERROR); + } + + wpa_printf(MSG_DEBUG, "EAP-EKE: Received EAP-EKE-Commit/Request"); + + password = eap_get_config_password(sm, &password_len); + if (password == NULL) { + wpa_printf(MSG_INFO, "EAP-EKE: No password configured!"); + return eap_eke_build_fail(data, ret, reqData, + EAP_EKE_FAIL_PASSWD_NOT_FOUND); + } + + pos = payload; + end = payload + payload_len; + + if (pos + data->sess.dhcomp_len > end) { + wpa_printf(MSG_DEBUG, "EAP-EKE: Too short EAP-EKE-Commit"); + return eap_eke_build_fail(data, ret, reqData, + EAP_EKE_FAIL_PROTO_ERROR); + } + + wpa_hexdump(MSG_DEBUG, "EAP-EKE: DHComponent_S", + pos, data->sess.dhcomp_len); + dhcomp = pos; + pos += data->sess.dhcomp_len; + wpa_hexdump(MSG_DEBUG, "EAP-EKE: CBValue", pos, end - pos); + + /* + * temp = prf(0+, password) + * key = prf+(temp, ID_S | ID_P) + */ + if (eap_eke_derive_key(&data->sess, password, password_len, + data->serverid, data->serverid_len, + data->peerid, data->peerid_len, key) < 0) { + wpa_printf(MSG_INFO, "EAP-EKE: Failed to derive key"); + return eap_eke_build_fail(data, ret, reqData, + EAP_EKE_FAIL_PRIVATE_INTERNAL_ERROR); + } + + /* + * y_p = g ^ x_p (mod p) + * x_p = random number 2 .. p-1 + */ + if (eap_eke_dh_init(data->sess.dhgroup, data->dh_priv, pub) < 0) { + wpa_printf(MSG_INFO, "EAP-EKE: Failed to initialize DH"); + os_memset(key, 0, sizeof(key)); + return eap_eke_build_fail(data, ret, reqData, + EAP_EKE_FAIL_PRIVATE_INTERNAL_ERROR); + } + + if (eap_eke_shared_secret(&data->sess, key, data->dh_priv, dhcomp) < 0) + { + wpa_printf(MSG_INFO, "EAP-EKE: Failed to derive shared secret"); + os_memset(key, 0, sizeof(key)); + return eap_eke_build_fail(data, ret, reqData, + EAP_EKE_FAIL_PRIVATE_INTERNAL_ERROR); + } + + if (eap_eke_derive_ke_ki(&data->sess, + data->serverid, data->serverid_len, + data->peerid, data->peerid_len) < 0) { + wpa_printf(MSG_INFO, "EAP-EKE: Failed to derive Ke/Ki"); + os_memset(key, 0, sizeof(key)); + return eap_eke_build_fail(data, ret, reqData, + EAP_EKE_FAIL_PRIVATE_INTERNAL_ERROR); + } + + wpa_printf(MSG_DEBUG, "EAP-EKE: Sending EAP-EKE-Commit/Response"); + + resp = eap_eke_build_msg(data, eap_get_id(reqData), + data->sess.dhcomp_len + data->sess.pnonce_len, + EAP_EKE_COMMIT); + if (resp == NULL) { + os_memset(key, 0, sizeof(key)); + return eap_eke_build_fail(data, ret, reqData, + EAP_EKE_FAIL_PRIVATE_INTERNAL_ERROR); + } + + /* DHComponent_P = Encr(key, y_p) */ + rpos = wpabuf_put(resp, data->sess.dhcomp_len); + if (eap_eke_dhcomp(&data->sess, key, pub, rpos) < 0) { + wpa_printf(MSG_INFO, "EAP-EKE: Failed to build DHComponent_S"); + os_memset(key, 0, sizeof(key)); + return eap_eke_build_fail(data, ret, reqData, + EAP_EKE_FAIL_PRIVATE_INTERNAL_ERROR); + } + os_memset(key, 0, sizeof(key)); + + wpa_hexdump(MSG_DEBUG, "EAP-EKE: DHComponent_P", + rpos, data->sess.dhcomp_len); + + if (random_get_bytes(data->nonce_p, data->sess.nonce_len)) { + wpabuf_free(resp); + return eap_eke_build_fail(data, ret, reqData, + EAP_EKE_FAIL_PRIVATE_INTERNAL_ERROR); + } + wpa_hexdump_key(MSG_DEBUG, "EAP-EKE: Nonce_P", + data->nonce_p, data->sess.nonce_len); + prot_len = wpabuf_tailroom(resp); + if (eap_eke_prot(&data->sess, data->nonce_p, data->sess.nonce_len, + wpabuf_put(resp, 0), &prot_len) < 0) { + wpabuf_free(resp); + return eap_eke_build_fail(data, ret, reqData, + EAP_EKE_FAIL_PRIVATE_INTERNAL_ERROR); + } + wpa_hexdump(MSG_DEBUG, "EAP-EKE: PNonce_P", + wpabuf_put(resp, 0), prot_len); + wpabuf_put(resp, prot_len); + + /* TODO: CBValue */ + + if (wpabuf_resize(&data->msgs, wpabuf_len(reqData) + wpabuf_len(resp)) + < 0) { + wpabuf_free(resp); + return eap_eke_build_fail(data, ret, reqData, + EAP_EKE_FAIL_PRIVATE_INTERNAL_ERROR); + } + wpabuf_put_buf(data->msgs, reqData); + wpabuf_put_buf(data->msgs, resp); + + eap_eke_state(data, CONFIRM); + + return resp; +} + + +static struct wpabuf * eap_eke_process_confirm(struct eap_eke_data *data, + struct eap_method_ret *ret, + const struct wpabuf *reqData, + const u8 *payload, + size_t payload_len) +{ + struct wpabuf *resp; + const u8 *pos, *end; + size_t prot_len; + u8 nonces[2 * EAP_EKE_MAX_NONCE_LEN]; + u8 auth_s[EAP_EKE_MAX_HASH_LEN]; + size_t decrypt_len; + u8 *auth; + + if (data->state != CONFIRM) { + wpa_printf(MSG_DEBUG, "EAP-EKE: EAP-EKE-Confirm/Request received in unexpected state (%d)", + data->state); + return eap_eke_build_fail(data, ret, reqData, + EAP_EKE_FAIL_PROTO_ERROR); + } + + wpa_printf(MSG_DEBUG, "EAP-EKE: Received EAP-EKE-Confirm/Request"); + + pos = payload; + end = payload + payload_len; + + if (pos + data->sess.pnonce_ps_len + data->sess.prf_len > end) { + wpa_printf(MSG_DEBUG, "EAP-EKE: Too short EAP-EKE-Commit"); + return eap_eke_build_fail(data, ret, reqData, + EAP_EKE_FAIL_PROTO_ERROR); + } + + decrypt_len = sizeof(nonces); + if (eap_eke_decrypt_prot(&data->sess, pos, data->sess.pnonce_ps_len, + nonces, &decrypt_len) < 0) { + wpa_printf(MSG_INFO, "EAP-EKE: Failed to decrypt PNonce_PS"); + return eap_eke_build_fail(data, ret, reqData, + EAP_EKE_FAIL_AUTHENTICATION_FAIL); + } + if (decrypt_len != (size_t) 2 * data->sess.nonce_len) { + wpa_printf(MSG_INFO, "EAP-EKE: PNonce_PS protected data length does not match length of Nonce_P and Nonce_S"); + return eap_eke_build_fail(data, ret, reqData, + EAP_EKE_FAIL_AUTHENTICATION_FAIL); + } + wpa_hexdump_key(MSG_DEBUG, "EAP-EKE: Received Nonce_P | Nonce_S", + nonces, 2 * data->sess.nonce_len); + if (os_memcmp(data->nonce_p, nonces, data->sess.nonce_len) != 0) { + wpa_printf(MSG_INFO, "EAP-EKE: Received Nonce_P does not match trnsmitted Nonce_P"); + return eap_eke_build_fail(data, ret, reqData, + EAP_EKE_FAIL_AUTHENTICATION_FAIL); + } + + os_memcpy(data->nonce_s, nonces + data->sess.nonce_len, + data->sess.nonce_len); + wpa_hexdump_key(MSG_DEBUG, "EAP-EKE: Nonce_S", + data->nonce_s, data->sess.nonce_len); + + if (eap_eke_derive_ka(&data->sess, data->serverid, data->serverid_len, + data->peerid, data->peerid_len, + data->nonce_p, data->nonce_s) < 0) { + return eap_eke_build_fail(data, ret, reqData, + EAP_EKE_FAIL_PRIVATE_INTERNAL_ERROR); + } + + if (eap_eke_auth(&data->sess, "EAP-EKE server", data->msgs, auth_s) < 0) + { + return eap_eke_build_fail(data, ret, reqData, + EAP_EKE_FAIL_PRIVATE_INTERNAL_ERROR); + } + wpa_hexdump(MSG_DEBUG, "EAP-EKE: Auth_S", auth_s, data->sess.prf_len); + if (os_memcmp(auth_s, pos + data->sess.pnonce_ps_len, + data->sess.prf_len) != 0) { + wpa_printf(MSG_INFO, "EAP-EKE: Auth_S does not match"); + return eap_eke_build_fail(data, ret, reqData, + EAP_EKE_FAIL_AUTHENTICATION_FAIL); + } + + wpa_printf(MSG_DEBUG, "EAP-EKE: Sending EAP-EKE-Confirm/Response"); + + resp = eap_eke_build_msg(data, eap_get_id(reqData), + data->sess.pnonce_len + data->sess.prf_len, + EAP_EKE_CONFIRM); + if (resp == NULL) { + return eap_eke_build_fail(data, ret, reqData, + EAP_EKE_FAIL_PRIVATE_INTERNAL_ERROR); + } + + prot_len = wpabuf_tailroom(resp); + if (eap_eke_prot(&data->sess, data->nonce_s, data->sess.nonce_len, + wpabuf_put(resp, 0), &prot_len) < 0) { + wpabuf_free(resp); + return eap_eke_build_fail(data, ret, reqData, + EAP_EKE_FAIL_PRIVATE_INTERNAL_ERROR); + } + wpabuf_put(resp, prot_len); + + auth = wpabuf_put(resp, data->sess.prf_len); + if (eap_eke_auth(&data->sess, "EAP-EKE peer", data->msgs, auth) < 0) { + wpabuf_free(resp); + return eap_eke_build_fail(data, ret, reqData, + EAP_EKE_FAIL_PRIVATE_INTERNAL_ERROR); + } + wpa_hexdump(MSG_DEBUG, "EAP-EKE: Auth_P", auth, data->sess.prf_len); + + if (eap_eke_derive_msk(&data->sess, data->serverid, data->serverid_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"); + wpabuf_free(resp); + return eap_eke_build_fail(data, ret, reqData, + EAP_EKE_FAIL_PRIVATE_INTERNAL_ERROR); + } + + os_memset(data->dh_priv, 0, sizeof(data->dh_priv)); + eap_eke_session_clean(&data->sess); + + eap_eke_state(data, SUCCESS); + ret->methodState = METHOD_MAY_CONT; + ret->decision = DECISION_COND_SUCC; + ret->allowNotifications = FALSE; + + return resp; +} + + +static struct wpabuf * eap_eke_process_failure(struct eap_eke_data *data, + struct eap_method_ret *ret, + const struct wpabuf *reqData, + const u8 *payload, + size_t payload_len) +{ + wpa_printf(MSG_DEBUG, "EAP-EKE: Received EAP-EKE-Failure/Request"); + + if (payload_len < 4) { + wpa_printf(MSG_DEBUG, "EAP-EKE: Too short EAP-EKE-Failure"); + } else { + u32 code; + code = WPA_GET_BE32(payload); + wpa_printf(MSG_INFO, "EAP-EKE: Failure-Code 0x%x", code); + } + + return eap_eke_build_fail(data, ret, reqData, EAP_EKE_FAIL_NO_ERROR); +} + + +static struct wpabuf * eap_eke_process(struct eap_sm *sm, void *priv, + struct eap_method_ret *ret, + const struct wpabuf *reqData) +{ + struct eap_eke_data *data = priv; + struct wpabuf *resp; + const u8 *pos, *end; + size_t len; + u8 eke_exch; + + pos = eap_hdr_validate(EAP_VENDOR_IETF, EAP_TYPE_EKE, reqData, &len); + if (pos == NULL || len < 1) { + ret->ignore = TRUE; + return NULL; + } + + end = pos + len; + eke_exch = *pos++; + + wpa_printf(MSG_DEBUG, "EAP-EKE: Received frame: exch %d", eke_exch); + wpa_hexdump(MSG_DEBUG, "EAP-EKE: Received Data", pos, end - pos); + + ret->ignore = FALSE; + ret->methodState = METHOD_MAY_CONT; + ret->decision = DECISION_FAIL; + ret->allowNotifications = TRUE; + + switch (eke_exch) { + case EAP_EKE_ID: + resp = eap_eke_process_id(data, ret, reqData, pos, end - pos); + break; + case EAP_EKE_COMMIT: + resp = eap_eke_process_commit(sm, data, ret, reqData, + pos, end - pos); + break; + case EAP_EKE_CONFIRM: + resp = eap_eke_process_confirm(data, ret, reqData, + pos, end - pos); + break; + case EAP_EKE_FAILURE: + resp = eap_eke_process_failure(data, ret, reqData, + pos, end - pos); + break; + default: + wpa_printf(MSG_DEBUG, "EAP-EKE: Ignoring message with unknown EKE-Exch %d", eke_exch); + ret->ignore = TRUE; + return NULL; + } + + if (ret->methodState == METHOD_DONE) + ret->allowNotifications = FALSE; + + return resp; +} + + +static Boolean eap_eke_isKeyAvailable(struct eap_sm *sm, void *priv) +{ + struct eap_eke_data *data = priv; + return data->state == SUCCESS; +} + + +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; +} + + +int eap_peer_eke_register(void) +{ + struct eap_method *eap; + int ret; + + eap = eap_peer_method_alloc(EAP_PEER_METHOD_INTERFACE_VERSION, + EAP_VENDOR_IETF, EAP_TYPE_EKE, "EKE"); + if (eap == NULL) + return -1; + + eap->init = eap_eke_init; + eap->deinit = eap_eke_deinit; + eap->process = eap_eke_process; + eap->isKeyAvailable = eap_eke_isKeyAvailable; + eap->getKey = eap_eke_getKey; + eap->get_emsk = eap_eke_get_emsk; + + ret = eap_peer_method_register(eap); + if (ret) + eap_peer_method_free(eap); + return ret; +} diff --git a/src/eap_peer/eap_methods.h b/src/eap_peer/eap_methods.h index 4994ff1cd..a465fd235 100644 --- a/src/eap_peer/eap_methods.h +++ b/src/eap_peer/eap_methods.h @@ -105,5 +105,6 @@ int eap_peer_ikev2_register(void); int eap_peer_vendor_test_register(void); int eap_peer_tnc_register(void); int eap_peer_pwd_register(void); +int eap_peer_eke_register(void); #endif /* EAP_METHODS_H */ diff --git a/wpa_supplicant/Android.mk b/wpa_supplicant/Android.mk index 6b6fc06c9..5a6d6b36d 100644 --- a/wpa_supplicant/Android.mk +++ b/wpa_supplicant/Android.mk @@ -574,6 +574,22 @@ CONFIG_IEEE8021X_EAPOL=y NEED_SHA256=y endif +ifdef CONFIG_EAP_EKE +# EAP-EKE +ifeq ($(CONFIG_EAP_EKE), dyn) +L_CFLAGS += -DEAP_EKE_DYNAMIC +EAPDYN += src/eap_peer/eap_eke.so +else +L_CFLAGS += -DEAP_EKE +OBJS += src/eap_peer/eap_eke.c src/eap_common/eap_eke_common.c +OBJS_h += src/eap_server/eap_server_eke.c +endif +CONFIG_IEEE8021X_EAPOL=y +NEED_DH_GROUPS=y +NEED_DH_GROUPS_ALL=y +NEED_SHA256=y +endif + ifdef CONFIG_WPS ifdef CONFIG_WPS2 L_CFLAGS += -DCONFIG_WPS2 diff --git a/wpa_supplicant/Makefile b/wpa_supplicant/Makefile index 6280a2319..569861962 100644 --- a/wpa_supplicant/Makefile +++ b/wpa_supplicant/Makefile @@ -575,6 +575,22 @@ CONFIG_IEEE8021X_EAPOL=y NEED_SHA256=y endif +ifdef CONFIG_EAP_EKE +# EAP-EKE +ifeq ($(CONFIG_EAP_EKE), dyn) +CFLAGS += -DEAP_EKE_DYNAMIC +EAPDYN += ../src/eap_peer/eap_eke.so +else +CFLAGS += -DEAP_EKE +OBJS += ../src/eap_peer/eap_eke.o ../src/eap_common/eap_eke_common.o +OBJS_h += ../src/eap_server/eap_server_eke.o +endif +CONFIG_IEEE8021X_EAPOL=y +NEED_DH_GROUPS=y +NEED_DH_GROUPS_ALL=y +NEED_SHA256=y +endif + ifdef CONFIG_WPS ifdef CONFIG_WPS2 CFLAGS += -DCONFIG_WPS2 @@ -1577,6 +1593,10 @@ eap_ikev2.so: ../src/eap_peer/eap_ikev2.c ../src/eap_peer/ikev2.c ../src/eap_com $(CC) $(LDFLAGS) -o $@ $(CFLAGS) -shared -rdynamic -fPIC $^ \ -Deap_peer_ikev2_register=eap_peer_method_dynamic_init +eap_eke.so: ../src/eap_peer/eap_eke.c ../src/eap_common/eap_eke_common.c + $(CC) $(LDFLAGS) -o $@ $(CFLAGS) -shared -rdynamic -fPIC $^ \ + -Deap_peer_eke_register=eap_peer_method_dynamic_init + %.so: %.c $(CC) $(LDFLAGS) -o $@ $(CFLAGS) -shared -rdynamic -fPIC $< \ -D$(*F:eap_%=eap_peer_%)_register=eap_peer_method_dynamic_init diff --git a/wpa_supplicant/defconfig b/wpa_supplicant/defconfig index fe2e0db27..aa6005f7a 100644 --- a/wpa_supplicant/defconfig +++ b/wpa_supplicant/defconfig @@ -210,6 +210,9 @@ CONFIG_EAP_LEAP=y # EAP-IKEv2 #CONFIG_EAP_IKEV2=y +# EAP-EKE +#CONFIG_EAP_EKE=y + # PKCS#12 (PFX) support (used to read private key and certificate file from # a file that usually has extension .p12 or .pfx) CONFIG_PKCS12=y diff --git a/wpa_supplicant/eap_register.c b/wpa_supplicant/eap_register.c index d1eb4ff3b..6cd2fc507 100644 --- a/wpa_supplicant/eap_register.c +++ b/wpa_supplicant/eap_register.c @@ -135,6 +135,11 @@ int eap_register_methods(void) ret = eap_peer_pwd_register(); #endif /* EAP_PWD */ +#ifdef EAP_EKE + if (ret == 0) + ret = eap_peer_eke_register(); +#endif /* EAP_EKE */ + #ifdef EAP_SERVER_IDENTITY if (ret == 0) ret = eap_server_identity_register();