hostap/src/eap_server/eap_server_fast.c
David Benjamin 7358170787 TLS: Split tls_connection_prf() into two functions
Most protocols extracting keys from TLS use RFC 5705 exporters which is
commonly implemented in TLS libraries. This is the mechanism used by
EAP-TLS. (EAP-TLS actually predates RFC 5705, but RFC 5705 was defined
to be compatible with it.)

EAP-FAST, however, uses a legacy mechanism. It reuses the TLS internal
key block derivation and derives key material after the key block. This
is uncommon and a misuse of TLS internals, so not all TLS libraries
support this. Instead, we reimplement the PRF for the OpenSSL backend
and don't support it at all in the GnuTLS one.

Since these two are very different operations, split
tls_connection_prf() in two. tls_connection_export_key() implements the
standard RFC 5705 mechanism that we expect most TLS libraries to
support. tls_connection_get_eap_fast_key() implements the
EAP-FAST-specific legacy mechanism which may not be implemented on all
backends but is only used by EAP-FAST.

Signed-Off-By: David Benjamin <davidben@google.com>
2016-05-23 20:40:12 +03:00

1640 lines
42 KiB
C

/*
* EAP-FAST server (RFC 4851)
* Copyright (c) 2004-2008, Jouni Malinen <j@w1.fi>
*
* This software may be distributed under the terms of the BSD license.
* See README for more details.
*/
#include "includes.h"
#include "common.h"
#include "crypto/aes_wrap.h"
#include "crypto/sha1.h"
#include "crypto/tls.h"
#include "crypto/random.h"
#include "eap_common/eap_tlv_common.h"
#include "eap_common/eap_fast_common.h"
#include "eap_i.h"
#include "eap_tls_common.h"
static void eap_fast_reset(struct eap_sm *sm, void *priv);
/* Private PAC-Opaque TLV types */
#define PAC_OPAQUE_TYPE_PAD 0
#define PAC_OPAQUE_TYPE_KEY 1
#define PAC_OPAQUE_TYPE_LIFETIME 2
#define PAC_OPAQUE_TYPE_IDENTITY 3
struct eap_fast_data {
struct eap_ssl_data ssl;
enum {
START, PHASE1, PHASE2_START, PHASE2_ID, PHASE2_METHOD,
CRYPTO_BINDING, REQUEST_PAC, SUCCESS, FAILURE
} state;
int fast_version;
const struct eap_method *phase2_method;
void *phase2_priv;
int force_version;
int peer_version;
u8 crypto_binding_nonce[32];
int final_result;
struct eap_fast_key_block_provisioning *key_block_p;
u8 simck[EAP_FAST_SIMCK_LEN];
u8 cmk[EAP_FAST_CMK_LEN];
int simck_idx;
u8 pac_opaque_encr[16];
u8 *srv_id;
size_t srv_id_len;
char *srv_id_info;
int anon_provisioning;
int send_new_pac; /* server triggered re-keying of Tunnel PAC */
struct wpabuf *pending_phase2_resp;
u8 *identity; /* from PAC-Opaque */
size_t identity_len;
int eap_seq;
int tnc_started;
int pac_key_lifetime;
int pac_key_refresh_time;
};
static int eap_fast_process_phase2_start(struct eap_sm *sm,
struct eap_fast_data *data);
static const char * eap_fast_state_txt(int state)
{
switch (state) {
case START:
return "START";
case PHASE1:
return "PHASE1";
case PHASE2_START:
return "PHASE2_START";
case PHASE2_ID:
return "PHASE2_ID";
case PHASE2_METHOD:
return "PHASE2_METHOD";
case CRYPTO_BINDING:
return "CRYPTO_BINDING";
case REQUEST_PAC:
return "REQUEST_PAC";
case SUCCESS:
return "SUCCESS";
case FAILURE:
return "FAILURE";
default:
return "Unknown?!";
}
}
static void eap_fast_state(struct eap_fast_data *data, int state)
{
wpa_printf(MSG_DEBUG, "EAP-FAST: %s -> %s",
eap_fast_state_txt(data->state),
eap_fast_state_txt(state));
data->state = state;
}
static EapType eap_fast_req_failure(struct eap_sm *sm,
struct eap_fast_data *data)
{
/* TODO: send Result TLV(FAILURE) */
eap_fast_state(data, FAILURE);
return EAP_TYPE_NONE;
}
static int eap_fast_session_ticket_cb(void *ctx, const u8 *ticket, size_t len,
const u8 *client_random,
const u8 *server_random,
u8 *master_secret)
{
struct eap_fast_data *data = ctx;
const u8 *pac_opaque;
size_t pac_opaque_len;
u8 *buf, *pos, *end, *pac_key = NULL;
os_time_t lifetime = 0;
struct os_time now;
u8 *identity = NULL;
size_t identity_len = 0;
wpa_printf(MSG_DEBUG, "EAP-FAST: SessionTicket callback");
wpa_hexdump(MSG_DEBUG, "EAP-FAST: SessionTicket (PAC-Opaque)",
ticket, len);
if (len < 4 || WPA_GET_BE16(ticket) != PAC_TYPE_PAC_OPAQUE) {
wpa_printf(MSG_DEBUG, "EAP-FAST: Ignore invalid "
"SessionTicket");
return 0;
}
pac_opaque_len = WPA_GET_BE16(ticket + 2);
pac_opaque = ticket + 4;
if (pac_opaque_len < 8 || pac_opaque_len % 8 ||
pac_opaque_len > len - 4) {
wpa_printf(MSG_DEBUG, "EAP-FAST: Ignore invalid PAC-Opaque "
"(len=%lu left=%lu)",
(unsigned long) pac_opaque_len,
(unsigned long) len);
return 0;
}
wpa_hexdump(MSG_DEBUG, "EAP-FAST: Received PAC-Opaque",
pac_opaque, pac_opaque_len);
buf = os_malloc(pac_opaque_len - 8);
if (buf == NULL) {
wpa_printf(MSG_DEBUG, "EAP-FAST: Failed to allocate memory "
"for decrypting PAC-Opaque");
return 0;
}
if (aes_unwrap(data->pac_opaque_encr, sizeof(data->pac_opaque_encr),
(pac_opaque_len - 8) / 8, pac_opaque, buf) < 0) {
wpa_printf(MSG_DEBUG, "EAP-FAST: Failed to decrypt "
"PAC-Opaque");
os_free(buf);
/*
* This may have been caused by server changing the PAC-Opaque
* encryption key, so just ignore this PAC-Opaque instead of
* failing the authentication completely. Provisioning can now
* be used to provision a new PAC.
*/
return 0;
}
end = buf + pac_opaque_len - 8;
wpa_hexdump_key(MSG_DEBUG, "EAP-FAST: Decrypted PAC-Opaque",
buf, end - buf);
pos = buf;
while (end - pos > 1) {
u8 id, elen;
id = *pos++;
elen = *pos++;
if (elen > end - pos)
break;
switch (id) {
case PAC_OPAQUE_TYPE_PAD:
goto done;
case PAC_OPAQUE_TYPE_KEY:
if (elen != EAP_FAST_PAC_KEY_LEN) {
wpa_printf(MSG_DEBUG,
"EAP-FAST: Invalid PAC-Key length %d",
elen);
os_free(buf);
return -1;
}
pac_key = pos;
wpa_hexdump_key(MSG_DEBUG, "EAP-FAST: PAC-Key from "
"decrypted PAC-Opaque",
pac_key, EAP_FAST_PAC_KEY_LEN);
break;
case PAC_OPAQUE_TYPE_LIFETIME:
if (elen != 4) {
wpa_printf(MSG_DEBUG, "EAP-FAST: Invalid "
"PAC-Key lifetime length %d",
elen);
os_free(buf);
return -1;
}
lifetime = WPA_GET_BE32(pos);
break;
case PAC_OPAQUE_TYPE_IDENTITY:
identity = pos;
identity_len = elen;
break;
}
pos += elen;
}
done:
if (pac_key == NULL) {
wpa_printf(MSG_DEBUG, "EAP-FAST: No PAC-Key included in "
"PAC-Opaque");
os_free(buf);
return -1;
}
if (identity) {
wpa_hexdump_ascii(MSG_DEBUG, "EAP-FAST: Identity from "
"PAC-Opaque", identity, identity_len);
os_free(data->identity);
data->identity = os_malloc(identity_len);
if (data->identity) {
os_memcpy(data->identity, identity, identity_len);
data->identity_len = identity_len;
}
}
if (os_get_time(&now) < 0 || lifetime <= 0 || now.sec > lifetime) {
wpa_printf(MSG_DEBUG, "EAP-FAST: PAC-Key not valid anymore "
"(lifetime=%ld now=%ld)", lifetime, now.sec);
data->send_new_pac = 2;
/*
* Allow PAC to be used to allow a PAC update with some level
* of server authentication (i.e., do not fall back to full TLS
* handshake since we cannot be sure that the peer would be
* able to validate server certificate now). However, reject
* the authentication since the PAC was not valid anymore. Peer
* can connect again with the newly provisioned PAC after this.
*/
} else if (lifetime - now.sec < data->pac_key_refresh_time) {
wpa_printf(MSG_DEBUG, "EAP-FAST: PAC-Key soft timeout; send "
"an update if authentication succeeds");
data->send_new_pac = 1;
}
eap_fast_derive_master_secret(pac_key, server_random, client_random,
master_secret);
os_free(buf);
return 1;
}
static void eap_fast_derive_key_auth(struct eap_sm *sm,
struct eap_fast_data *data)
{
u8 *sks;
/* RFC 4851, Section 5.1:
* Extra key material after TLS key_block: session_key_seed[40]
*/
sks = eap_fast_derive_key(sm->ssl_ctx, data->ssl.conn,
EAP_FAST_SKS_LEN);
if (sks == NULL) {
wpa_printf(MSG_DEBUG, "EAP-FAST: Failed to derive "
"session_key_seed");
return;
}
/*
* RFC 4851, Section 5.2:
* S-IMCK[0] = session_key_seed
*/
wpa_hexdump_key(MSG_DEBUG,
"EAP-FAST: session_key_seed (SKS = S-IMCK[0])",
sks, EAP_FAST_SKS_LEN);
data->simck_idx = 0;
os_memcpy(data->simck, sks, EAP_FAST_SIMCK_LEN);
os_free(sks);
}
static void eap_fast_derive_key_provisioning(struct eap_sm *sm,
struct eap_fast_data *data)
{
os_free(data->key_block_p);
data->key_block_p = (struct eap_fast_key_block_provisioning *)
eap_fast_derive_key(sm->ssl_ctx, data->ssl.conn,
sizeof(*data->key_block_p));
if (data->key_block_p == NULL) {
wpa_printf(MSG_DEBUG, "EAP-FAST: Failed to derive key block");
return;
}
/*
* RFC 4851, Section 5.2:
* S-IMCK[0] = session_key_seed
*/
wpa_hexdump_key(MSG_DEBUG,
"EAP-FAST: session_key_seed (SKS = S-IMCK[0])",
data->key_block_p->session_key_seed,
sizeof(data->key_block_p->session_key_seed));
data->simck_idx = 0;
os_memcpy(data->simck, data->key_block_p->session_key_seed,
EAP_FAST_SIMCK_LEN);
wpa_hexdump_key(MSG_DEBUG, "EAP-FAST: server_challenge",
data->key_block_p->server_challenge,
sizeof(data->key_block_p->server_challenge));
wpa_hexdump_key(MSG_DEBUG, "EAP-FAST: client_challenge",
data->key_block_p->client_challenge,
sizeof(data->key_block_p->client_challenge));
}
static int eap_fast_get_phase2_key(struct eap_sm *sm,
struct eap_fast_data *data,
u8 *isk, size_t isk_len)
{
u8 *key;
size_t key_len;
os_memset(isk, 0, isk_len);
if (data->phase2_method == NULL || data->phase2_priv == NULL) {
wpa_printf(MSG_DEBUG, "EAP-FAST: Phase 2 method not "
"available");
return -1;
}
if (data->phase2_method->getKey == NULL)
return 0;
if ((key = data->phase2_method->getKey(sm, data->phase2_priv,
&key_len)) == NULL) {
wpa_printf(MSG_DEBUG, "EAP-FAST: Could not get key material "
"from Phase 2");
return -1;
}
if (key_len > isk_len)
key_len = isk_len;
if (key_len == 32 &&
data->phase2_method->vendor == EAP_VENDOR_IETF &&
data->phase2_method->method == EAP_TYPE_MSCHAPV2) {
/*
* EAP-FAST uses reverse order for MS-MPPE keys when deriving
* MSK from EAP-MSCHAPv2. Swap the keys here to get the correct
* ISK for EAP-FAST cryptobinding.
*/
os_memcpy(isk, key + 16, 16);
os_memcpy(isk + 16, key, 16);
} else
os_memcpy(isk, key, key_len);
os_free(key);
return 0;
}
static int eap_fast_update_icmk(struct eap_sm *sm, struct eap_fast_data *data)
{
u8 isk[32], imck[60];
wpa_printf(MSG_DEBUG, "EAP-FAST: Deriving ICMK[%d] (S-IMCK and CMK)",
data->simck_idx + 1);
/*
* RFC 4851, Section 5.2:
* IMCK[j] = T-PRF(S-IMCK[j-1], "Inner Methods Compound Keys",
* MSK[j], 60)
* S-IMCK[j] = first 40 octets of IMCK[j]
* CMK[j] = last 20 octets of IMCK[j]
*/
if (eap_fast_get_phase2_key(sm, data, isk, sizeof(isk)) < 0)
return -1;
wpa_hexdump_key(MSG_MSGDUMP, "EAP-FAST: ISK[j]", isk, sizeof(isk));
sha1_t_prf(data->simck, EAP_FAST_SIMCK_LEN,
"Inner Methods Compound Keys",
isk, sizeof(isk), imck, sizeof(imck));
data->simck_idx++;
os_memcpy(data->simck, imck, EAP_FAST_SIMCK_LEN);
wpa_hexdump_key(MSG_MSGDUMP, "EAP-FAST: S-IMCK[j]",
data->simck, EAP_FAST_SIMCK_LEN);
os_memcpy(data->cmk, imck + EAP_FAST_SIMCK_LEN, EAP_FAST_CMK_LEN);
wpa_hexdump_key(MSG_MSGDUMP, "EAP-FAST: CMK[j]",
data->cmk, EAP_FAST_CMK_LEN);
return 0;
}
static void * eap_fast_init(struct eap_sm *sm)
{
struct eap_fast_data *data;
u8 ciphers[7] = {
TLS_CIPHER_ANON_DH_AES128_SHA,
TLS_CIPHER_AES128_SHA,
TLS_CIPHER_RSA_DHE_AES128_SHA,
TLS_CIPHER_RC4_SHA,
TLS_CIPHER_RSA_DHE_AES256_SHA,
TLS_CIPHER_AES256_SHA,
TLS_CIPHER_NONE
};
data = os_zalloc(sizeof(*data));
if (data == NULL)
return NULL;
data->fast_version = EAP_FAST_VERSION;
data->force_version = -1;
if (sm->user && sm->user->force_version >= 0) {
data->force_version = sm->user->force_version;
wpa_printf(MSG_DEBUG, "EAP-FAST: forcing version %d",
data->force_version);
data->fast_version = data->force_version;
}
data->state = START;
if (eap_server_tls_ssl_init(sm, &data->ssl, 0, EAP_TYPE_FAST)) {
wpa_printf(MSG_INFO, "EAP-FAST: Failed to initialize SSL.");
eap_fast_reset(sm, data);
return NULL;
}
if (tls_connection_set_cipher_list(sm->ssl_ctx, data->ssl.conn,
ciphers) < 0) {
wpa_printf(MSG_INFO, "EAP-FAST: Failed to set TLS cipher "
"suites");
eap_fast_reset(sm, data);
return NULL;
}
if (tls_connection_set_session_ticket_cb(sm->ssl_ctx, data->ssl.conn,
eap_fast_session_ticket_cb,
data) < 0) {
wpa_printf(MSG_INFO, "EAP-FAST: Failed to set SessionTicket "
"callback");
eap_fast_reset(sm, data);
return NULL;
}
if (sm->pac_opaque_encr_key == NULL) {
wpa_printf(MSG_INFO, "EAP-FAST: No PAC-Opaque encryption key "
"configured");
eap_fast_reset(sm, data);
return NULL;
}
os_memcpy(data->pac_opaque_encr, sm->pac_opaque_encr_key,
sizeof(data->pac_opaque_encr));
if (sm->eap_fast_a_id == NULL) {
wpa_printf(MSG_INFO, "EAP-FAST: No A-ID configured");
eap_fast_reset(sm, data);
return NULL;
}
data->srv_id = os_malloc(sm->eap_fast_a_id_len);
if (data->srv_id == NULL) {
eap_fast_reset(sm, data);
return NULL;
}
os_memcpy(data->srv_id, sm->eap_fast_a_id, sm->eap_fast_a_id_len);
data->srv_id_len = sm->eap_fast_a_id_len;
if (sm->eap_fast_a_id_info == NULL) {
wpa_printf(MSG_INFO, "EAP-FAST: No A-ID-Info configured");
eap_fast_reset(sm, data);
return NULL;
}
data->srv_id_info = os_strdup(sm->eap_fast_a_id_info);
if (data->srv_id_info == NULL) {
eap_fast_reset(sm, data);
return NULL;
}
/* PAC-Key lifetime in seconds (hard limit) */
data->pac_key_lifetime = sm->pac_key_lifetime;
/*
* PAC-Key refresh time in seconds (soft limit on remaining hard
* limit). The server will generate a new PAC-Key when this number of
* seconds (or fewer) of the lifetime remains.
*/
data->pac_key_refresh_time = sm->pac_key_refresh_time;
return data;
}
static void eap_fast_reset(struct eap_sm *sm, void *priv)
{
struct eap_fast_data *data = priv;
if (data == NULL)
return;
if (data->phase2_priv && data->phase2_method)
data->phase2_method->reset(sm, data->phase2_priv);
eap_server_tls_ssl_deinit(sm, &data->ssl);
os_free(data->srv_id);
os_free(data->srv_id_info);
os_free(data->key_block_p);
wpabuf_free(data->pending_phase2_resp);
os_free(data->identity);
bin_clear_free(data, sizeof(*data));
}
static struct wpabuf * eap_fast_build_start(struct eap_sm *sm,
struct eap_fast_data *data, u8 id)
{
struct wpabuf *req;
req = eap_msg_alloc(EAP_VENDOR_IETF, EAP_TYPE_FAST,
1 + sizeof(struct pac_tlv_hdr) + data->srv_id_len,
EAP_CODE_REQUEST, id);
if (req == NULL) {
wpa_printf(MSG_ERROR, "EAP-FAST: Failed to allocate memory for"
" request");
eap_fast_state(data, FAILURE);
return NULL;
}
wpabuf_put_u8(req, EAP_TLS_FLAGS_START | data->fast_version);
/* RFC 4851, 4.1.1. Authority ID Data */
eap_fast_put_tlv(req, PAC_TYPE_A_ID, data->srv_id, data->srv_id_len);
eap_fast_state(data, PHASE1);
return req;
}
static int eap_fast_phase1_done(struct eap_sm *sm, struct eap_fast_data *data)
{
char cipher[64];
wpa_printf(MSG_DEBUG, "EAP-FAST: Phase1 done, starting Phase2");
if (tls_get_cipher(sm->ssl_ctx, data->ssl.conn, cipher, sizeof(cipher))
< 0) {
wpa_printf(MSG_DEBUG, "EAP-FAST: Failed to get cipher "
"information");
eap_fast_state(data, FAILURE);
return -1;
}
data->anon_provisioning = os_strstr(cipher, "ADH") != NULL;
if (data->anon_provisioning) {
wpa_printf(MSG_DEBUG, "EAP-FAST: Anonymous provisioning");
eap_fast_derive_key_provisioning(sm, data);
} else
eap_fast_derive_key_auth(sm, data);
eap_fast_state(data, PHASE2_START);
return 0;
}
static struct wpabuf * eap_fast_build_phase2_req(struct eap_sm *sm,
struct eap_fast_data *data,
u8 id)
{
struct wpabuf *req;
if (data->phase2_priv == NULL) {
wpa_printf(MSG_DEBUG, "EAP-FAST: Phase 2 method not "
"initialized");
return NULL;
}
req = data->phase2_method->buildReq(sm, data->phase2_priv, id);
if (req == NULL)
return NULL;
wpa_hexdump_buf_key(MSG_MSGDUMP, "EAP-FAST: Phase 2 EAP-Request", req);
return eap_fast_tlv_eap_payload(req);
}
static struct wpabuf * eap_fast_build_crypto_binding(
struct eap_sm *sm, struct eap_fast_data *data)
{
struct wpabuf *buf;
struct eap_tlv_result_tlv *result;
struct eap_tlv_crypto_binding_tlv *binding;
buf = wpabuf_alloc(2 * sizeof(*result) + sizeof(*binding));
if (buf == NULL)
return NULL;
if (data->send_new_pac || data->anon_provisioning ||
data->phase2_method)
data->final_result = 0;
else
data->final_result = 1;
if (!data->final_result || data->eap_seq > 1) {
/* Intermediate-Result */
wpa_printf(MSG_DEBUG, "EAP-FAST: Add Intermediate-Result TLV "
"(status=SUCCESS)");
result = wpabuf_put(buf, sizeof(*result));
result->tlv_type = host_to_be16(
EAP_TLV_TYPE_MANDATORY |
EAP_TLV_INTERMEDIATE_RESULT_TLV);
result->length = host_to_be16(2);
result->status = host_to_be16(EAP_TLV_RESULT_SUCCESS);
}
if (data->final_result) {
/* Result TLV */
wpa_printf(MSG_DEBUG, "EAP-FAST: Add Result TLV "
"(status=SUCCESS)");
result = wpabuf_put(buf, sizeof(*result));
result->tlv_type = host_to_be16(EAP_TLV_TYPE_MANDATORY |
EAP_TLV_RESULT_TLV);
result->length = host_to_be16(2);
result->status = host_to_be16(EAP_TLV_RESULT_SUCCESS);
}
/* Crypto-Binding TLV */
binding = wpabuf_put(buf, sizeof(*binding));
binding->tlv_type = host_to_be16(EAP_TLV_TYPE_MANDATORY |
EAP_TLV_CRYPTO_BINDING_TLV);
binding->length = host_to_be16(sizeof(*binding) -
sizeof(struct eap_tlv_hdr));
binding->version = EAP_FAST_VERSION;
binding->received_version = data->peer_version;
binding->subtype = EAP_TLV_CRYPTO_BINDING_SUBTYPE_REQUEST;
if (random_get_bytes(binding->nonce, sizeof(binding->nonce)) < 0) {
wpabuf_free(buf);
return NULL;
}
/*
* RFC 4851, Section 4.2.8:
* The nonce in a request MUST have its least significant bit set to 0.
*/
binding->nonce[sizeof(binding->nonce) - 1] &= ~0x01;
os_memcpy(data->crypto_binding_nonce, binding->nonce,
sizeof(binding->nonce));
/*
* RFC 4851, Section 5.3:
* CMK = CMK[j]
* Compound-MAC = HMAC-SHA1( CMK, Crypto-Binding TLV )
*/
hmac_sha1(data->cmk, EAP_FAST_CMK_LEN,
(u8 *) binding, sizeof(*binding),
binding->compound_mac);
wpa_printf(MSG_DEBUG, "EAP-FAST: Add Crypto-Binding TLV: Version %d "
"Received Version %d SubType %d",
binding->version, binding->received_version,
binding->subtype);
wpa_hexdump(MSG_MSGDUMP, "EAP-FAST: NONCE",
binding->nonce, sizeof(binding->nonce));
wpa_hexdump(MSG_MSGDUMP, "EAP-FAST: Compound MAC",
binding->compound_mac, sizeof(binding->compound_mac));
return buf;
}
static struct wpabuf * eap_fast_build_pac(struct eap_sm *sm,
struct eap_fast_data *data)
{
u8 pac_key[EAP_FAST_PAC_KEY_LEN];
u8 *pac_buf, *pac_opaque;
struct wpabuf *buf;
u8 *pos;
size_t buf_len, srv_id_info_len, pac_len;
struct eap_tlv_hdr *pac_tlv;
struct pac_tlv_hdr *pac_info;
struct eap_tlv_result_tlv *result;
struct os_time now;
if (random_get_bytes(pac_key, EAP_FAST_PAC_KEY_LEN) < 0 ||
os_get_time(&now) < 0)
return NULL;
wpa_hexdump_key(MSG_DEBUG, "EAP-FAST: Generated PAC-Key",
pac_key, EAP_FAST_PAC_KEY_LEN);
pac_len = (2 + EAP_FAST_PAC_KEY_LEN) + (2 + 4) +
(2 + sm->identity_len) + 8;
pac_buf = os_malloc(pac_len);
if (pac_buf == NULL)
return NULL;
srv_id_info_len = os_strlen(data->srv_id_info);
pos = pac_buf;
*pos++ = PAC_OPAQUE_TYPE_KEY;
*pos++ = EAP_FAST_PAC_KEY_LEN;
os_memcpy(pos, pac_key, EAP_FAST_PAC_KEY_LEN);
pos += EAP_FAST_PAC_KEY_LEN;
*pos++ = PAC_OPAQUE_TYPE_LIFETIME;
*pos++ = 4;
WPA_PUT_BE32(pos, now.sec + data->pac_key_lifetime);
pos += 4;
if (sm->identity) {
*pos++ = PAC_OPAQUE_TYPE_IDENTITY;
*pos++ = sm->identity_len;
os_memcpy(pos, sm->identity, sm->identity_len);
pos += sm->identity_len;
}
pac_len = pos - pac_buf;
while (pac_len % 8) {
*pos++ = PAC_OPAQUE_TYPE_PAD;
pac_len++;
}
pac_opaque = os_malloc(pac_len + 8);
if (pac_opaque == NULL) {
os_free(pac_buf);
return NULL;
}
if (aes_wrap(data->pac_opaque_encr, sizeof(data->pac_opaque_encr),
pac_len / 8, pac_buf, pac_opaque) < 0) {
os_free(pac_buf);
os_free(pac_opaque);
return NULL;
}
os_free(pac_buf);
pac_len += 8;
wpa_hexdump(MSG_DEBUG, "EAP-FAST: PAC-Opaque",
pac_opaque, pac_len);
buf_len = sizeof(*pac_tlv) +
sizeof(struct pac_tlv_hdr) + EAP_FAST_PAC_KEY_LEN +
sizeof(struct pac_tlv_hdr) + pac_len +
data->srv_id_len + srv_id_info_len + 100 + sizeof(*result);
buf = wpabuf_alloc(buf_len);
if (buf == NULL) {
os_free(pac_opaque);
return NULL;
}
/* Result TLV */
wpa_printf(MSG_DEBUG, "EAP-FAST: Add Result TLV (status=SUCCESS)");
result = wpabuf_put(buf, sizeof(*result));
WPA_PUT_BE16((u8 *) &result->tlv_type,
EAP_TLV_TYPE_MANDATORY | EAP_TLV_RESULT_TLV);
WPA_PUT_BE16((u8 *) &result->length, 2);
WPA_PUT_BE16((u8 *) &result->status, EAP_TLV_RESULT_SUCCESS);
/* PAC TLV */
wpa_printf(MSG_DEBUG, "EAP-FAST: Add PAC TLV");
pac_tlv = wpabuf_put(buf, sizeof(*pac_tlv));
pac_tlv->tlv_type = host_to_be16(EAP_TLV_TYPE_MANDATORY |
EAP_TLV_PAC_TLV);
/* PAC-Key */
eap_fast_put_tlv(buf, PAC_TYPE_PAC_KEY, pac_key, EAP_FAST_PAC_KEY_LEN);
/* PAC-Opaque */
eap_fast_put_tlv(buf, PAC_TYPE_PAC_OPAQUE, pac_opaque, pac_len);
os_free(pac_opaque);
/* PAC-Info */
pac_info = wpabuf_put(buf, sizeof(*pac_info));
pac_info->type = host_to_be16(PAC_TYPE_PAC_INFO);
/* PAC-Lifetime (inside PAC-Info) */
eap_fast_put_tlv_hdr(buf, PAC_TYPE_CRED_LIFETIME, 4);
wpabuf_put_be32(buf, now.sec + data->pac_key_lifetime);
/* A-ID (inside PAC-Info) */
eap_fast_put_tlv(buf, PAC_TYPE_A_ID, data->srv_id, data->srv_id_len);
/* Note: headers may be misaligned after A-ID */
if (sm->identity) {
eap_fast_put_tlv(buf, PAC_TYPE_I_ID, sm->identity,
sm->identity_len);
}
/* A-ID-Info (inside PAC-Info) */
eap_fast_put_tlv(buf, PAC_TYPE_A_ID_INFO, data->srv_id_info,
srv_id_info_len);
/* PAC-Type (inside PAC-Info) */
eap_fast_put_tlv_hdr(buf, PAC_TYPE_PAC_TYPE, 2);
wpabuf_put_be16(buf, PAC_TYPE_TUNNEL_PAC);
/* Update PAC-Info and PAC TLV Length fields */
pos = wpabuf_put(buf, 0);
pac_info->len = host_to_be16(pos - (u8 *) (pac_info + 1));
pac_tlv->length = host_to_be16(pos - (u8 *) (pac_tlv + 1));
return buf;
}
static int eap_fast_encrypt_phase2(struct eap_sm *sm,
struct eap_fast_data *data,
struct wpabuf *plain, int piggyback)
{
struct wpabuf *encr;
wpa_hexdump_buf_key(MSG_DEBUG, "EAP-FAST: Encrypting Phase 2 TLVs",
plain);
encr = eap_server_tls_encrypt(sm, &data->ssl, plain);
wpabuf_free(plain);
if (!encr)
return -1;
if (data->ssl.tls_out && piggyback) {
wpa_printf(MSG_DEBUG, "EAP-FAST: Piggyback Phase 2 data "
"(len=%d) with last Phase 1 Message (len=%d "
"used=%d)",
(int) wpabuf_len(encr),
(int) wpabuf_len(data->ssl.tls_out),
(int) data->ssl.tls_out_pos);
if (wpabuf_resize(&data->ssl.tls_out, wpabuf_len(encr)) < 0) {
wpa_printf(MSG_WARNING, "EAP-FAST: Failed to resize "
"output buffer");
wpabuf_free(encr);
return -1;
}
wpabuf_put_buf(data->ssl.tls_out, encr);
wpabuf_free(encr);
} else {
wpabuf_free(data->ssl.tls_out);
data->ssl.tls_out_pos = 0;
data->ssl.tls_out = encr;
}
return 0;
}
static struct wpabuf * eap_fast_buildReq(struct eap_sm *sm, void *priv, u8 id)
{
struct eap_fast_data *data = priv;
struct wpabuf *req = NULL;
int piggyback = 0;
if (data->ssl.state == FRAG_ACK) {
return eap_server_tls_build_ack(id, EAP_TYPE_FAST,
data->fast_version);
}
if (data->ssl.state == WAIT_FRAG_ACK) {
return eap_server_tls_build_msg(&data->ssl, EAP_TYPE_FAST,
data->fast_version, id);
}
switch (data->state) {
case START:
return eap_fast_build_start(sm, data, id);
case PHASE1:
if (tls_connection_established(sm->ssl_ctx, data->ssl.conn)) {
if (eap_fast_phase1_done(sm, data) < 0)
return NULL;
if (data->state == PHASE2_START) {
/*
* Try to generate Phase 2 data to piggyback
* with the end of Phase 1 to avoid extra
* roundtrip.
*/
wpa_printf(MSG_DEBUG, "EAP-FAST: Try to start "
"Phase 2");
if (eap_fast_process_phase2_start(sm, data))
break;
req = eap_fast_build_phase2_req(sm, data, id);
piggyback = 1;
}
}
break;
case PHASE2_ID:
case PHASE2_METHOD:
req = eap_fast_build_phase2_req(sm, data, id);
break;
case CRYPTO_BINDING:
req = eap_fast_build_crypto_binding(sm, data);
if (data->phase2_method) {
/*
* Include the start of the next EAP method in the
* sequence in the same message with Crypto-Binding to
* save a round-trip.
*/
struct wpabuf *eap;
eap = eap_fast_build_phase2_req(sm, data, id);
req = wpabuf_concat(req, eap);
eap_fast_state(data, PHASE2_METHOD);
}
break;
case REQUEST_PAC:
req = eap_fast_build_pac(sm, data);
break;
default:
wpa_printf(MSG_DEBUG, "EAP-FAST: %s - unexpected state %d",
__func__, data->state);
return NULL;
}
if (req &&
eap_fast_encrypt_phase2(sm, data, req, piggyback) < 0)
return NULL;
return eap_server_tls_build_msg(&data->ssl, EAP_TYPE_FAST,
data->fast_version, id);
}
static Boolean eap_fast_check(struct eap_sm *sm, void *priv,
struct wpabuf *respData)
{
const u8 *pos;
size_t len;
pos = eap_hdr_validate(EAP_VENDOR_IETF, EAP_TYPE_FAST, respData, &len);
if (pos == NULL || len < 1) {
wpa_printf(MSG_INFO, "EAP-FAST: Invalid frame");
return TRUE;
}
return FALSE;
}
static int eap_fast_phase2_init(struct eap_sm *sm, struct eap_fast_data *data,
EapType eap_type)
{
if (data->phase2_priv && data->phase2_method) {
data->phase2_method->reset(sm, data->phase2_priv);
data->phase2_method = NULL;
data->phase2_priv = NULL;
}
data->phase2_method = eap_server_get_eap_method(EAP_VENDOR_IETF,
eap_type);
if (!data->phase2_method)
return -1;
if (data->key_block_p) {
sm->auth_challenge = data->key_block_p->server_challenge;
sm->peer_challenge = data->key_block_p->client_challenge;
}
sm->init_phase2 = 1;
data->phase2_priv = data->phase2_method->init(sm);
sm->init_phase2 = 0;
sm->auth_challenge = NULL;
sm->peer_challenge = NULL;
return data->phase2_priv == NULL ? -1 : 0;
}
static void eap_fast_process_phase2_response(struct eap_sm *sm,
struct eap_fast_data *data,
u8 *in_data, size_t in_len)
{
u8 next_type = EAP_TYPE_NONE;
struct eap_hdr *hdr;
u8 *pos;
size_t left;
struct wpabuf buf;
const struct eap_method *m = data->phase2_method;
void *priv = data->phase2_priv;
if (priv == NULL) {
wpa_printf(MSG_DEBUG, "EAP-FAST: %s - Phase2 not "
"initialized?!", __func__);
return;
}
hdr = (struct eap_hdr *) in_data;
pos = (u8 *) (hdr + 1);
if (in_len > sizeof(*hdr) && *pos == EAP_TYPE_NAK) {
left = in_len - sizeof(*hdr);
wpa_hexdump(MSG_DEBUG, "EAP-FAST: Phase2 type Nak'ed; "
"allowed types", pos + 1, left - 1);
#ifdef EAP_SERVER_TNC
if (m && m->vendor == EAP_VENDOR_IETF &&
m->method == EAP_TYPE_TNC) {
wpa_printf(MSG_DEBUG, "EAP-FAST: Peer Nak'ed required "
"TNC negotiation");
next_type = eap_fast_req_failure(sm, data);
eap_fast_phase2_init(sm, data, next_type);
return;
}
#endif /* EAP_SERVER_TNC */
eap_sm_process_nak(sm, pos + 1, left - 1);
if (sm->user && sm->user_eap_method_index < EAP_MAX_METHODS &&
sm->user->methods[sm->user_eap_method_index].method !=
EAP_TYPE_NONE) {
next_type = sm->user->methods[
sm->user_eap_method_index++].method;
wpa_printf(MSG_DEBUG, "EAP-FAST: try EAP type %d",
next_type);
} else {
next_type = eap_fast_req_failure(sm, data);
}
eap_fast_phase2_init(sm, data, next_type);
return;
}
wpabuf_set(&buf, in_data, in_len);
if (m->check(sm, priv, &buf)) {
wpa_printf(MSG_DEBUG, "EAP-FAST: Phase2 check() asked to "
"ignore the packet");
eap_fast_req_failure(sm, data);
return;
}
m->process(sm, priv, &buf);
if (!m->isDone(sm, priv))
return;
if (!m->isSuccess(sm, priv)) {
wpa_printf(MSG_DEBUG, "EAP-FAST: Phase2 method failed");
next_type = eap_fast_req_failure(sm, data);
eap_fast_phase2_init(sm, data, next_type);
return;
}
switch (data->state) {
case PHASE2_ID:
if (eap_user_get(sm, sm->identity, sm->identity_len, 1) != 0) {
wpa_hexdump_ascii(MSG_DEBUG, "EAP-FAST: Phase2 "
"Identity not found in the user "
"database",
sm->identity, sm->identity_len);
next_type = eap_fast_req_failure(sm, data);
break;
}
eap_fast_state(data, PHASE2_METHOD);
if (data->anon_provisioning) {
/*
* Only EAP-MSCHAPv2 is allowed for anonymous
* provisioning.
*/
next_type = EAP_TYPE_MSCHAPV2;
sm->user_eap_method_index = 0;
} else {
next_type = sm->user->methods[0].method;
sm->user_eap_method_index = 1;
}
wpa_printf(MSG_DEBUG, "EAP-FAST: try EAP type %d", next_type);
break;
case PHASE2_METHOD:
case CRYPTO_BINDING:
eap_fast_update_icmk(sm, data);
eap_fast_state(data, CRYPTO_BINDING);
data->eap_seq++;
next_type = EAP_TYPE_NONE;
#ifdef EAP_SERVER_TNC
if (sm->tnc && !data->tnc_started) {
wpa_printf(MSG_DEBUG, "EAP-FAST: Initialize TNC");
next_type = EAP_TYPE_TNC;
data->tnc_started = 1;
}
#endif /* EAP_SERVER_TNC */
break;
case FAILURE:
break;
default:
wpa_printf(MSG_DEBUG, "EAP-FAST: %s - unexpected state %d",
__func__, data->state);
break;
}
eap_fast_phase2_init(sm, data, next_type);
}
static void eap_fast_process_phase2_eap(struct eap_sm *sm,
struct eap_fast_data *data,
u8 *in_data, size_t in_len)
{
struct eap_hdr *hdr;
size_t len;
hdr = (struct eap_hdr *) in_data;
if (in_len < (int) sizeof(*hdr)) {
wpa_printf(MSG_INFO, "EAP-FAST: Too short Phase 2 "
"EAP frame (len=%lu)", (unsigned long) in_len);
eap_fast_req_failure(sm, data);
return;
}
len = be_to_host16(hdr->length);
if (len > in_len) {
wpa_printf(MSG_INFO, "EAP-FAST: Length mismatch in "
"Phase 2 EAP frame (len=%lu hdr->length=%lu)",
(unsigned long) in_len, (unsigned long) len);
eap_fast_req_failure(sm, data);
return;
}
wpa_printf(MSG_DEBUG, "EAP-FAST: Received Phase 2: code=%d "
"identifier=%d length=%lu", hdr->code, hdr->identifier,
(unsigned long) len);
switch (hdr->code) {
case EAP_CODE_RESPONSE:
eap_fast_process_phase2_response(sm, data, (u8 *) hdr, len);
break;
default:
wpa_printf(MSG_INFO, "EAP-FAST: Unexpected code=%d in "
"Phase 2 EAP header", hdr->code);
break;
}
}
static int eap_fast_parse_tlvs(struct wpabuf *data,
struct eap_fast_tlv_parse *tlv)
{
int mandatory, tlv_type, res;
size_t len;
u8 *pos, *end;
os_memset(tlv, 0, sizeof(*tlv));
pos = wpabuf_mhead(data);
end = pos + wpabuf_len(data);
while (end - pos > 4) {
mandatory = pos[0] & 0x80;
tlv_type = WPA_GET_BE16(pos) & 0x3fff;
pos += 2;
len = WPA_GET_BE16(pos);
pos += 2;
if (len > (size_t) (end - pos)) {
wpa_printf(MSG_INFO, "EAP-FAST: TLV overflow");
return -1;
}
wpa_printf(MSG_DEBUG, "EAP-FAST: Received Phase 2: "
"TLV type %d length %u%s",
tlv_type, (unsigned int) len,
mandatory ? " (mandatory)" : "");
res = eap_fast_parse_tlv(tlv, tlv_type, pos, len);
if (res == -2)
break;
if (res < 0) {
if (mandatory) {
wpa_printf(MSG_DEBUG, "EAP-FAST: Nak unknown "
"mandatory TLV type %d", tlv_type);
/* TODO: generate Nak TLV */
break;
} else {
wpa_printf(MSG_DEBUG, "EAP-FAST: Ignored "
"unknown optional TLV type %d",
tlv_type);
}
}
pos += len;
}
return 0;
}
static int eap_fast_validate_crypto_binding(
struct eap_fast_data *data, struct eap_tlv_crypto_binding_tlv *b,
size_t bind_len)
{
u8 cmac[SHA1_MAC_LEN];
wpa_printf(MSG_DEBUG, "EAP-FAST: Reply Crypto-Binding TLV: "
"Version %d Received Version %d SubType %d",
b->version, b->received_version, b->subtype);
wpa_hexdump(MSG_MSGDUMP, "EAP-FAST: NONCE",
b->nonce, sizeof(b->nonce));
wpa_hexdump(MSG_MSGDUMP, "EAP-FAST: Compound MAC",
b->compound_mac, sizeof(b->compound_mac));
if (b->version != EAP_FAST_VERSION ||
b->received_version != EAP_FAST_VERSION) {
wpa_printf(MSG_DEBUG, "EAP-FAST: Unexpected version "
"in Crypto-Binding: version %d "
"received_version %d", b->version,
b->received_version);
return -1;
}
if (b->subtype != EAP_TLV_CRYPTO_BINDING_SUBTYPE_RESPONSE) {
wpa_printf(MSG_DEBUG, "EAP-FAST: Unexpected subtype in "
"Crypto-Binding: %d", b->subtype);
return -1;
}
if (os_memcmp_const(data->crypto_binding_nonce, b->nonce, 31) != 0 ||
(data->crypto_binding_nonce[31] | 1) != b->nonce[31]) {
wpa_printf(MSG_DEBUG, "EAP-FAST: Invalid nonce in "
"Crypto-Binding");
return -1;
}
os_memcpy(cmac, b->compound_mac, sizeof(cmac));
os_memset(b->compound_mac, 0, sizeof(cmac));
wpa_hexdump(MSG_MSGDUMP, "EAP-FAST: Crypto-Binding TLV for "
"Compound MAC calculation",
(u8 *) b, bind_len);
hmac_sha1(data->cmk, EAP_FAST_CMK_LEN, (u8 *) b, bind_len,
b->compound_mac);
if (os_memcmp_const(cmac, b->compound_mac, sizeof(cmac)) != 0) {
wpa_hexdump(MSG_MSGDUMP,
"EAP-FAST: Calculated Compound MAC",
b->compound_mac, sizeof(cmac));
wpa_printf(MSG_INFO, "EAP-FAST: Compound MAC did not "
"match");
return -1;
}
return 0;
}
static int eap_fast_pac_type(u8 *pac, size_t len, u16 type)
{
struct eap_tlv_pac_type_tlv *tlv;
if (pac == NULL || len != sizeof(*tlv))
return 0;
tlv = (struct eap_tlv_pac_type_tlv *) pac;
return be_to_host16(tlv->tlv_type) == PAC_TYPE_PAC_TYPE &&
be_to_host16(tlv->length) == 2 &&
be_to_host16(tlv->pac_type) == type;
}
static void eap_fast_process_phase2_tlvs(struct eap_sm *sm,
struct eap_fast_data *data,
struct wpabuf *in_data)
{
struct eap_fast_tlv_parse tlv;
int check_crypto_binding = data->state == CRYPTO_BINDING;
if (eap_fast_parse_tlvs(in_data, &tlv) < 0) {
wpa_printf(MSG_DEBUG, "EAP-FAST: Failed to parse received "
"Phase 2 TLVs");
return;
}
if (tlv.result == EAP_TLV_RESULT_FAILURE) {
wpa_printf(MSG_DEBUG, "EAP-FAST: Result TLV indicated "
"failure");
eap_fast_state(data, FAILURE);
return;
}
if (data->state == REQUEST_PAC) {
u16 type, len, res;
if (tlv.pac == NULL || tlv.pac_len < 6) {
wpa_printf(MSG_DEBUG, "EAP-FAST: No PAC "
"Acknowledgement received");
eap_fast_state(data, FAILURE);
return;
}
type = WPA_GET_BE16(tlv.pac);
len = WPA_GET_BE16(tlv.pac + 2);
res = WPA_GET_BE16(tlv.pac + 4);
if (type != PAC_TYPE_PAC_ACKNOWLEDGEMENT || len != 2 ||
res != EAP_TLV_RESULT_SUCCESS) {
wpa_printf(MSG_DEBUG, "EAP-FAST: PAC TLV did not "
"contain acknowledgement");
eap_fast_state(data, FAILURE);
return;
}
wpa_printf(MSG_DEBUG, "EAP-FAST: PAC-Acknowledgement received "
"- PAC provisioning succeeded");
eap_fast_state(data, (data->anon_provisioning ||
data->send_new_pac == 2) ?
FAILURE : SUCCESS);
return;
}
if (check_crypto_binding) {
if (tlv.crypto_binding == NULL) {
wpa_printf(MSG_DEBUG, "EAP-FAST: No Crypto-Binding "
"TLV received");
eap_fast_state(data, FAILURE);
return;
}
if (data->final_result &&
tlv.result != EAP_TLV_RESULT_SUCCESS) {
wpa_printf(MSG_DEBUG, "EAP-FAST: Crypto-Binding TLV "
"without Success Result");
eap_fast_state(data, FAILURE);
return;
}
if (!data->final_result &&
tlv.iresult != EAP_TLV_RESULT_SUCCESS) {
wpa_printf(MSG_DEBUG, "EAP-FAST: Crypto-Binding TLV "
"without intermediate Success Result");
eap_fast_state(data, FAILURE);
return;
}
if (eap_fast_validate_crypto_binding(data, tlv.crypto_binding,
tlv.crypto_binding_len)) {
eap_fast_state(data, FAILURE);
return;
}
wpa_printf(MSG_DEBUG, "EAP-FAST: Valid Crypto-Binding TLV "
"received");
if (data->final_result) {
wpa_printf(MSG_DEBUG, "EAP-FAST: Authentication "
"completed successfully");
}
if (data->anon_provisioning &&
sm->eap_fast_prov != ANON_PROV &&
sm->eap_fast_prov != BOTH_PROV) {
wpa_printf(MSG_DEBUG, "EAP-FAST: Client is trying to "
"use unauthenticated provisioning which is "
"disabled");
eap_fast_state(data, FAILURE);
return;
}
if (sm->eap_fast_prov != AUTH_PROV &&
sm->eap_fast_prov != BOTH_PROV &&
tlv.request_action == EAP_TLV_ACTION_PROCESS_TLV &&
eap_fast_pac_type(tlv.pac, tlv.pac_len,
PAC_TYPE_TUNNEL_PAC)) {
wpa_printf(MSG_DEBUG, "EAP-FAST: Client is trying to "
"use authenticated provisioning which is "
"disabled");
eap_fast_state(data, FAILURE);
return;
}
if (data->anon_provisioning ||
(tlv.request_action == EAP_TLV_ACTION_PROCESS_TLV &&
eap_fast_pac_type(tlv.pac, tlv.pac_len,
PAC_TYPE_TUNNEL_PAC))) {
wpa_printf(MSG_DEBUG, "EAP-FAST: Requested a new "
"Tunnel PAC");
eap_fast_state(data, REQUEST_PAC);
} else if (data->send_new_pac) {
wpa_printf(MSG_DEBUG, "EAP-FAST: Server triggered "
"re-keying of Tunnel PAC");
eap_fast_state(data, REQUEST_PAC);
} else if (data->final_result)
eap_fast_state(data, SUCCESS);
}
if (tlv.eap_payload_tlv) {
eap_fast_process_phase2_eap(sm, data, tlv.eap_payload_tlv,
tlv.eap_payload_tlv_len);
}
}
static void eap_fast_process_phase2(struct eap_sm *sm,
struct eap_fast_data *data,
struct wpabuf *in_buf)
{
struct wpabuf *in_decrypted;
wpa_printf(MSG_DEBUG, "EAP-FAST: Received %lu bytes encrypted data for"
" Phase 2", (unsigned long) wpabuf_len(in_buf));
if (data->pending_phase2_resp) {
wpa_printf(MSG_DEBUG, "EAP-PEAP: Pending Phase 2 response - "
"skip decryption and use old data");
eap_fast_process_phase2_tlvs(sm, data,
data->pending_phase2_resp);
wpabuf_free(data->pending_phase2_resp);
data->pending_phase2_resp = NULL;
return;
}
in_decrypted = tls_connection_decrypt(sm->ssl_ctx, data->ssl.conn,
in_buf);
if (in_decrypted == NULL) {
wpa_printf(MSG_INFO, "EAP-FAST: Failed to decrypt Phase 2 "
"data");
eap_fast_state(data, FAILURE);
return;
}
wpa_hexdump_buf_key(MSG_DEBUG, "EAP-FAST: Decrypted Phase 2 TLVs",
in_decrypted);
eap_fast_process_phase2_tlvs(sm, data, in_decrypted);
if (sm->method_pending == METHOD_PENDING_WAIT) {
wpa_printf(MSG_DEBUG, "EAP-FAST: Phase2 method is in "
"pending wait state - save decrypted response");
wpabuf_free(data->pending_phase2_resp);
data->pending_phase2_resp = in_decrypted;
return;
}
wpabuf_free(in_decrypted);
}
static int eap_fast_process_version(struct eap_sm *sm, void *priv,
int peer_version)
{
struct eap_fast_data *data = priv;
data->peer_version = peer_version;
if (data->force_version >= 0 && peer_version != data->force_version) {
wpa_printf(MSG_INFO, "EAP-FAST: peer did not select the forced"
" version (forced=%d peer=%d) - reject",
data->force_version, peer_version);
return -1;
}
if (peer_version < data->fast_version) {
wpa_printf(MSG_DEBUG, "EAP-FAST: peer ver=%d, own ver=%d; "
"use version %d",
peer_version, data->fast_version, peer_version);
data->fast_version = peer_version;
}
return 0;
}
static int eap_fast_process_phase1(struct eap_sm *sm,
struct eap_fast_data *data)
{
if (eap_server_tls_phase1(sm, &data->ssl) < 0) {
wpa_printf(MSG_INFO, "EAP-FAST: TLS processing failed");
eap_fast_state(data, FAILURE);
return -1;
}
if (!tls_connection_established(sm->ssl_ctx, data->ssl.conn) ||
wpabuf_len(data->ssl.tls_out) > 0)
return 1;
/*
* Phase 1 was completed with the received message (e.g., when using
* abbreviated handshake), so Phase 2 can be started immediately
* without having to send through an empty message to the peer.
*/
return eap_fast_phase1_done(sm, data);
}
static int eap_fast_process_phase2_start(struct eap_sm *sm,
struct eap_fast_data *data)
{
u8 next_type;
if (data->identity) {
os_free(sm->identity);
sm->identity = data->identity;
data->identity = NULL;
sm->identity_len = data->identity_len;
data->identity_len = 0;
sm->require_identity_match = 1;
if (eap_user_get(sm, sm->identity, sm->identity_len, 1) != 0) {
wpa_hexdump_ascii(MSG_DEBUG, "EAP-FAST: "
"Phase2 Identity not found "
"in the user database",
sm->identity, sm->identity_len);
next_type = eap_fast_req_failure(sm, data);
} else {
wpa_printf(MSG_DEBUG, "EAP-FAST: Identity already "
"known - skip Phase 2 Identity Request");
next_type = sm->user->methods[0].method;
sm->user_eap_method_index = 1;
}
eap_fast_state(data, PHASE2_METHOD);
} else {
eap_fast_state(data, PHASE2_ID);
next_type = EAP_TYPE_IDENTITY;
}
return eap_fast_phase2_init(sm, data, next_type);
}
static void eap_fast_process_msg(struct eap_sm *sm, void *priv,
const struct wpabuf *respData)
{
struct eap_fast_data *data = priv;
switch (data->state) {
case PHASE1:
if (eap_fast_process_phase1(sm, data))
break;
/* fall through to PHASE2_START */
case PHASE2_START:
eap_fast_process_phase2_start(sm, data);
break;
case PHASE2_ID:
case PHASE2_METHOD:
case CRYPTO_BINDING:
case REQUEST_PAC:
eap_fast_process_phase2(sm, data, data->ssl.tls_in);
break;
default:
wpa_printf(MSG_DEBUG, "EAP-FAST: Unexpected state %d in %s",
data->state, __func__);
break;
}
}
static void eap_fast_process(struct eap_sm *sm, void *priv,
struct wpabuf *respData)
{
struct eap_fast_data *data = priv;
if (eap_server_tls_process(sm, &data->ssl, respData, data,
EAP_TYPE_FAST, eap_fast_process_version,
eap_fast_process_msg) < 0)
eap_fast_state(data, FAILURE);
}
static Boolean eap_fast_isDone(struct eap_sm *sm, void *priv)
{
struct eap_fast_data *data = priv;
return data->state == SUCCESS || data->state == FAILURE;
}
static u8 * eap_fast_getKey(struct eap_sm *sm, void *priv, size_t *len)
{
struct eap_fast_data *data = priv;
u8 *eapKeyData;
if (data->state != SUCCESS)
return NULL;
eapKeyData = os_malloc(EAP_FAST_KEY_LEN);
if (eapKeyData == NULL)
return NULL;
if (eap_fast_derive_eap_msk(data->simck, eapKeyData) < 0) {
os_free(eapKeyData);
return NULL;
}
*len = EAP_FAST_KEY_LEN;
return eapKeyData;
}
static u8 * eap_fast_get_emsk(struct eap_sm *sm, void *priv, size_t *len)
{
struct eap_fast_data *data = priv;
u8 *eapKeyData;
if (data->state != SUCCESS)
return NULL;
eapKeyData = os_malloc(EAP_EMSK_LEN);
if (eapKeyData == NULL)
return NULL;
if (eap_fast_derive_eap_emsk(data->simck, eapKeyData) < 0) {
os_free(eapKeyData);
return NULL;
}
*len = EAP_EMSK_LEN;
return eapKeyData;
}
static Boolean eap_fast_isSuccess(struct eap_sm *sm, void *priv)
{
struct eap_fast_data *data = priv;
return data->state == SUCCESS;
}
static u8 * eap_fast_get_session_id(struct eap_sm *sm, void *priv, size_t *len)
{
struct eap_fast_data *data = priv;
if (data->state != SUCCESS)
return NULL;
return eap_server_tls_derive_session_id(sm, &data->ssl, EAP_TYPE_FAST,
len);
}
int eap_server_fast_register(void)
{
struct eap_method *eap;
eap = eap_server_method_alloc(EAP_SERVER_METHOD_INTERFACE_VERSION,
EAP_VENDOR_IETF, EAP_TYPE_FAST, "FAST");
if (eap == NULL)
return -1;
eap->init = eap_fast_init;
eap->reset = eap_fast_reset;
eap->buildReq = eap_fast_buildReq;
eap->check = eap_fast_check;
eap->process = eap_fast_process;
eap->isDone = eap_fast_isDone;
eap->getKey = eap_fast_getKey;
eap->get_emsk = eap_fast_get_emsk;
eap->isSuccess = eap_fast_isSuccess;
eap->getSessionId = eap_fast_get_session_id;
return eap_server_method_register(eap);
}