You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
hostap/src/eap_server/eap_server_teap.c

1948 lines
50 KiB
C

/*
* EAP-TEAP server (RFC 7170)
* Copyright (c) 2004-2019, 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/tls.h"
#include "crypto/random.h"
#include "eap_common/eap_teap_common.h"
#include "eap_i.h"
#include "eap_tls_common.h"
static void eap_teap_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_teap_data {
struct eap_ssl_data ssl;
enum {
START, PHASE1, PHASE1B, PHASE2_START, PHASE2_ID,
PHASE2_BASIC_AUTH, PHASE2_METHOD, CRYPTO_BINDING, REQUEST_PAC,
FAILURE_SEND_RESULT, SUCCESS, FAILURE
} state;
u8 teap_version;
u8 peer_version;
u16 tls_cs;
const struct eap_method *phase2_method;
void *phase2_priv;
u8 crypto_binding_nonce[32];
int final_result;
u8 simck_msk[EAP_TEAP_SIMCK_LEN];
u8 cmk_msk[EAP_TEAP_CMK_LEN];
u8 simck_emsk[EAP_TEAP_SIMCK_LEN];
u8 cmk_emsk[EAP_TEAP_CMK_LEN];
int simck_idx;
int cmk_emsk_available;
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;
struct wpabuf *server_outer_tlvs;
struct wpabuf *peer_outer_tlvs;
u8 *identity; /* from PAC-Opaque */
size_t identity_len;
int eap_seq;
int tnc_started;
int pac_key_lifetime;
int pac_key_refresh_time;
enum teap_error_codes error_code;
};
static int eap_teap_process_phase2_start(struct eap_sm *sm,
struct eap_teap_data *data);
static const char * eap_teap_state_txt(int state)
{
switch (state) {
case START:
return "START";
case PHASE1:
return "PHASE1";
case PHASE1B:
return "PHASE1B";
case PHASE2_START:
return "PHASE2_START";
case PHASE2_ID:
return "PHASE2_ID";
case PHASE2_BASIC_AUTH:
return "PHASE2_BASIC_AUTH";
case PHASE2_METHOD:
return "PHASE2_METHOD";
case CRYPTO_BINDING:
return "CRYPTO_BINDING";
case REQUEST_PAC:
return "REQUEST_PAC";
case FAILURE_SEND_RESULT:
return "FAILURE_SEND_RESULT";
case SUCCESS:
return "SUCCESS";
case FAILURE:
return "FAILURE";
default:
return "Unknown?!";
}
}
static void eap_teap_state(struct eap_teap_data *data, int state)
{
wpa_printf(MSG_DEBUG, "EAP-TEAP: %s -> %s",
eap_teap_state_txt(data->state),
eap_teap_state_txt(state));
data->state = state;
}
static EapType eap_teap_req_failure(struct eap_teap_data *data,
enum teap_error_codes error)
{
eap_teap_state(data, FAILURE_SEND_RESULT);
return EAP_TYPE_NONE;
}
static int eap_teap_session_ticket_cb(void *ctx, const u8 *ticket, size_t len,
const u8 *client_random,
const u8 *server_random,
u8 *master_secret)
{
struct eap_teap_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-TEAP: SessionTicket callback");
wpa_hexdump(MSG_DEBUG, "EAP-TEAP: SessionTicket (PAC-Opaque)",
ticket, len);
if (len < 4 || WPA_GET_BE16(ticket) != PAC_TYPE_PAC_OPAQUE) {
wpa_printf(MSG_DEBUG, "EAP-TEAP: 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-TEAP: Ignore invalid PAC-Opaque (len=%lu left=%lu)",
(unsigned long) pac_opaque_len,
(unsigned long) len);
return 0;
}
wpa_hexdump(MSG_DEBUG, "EAP-TEAP: Received PAC-Opaque",
pac_opaque, pac_opaque_len);
buf = os_malloc(pac_opaque_len - 8);
if (!buf) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: 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-TEAP: 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-TEAP: 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_TEAP_PAC_KEY_LEN) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Invalid PAC-Key length %d",
elen);
os_free(buf);
return -1;
}
pac_key = pos;
wpa_hexdump_key(MSG_DEBUG,
"EAP-TEAP: PAC-Key from decrypted PAC-Opaque",
pac_key, EAP_TEAP_PAC_KEY_LEN);
break;
case PAC_OPAQUE_TYPE_LIFETIME:
if (elen != 4) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: 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) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: No PAC-Key included in PAC-Opaque");
os_free(buf);
return -1;
}
if (identity) {
wpa_hexdump_ascii(MSG_DEBUG,
"EAP-TEAP: 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-TEAP: 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-TEAP: PAC-Key soft timeout; send an update if authentication succeeds");
data->send_new_pac = 1;
}
/* EAP-TEAP uses PAC-Key as the TLS master_secret */
os_memcpy(master_secret, pac_key, EAP_TEAP_PAC_KEY_LEN);
os_free(buf);
return 1;
}
static int eap_teap_derive_key_auth(struct eap_sm *sm,
struct eap_teap_data *data)
{
int res;
/* RFC 7170, Section 5.1 */
res = tls_connection_export_key(sm->ssl_ctx, data->ssl.conn,
TEAP_TLS_EXPORTER_LABEL_SKS, NULL, 0,
data->simck_msk, EAP_TEAP_SIMCK_LEN);
if (res)
return res;
wpa_hexdump_key(MSG_DEBUG,
"EAP-TEAP: session_key_seed (S-IMCK[0])",
data->simck_msk, EAP_TEAP_SIMCK_LEN);
os_memcpy(data->simck_emsk, data->simck_msk, EAP_TEAP_SIMCK_LEN);
data->simck_idx = 0;
return 0;
}
static int eap_teap_update_icmk(struct eap_sm *sm, struct eap_teap_data *data)
{
u8 *msk = NULL, *emsk = NULL;
size_t msk_len = 0, emsk_len = 0;
int res;
wpa_printf(MSG_DEBUG, "EAP-TEAP: Deriving ICMK[%d] (S-IMCK and CMK)",
data->simck_idx + 1);
if (sm->eap_teap_auth == 1)
return eap_teap_derive_cmk_basic_pw_auth(data->simck_msk,
data->cmk_msk);
if (!data->phase2_method || !data->phase2_priv) {
wpa_printf(MSG_INFO, "EAP-TEAP: Phase 2 method not available");
return -1;
}
if (data->phase2_method->getKey) {
msk = data->phase2_method->getKey(sm, data->phase2_priv,
&msk_len);
if (!msk) {
wpa_printf(MSG_INFO,
"EAP-TEAP: Could not fetch Phase 2 MSK");
return -1;
}
}
if (data->phase2_method->get_emsk) {
emsk = data->phase2_method->get_emsk(sm, data->phase2_priv,
&emsk_len);
}
res = eap_teap_derive_imck(data->simck_msk, data->simck_emsk,
msk, msk_len, emsk, emsk_len,
data->simck_msk, data->cmk_msk,
data->simck_emsk, data->cmk_emsk);
bin_clear_free(msk, msk_len);
bin_clear_free(emsk, emsk_len);
if (res == 0) {
data->simck_idx++;
if (emsk)
data->cmk_emsk_available = 1;
}
return 0;
}
static void * eap_teap_init(struct eap_sm *sm)
{
struct eap_teap_data *data;
data = os_zalloc(sizeof(*data));
if (!data)
return NULL;
data->teap_version = EAP_TEAP_VERSION;
data->state = START;
if (eap_server_tls_ssl_init(sm, &data->ssl, 0, EAP_TYPE_TEAP)) {
wpa_printf(MSG_INFO, "EAP-TEAP: Failed to initialize SSL.");
eap_teap_reset(sm, data);
return NULL;
}
/* TODO: Add anon-DH TLS cipher suites (and if one is negotiated,
* enforce inner EAP with mutual authentication to be used) */
if (tls_connection_set_session_ticket_cb(sm->ssl_ctx, data->ssl.conn,
eap_teap_session_ticket_cb,
data) < 0) {
wpa_printf(MSG_INFO,
"EAP-TEAP: Failed to set SessionTicket callback");
eap_teap_reset(sm, data);
return NULL;
}
if (!sm->pac_opaque_encr_key) {
wpa_printf(MSG_INFO,
"EAP-TEAP: No PAC-Opaque encryption key configured");
eap_teap_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) {
wpa_printf(MSG_INFO, "EAP-TEAP: No A-ID configured");
eap_teap_reset(sm, data);
return NULL;
}
data->srv_id = os_malloc(sm->eap_fast_a_id_len);
if (!data->srv_id) {
eap_teap_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) {
wpa_printf(MSG_INFO, "EAP-TEAP: No A-ID-Info configured");
eap_teap_reset(sm, data);
return NULL;
}
data->srv_id_info = os_strdup(sm->eap_fast_a_id_info);
if (!data->srv_id_info) {
eap_teap_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_teap_reset(struct eap_sm *sm, void *priv)
{
struct eap_teap_data *data = priv;
if (!data)
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);
wpabuf_free(data->pending_phase2_resp);
wpabuf_free(data->server_outer_tlvs);
wpabuf_free(data->peer_outer_tlvs);
os_free(data->identity);
forced_memzero(data->simck_msk, EAP_TEAP_SIMCK_LEN);
forced_memzero(data->simck_emsk, EAP_TEAP_SIMCK_LEN);
forced_memzero(data->cmk_msk, EAP_TEAP_CMK_LEN);
forced_memzero(data->cmk_emsk, EAP_TEAP_CMK_LEN);
forced_memzero(data->pac_opaque_encr, sizeof(data->pac_opaque_encr));
bin_clear_free(data, sizeof(*data));
}
static struct wpabuf * eap_teap_build_start(struct eap_sm *sm,
struct eap_teap_data *data, u8 id)
{
struct wpabuf *req;
size_t outer_tlv_len = sizeof(struct teap_tlv_hdr) + data->srv_id_len;
const u8 *start, *end;
req = eap_msg_alloc(EAP_VENDOR_IETF, EAP_TYPE_TEAP,
1 + 4 + outer_tlv_len, EAP_CODE_REQUEST, id);
if (!req) {
wpa_printf(MSG_ERROR,
"EAP-TEAP: Failed to allocate memory for request");
eap_teap_state(data, FAILURE);
return NULL;
}
wpabuf_put_u8(req, EAP_TLS_FLAGS_START | EAP_TEAP_FLAGS_OUTER_TLV_LEN |
data->teap_version);
wpabuf_put_be32(req, outer_tlv_len);
start = wpabuf_put(req, 0);
/* RFC 7170, Section 4.2.2: Authority-ID TLV */
eap_teap_put_tlv(req, TEAP_TLV_AUTHORITY_ID,
data->srv_id, data->srv_id_len);
end = wpabuf_put(req, 0);
wpabuf_free(data->server_outer_tlvs);
data->server_outer_tlvs = wpabuf_alloc_copy(start, end - start);
if (!data->server_outer_tlvs) {
eap_teap_state(data, FAILURE);
return NULL;
}
eap_teap_state(data, PHASE1);
return req;
}
static int eap_teap_phase1_done(struct eap_sm *sm, struct eap_teap_data *data)
{
char cipher[64];
wpa_printf(MSG_DEBUG, "EAP-TEAP: Phase 1 done, starting Phase 2");
data->tls_cs = tls_connection_get_cipher_suite(data->ssl.conn);
wpa_printf(MSG_DEBUG, "EAP-TEAP: TLS cipher suite 0x%04x",
data->tls_cs);
if (tls_get_cipher(sm->ssl_ctx, data->ssl.conn, cipher, sizeof(cipher))
< 0) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Failed to get cipher information");
eap_teap_state(data, FAILURE);
return -1;
}
data->anon_provisioning = os_strstr(cipher, "ADH") != NULL;
if (data->anon_provisioning)
wpa_printf(MSG_DEBUG, "EAP-TEAP: Anonymous provisioning");
if (eap_teap_derive_key_auth(sm, data) < 0) {
eap_teap_state(data, FAILURE);
return -1;
}
eap_teap_state(data, PHASE2_START);
return 0;
}
static struct wpabuf * eap_teap_build_phase2_req(struct eap_sm *sm,
struct eap_teap_data *data,
u8 id)
{
struct wpabuf *req;
if (sm->eap_teap_auth == 1) {
wpa_printf(MSG_DEBUG, "EAP-TEAP: Initiate Basic-Password-Auth");
req = wpabuf_alloc(sizeof(struct teap_tlv_hdr));
if (!req)
return NULL;
eap_teap_put_tlv_hdr(req, TEAP_TLV_BASIC_PASSWORD_AUTH_REQ, 0);
return req;
}
wpa_printf(MSG_DEBUG, "EAP-TEAP: Initiate inner EAP method");
if (!data->phase2_priv) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Phase 2 method not initialized");
return NULL;
}
req = data->phase2_method->buildReq(sm, data->phase2_priv, id);
if (!req)
return NULL;
wpa_hexdump_buf_key(MSG_MSGDUMP, "EAP-TEAP: Phase 2 EAP-Request", req);
return eap_teap_tlv_eap_payload(req);
}
static struct wpabuf * eap_teap_build_crypto_binding(
struct eap_sm *sm, struct eap_teap_data *data)
{
struct wpabuf *buf;
struct teap_tlv_result *result;
struct teap_tlv_crypto_binding *cb;
u8 subtype, flags;
buf = wpabuf_alloc(2 * sizeof(*result) + sizeof(*cb));
if (!buf)
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 > 0) {
/* Intermediate-Result */
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Add Intermediate-Result TLV (status=SUCCESS)");
result = wpabuf_put(buf, sizeof(*result));
result->tlv_type = host_to_be16(TEAP_TLV_MANDATORY |
TEAP_TLV_INTERMEDIATE_RESULT);
result->length = host_to_be16(2);
result->status = host_to_be16(TEAP_STATUS_SUCCESS);
}
if (data->final_result) {
/* Result TLV */
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Add Result TLV (status=SUCCESS)");
result = wpabuf_put(buf, sizeof(*result));
result->tlv_type = host_to_be16(TEAP_TLV_MANDATORY |
TEAP_TLV_RESULT);
result->length = host_to_be16(2);
result->status = host_to_be16(TEAP_STATUS_SUCCESS);
}
/* Crypto-Binding TLV */
cb = wpabuf_put(buf, sizeof(*cb));
cb->tlv_type = host_to_be16(TEAP_TLV_MANDATORY |
TEAP_TLV_CRYPTO_BINDING);
cb->length = host_to_be16(sizeof(*cb) - sizeof(struct teap_tlv_hdr));
cb->version = EAP_TEAP_VERSION;
cb->received_version = data->peer_version;
/* FIX: RFC 7170 is not clear on which Flags value to use when
* Crypto-Binding TLV is used with Basic-Password-Auth */
flags = data->cmk_emsk_available ?
TEAP_CRYPTO_BINDING_EMSK_AND_MSK_CMAC :
TEAP_CRYPTO_BINDING_MSK_CMAC;
subtype = TEAP_CRYPTO_BINDING_SUBTYPE_REQUEST;
cb->subtype = (flags << 4) | subtype;
if (random_get_bytes(cb->nonce, sizeof(cb->nonce)) < 0) {
wpabuf_free(buf);
return NULL;
}
/*
* RFC 7170, Section 4.2.13:
* The nonce in a request MUST have its least significant bit set to 0.
*/
cb->nonce[sizeof(cb->nonce) - 1] &= ~0x01;
os_memcpy(data->crypto_binding_nonce, cb->nonce, sizeof(cb->nonce));
if (eap_teap_compound_mac(data->tls_cs, cb, data->server_outer_tlvs,
data->peer_outer_tlvs, data->cmk_msk,
cb->msk_compound_mac) < 0) {
wpabuf_free(buf);
return NULL;
}
if (data->cmk_emsk_available &&
eap_teap_compound_mac(data->tls_cs, cb, data->server_outer_tlvs,
data->peer_outer_tlvs, data->cmk_emsk,
cb->emsk_compound_mac) < 0) {
wpabuf_free(buf);
return NULL;
}
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Add Crypto-Binding TLV: Version %u Received Version %u Flags %u Sub-Type %u",
cb->version, cb->received_version, flags, subtype);
wpa_hexdump(MSG_MSGDUMP, "EAP-TEAP: Nonce",
cb->nonce, sizeof(cb->nonce));
wpa_hexdump(MSG_MSGDUMP, "EAP-TEAP: EMSK Compound MAC",
cb->emsk_compound_mac, sizeof(cb->emsk_compound_mac));
wpa_hexdump(MSG_MSGDUMP, "EAP-TEAP: MSK Compound MAC",
cb->msk_compound_mac, sizeof(cb->msk_compound_mac));
return buf;
}
static struct wpabuf * eap_teap_build_pac(struct eap_sm *sm,
struct eap_teap_data *data)
{
u8 pac_key[EAP_TEAP_PAC_KEY_LEN];
u8 *pac_buf, *pac_opaque;
struct wpabuf *buf;
u8 *pos;
size_t buf_len, srv_id_info_len, pac_len;
struct teap_tlv_hdr *pac_tlv;
struct pac_attr_hdr *pac_info;
struct teap_tlv_result *result;
struct os_time now;
wpa_printf(MSG_DEBUG, "EAP-TEAP: Build a new PAC");
if (random_get_bytes(pac_key, EAP_TEAP_PAC_KEY_LEN) < 0 ||
os_get_time(&now) < 0)
return NULL;
wpa_hexdump_key(MSG_DEBUG, "EAP-TEAP: Generated PAC-Key",
pac_key, EAP_TEAP_PAC_KEY_LEN);
pac_len = (2 + EAP_TEAP_PAC_KEY_LEN) + (2 + 4) +
(2 + sm->identity_len) + 8;
pac_buf = os_malloc(pac_len);
if (!pac_buf)
return NULL;
srv_id_info_len = os_strlen(data->srv_id_info);
pos = pac_buf;
*pos++ = PAC_OPAQUE_TYPE_KEY;
*pos++ = EAP_TEAP_PAC_KEY_LEN;
os_memcpy(pos, pac_key, EAP_TEAP_PAC_KEY_LEN);
pos += EAP_TEAP_PAC_KEY_LEN;
wpa_printf(MSG_DEBUG, "EAP-TEAP: PAC-Key lifetime: %u seconds",
data->pac_key_lifetime);
*pos++ = PAC_OPAQUE_TYPE_LIFETIME;
*pos++ = 4;
WPA_PUT_BE32(pos, now.sec + data->pac_key_lifetime);
pos += 4;
if (sm->identity) {
wpa_hexdump_ascii(MSG_DEBUG, "EAP-TEAP: PAC-Opaque Identity",
sm->identity, sm->identity_len);
*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) {
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-TEAP: PAC-Opaque", pac_opaque, pac_len);
buf_len = sizeof(*pac_tlv) +
sizeof(struct pac_attr_hdr) + EAP_TEAP_PAC_KEY_LEN +
sizeof(struct pac_attr_hdr) + pac_len +
data->srv_id_len + srv_id_info_len + 100 + sizeof(*result);
buf = wpabuf_alloc(buf_len);
if (!buf) {
os_free(pac_opaque);
return NULL;
}
/* Result TLV */
wpa_printf(MSG_DEBUG, "EAP-TEAP: Add Result TLV (status=SUCCESS)");
result = wpabuf_put(buf, sizeof(*result));
WPA_PUT_BE16((u8 *) &result->tlv_type,
TEAP_TLV_MANDATORY | TEAP_TLV_RESULT);
WPA_PUT_BE16((u8 *) &result->length, 2);
WPA_PUT_BE16((u8 *) &result->status, TEAP_STATUS_SUCCESS);
/* PAC TLV */
wpa_printf(MSG_DEBUG, "EAP-TEAP: Add PAC TLV");
pac_tlv = wpabuf_put(buf, sizeof(*pac_tlv));
pac_tlv->tlv_type = host_to_be16(TEAP_TLV_MANDATORY | TEAP_TLV_PAC);
/* PAC-Key */
eap_teap_put_tlv(buf, PAC_TYPE_PAC_KEY, pac_key, EAP_TEAP_PAC_KEY_LEN);
/* PAC-Opaque */
eap_teap_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_teap_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_teap_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_teap_put_tlv(buf, PAC_TYPE_I_ID, sm->identity,
sm->identity_len);
}
/* A-ID-Info (inside PAC-Info) */
eap_teap_put_tlv(buf, PAC_TYPE_A_ID_INFO, data->srv_id_info,
srv_id_info_len);
/* PAC-Type (inside PAC-Info) */
eap_teap_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_teap_encrypt_phase2(struct eap_sm *sm,
struct eap_teap_data *data,
struct wpabuf *plain, int piggyback)
{
struct wpabuf *encr;
wpa_hexdump_buf_key(MSG_DEBUG, "EAP-TEAP: 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-TEAP: 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-TEAP: 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_teap_buildReq(struct eap_sm *sm, void *priv, u8 id)
{
struct eap_teap_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_TEAP,
data->teap_version);
}
if (data->ssl.state == WAIT_FRAG_ACK) {
return eap_server_tls_build_msg(&data->ssl, EAP_TYPE_TEAP,
data->teap_version, id);
}
switch (data->state) {
case START:
return eap_teap_build_start(sm, data, id);
case PHASE1B:
if (tls_connection_established(sm->ssl_ctx, data->ssl.conn)) {
if (eap_teap_phase1_done(sm, data) < 0)
return NULL;
if (data->state == PHASE2_START) {
int res;
/*
* Try to generate Phase 2 data to piggyback
* with the end of Phase 1 to avoid extra
* roundtrip.
*/
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Try to start Phase 2");
res = eap_teap_process_phase2_start(sm, data);
if (res == 1) {
req = eap_teap_build_crypto_binding(
sm, data);
piggyback = 1;
break;
}
if (res)
break;
req = eap_teap_build_phase2_req(sm, data, id);
piggyback = 1;
}
}
break;
case PHASE2_ID:
case PHASE2_BASIC_AUTH:
case PHASE2_METHOD:
req = eap_teap_build_phase2_req(sm, data, id);
break;
case CRYPTO_BINDING:
req = eap_teap_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_teap_build_phase2_req(sm, data, id);
req = wpabuf_concat(req, eap);
eap_teap_state(data, PHASE2_METHOD);
}
break;
case REQUEST_PAC:
req = eap_teap_build_pac(sm, data);
break;
case FAILURE_SEND_RESULT:
req = eap_teap_tlv_result(TEAP_STATUS_FAILURE, 0);
if (data->error_code)
req = wpabuf_concat(
req, eap_teap_tlv_error(data->error_code));
break;
default:
wpa_printf(MSG_DEBUG, "EAP-TEAP: %s - unexpected state %d",
__func__, data->state);
return NULL;
}
if (req && eap_teap_encrypt_phase2(sm, data, req, piggyback) < 0)
return NULL;
return eap_server_tls_build_msg(&data->ssl, EAP_TYPE_TEAP,
data->teap_version, id);
}
static Boolean eap_teap_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_TEAP, respData, &len);
if (!pos || len < 1) {
wpa_printf(MSG_INFO, "EAP-TEAP: Invalid frame");
return TRUE;
}
return FALSE;
}
static int eap_teap_phase2_init(struct eap_sm *sm, struct eap_teap_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;
sm->init_phase2 = 1;
data->phase2_priv = data->phase2_method->init(sm);
sm->init_phase2 = 0;
return data->phase2_priv ? 0 : -1;
}
static void eap_teap_process_phase2_response(struct eap_sm *sm,
struct eap_teap_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) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: %s - Phase 2 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-TEAP: Phase 2 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-TEAP: Peer Nak'ed required TNC negotiation");
next_type = eap_teap_req_failure(data, 0);
eap_teap_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-TEAP: try EAP type %d",
next_type);
} else {
next_type = eap_teap_req_failure(data, 0);
}
eap_teap_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-TEAP: Phase 2 check() asked to ignore the packet");
eap_teap_req_failure(data, TEAP_ERROR_INNER_METHOD);
return;
}
m->process(sm, priv, &buf);
if (!m->isDone(sm, priv))
return;
if (!m->isSuccess(sm, priv)) {
wpa_printf(MSG_DEBUG, "EAP-TEAP: Phase 2 method failed");
next_type = eap_teap_req_failure(data, TEAP_ERROR_INNER_METHOD);
eap_teap_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-TEAP: Phase 2 Identity not found in the user database",
sm->identity, sm->identity_len);
next_type = eap_teap_req_failure(
data, TEAP_ERROR_INNER_METHOD);
break;
}
eap_teap_state(data, PHASE2_METHOD);
if (data->anon_provisioning) {
/* TODO: Allow any inner EAP method that provides
* mutual authentication and EMSK derivation (i.e.,
* EAP-pwd or EAP-EKE). */
next_type = EAP_TYPE_PWD;
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-TEAP: Try EAP type %d", next_type);
break;
case PHASE2_METHOD:
case CRYPTO_BINDING:
eap_teap_update_icmk(sm, data);
eap_teap_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-TEAP: 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-TEAP: %s - unexpected state %d",
__func__, data->state);
break;
}
eap_teap_phase2_init(sm, data, next_type);
}
static void eap_teap_process_phase2_eap(struct eap_sm *sm,
struct eap_teap_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-TEAP: Too short Phase 2 EAP frame (len=%lu)",
(unsigned long) in_len);
eap_teap_req_failure(data, TEAP_ERROR_INNER_METHOD);
return;
}
len = be_to_host16(hdr->length);
if (len > in_len) {
wpa_printf(MSG_INFO,
"EAP-TEAP: Length mismatch in Phase 2 EAP frame (len=%lu hdr->length=%lu)",
(unsigned long) in_len, (unsigned long) len);
eap_teap_req_failure(data, TEAP_ERROR_INNER_METHOD);
return;
}
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Received Phase 2: code=%d identifier=%d length=%lu",
hdr->code, hdr->identifier,
(unsigned long) len);
switch (hdr->code) {
case EAP_CODE_RESPONSE:
eap_teap_process_phase2_response(sm, data, (u8 *) hdr, len);
break;
default:
wpa_printf(MSG_INFO,
"EAP-TEAP: Unexpected code=%d in Phase 2 EAP header",
hdr->code);
break;
}
}
static void eap_teap_process_basic_auth_resp(struct eap_sm *sm,
struct eap_teap_data *data,
u8 *in_data, size_t in_len)
{
u8 *pos, *end, *username, *password, *new_id;
u8 userlen, passlen;
pos = in_data;
end = pos + in_len;
if (end - pos < 1) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: No room for Basic-Password-Auth-Resp Userlen field");
eap_teap_req_failure(data, 0);
return;
}
userlen = *pos++;
if (end - pos < userlen) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Truncated Basic-Password-Auth-Resp Username field");
eap_teap_req_failure(data, 0);
return;
}
username = pos;
pos += userlen;
wpa_hexdump_ascii(MSG_DEBUG,
"EAP-TEAP: Basic-Password-Auth-Resp Username",
username, userlen);
if (end - pos < 1) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: No room for Basic-Password-Auth-Resp Passlen field");
eap_teap_req_failure(data, 0);
return;
}
passlen = *pos++;
if (end - pos < passlen) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Truncated Basic-Password-Auth-Resp Password field");
eap_teap_req_failure(data, 0);
return;
}
password = pos;
pos += passlen;
wpa_hexdump_ascii_key(MSG_DEBUG,
"EAP-TEAP: Basic-Password-Auth-Resp Password",
password, passlen);
if (end > pos) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Unexpected %d extra octet(s) at the end of Basic-Password-Auth-Resp TLV",
(int) (end - pos));
eap_teap_req_failure(data, 0);
return;
}
if (eap_user_get(sm, username, userlen, 1) != 0) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Username not found in the user database");
eap_teap_req_failure(data, 0);
return;
}
if (!sm->user || !sm->user->password || sm->user->password_hash) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: No plaintext user password configured");
eap_teap_req_failure(data, 0);
return;
}
if (sm->user->password_len != passlen ||
os_memcmp_const(sm->user->password, password, passlen) != 0) {
wpa_printf(MSG_DEBUG, "EAP-TEAP: Invalid password");
eap_teap_req_failure(data, 0);
return;
}
wpa_printf(MSG_DEBUG, "EAP-TEAP: Correct password");
new_id = os_memdup(username, userlen);
if (new_id) {
os_free(sm->identity);
sm->identity = new_id;
sm->identity_len = userlen;
}
eap_teap_state(data, CRYPTO_BINDING);
eap_teap_update_icmk(sm, data);
}
static int eap_teap_parse_tlvs(struct wpabuf *data,
struct eap_teap_tlv_parse *tlv)
{
u16 tlv_type;
int mandatory, 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-TEAP: TLV overflow");
return -1;
}
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Received Phase 2: TLV type %u (%s) length %u%s",
tlv_type, eap_teap_tlv_type_str(tlv_type),
(unsigned int) len,
mandatory ? " (mandatory)" : "");
res = eap_teap_parse_tlv(tlv, tlv_type, pos, len);
if (res == -2)
break;
if (res < 0) {
if (mandatory) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: NAK unknown mandatory TLV type %u",
tlv_type);
/* TODO: generate NAK TLV */
break;
}
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Ignore unknown optional TLV type %u",
tlv_type);
}
pos += len;
}
return 0;
}
static int eap_teap_validate_crypto_binding(
struct eap_teap_data *data, const struct teap_tlv_crypto_binding *cb,
size_t bind_len)
{
u8 flags, subtype;
subtype = cb->subtype & 0x0f;
flags = cb->subtype >> 4;
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Reply Crypto-Binding TLV: Version %u Received Version %u Flags %u Sub-Type %u",
cb->version, cb->received_version, flags, subtype);
wpa_hexdump(MSG_MSGDUMP, "EAP-TEAP: Nonce",
cb->nonce, sizeof(cb->nonce));
wpa_hexdump(MSG_MSGDUMP, "EAP-TEAP: EMSK Compound MAC",
cb->emsk_compound_mac, sizeof(cb->emsk_compound_mac));
wpa_hexdump(MSG_MSGDUMP, "EAP-TEAP: MSK Compound MAC",
cb->msk_compound_mac, sizeof(cb->msk_compound_mac));
if (cb->version != EAP_TEAP_VERSION ||
cb->received_version != data->peer_version) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Unexpected version in Crypto-Binding: Version %u Received Version %u",
cb->version, cb->received_version);
return -1;
}
if (flags < 1 || flags > 3) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Unexpected Flags in Crypto-Binding: %u",
flags);
return -1;
}
if (subtype != TEAP_CRYPTO_BINDING_SUBTYPE_RESPONSE) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Unexpected Sub-Type in Crypto-Binding: %u",
subtype);
return -1;
}
if (os_memcmp_const(data->crypto_binding_nonce, cb->nonce,
EAP_TEAP_NONCE_LEN - 1) != 0 ||
(data->crypto_binding_nonce[EAP_TEAP_NONCE_LEN - 1] | 1) !=
cb->nonce[EAP_TEAP_NONCE_LEN - 1]) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Invalid Nonce in Crypto-Binding");
return -1;
}
if (flags == TEAP_CRYPTO_BINDING_MSK_CMAC ||
flags == TEAP_CRYPTO_BINDING_EMSK_AND_MSK_CMAC) {
u8 msk_compound_mac[EAP_TEAP_COMPOUND_MAC_LEN];
if (eap_teap_compound_mac(data->tls_cs, cb,
data->server_outer_tlvs,
data->peer_outer_tlvs, data->cmk_msk,
msk_compound_mac) < 0)
return -1;
if (os_memcmp_const(msk_compound_mac, cb->msk_compound_mac,
EAP_TEAP_COMPOUND_MAC_LEN) != 0) {
wpa_hexdump(MSG_DEBUG,
"EAP-TEAP: Calculated MSK Compound MAC",
msk_compound_mac,
EAP_TEAP_COMPOUND_MAC_LEN);
wpa_printf(MSG_INFO,
"EAP-TEAP: MSK Compound MAC did not match");
return -1;
}
}
if ((flags == TEAP_CRYPTO_BINDING_EMSK_CMAC ||
flags == TEAP_CRYPTO_BINDING_EMSK_AND_MSK_CMAC) &&
data->cmk_emsk_available) {
u8 emsk_compound_mac[EAP_TEAP_COMPOUND_MAC_LEN];
if (eap_teap_compound_mac(data->tls_cs, cb,
data->server_outer_tlvs,
data->peer_outer_tlvs, data->cmk_emsk,
emsk_compound_mac) < 0)
return -1;
if (os_memcmp_const(emsk_compound_mac, cb->emsk_compound_mac,
EAP_TEAP_COMPOUND_MAC_LEN) != 0) {
wpa_hexdump(MSG_DEBUG,
"EAP-TEAP: Calculated EMSK Compound MAC",
emsk_compound_mac,
EAP_TEAP_COMPOUND_MAC_LEN);
wpa_printf(MSG_INFO,
"EAP-TEAP: EMSK Compound MAC did not match");
return -1;
}
}
if (flags == TEAP_CRYPTO_BINDING_EMSK_CMAC &&
!data->cmk_emsk_available) {
wpa_printf(MSG_INFO,
"EAP-TEAP: Peer included only EMSK Compound MAC, but no locally generated inner EAP EMSK to validate this");
return -1;
}
return 0;
}
static int eap_teap_pac_type(u8 *pac, size_t len, u16 type)
{
struct teap_attr_pac_type *tlv;
if (!pac || len != sizeof(*tlv))
return 0;
tlv = (struct teap_attr_pac_type *) pac;
return be_to_host16(tlv->type) == PAC_TYPE_PAC_TYPE &&
be_to_host16(tlv->length) == 2 &&
be_to_host16(tlv->pac_type) == type;
}
static void eap_teap_process_phase2_tlvs(struct eap_sm *sm,
struct eap_teap_data *data,
struct wpabuf *in_data)
{
struct eap_teap_tlv_parse tlv;
int check_crypto_binding = data->state == CRYPTO_BINDING;
if (eap_teap_parse_tlvs(in_data, &tlv) < 0) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Failed to parse received Phase 2 TLVs");
return;
}
if (tlv.result == TEAP_STATUS_FAILURE) {
wpa_printf(MSG_DEBUG, "EAP-TEAP: Result TLV indicated failure");
eap_teap_state(data, FAILURE);
return;
}
if (tlv.nak) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Peer NAK'ed Vendor-Id %u NAK-Type %u",
WPA_GET_BE32(tlv.nak), WPA_GET_BE16(tlv.nak + 4));
eap_teap_state(data, FAILURE_SEND_RESULT);
return;
}
if (data->state == REQUEST_PAC) {
u16 type, len, res;
if (!tlv.pac || tlv.pac_len < 6) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: No PAC Acknowledgement received");
eap_teap_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 != TEAP_STATUS_SUCCESS) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: PAC TLV did not contain acknowledgement");
eap_teap_state(data, FAILURE);
return;
}
wpa_printf(MSG_DEBUG,
"EAP-TEAP: PAC-Acknowledgement received - PAC provisioning succeeded");
eap_teap_state(data, SUCCESS);
return;
}
if (check_crypto_binding) {
if (!tlv.crypto_binding) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: No Crypto-Binding TLV received");
eap_teap_state(data, FAILURE);
return;
}
if (data->final_result &&
tlv.result != TEAP_STATUS_SUCCESS) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Crypto-Binding TLV without Success Result");
eap_teap_state(data, FAILURE);
return;
}
if (!data->final_result &&
tlv.iresult != TEAP_STATUS_SUCCESS) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Crypto-Binding TLV without intermediate Success Result");
eap_teap_state(data, FAILURE);
return;
}
if (eap_teap_validate_crypto_binding(data, tlv.crypto_binding,
tlv.crypto_binding_len)) {
eap_teap_state(data, FAILURE);
return;
}
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Valid Crypto-Binding TLV received");
if (data->final_result) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Authentication completed successfully");
}
if (data->anon_provisioning &&
sm->eap_fast_prov != ANON_PROV &&
sm->eap_fast_prov != BOTH_PROV) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Client is trying to use unauthenticated provisioning which is disabled");
eap_teap_state(data, FAILURE);
return;
}
if (sm->eap_fast_prov != AUTH_PROV &&
sm->eap_fast_prov != BOTH_PROV &&
tlv.request_action == TEAP_REQUEST_ACTION_PROCESS_TLV &&
eap_teap_pac_type(tlv.pac, tlv.pac_len,
PAC_TYPE_TUNNEL_PAC)) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Client is trying to use authenticated provisioning which is disabled");
eap_teap_state(data, FAILURE);
return;
}
if (data->anon_provisioning ||
(tlv.request_action == TEAP_REQUEST_ACTION_PROCESS_TLV &&
eap_teap_pac_type(tlv.pac, tlv.pac_len,
PAC_TYPE_TUNNEL_PAC))) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Requested a new Tunnel PAC");
eap_teap_state(data, REQUEST_PAC);
} else if (data->send_new_pac) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Server triggered re-keying of Tunnel PAC");
eap_teap_state(data, REQUEST_PAC);
} else if (data->final_result)
eap_teap_state(data, SUCCESS);
}
if (tlv.basic_auth_resp) {
if (sm->eap_teap_auth != 1) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Unexpected Basic-Password-Auth-Resp when trying to use inner EAP");
eap_teap_state(data, FAILURE);
return;
}
eap_teap_process_basic_auth_resp(sm, data, tlv.basic_auth_resp,
tlv.basic_auth_resp_len);
}
if (tlv.eap_payload_tlv) {
if (sm->eap_teap_auth == 1) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Unexpected EAP Payload TLV when trying to use Basic-Password-Auth");
eap_teap_state(data, FAILURE);
return;
}
eap_teap_process_phase2_eap(sm, data, tlv.eap_payload_tlv,
tlv.eap_payload_tlv_len);
}
}
static void eap_teap_process_phase2(struct eap_sm *sm,
struct eap_teap_data *data,
struct wpabuf *in_buf)
{
struct wpabuf *in_decrypted;
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Received %lu bytes encrypted data for Phase 2",
(unsigned long) wpabuf_len(in_buf));
if (data->pending_phase2_resp) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Pending Phase 2 response - skip decryption and use old data");
eap_teap_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) {
wpa_printf(MSG_INFO,
"EAP-TEAP: Failed to decrypt Phase 2 data");
eap_teap_state(data, FAILURE);
return;
}
wpa_hexdump_buf_key(MSG_DEBUG, "EAP-TEAP: Decrypted Phase 2 TLVs",
in_decrypted);
eap_teap_process_phase2_tlvs(sm, data, in_decrypted);
if (sm->method_pending == METHOD_PENDING_WAIT) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Phase 2 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_teap_process_version(struct eap_sm *sm, void *priv,
int peer_version)
{
struct eap_teap_data *data = priv;
if (peer_version < 1) {
/* Version 1 was the first defined version, so reject 0 */
wpa_printf(MSG_INFO,
"EAP-TEAP: Peer used unknown TEAP version %u",
peer_version);
return -1;
}
if (peer_version < data->teap_version) {
wpa_printf(MSG_DEBUG, "EAP-TEAP: peer ver=%u, own ver=%u; "
"use version %u",
peer_version, data->teap_version, peer_version);
data->teap_version = peer_version;
}
data->peer_version = peer_version;
return 0;
}
static int eap_teap_process_phase1(struct eap_sm *sm,
struct eap_teap_data *data)
{
if (eap_server_tls_phase1(sm, &data->ssl) < 0) {
wpa_printf(MSG_INFO, "EAP-TEAP: TLS processing failed");
eap_teap_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_teap_phase1_done(sm, data);
}
static int eap_teap_process_phase2_start(struct eap_sm *sm,
struct eap_teap_data *data)
{
u8 next_type;
if (data->identity) {
/* Used PAC and identity is from PAC-Opaque */
os_free(sm->identity);
sm->identity = data->identity;
data->identity = NULL;
sm->identity_len = data->identity_len;
data->identity_len = 0;
if (eap_user_get(sm, sm->identity, sm->identity_len, 1) != 0) {
wpa_hexdump_ascii(MSG_DEBUG,
"EAP-TEAP: Phase 2 Identity not found in the user database",
sm->identity, sm->identity_len);
next_type = EAP_TYPE_NONE;
eap_teap_state(data, PHASE2_METHOD);
} else if (sm->eap_teap_pac_no_inner) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Used PAC and identity already known - skip inner auth");
/* FIX: Need to derive CMK here. However, how is that
* supposed to be done? RFC 7170 does not tell that for
* the no-inner-auth case. */
eap_teap_derive_cmk_basic_pw_auth(data->simck_msk,
data->cmk_msk);
eap_teap_state(data, CRYPTO_BINDING);
return 1;
} else if (sm->eap_teap_auth == 1) {
eap_teap_state(data, PHASE2_BASIC_AUTH);
return 1;
} else {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Identity already known - skip Phase 2 Identity Request");
next_type = sm->user->methods[0].method;
sm->user_eap_method_index = 1;
eap_teap_state(data, PHASE2_METHOD);
}
} else if (sm->eap_teap_auth == 1) {
eap_teap_state(data, PHASE2_BASIC_AUTH);
return 0;
} else {
eap_teap_state(data, PHASE2_ID);
next_type = EAP_TYPE_IDENTITY;
}
return eap_teap_phase2_init(sm, data, next_type);
}
static void eap_teap_process_msg(struct eap_sm *sm, void *priv,
const struct wpabuf *respData)
{
struct eap_teap_data *data = priv;
switch (data->state) {
case PHASE1:
case PHASE1B:
if (eap_teap_process_phase1(sm, data))
break;
/* fall through */
case PHASE2_START:
eap_teap_process_phase2_start(sm, data);
break;
case PHASE2_ID:
case PHASE2_BASIC_AUTH:
case PHASE2_METHOD:
case CRYPTO_BINDING:
case REQUEST_PAC:
eap_teap_process_phase2(sm, data, data->ssl.tls_in);
break;
case FAILURE_SEND_RESULT:
/* Protected failure result indication completed. Ignore the
* received message (which is supposed to include Result TLV
* indicating failure) and terminate exchange with cleartext
* EAP-Failure. */
eap_teap_state(data, FAILURE);
break;
default:
wpa_printf(MSG_DEBUG, "EAP-TEAP: Unexpected state %d in %s",
data->state, __func__);
break;
}
}
static void eap_teap_process(struct eap_sm *sm, void *priv,
struct wpabuf *respData)
{
struct eap_teap_data *data = priv;
const u8 *pos;
size_t len;
struct wpabuf *resp = respData;
u8 flags;
pos = eap_hdr_validate(EAP_VENDOR_IETF, EAP_TYPE_TEAP, respData, &len);
if (!pos || len < 1)
return;
flags = *pos++;
len--;
if (flags & EAP_TEAP_FLAGS_OUTER_TLV_LEN) {
/* Extract Outer TLVs from the message before common TLS
* processing */
u32 message_len = 0, outer_tlv_len;
const u8 *hdr;
if (data->state != PHASE1) {
wpa_printf(MSG_INFO,
"EAP-TEAP: Unexpected Outer TLVs in a message that is not the first message from the peer");
return;
}
if (flags & EAP_TLS_FLAGS_LENGTH_INCLUDED) {
if (len < 4) {
wpa_printf(MSG_INFO,
"EAP-TEAP: Too short message to include Message Length field");
return;
}
message_len = WPA_GET_BE32(pos);
pos += 4;
len -= 4;
if (message_len < 4) {
wpa_printf(MSG_INFO,
"EAP-TEAP: Message Length field has too msall value to include Outer TLV Length field");
return;
}
}
if (len < 4) {
wpa_printf(MSG_INFO,
"EAP-TEAP: Too short message to include Outer TLVs Length field");
return;
}
outer_tlv_len = WPA_GET_BE32(pos);
pos += 4;
len -= 4;
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Message Length %u Outer TLV Length %u",
message_len, outer_tlv_len);
if (len < outer_tlv_len) {
wpa_printf(MSG_INFO,
"EAP-TEAP: Too short message to include Outer TLVs field");
return;
}
if (message_len &&
(message_len < outer_tlv_len ||
message_len < 4 + outer_tlv_len)) {
wpa_printf(MSG_INFO,
"EAP-TEAP: Message Length field has too small value to include Outer TLVs");
return;
}
if (wpabuf_len(respData) < 4 + outer_tlv_len ||
len < outer_tlv_len)
return;
resp = wpabuf_alloc(wpabuf_len(respData) - 4 - outer_tlv_len);
if (!resp)
return;
hdr = wpabuf_head(respData);
wpabuf_put_u8(resp, *hdr++); /* Code */
wpabuf_put_u8(resp, *hdr++); /* Identifier */
wpabuf_put_be16(resp, WPA_GET_BE16(hdr) - 4 - outer_tlv_len);
hdr += 2;
wpabuf_put_u8(resp, *hdr++); /* Type */
/* Flags | Ver */
wpabuf_put_u8(resp, flags & ~EAP_TEAP_FLAGS_OUTER_TLV_LEN);
if (flags & EAP_TLS_FLAGS_LENGTH_INCLUDED)
wpabuf_put_be32(resp, message_len - 4 - outer_tlv_len);
wpabuf_put_data(resp, pos, len - outer_tlv_len);
pos += len - outer_tlv_len;
wpabuf_free(data->peer_outer_tlvs);
data->peer_outer_tlvs = wpabuf_alloc_copy(pos, outer_tlv_len);
if (!data->peer_outer_tlvs)
return;
wpa_hexdump_buf(MSG_DEBUG, "EAP-TEAP: Outer TLVs",
data->peer_outer_tlvs);
wpa_hexdump_buf(MSG_DEBUG,
"EAP-TEAP: TLS Data message after Outer TLV removal",
resp);
pos = eap_hdr_validate(EAP_VENDOR_IETF, EAP_TYPE_TEAP, resp,
&len);
if (!pos || len < 1) {
wpa_printf(MSG_INFO,
"EAP-TEAP: Invalid frame after Outer TLV removal");
return;
}
}
if (data->state == PHASE1)
eap_teap_state(data, PHASE1B);
if (eap_server_tls_process(sm, &data->ssl, resp, data,
EAP_TYPE_TEAP, eap_teap_process_version,
eap_teap_process_msg) < 0)
eap_teap_state(data, FAILURE);
if (resp != respData)
wpabuf_free(resp);
}
static Boolean eap_teap_isDone(struct eap_sm *sm, void *priv)
{
struct eap_teap_data *data = priv;
return data->state == SUCCESS || data->state == FAILURE;
}
static u8 * eap_teap_getKey(struct eap_sm *sm, void *priv, size_t *len)
{
struct eap_teap_data *data = priv;
u8 *eapKeyData;
if (data->state != SUCCESS)
return NULL;
eapKeyData = os_malloc(EAP_TEAP_KEY_LEN);
if (!eapKeyData)
return NULL;
/* FIX: RFC 7170 does not describe whether MSK or EMSK based S-IMCK[j]
* is used in this derivation */
if (eap_teap_derive_eap_msk(data->simck_msk, eapKeyData) < 0) {
os_free(eapKeyData);
return NULL;
}
*len = EAP_TEAP_KEY_LEN;
return eapKeyData;
}
static u8 * eap_teap_get_emsk(struct eap_sm *sm, void *priv, size_t *len)
{
struct eap_teap_data *data = priv;
u8 *eapKeyData;
if (data->state != SUCCESS)
return NULL;
eapKeyData = os_malloc(EAP_EMSK_LEN);
if (!eapKeyData)
return NULL;
/* FIX: RFC 7170 does not describe whether MSK or EMSK based S-IMCK[j]
* is used in this derivation */
if (eap_teap_derive_eap_emsk(data->simck_msk, eapKeyData) < 0) {
os_free(eapKeyData);
return NULL;
}
*len = EAP_EMSK_LEN;
return eapKeyData;
}
static Boolean eap_teap_isSuccess(struct eap_sm *sm, void *priv)
{
struct eap_teap_data *data = priv;
return data->state == SUCCESS;
}
static u8 * eap_teap_get_session_id(struct eap_sm *sm, void *priv, size_t *len)
{
struct eap_teap_data *data = priv;
const size_t max_id_len = 100;
int res;
u8 *id;
if (data->state != SUCCESS)
return NULL;
id = os_malloc(max_id_len);
if (!id)
return NULL;
id[0] = EAP_TYPE_TEAP;
res = tls_get_tls_unique(data->ssl.conn, id + 1, max_id_len - 1);
if (res < 0) {
os_free(id);
wpa_printf(MSG_ERROR, "EAP-TEAP: Failed to derive Session-Id");
return NULL;
}
*len = 1 + res;
wpa_hexdump(MSG_DEBUG, "EAP-TEAP: Derived Session-Id", id, *len);
return id;
}
int eap_server_teap_register(void)
{
struct eap_method *eap;
eap = eap_server_method_alloc(EAP_SERVER_METHOD_INTERFACE_VERSION,
EAP_VENDOR_IETF, EAP_TYPE_TEAP, "TEAP");
if (!eap)
return -1;
eap->init = eap_teap_init;
eap->reset = eap_teap_reset;
eap->buildReq = eap_teap_buildReq;
eap->check = eap_teap_check;
eap->process = eap_teap_process;
eap->isDone = eap_teap_isDone;
eap->getKey = eap_teap_getKey;
eap->get_emsk = eap_teap_get_emsk;
eap->isSuccess = eap_teap_isSuccess;
eap->getSessionId = eap_teap_get_session_id;
return eap_server_method_register(eap);
}