ERP: Add support for ERP on EAP server and authenticator

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>
This commit is contained in:
Jouni Malinen 2014-11-29 21:28:24 +02:00
parent e2ee327b19
commit d3bddd8b84
15 changed files with 676 additions and 13 deletions

View 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;

View 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;

View 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;