diff --git a/hostapd/Makefile b/hostapd/Makefile index 74cd9fa73..dea4c1595 100644 --- a/hostapd/Makefile +++ b/hostapd/Makefile @@ -258,6 +258,12 @@ OBJS += ../src/l2_packet/l2_packet_none.o endif +ifdef CONFIG_ERP +CFLAGS += -DCONFIG_ERP +NEED_SHA256=y +NEED_HMAC_SHA256_KDF=y +endif + ifdef CONFIG_EAP_MD5 CFLAGS += -DEAP_SERVER_MD5 OBJS += ../src/eap_server/eap_server_md5.o @@ -767,6 +773,9 @@ endif ifdef NEED_TLS_PRF_SHA256 OBJS += ../src/crypto/sha256-tlsprf.o endif +ifdef NEED_HMAC_SHA256_KDF +OBJS += ../src/crypto/sha256-kdf.o +endif endif ifdef NEED_DH_GROUPS diff --git a/hostapd/config_file.c b/hostapd/config_file.c index 76d89649f..c9ff3ec51 100644 --- a/hostapd/config_file.c +++ b/hostapd/config_file.c @@ -2052,6 +2052,8 @@ static int hostapd_config_fill(struct hostapd_config *conf, } else if (os_strcmp(buf, "pwd_group") == 0) { bss->pwd_group = atoi(pos); #endif /* EAP_SERVER_PWD */ + } else if (os_strcmp(buf, "eap_server_erp") == 0) { + bss->eap_server_erp = atoi(pos); #endif /* EAP_SERVER */ } else if (os_strcmp(buf, "eap_message") == 0) { char *term; diff --git a/hostapd/defconfig b/hostapd/defconfig index 8f8a53ff1..4cde2b563 100644 --- a/hostapd/defconfig +++ b/hostapd/defconfig @@ -56,6 +56,9 @@ CONFIG_IEEE80211W=y # Integrated EAP server CONFIG_EAP=y +# EAP Re-authentication Protocol (ERP) in integrated EAP server +CONFIG_ERP=y + # EAP-MD5 for the integrated EAP server CONFIG_EAP_MD5=y diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf index 44dacb733..2f6126c3a 100644 --- a/hostapd/hostapd.conf +++ b/hostapd/hostapd.conf @@ -703,7 +703,8 @@ eapol_key_index_workaround=0 #erp_send_reauth_start=1 # # Domain name for EAP-Initiate/Re-auth-Start. Omitted from the message if not -# set (no local ER server). +# set (no local ER server). This is also used by the integrated EAP server if +# ERP is enabled (eap_server_erp=1). #erp_domain=example.com ##### Integrated EAP server ################################################### @@ -851,6 +852,10 @@ eap_server=0 # EAP method is enabled, the peer will be allowed to connect without TNC. #tnc=1 +# EAP Re-authentication Protocol (ERP) - RFC 6696 +# +# Whether to enable ERP on the EAP server. +#eap_server_erp=1 ##### IEEE 802.11f - Inter-Access Point Protocol (IAPP) ####################### diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h index 874ce6171..64df60d34 100644 --- a/src/ap/ap_config.h +++ b/src/ap/ap_config.h @@ -233,6 +233,7 @@ struct hostapd_bss_config { struct hostapd_eap_user *eap_user; char *eap_user_sqlite; char *eap_sim_db; + int eap_server_erp; /* Whether ERP is enabled on internal EAP server */ struct hostapd_ip_addr own_ip_addr; char *nas_identifier; struct hostapd_radius_servers *radius; diff --git a/src/ap/authsrv.c b/src/ap/authsrv.c index 690f1dc41..bd1778e41 100644 --- a/src/ap/authsrv.c +++ b/src/ap/authsrv.c @@ -124,6 +124,8 @@ static int hostapd_setup_radius_srv(struct hostapd_data *hapd) srv.subscr_remediation_url = conf->subscr_remediation_url; srv.subscr_remediation_method = conf->subscr_remediation_method; #endif /* CONFIG_HS20 */ + srv.erp = conf->eap_server_erp; + srv.erp_domain = conf->erp_domain; hapd->radius_srv = radius_server_init(&srv); if (hapd->radius_srv == NULL) { diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h index ba169f499..4a50a008d 100644 --- a/src/ap/hostapd.h +++ b/src/ap/hostapd.h @@ -1,6 +1,6 @@ /* * hostapd / Initialization and configuration - * Copyright (c) 2002-2013, Jouni Malinen + * Copyright (c) 2002-2014, Jouni Malinen * * This software may be distributed under the terms of the BSD license. * See README for more details. @@ -10,6 +10,7 @@ #define HOSTAPD_H #include "common/defs.h" +#include "utils/list.h" #include "ap_config.h" #include "drivers/driver.h" @@ -153,6 +154,7 @@ struct hostapd_data { void *ssl_ctx; void *eap_sim_db_priv; struct radius_server_data *radius_srv; + struct dl_list erp_keys; /* struct eap_server_erp_key */ int parameter_set_count; diff --git a/src/ap/ieee802_1x.c b/src/ap/ieee802_1x.c index d29838564..84a98abc4 100644 --- a/src/ap/ieee802_1x.c +++ b/src/ap/ieee802_1x.c @@ -296,9 +296,15 @@ static void ieee802_1x_learn_identity(struct hostapd_data *hapd, { const u8 *identity; size_t identity_len; + const struct eap_hdr *hdr = (const struct eap_hdr *) eap; if (len <= sizeof(struct eap_hdr) || - eap[sizeof(struct eap_hdr)] != EAP_TYPE_IDENTITY) + (hdr->code == EAP_CODE_RESPONSE && + eap[sizeof(struct eap_hdr)] != EAP_TYPE_IDENTITY) || + (hdr->code == EAP_CODE_INITIATE && + eap[sizeof(struct eap_hdr)] != EAP_ERP_TYPE_REAUTH) || + (hdr->code != EAP_CODE_RESPONSE && + hdr->code != EAP_CODE_INITIATE)) return; identity = eap_get_identity(sm->eap, &identity_len); @@ -711,6 +717,39 @@ static void handle_eap_response(struct hostapd_data *hapd, } +static void handle_eap_initiate(struct hostapd_data *hapd, + struct sta_info *sta, struct eap_hdr *eap, + size_t len) +{ +#ifdef CONFIG_ERP + u8 type, *data; + struct eapol_state_machine *sm = sta->eapol_sm; + + if (sm == NULL) + return; + + if (len < sizeof(*eap) + 1) { + wpa_printf(MSG_INFO, + "handle_eap_initiate: too short response data"); + return; + } + + data = (u8 *) (eap + 1); + type = data[0]; + + hostapd_logger(hapd, sm->addr, HOSTAPD_MODULE_IEEE8021X, + HOSTAPD_LEVEL_DEBUG, "received EAP packet (code=%d " + "id=%d len=%d) from STA: EAP Initiate type %u", + eap->code, eap->identifier, be_to_host16(eap->length), + type); + + wpabuf_free(sm->eap_if->eapRespData); + sm->eap_if->eapRespData = wpabuf_alloc_copy(eap, len); + sm->eapolEap = TRUE; +#endif /* CONFIG_ERP */ +} + + /* Process incoming EAP packet from Supplicant */ static void handle_eap(struct hostapd_data *hapd, struct sta_info *sta, u8 *buf, size_t len) @@ -754,6 +793,13 @@ static void handle_eap(struct hostapd_data *hapd, struct sta_info *sta, case EAP_CODE_FAILURE: wpa_printf(MSG_DEBUG, " (failure)"); return; + case EAP_CODE_INITIATE: + wpa_printf(MSG_DEBUG, " (initiate)"); + handle_eap_initiate(hapd, sta, eap, eap_len); + break; + case EAP_CODE_FINISH: + wpa_printf(MSG_DEBUG, " (finish)"); + break; default: wpa_printf(MSG_DEBUG, " (unknown code)"); return; @@ -1986,12 +2032,43 @@ static void ieee802_1x_eapol_event(void *ctx, void *sta_ctx, } +#ifdef CONFIG_ERP + +static struct eap_server_erp_key * +ieee802_1x_erp_get_key(void *ctx, const char *keyname) +{ + struct hostapd_data *hapd = ctx; + struct eap_server_erp_key *erp; + + dl_list_for_each(erp, &hapd->erp_keys, struct eap_server_erp_key, + list) { + if (os_strcmp(erp->keyname_nai, keyname) == 0) + return erp; + } + + return NULL; +} + + +static int ieee802_1x_erp_add_key(void *ctx, struct eap_server_erp_key *erp) +{ + struct hostapd_data *hapd = ctx; + + dl_list_add(&hapd->erp_keys, &erp->list); + return 0; +} + +#endif /* CONFIG_ERP */ + + int ieee802_1x_init(struct hostapd_data *hapd) { int i; struct eapol_auth_config conf; struct eapol_auth_cb cb; + dl_list_init(&hapd->erp_keys); + os_memset(&conf, 0, sizeof(conf)); conf.ctx = hapd; conf.eap_reauth_period = hapd->conf->eap_reauth_period; @@ -2005,6 +2082,7 @@ int ieee802_1x_init(struct hostapd_data *hapd) conf.eap_req_id_text_len = hapd->conf->eap_req_id_text_len; conf.erp_send_reauth_start = hapd->conf->erp_send_reauth_start; conf.erp_domain = hapd->conf->erp_domain; + conf.erp = hapd->conf->eap_server_erp; conf.pac_opaque_encr_key = hapd->conf->pac_opaque_encr_key; conf.eap_fast_a_id = hapd->conf->eap_fast_a_id; conf.eap_fast_a_id_len = hapd->conf->eap_fast_a_id_len; @@ -2037,6 +2115,10 @@ int ieee802_1x_init(struct hostapd_data *hapd) cb.abort_auth = _ieee802_1x_abort_auth; cb.tx_key = _ieee802_1x_tx_key; cb.eapol_event = ieee802_1x_eapol_event; +#ifdef CONFIG_ERP + cb.erp_get_key = ieee802_1x_erp_get_key; + cb.erp_add_key = ieee802_1x_erp_add_key; +#endif /* CONFIG_ERP */ hapd->eapol_auth = eapol_auth_init(&conf, &cb); if (hapd->eapol_auth == NULL) @@ -2070,6 +2152,8 @@ int ieee802_1x_init(struct hostapd_data *hapd) void ieee802_1x_deinit(struct hostapd_data *hapd) { + struct eap_server_erp_key *erp; + eloop_cancel_timeout(ieee802_1x_rekey, hapd, NULL); if (hapd->driver != NULL && @@ -2078,6 +2162,12 @@ void ieee802_1x_deinit(struct hostapd_data *hapd) eapol_auth_deinit(hapd->eapol_auth); hapd->eapol_auth = NULL; + + while ((erp = dl_list_first(&hapd->erp_keys, struct eap_server_erp_key, + list)) != NULL) { + dl_list_del(&erp->list); + bin_clear_free(erp, sizeof(*erp)); + } } diff --git a/src/eap_server/eap.h b/src/eap_server/eap.h index 395d8955f..9de6cb62f 100644 --- a/src/eap_server/eap.h +++ b/src/eap_server/eap.h @@ -1,6 +1,6 @@ /* * hostapd / EAP Full Authenticator state machine (RFC 4137) - * Copyright (c) 2004-2007, Jouni Malinen + * Copyright (c) 2004-2014, Jouni Malinen * * This software may be distributed under the terms of the BSD license. * See README for more details. @@ -10,6 +10,7 @@ #define EAP_H #include "common/defs.h" +#include "utils/list.h" #include "eap_common/eap_defs.h" #include "eap_server/eap_methods.h" #include "wpabuf.h" @@ -80,6 +81,17 @@ struct eap_eapol_interface { Boolean aaaTimeout; }; +struct eap_server_erp_key { + struct dl_list list; + size_t rRK_len; + size_t rIK_len; + u8 rRK[ERP_MAX_KEY_LEN]; + u8 rIK[ERP_MAX_KEY_LEN]; + u32 recv_seq; + u8 cryptosuite; + char keyname_nai[]; +}; + struct eapol_callbacks { int (*get_eap_user)(void *ctx, const u8 *identity, size_t identity_len, int phase2, struct eap_user *user); @@ -87,6 +99,9 @@ struct eapol_callbacks { void (*log_msg)(void *ctx, const char *msg); int (*get_erp_send_reauth_start)(void *ctx); const char * (*get_erp_domain)(void *ctx); + struct eap_server_erp_key * (*erp_get_key)(void *ctx, + const char *keyname); + int (*erp_add_key)(void *ctx, struct eap_server_erp_key *erp); }; struct eap_config { @@ -115,6 +130,7 @@ struct eap_config { const u8 *server_id; size_t server_id_len; + int erp; #ifdef CONFIG_TESTING_OPTIONS u32 tls_test_flags; diff --git a/src/eap_server/eap_i.h b/src/eap_server/eap_i.h index 9c757d9c8..7d723091f 100644 --- a/src/eap_server/eap_i.h +++ b/src/eap_server/eap_i.h @@ -117,7 +117,7 @@ struct eap_sm { EAP_RECEIVED2, EAP_DISCARD2, EAP_SEND_REQUEST2, EAP_AAA_REQUEST, EAP_AAA_RESPONSE, EAP_AAA_IDLE, EAP_TIMEOUT_FAILURE2, EAP_FAILURE2, EAP_SUCCESS2, - EAP_INITIATE_REAUTH_START + EAP_INITIATE_REAUTH_START, EAP_INITIATE_RECEIVED } EAP_state; /* Constants */ @@ -139,6 +139,7 @@ struct eap_sm { /* Short-term (not maintained between packets) */ Boolean rxResp; + Boolean rxInitiate; int respId; EapType respMethod; int respVendor; @@ -208,6 +209,7 @@ struct eap_sm { Boolean initiate_reauth_start_sent; Boolean try_initiate_reauth; + int erp; #ifdef CONFIG_TESTING_OPTIONS u32 tls_test_flags; diff --git a/src/eap_server/eap_server.c b/src/eap_server/eap_server.c index b0ec75700..53e32c381 100644 --- a/src/eap_server/eap_server.c +++ b/src/eap_server/eap_server.c @@ -15,6 +15,7 @@ #include "includes.h" #include "common.h" +#include "crypto/sha256.h" #include "eap_i.h" #include "state_machine.h" #include "common/wpa_ctrl.h" @@ -60,6 +61,27 @@ static const char * eap_get_erp_domain(struct eap_sm *sm) } +#ifdef CONFIG_ERP + +static struct eap_server_erp_key * eap_erp_get_key(struct eap_sm *sm, + const char *keyname) +{ + if (sm->eapol_cb->erp_get_key) + return sm->eapol_cb->erp_get_key(sm->eapol_ctx, keyname); + return NULL; +} + + +static int eap_erp_add_key(struct eap_sm *sm, struct eap_server_erp_key *erp) +{ + if (sm->eapol_cb->erp_add_key) + return sm->eapol_cb->erp_add_key(sm->eapol_ctx, erp); + return -1; +} + +#endif /* CONFIG_ERP */ + + static struct wpabuf * eap_sm_buildInitiateReauthStart(struct eap_sm *sm, u8 id) { @@ -71,7 +93,7 @@ static struct wpabuf * eap_sm_buildInitiateReauthStart(struct eap_sm *sm, domain = eap_get_erp_domain(sm); if (domain) { domain_len = os_strlen(domain); - plen += 2 + domain_len;; + plen += 2 + domain_len; } msg = eap_msg_alloc(EAP_VENDOR_IETF, EAP_ERP_TYPE_REAUTH_START, plen, @@ -210,7 +232,6 @@ SM_STATE(EAP, INITIALIZE) eap_server_clear_identity(sm); } - sm->initiate_reauth_start_sent = FALSE; sm->try_initiate_reauth = FALSE; sm->currentId = -1; sm->eap_if.eapSuccess = FALSE; @@ -387,6 +408,95 @@ SM_STATE(EAP, METHOD_REQUEST) } +static void eap_server_erp_init(struct eap_sm *sm) +{ +#ifdef CONFIG_ERP + u8 *emsk = NULL; + size_t emsk_len; + u8 EMSKname[EAP_EMSK_NAME_LEN]; + u8 len[2]; + const char *domain; + size_t domain_len, nai_buf_len; + struct eap_server_erp_key *erp = NULL; + int pos; + + domain = eap_get_erp_domain(sm); + if (!domain) + return; + + domain_len = os_strlen(domain); + + nai_buf_len = 2 * EAP_EMSK_NAME_LEN + 1 + domain_len; + if (nai_buf_len > 253) { + /* + * keyName-NAI has a maximum length of 253 octet to fit in + * RADIUS attributes. + */ + wpa_printf(MSG_DEBUG, + "EAP: Too long realm for ERP keyName-NAI maximum length"); + return; + } + nai_buf_len++; /* null termination */ + erp = os_zalloc(sizeof(*erp) + nai_buf_len); + if (erp == NULL) + goto fail; + erp->recv_seq = (u32) -1; + + emsk = sm->m->get_emsk(sm, sm->eap_method_priv, &emsk_len); + if (!emsk || emsk_len == 0 || emsk_len > ERP_MAX_KEY_LEN) { + wpa_printf(MSG_DEBUG, + "EAP: No suitable EMSK available for ERP"); + goto fail; + } + + wpa_hexdump_key(MSG_DEBUG, "EAP: EMSK", emsk, emsk_len); + + WPA_PUT_BE16(len, 8); + if (hmac_sha256_kdf(sm->eap_if.eapSessionId, sm->eap_if.eapSessionIdLen, + "EMSK", len, sizeof(len), + EMSKname, EAP_EMSK_NAME_LEN) < 0) { + wpa_printf(MSG_DEBUG, "EAP: Could not derive EMSKname"); + goto fail; + } + wpa_hexdump(MSG_DEBUG, "EAP: EMSKname", EMSKname, EAP_EMSK_NAME_LEN); + + pos = wpa_snprintf_hex(erp->keyname_nai, nai_buf_len, + EMSKname, EAP_EMSK_NAME_LEN); + erp->keyname_nai[pos] = '@'; + os_memcpy(&erp->keyname_nai[pos + 1], domain, domain_len); + + WPA_PUT_BE16(len, emsk_len); + if (hmac_sha256_kdf(emsk, emsk_len, + "EAP Re-authentication Root Key@ietf.org", + len, sizeof(len), erp->rRK, emsk_len) < 0) { + wpa_printf(MSG_DEBUG, "EAP: Could not derive rRK for ERP"); + goto fail; + } + erp->rRK_len = emsk_len; + wpa_hexdump_key(MSG_DEBUG, "EAP: ERP rRK", erp->rRK, erp->rRK_len); + + if (hmac_sha256_kdf(erp->rRK, erp->rRK_len, + "EAP Re-authentication Integrity Key@ietf.org", + len, sizeof(len), erp->rIK, erp->rRK_len) < 0) { + wpa_printf(MSG_DEBUG, "EAP: Could not derive rIK for ERP"); + goto fail; + } + erp->rIK_len = erp->rRK_len; + wpa_hexdump_key(MSG_DEBUG, "EAP: ERP rIK", erp->rIK, erp->rIK_len); + + if (eap_erp_add_key(sm, erp) == 0) { + wpa_printf(MSG_DEBUG, "EAP: Stored ERP keys %s", + erp->keyname_nai); + erp = NULL; + } + +fail: + bin_clear_free(emsk, emsk_len); + bin_clear_free(erp, sizeof(*erp)); +#endif /* CONFIG_ERP */ +} + + SM_STATE(EAP, METHOD_RESPONSE) { SM_ENTRY(EAP, METHOD_RESPONSE); @@ -416,6 +526,8 @@ SM_STATE(EAP, METHOD_RESPONSE) sm->eap_if.eapSessionId, sm->eap_if.eapSessionIdLen); } + if (sm->erp && sm->m->get_emsk && sm->eap_if.eapSessionId) + eap_server_erp_init(sm); sm->methodState = METHOD_END; } else { sm->methodState = METHOD_CONTINUE; @@ -573,12 +685,307 @@ SM_STATE(EAP, INITIATE_REAUTH_START) } +#ifdef CONFIG_ERP + +static void erp_send_finish_reauth(struct eap_sm *sm, + struct eap_server_erp_key *erp, u8 id, + u8 flags, u16 seq, const char *nai) +{ + size_t plen; + struct wpabuf *msg; + u8 hash[SHA256_MAC_LEN]; + size_t hash_len; + u8 seed[4]; + + if (erp) { + switch (erp->cryptosuite) { + case EAP_ERP_CS_HMAC_SHA256_256: + hash_len = 32; + break; + case EAP_ERP_CS_HMAC_SHA256_128: + hash_len = 16; + break; + default: + return; + } + } else + hash_len = 0; + + plen = 1 + 2 + 2 + os_strlen(nai); + if (hash_len) + plen += 1 + hash_len; + msg = eap_msg_alloc(EAP_VENDOR_IETF, EAP_ERP_TYPE_REAUTH, plen, + EAP_CODE_FINISH, id); + if (msg == NULL) + return; + wpabuf_put_u8(msg, flags); + wpabuf_put_be16(msg, seq); + + wpabuf_put_u8(msg, EAP_ERP_TLV_KEYNAME_NAI); + wpabuf_put_u8(msg, os_strlen(nai)); + wpabuf_put_str(msg, nai); + + if (erp) { + wpabuf_put_u8(msg, erp->cryptosuite); + if (hmac_sha256(erp->rIK, erp->rIK_len, + wpabuf_head(msg), wpabuf_len(msg), hash) < 0) { + wpabuf_free(msg); + return; + } + wpabuf_put_data(msg, hash, hash_len); + } + + wpa_printf(MSG_DEBUG, "EAP: Send EAP-Finish/Re-auth (%s)", + flags & 0x80 ? "failure" : "success"); + + sm->lastId = sm->currentId; + sm->currentId = id; + wpabuf_free(sm->eap_if.eapReqData); + sm->eap_if.eapReqData = msg; + wpabuf_free(sm->lastReqData); + sm->lastReqData = NULL; + + if (flags & 0x80) { + sm->eap_if.eapFail = TRUE; + wpa_msg(sm->msg_ctx, MSG_INFO, WPA_EVENT_EAP_FAILURE + MACSTR, MAC2STR(sm->peer_addr)); + return; + } + + bin_clear_free(sm->eap_if.eapKeyData, sm->eap_if.eapKeyDataLen); + sm->eap_if.eapKeyDataLen = 0; + sm->eap_if.eapKeyData = os_malloc(erp->rRK_len); + if (!sm->eap_if.eapKeyData) + return; + + WPA_PUT_BE16(seed, seq); + WPA_PUT_BE16(&seed[2], erp->rRK_len); + if (hmac_sha256_kdf(erp->rRK, erp->rRK_len, + "Re-authentication Master Session Key@ietf.org", + seed, sizeof(seed), + sm->eap_if.eapKeyData, erp->rRK_len) < 0) { + wpa_printf(MSG_DEBUG, "EAP: Could not derive rMSK for ERP"); + bin_clear_free(sm->eap_if.eapKeyData, erp->rRK_len); + sm->eap_if.eapKeyData = NULL; + return; + } + sm->eap_if.eapKeyDataLen = erp->rRK_len; + sm->eap_if.eapKeyAvailable = TRUE; + wpa_hexdump_key(MSG_DEBUG, "EAP: ERP rMSK", + sm->eap_if.eapKeyData, sm->eap_if.eapKeyDataLen); + sm->eap_if.eapSuccess = TRUE; + + wpa_msg(sm->msg_ctx, MSG_INFO, WPA_EVENT_EAP_SUCCESS + MACSTR, MAC2STR(sm->peer_addr)); +} + + +SM_STATE(EAP, INITIATE_RECEIVED) +{ + const u8 *pos, *end, *start, *tlvs, *hdr; + const struct eap_hdr *ehdr; + size_t len; + u8 flags; + u16 seq; + char nai[254]; + struct eap_server_erp_key *erp; + int max_len; + u8 hash[SHA256_MAC_LEN]; + size_t hash_len; + struct erp_tlvs parse; + u8 resp_flags = 0x80; /* default to failure; cleared on success */ + + SM_ENTRY(EAP, INITIATE_RECEIVED); + + sm->rxInitiate = FALSE; + + pos = eap_hdr_validate(EAP_VENDOR_IETF, EAP_ERP_TYPE_REAUTH, + sm->eap_if.eapRespData, &len); + if (pos == NULL) { + wpa_printf(MSG_INFO, "EAP-Initiate: Invalid frame"); + goto fail; + } + hdr = wpabuf_head(sm->eap_if.eapRespData); + ehdr = wpabuf_head(sm->eap_if.eapRespData); + + wpa_hexdump(MSG_DEBUG, "EAP: EAP-Initiate/Re-Auth", pos, len); + if (len < 4) { + wpa_printf(MSG_INFO, "EAP: Too short EAP-Initiate/Re-auth"); + goto fail; + } + end = pos + len; + + flags = *pos++; + seq = WPA_GET_BE16(pos); + pos += 2; + wpa_printf(MSG_DEBUG, "EAP: Flags=0x%x SEQ=%u", flags, seq); + tlvs = pos; + + /* + * Parse TVs/TLVs. Since we do not yet know the length of the + * Authentication Tag, stop parsing if an unknown TV/TLV is seen and + * just try to find the keyName-NAI first so that we can check the + * Authentication Tag. + */ + if (erp_parse_tlvs(tlvs, end, &parse, 1) < 0) + goto fail; + + if (!parse.keyname) { + wpa_printf(MSG_DEBUG, + "EAP: No keyName-NAI in EAP-Initiate/Re-auth Packet"); + goto fail; + } + + wpa_hexdump_ascii(MSG_DEBUG, "EAP: EAP-Initiate/Re-auth - keyName-NAI", + parse.keyname, parse.keyname_len); + if (parse.keyname_len > 253) { + wpa_printf(MSG_DEBUG, + "EAP: Too long keyName-NAI in EAP-Initiate/Re-auth"); + goto fail; + } + os_memcpy(nai, parse.keyname, parse.keyname_len); + nai[parse.keyname_len] = '\0'; + + if (!sm->eap_server) { + /* + * In passthrough case, EAP-Initiate/Re-auth replaces + * EAP Identity exchange. Use keyName-NAI as the user identity + * and forward EAP-Initiate/Re-auth to the backend + * authentication server. + */ + wpa_printf(MSG_DEBUG, + "EAP: Use keyName-NAI as user identity for backend authentication"); + eap_server_clear_identity(sm); + sm->identity = (u8 *) dup_binstr(parse.keyname, + parse.keyname_len); + if (!sm->identity) + goto fail; + sm->identity_len = parse.keyname_len; + return; + } + + erp = eap_erp_get_key(sm, nai); + if (!erp) { + wpa_printf(MSG_DEBUG, "EAP: No matching ERP key found for %s", + nai); + goto report_error; + } + + if (erp->recv_seq != (u32) -1 && erp->recv_seq >= seq) { + wpa_printf(MSG_DEBUG, + "EAP: SEQ=%u replayed (already received SEQ=%u)", + seq, erp->recv_seq); + goto fail; + } + + /* Is there enough room for Cryptosuite and Authentication Tag? */ + start = parse.keyname + parse.keyname_len; + max_len = end - start; + if (max_len < + 1 + (erp->cryptosuite == EAP_ERP_CS_HMAC_SHA256_256 ? 32 : 16)) { + wpa_printf(MSG_DEBUG, + "EAP: Not enough room for Authentication Tag"); + goto fail; + } + + switch (erp->cryptosuite) { + case EAP_ERP_CS_HMAC_SHA256_256: + if (end[-33] != erp->cryptosuite) { + wpa_printf(MSG_DEBUG, + "EAP: Different Cryptosuite used"); + goto fail; + } + hash_len = 32; + break; + case EAP_ERP_CS_HMAC_SHA256_128: + if (end[-17] != erp->cryptosuite) { + wpa_printf(MSG_DEBUG, + "EAP: Different Cryptosuite used"); + goto fail; + } + hash_len = 16; + break; + default: + hash_len = 0; + break; + } + + if (hash_len) { + if (hmac_sha256(erp->rIK, erp->rIK_len, hdr, + end - hdr - hash_len, hash) < 0) + goto fail; + if (os_memcmp(end - hash_len, hash, hash_len) != 0) { + wpa_printf(MSG_DEBUG, + "EAP: Authentication Tag mismatch"); + goto fail; + } + } + + /* Check if any supported CS results in matching tag */ + if (!hash_len && max_len >= 1 + 32 && + end[-33] == EAP_ERP_CS_HMAC_SHA256_256) { + if (hmac_sha256(erp->rIK, erp->rIK_len, hdr, + end - hdr - 32, hash) < 0) + goto fail; + if (os_memcmp(end - 32, hash, 32) == 0) { + wpa_printf(MSG_DEBUG, + "EAP: Authentication Tag match using HMAC-SHA256-256"); + hash_len = 32; + erp->cryptosuite = EAP_ERP_CS_HMAC_SHA256_256; + } + } + + if (!hash_len && end[-17] == EAP_ERP_CS_HMAC_SHA256_128) { + if (hmac_sha256(erp->rIK, erp->rIK_len, hdr, + end - hdr - 16, hash) < 0) + goto fail; + if (os_memcmp(end - 16, hash, 16) == 0) { + wpa_printf(MSG_DEBUG, + "EAP: Authentication Tag match using HMAC-SHA256-128"); + hash_len = 16; + erp->cryptosuite = EAP_ERP_CS_HMAC_SHA256_128; + } + } + + if (!hash_len) { + wpa_printf(MSG_DEBUG, + "EAP: No supported cryptosuite matched Authentication Tag"); + goto fail; + } + end -= 1 + hash_len; + + /* + * Parse TVs/TLVs again now that we know the exact part of the buffer + * that contains them. + */ + wpa_hexdump(MSG_DEBUG, "EAP: EAP-Initiate/Re-Auth TVs/TLVs", + tlvs, end - tlvs); + if (erp_parse_tlvs(tlvs, end, &parse, 0) < 0) + goto fail; + + wpa_printf(MSG_DEBUG, "EAP: ERP key %s SEQ updated to %u", + erp->keyname_nai, seq); + erp->recv_seq = seq; + resp_flags &= ~0x80; /* R=0 - success */ + +report_error: + erp_send_finish_reauth(sm, erp, ehdr->identifier, resp_flags, seq, nai); + return; + +fail: + sm->ignore = TRUE; +} + +#endif /* CONFIG_ERP */ + + SM_STATE(EAP, INITIALIZE_PASSTHROUGH) { SM_ENTRY(EAP, INITIALIZE_PASSTHROUGH); wpabuf_free(sm->eap_if.aaaEapRespData); sm->eap_if.aaaEapRespData = NULL; + sm->try_initiate_reauth = FALSE; } @@ -802,6 +1209,10 @@ SM_STEP(EAP) sm->respVendor == EAP_VENDOR_IETF && sm->respVendorMethod == sm->currentMethod))) SM_ENTER(EAP, INTEGRITY_CHECK); +#ifdef CONFIG_ERP + else if (sm->rxInitiate) + SM_ENTER(EAP, INITIATE_RECEIVED); +#endif /* CONFIG_ERP */ else { wpa_printf(MSG_DEBUG, "EAP: RECEIVED->DISCARD: " "rxResp=%d respId=%d currentId=%d " @@ -892,12 +1303,20 @@ SM_STEP(EAP) SM_ENTER(EAP, INITIALIZE_PASSTHROUGH); else if (sm->decision == DECISION_INITIATE_REAUTH_START) SM_ENTER(EAP, INITIATE_REAUTH_START); +#ifdef CONFIG_ERP + else if (sm->eap_server && sm->erp && sm->rxInitiate) + SM_ENTER(EAP, INITIATE_RECEIVED); +#endif /* CONFIG_ERP */ else SM_ENTER(EAP, PROPOSE_METHOD); break; case EAP_INITIATE_REAUTH_START: SM_ENTER(EAP, SEND_REQUEST); break; + case EAP_INITIATE_RECEIVED: + if (!sm->eap_server) + SM_ENTER(EAP, SELECT_ACTION); + break; case EAP_TIMEOUT_FAILURE: break; case EAP_FAILURE: @@ -1026,6 +1445,7 @@ static void eap_sm_parseEapResp(struct eap_sm *sm, const struct wpabuf *resp) /* parse rxResp, respId, respMethod */ sm->rxResp = FALSE; + sm->rxInitiate = FALSE; sm->respId = -1; sm->respMethod = EAP_TYPE_NONE; sm->respVendor = EAP_VENDOR_IETF; @@ -1052,6 +1472,8 @@ static void eap_sm_parseEapResp(struct eap_sm *sm, const struct wpabuf *resp) if (hdr->code == EAP_CODE_RESPONSE) sm->rxResp = TRUE; + else if (hdr->code == EAP_CODE_INITIATE) + sm->rxInitiate = TRUE; if (plen > sizeof(*hdr)) { u8 *pos = (u8 *) (hdr + 1); @@ -1069,10 +1491,10 @@ static void eap_sm_parseEapResp(struct eap_sm *sm, const struct wpabuf *resp) } } - wpa_printf(MSG_DEBUG, "EAP: parseEapResp: rxResp=%d respId=%d " - "respMethod=%u respVendor=%u respVendorMethod=%u", - sm->rxResp, sm->respId, sm->respMethod, sm->respVendor, - sm->respVendorMethod); + wpa_printf(MSG_DEBUG, + "EAP: parseEapResp: rxResp=%d rxInitiate=%d respId=%d respMethod=%u respVendor=%u respVendorMethod=%u", + sm->rxResp, sm->rxInitiate, sm->respId, sm->respMethod, + sm->respVendor, sm->respVendorMethod); } @@ -1430,6 +1852,7 @@ struct eap_sm * eap_server_sm_init(void *eapol_ctx, sm->pbc_in_m1 = conf->pbc_in_m1; sm->server_id = conf->server_id; sm->server_id_len = conf->server_id_len; + sm->erp = conf->erp; #ifdef CONFIG_TESTING_OPTIONS sm->tls_test_flags = conf->tls_test_flags; diff --git a/src/eapol_auth/eapol_auth_sm.c b/src/eapol_auth/eapol_auth_sm.c index 088e9d3e2..0df6eb564 100644 --- a/src/eapol_auth/eapol_auth_sm.c +++ b/src/eapol_auth/eapol_auth_sm.c @@ -834,6 +834,7 @@ eapol_auth_alloc(struct eapol_authenticator *eapol, const u8 *addr, eap_conf.pbc_in_m1 = eapol->conf.pbc_in_m1; eap_conf.server_id = eapol->conf.server_id; eap_conf.server_id_len = eapol->conf.server_id_len; + eap_conf.erp = eapol->conf.erp; sm->eap = eap_server_sm_init(sm, &eapol_cb, &eap_conf); if (sm->eap == NULL) { eapol_auth_free(sm); @@ -1040,6 +1041,21 @@ static const char * eapol_sm_get_erp_domain(void *ctx) } +static struct eap_server_erp_key * eapol_sm_erp_get_key(void *ctx, + const char *keyname) +{ + struct eapol_state_machine *sm = ctx; + return sm->eapol->cb.erp_get_key(sm->eapol->conf.ctx, keyname); +} + + +static int eapol_sm_erp_add_key(void *ctx, struct eap_server_erp_key *erp) +{ + struct eapol_state_machine *sm = ctx; + return sm->eapol->cb.erp_add_key(sm->eapol->conf.ctx, erp); +} + + static struct eapol_callbacks eapol_cb = { eapol_sm_get_eap_user, @@ -1047,6 +1063,8 @@ static struct eapol_callbacks eapol_cb = NULL, eapol_sm_get_erp_send_reauth_start, eapol_sm_get_erp_domain, + eapol_sm_erp_get_key, + eapol_sm_erp_add_key, }; @@ -1129,6 +1147,7 @@ static int eapol_auth_conf_clone(struct eapol_auth_config *dst, dst->erp_domain = NULL; } dst->erp_send_reauth_start = src->erp_send_reauth_start; + dst->erp = src->erp; return 0; @@ -1183,6 +1202,8 @@ struct eapol_authenticator * eapol_auth_init(struct eapol_auth_config *conf, eapol->cb.abort_auth = cb->abort_auth; eapol->cb.tx_key = cb->tx_key; eapol->cb.eapol_event = cb->eapol_event; + eapol->cb.erp_get_key = cb->erp_get_key; + eapol->cb.erp_add_key = cb->erp_add_key; /* Acct-Multi-Session-Id should be unique over reboots. If reliable * clock is not available, this could be replaced with reboot counter, diff --git a/src/eapol_auth/eapol_auth_sm.h b/src/eapol_auth/eapol_auth_sm.h index 90194d1f6..ebed19ade 100644 --- a/src/eapol_auth/eapol_auth_sm.h +++ b/src/eapol_auth/eapol_auth_sm.h @@ -26,6 +26,7 @@ struct eapol_auth_config { size_t eap_req_id_text_len; int erp_send_reauth_start; char *erp_domain; /* a copy of this will be allocated */ + int erp; /* Whether ERP is enabled on authentication server */ u8 *pac_opaque_encr_key; u8 *eap_fast_a_id; size_t eap_fast_a_id_len; @@ -47,6 +48,7 @@ struct eapol_auth_config { }; struct eap_user; +struct eap_server_erp_key; typedef enum { EAPOL_LOGGER_DEBUG, EAPOL_LOGGER_INFO, EAPOL_LOGGER_WARNING @@ -73,6 +75,9 @@ struct eapol_auth_cb { void (*abort_auth)(void *ctx, void *sta_ctx); void (*tx_key)(void *ctx, void *sta_ctx); void (*eapol_event)(void *ctx, void *sta_ctx, enum eapol_event type); + struct eap_server_erp_key * (*erp_get_key)(void *ctx, + const char *keyname); + int (*erp_add_key)(void *ctx, struct eap_server_erp_key *erp); }; diff --git a/src/radius/radius_server.c b/src/radius/radius_server.c index 54b258993..d9f2df655 100644 --- a/src/radius/radius_server.c +++ b/src/radius/radius_server.c @@ -251,6 +251,20 @@ struct radius_server_data { */ const char *server_id; + /** + * erp - Whether EAP Re-authentication Protocol (ERP) is enabled + * + * This controls whether the authentication server derives ERP key + * hierarchy (rRK and rIK) from full EAP authentication and allows + * these keys to be used to perform ERP to derive rMSK instead of full + * EAP authentication to derive MSK. + */ + int erp; + + const char *erp_domain; + + struct dl_list erp_keys; /* struct eap_server_erp_key */ + /** * wps - Wi-Fi Protected Setup context * @@ -673,6 +687,7 @@ radius_server_get_new_session(struct radius_server_data *data, eap_conf.pwd_group = data->pwd_group; eap_conf.server_id = (const u8 *) data->server_id; eap_conf.server_id_len = os_strlen(data->server_id); + eap_conf.erp = data->erp; radius_server_testing_options(sess, &eap_conf); sess->eap = eap_server_sm_init(sess, &radius_server_eapol_cb, &eap_conf); @@ -1687,6 +1702,7 @@ radius_server_init(struct radius_server_conf *conf) if (data == NULL) return NULL; + dl_list_init(&data->erp_keys); os_get_reltime(&data->start_time); data->conf_ctx = conf->conf_ctx; data->eap_sim_db_priv = conf->eap_sim_db_priv; @@ -1725,6 +1741,8 @@ radius_server_init(struct radius_server_conf *conf) data->eap_req_id_text_len = conf->eap_req_id_text_len; } } + data->erp = conf->erp; + data->erp_domain = conf->erp_domain; if (conf->subscr_remediation_url) { data->subscr_remediation_url = @@ -1807,6 +1825,8 @@ radius_server_init(struct radius_server_conf *conf) */ void radius_server_deinit(struct radius_server_data *data) { + struct eap_server_erp_key *erp; + if (data == NULL) return; @@ -1836,6 +1856,12 @@ void radius_server_deinit(struct radius_server_data *data) sqlite3_close(data->db); #endif /* CONFIG_SQLITE */ + while ((erp = dl_list_first(&data->erp_keys, struct eap_server_erp_key, + list)) != NULL) { + dl_list_del(&erp->list); + bin_clear_free(erp, sizeof(*erp)); + } + os_free(data); } @@ -2017,13 +2043,57 @@ static void radius_server_log_msg(void *ctx, const char *msg) } +#ifdef CONFIG_ERP + +static const char * radius_server_get_erp_domain(void *ctx) +{ + struct radius_session *sess = ctx; + struct radius_server_data *data = sess->server; + + return data->erp_domain; +} + + +static struct eap_server_erp_key * +radius_server_erp_get_key(void *ctx, const char *keyname) +{ + struct radius_session *sess = ctx; + struct radius_server_data *data = sess->server; + struct eap_server_erp_key *erp; + + dl_list_for_each(erp, &data->erp_keys, struct eap_server_erp_key, + list) { + if (os_strcmp(erp->keyname_nai, keyname) == 0) + return erp; + } + + return NULL; +} + + +static int radius_server_erp_add_key(void *ctx, struct eap_server_erp_key *erp) +{ + struct radius_session *sess = ctx; + struct radius_server_data *data = sess->server; + + dl_list_add(&data->erp_keys, &erp->list); + return 0; +} + +#endif /* CONFIG_ERP */ + + static struct eapol_callbacks radius_server_eapol_cb = { .get_eap_user = radius_server_get_eap_user, .get_eap_req_id_text = radius_server_get_eap_req_id_text, .log_msg = radius_server_log_msg, - NULL, - NULL, +#ifdef CONFIG_ERP + .get_erp_send_reauth_start = NULL, + .get_erp_domain = radius_server_get_erp_domain, + .erp_get_key = radius_server_erp_get_key, + .erp_add_key = radius_server_erp_add_key, +#endif /* CONFIG_ERP */ }; diff --git a/src/radius/radius_server.h b/src/radius/radius_server.h index 46ac3127e..1b8967c26 100644 --- a/src/radius/radius_server.h +++ b/src/radius/radius_server.h @@ -158,6 +158,18 @@ struct radius_server_conf { */ const char *server_id; + /** + * erp - Whether EAP Re-authentication Protocol (ERP) is enabled + * + * This controls whether the authentication server derives ERP key + * hierarchy (rRK and rIK) from full EAP authentication and allows + * these keys to be used to perform ERP to derive rMSK instead of full + * EAP authentication to derive MSK. + */ + int erp; + + const char *erp_domain; + /** * wps - Wi-Fi Protected Setup context *