]> git.ipfire.org Git - thirdparty/hostap.git/commitdiff
ERP: Add support for ERP on EAP server and authenticator
authorJouni Malinen <j@w1.fi>
Sat, 29 Nov 2014 19:28:24 +0000 (21:28 +0200)
committerJouni Malinen <j@w1.fi>
Thu, 4 Dec 2014 10:16:27 +0000 (12:16 +0200)
Derive rRK and rIK on EAP server if ERP is enabled and use these keys to
allow EAP re-authentication to be used and to derive rMSK.

The new hostapd configuration parameter eap_server_erp=1 can now be used
to configure the integrated EAP server to derive EMSK, rRK, and rIK at
the successful completion of an EAP authentication method. This
functionality is not included in the default build and can be enabled
with CONFIG_ERP=y.

Signed-off-by: Jouni Malinen <j@w1.fi>
15 files changed:
hostapd/Makefile
hostapd/config_file.c
hostapd/defconfig
hostapd/hostapd.conf
src/ap/ap_config.h
src/ap/authsrv.c
src/ap/hostapd.h
src/ap/ieee802_1x.c
src/eap_server/eap.h
src/eap_server/eap_i.h
src/eap_server/eap_server.c
src/eapol_auth/eapol_auth_sm.c
src/eapol_auth/eapol_auth_sm.h
src/radius/radius_server.c
src/radius/radius_server.h

index 74cd9fa736ba40e6a8c6ce60926fa5287a3e7cf9..dea4c159588fef0e6d32498e85a59d3b5ac1042f 100644 (file)
@@ -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
index 76d89649f832a14d20dea2deab2229562cae1c9c..c9ff3ec519b2cb49035b88db4469f235cdb0c5f1 100644 (file)
@@ -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;
index 8f8a53ff1d043dd1b29a963eeaa71c7a438e6a80..4cde2b563483b8bb0d5fa5721185bad581447bbf 100644 (file)
@@ -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
 
index 44dacb733b80e9d5e2ea847a4011b259a6e85017..2f6126c3af02412f19219256353c3c52efaf70cf 100644 (file)
@@ -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) #######################
 
index 874ce617112b1f5f17b1aedb68c93c3a6b043b5b..64df60d3480a0d561e6cd1acc1fd8356af9acaa2 100644 (file)
@@ -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;
index 690f1dc419ad81bb457b0b2341d711e0c76d4df0..bd1778e418651140157e4d39cd22694a00839f53 100644 (file)
@@ -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) {
index ba169f499ac78d9c1f926af1907af4ec1c53360f..4a50a008db60ebf2586612c32b29ffa4e6c1e638 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * hostapd / Initialization and configuration
- * Copyright (c) 2002-2013, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2002-2014, Jouni Malinen <j@w1.fi>
  *
  * 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;
 
index d29838564f682465c27e7aa74522c42293bbf223..84a98abc48232dbaef701da1390cdd819b4cedfa 100644 (file)
@@ -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));
+       }
 }
 
 
index 395d8955f0e1f9a7e8a2c1a3d537a2f11b55fe64..9de6cb62f517bc3314cd8a8128c0e9d0d613b5d4 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * hostapd / EAP Full Authenticator state machine (RFC 4137)
- * Copyright (c) 2004-2007, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2004-2014, Jouni Malinen <j@w1.fi>
  *
  * 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;
index 9c757d9c8c6f0f95ab196d7fbc21930e0844d563..7d723091ffb5c51518e7b8c9c820eceb8c6e593d 100644 (file)
@@ -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;
index b0ec75700f98c4e5c02bb46f3ea9086310ec7119..53e32c3817b1c19f2c47c3e237051992c35e0a58 100644 (file)
@@ -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;
index 088e9d3e2aeccadd996005012e22dd8543963fcc..0df6eb56416b64a8e6528a3fdbb54a75cd07fbd8 100644 (file)
@@ -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,
index 90194d1f6ce5d25e1dc9c0ed92629eda756f25e7..ebed19adefc7b84fc1d45578c102dc00249c41aa 100644 (file)
@@ -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);
 };
 
 
index 54b25899353e5ec0d5b3b58f6126584fe796bc47..d9f2df655e516fa33323e5cb406625a2f98776fa 100644 (file)
@@ -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 */
 };
 
 
index 46ac3127eef8d92793d3450875b2f8809184e045..1b8967c26bcfb6f366684de6eb055e9a50f7b3e1 100644 (file)
@@ -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
         *