hostap/src/eap_peer/eap_teap.c
Jouni Malinen 4d78ba9900 EAP-TEAP peer: Clear Phase 2 EAP method on new Identity exchange
This is needed to allow clean transition from one inner EAP
authentication method to another one if EAP method negotiation is needed
within Phase 2.

Signed-off-by: Jouni Malinen <j@w1.fi>
2019-09-01 17:19:43 +03:00

2137 lines
57 KiB
C

/*
* EAP peer method: EAP-TEAP (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/tls.h"
#include "eap_common/eap_teap_common.h"
#include "eap_i.h"
#include "eap_tls_common.h"
#include "eap_config.h"
#include "eap_teap_pac.h"
#ifdef EAP_TEAP_DYNAMIC
#include "eap_teap_pac.c"
#endif /* EAP_TEAP_DYNAMIC */
static void eap_teap_deinit(struct eap_sm *sm, void *priv);
struct eap_teap_data {
struct eap_ssl_data ssl;
u8 teap_version; /* Negotiated version */
u8 received_version; /* Version number received during negotiation */
u16 tls_cs;
const struct eap_method *phase2_method;
void *phase2_priv;
int phase2_success;
int inner_method_done;
int iresult_verified;
int result_success_done;
int on_tx_completion;
struct eap_method_type phase2_type;
struct eap_method_type *phase2_types;
size_t num_phase2_types;
int resuming; /* starting a resumed session */
#define EAP_TEAP_PROV_UNAUTH 1
#define EAP_TEAP_PROV_AUTH 2
int provisioning_allowed; /* Allowed PAC provisioning modes */
int provisioning; /* doing PAC provisioning (not the normal auth) */
int anon_provisioning; /* doing anonymous (unauthenticated)
* provisioning */
int session_ticket_used;
int test_outer_tlvs;
u8 key_data[EAP_TEAP_KEY_LEN];
u8 *session_id;
size_t id_len;
u8 emsk[EAP_EMSK_LEN];
int success;
struct eap_teap_pac *pac;
struct eap_teap_pac *current_pac;
size_t max_pac_list_len;
int use_pac_binary_format;
u8 simck_msk[EAP_TEAP_SIMCK_LEN];
u8 simck_emsk[EAP_TEAP_SIMCK_LEN];
int simck_idx;
int cmk_emsk_available;
struct wpabuf *pending_phase2_req;
struct wpabuf *pending_resp;
struct wpabuf *server_outer_tlvs;
struct wpabuf *peer_outer_tlvs;
};
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;
wpa_printf(MSG_DEBUG, "EAP-TEAP: SessionTicket callback");
if (!master_secret) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: SessionTicket failed - fall back to full TLS handshake");
data->session_ticket_used = 0;
if (data->provisioning_allowed) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Try to provision a new PAC-Key");
data->provisioning = 1;
data->current_pac = NULL;
}
return 0;
}
wpa_hexdump(MSG_DEBUG, "EAP-TEAP: SessionTicket", ticket, len);
if (!data->current_pac) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: No PAC-Key available for using SessionTicket");
data->session_ticket_used = 0;
return 0;
}
/* EAP-TEAP uses PAC-Key as the TLS master_secret */
os_memcpy(master_secret, data->current_pac->pac_key,
EAP_TEAP_PAC_KEY_LEN);
data->session_ticket_used = 1;
return 1;
}
static void eap_teap_parse_phase1(struct eap_teap_data *data,
const char *phase1)
{
const char *pos;
pos = os_strstr(phase1, "teap_provisioning=");
if (pos) {
data->provisioning_allowed = atoi(pos + 18);
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Automatic PAC provisioning mode: %d",
data->provisioning_allowed);
}
pos = os_strstr(phase1, "teap_max_pac_list_len=");
if (pos) {
data->max_pac_list_len = atoi(pos + 22);
if (data->max_pac_list_len == 0)
data->max_pac_list_len = 1;
wpa_printf(MSG_DEBUG, "EAP-TEAP: Maximum PAC list length: %lu",
(unsigned long) data->max_pac_list_len);
}
if (os_strstr(phase1, "teap_pac_format=binary")) {
data->use_pac_binary_format = 1;
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Using binary format for PAC list");
}
#ifdef CONFIG_TESTING_OPTIONS
if (os_strstr(phase1, "teap_test_outer_tlvs=1"))
data->test_outer_tlvs = 1;
#endif /* CONFIG_TESTING_OPTIONS */
}
static void * eap_teap_init(struct eap_sm *sm)
{
struct eap_teap_data *data;
struct eap_peer_config *config = eap_get_config(sm);
if (!config)
return NULL;
data = os_zalloc(sizeof(*data));
if (!data)
return NULL;
data->teap_version = EAP_TEAP_VERSION;
data->max_pac_list_len = 10;
if (config->phase1)
eap_teap_parse_phase1(data, config->phase1);
if ((data->provisioning_allowed & EAP_TEAP_PROV_AUTH) &&
!config->cert.ca_cert && !config->cert.ca_path) {
/* Prevent PAC provisioning without mutual authentication
* (either by validating server certificate or by suitable
* inner EAP method). */
wpa_printf(MSG_INFO,
"EAP-TEAP: Disable authenticated provisioning due to no ca_cert/ca_path");
data->provisioning_allowed &= ~EAP_TEAP_PROV_AUTH;
}
if (eap_peer_select_phase2_methods(config, "auth=",
&data->phase2_types,
&data->num_phase2_types, 0) < 0) {
eap_teap_deinit(sm, data);
return NULL;
}
data->phase2_type.vendor = EAP_VENDOR_IETF;
data->phase2_type.method = EAP_TYPE_NONE;
config->teap_anon_dh = !!(data->provisioning_allowed &
EAP_TEAP_PROV_UNAUTH);
if (eap_peer_tls_ssl_init(sm, &data->ssl, config, EAP_TYPE_TEAP)) {
wpa_printf(MSG_INFO, "EAP-TEAP: Failed to initialize SSL");
eap_teap_deinit(sm, data);
return NULL;
}
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_deinit(sm, data);
return NULL;
}
if (!config->pac_file) {
wpa_printf(MSG_INFO, "EAP-TEAP: No PAC file configured");
eap_teap_deinit(sm, data);
return NULL;
}
if (data->use_pac_binary_format &&
eap_teap_load_pac_bin(sm, &data->pac, config->pac_file) < 0) {
wpa_printf(MSG_INFO, "EAP-TEAP: Failed to load PAC file");
eap_teap_deinit(sm, data);
return NULL;
}
if (!data->use_pac_binary_format &&
eap_teap_load_pac(sm, &data->pac, config->pac_file) < 0) {
wpa_printf(MSG_INFO, "EAP-TEAP: Failed to load PAC file");
eap_teap_deinit(sm, data);
return NULL;
}
eap_teap_pac_list_truncate(data->pac, data->max_pac_list_len);
return data;
}
static void eap_teap_clear(struct eap_teap_data *data)
{
forced_memzero(data->key_data, EAP_TEAP_KEY_LEN);
forced_memzero(data->emsk, EAP_EMSK_LEN);
os_free(data->session_id);
data->session_id = NULL;
wpabuf_free(data->pending_phase2_req);
data->pending_phase2_req = NULL;
wpabuf_free(data->pending_resp);
data->pending_resp = NULL;
wpabuf_free(data->server_outer_tlvs);
data->server_outer_tlvs = NULL;
wpabuf_free(data->peer_outer_tlvs);
data->peer_outer_tlvs = NULL;
forced_memzero(data->simck_msk, EAP_TEAP_SIMCK_LEN);
forced_memzero(data->simck_emsk, EAP_TEAP_SIMCK_LEN);
}
static void eap_teap_deinit(struct eap_sm *sm, void *priv)
{
struct eap_teap_data *data = priv;
struct eap_teap_pac *pac, *prev;
if (!data)
return;
if (data->phase2_priv && data->phase2_method)
data->phase2_method->deinit(sm, data->phase2_priv);
eap_teap_clear(data);
os_free(data->phase2_types);
eap_peer_tls_ssl_deinit(sm, &data->ssl);
pac = data->pac;
prev = NULL;
while (pac) {
prev = pac;
pac = pac->next;
eap_teap_free_pac(prev);
}
os_free(data);
}
static int eap_teap_derive_msk(struct eap_teap_data *data)
{
/* 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->tls_cs, data->simck_msk,
data->key_data) < 0 ||
eap_teap_derive_eap_emsk(data->tls_cs, data->simck_msk,
data->emsk) < 0)
return -1;
data->success = 1;
return 0;
}
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_init_phase2_method(struct eap_sm *sm,
struct eap_teap_data *data)
{
data->inner_method_done = 0;
data->iresult_verified = 0;
data->phase2_method =
eap_peer_get_eap_method(data->phase2_type.vendor,
data->phase2_type.method);
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 == NULL ? -1 : 0;
}
static int eap_teap_select_phase2_method(struct eap_teap_data *data,
int vendor, enum eap_type type)
{
size_t i;
/* TODO: TNC with anonymous provisioning; need to require both
* completed inner EAP authentication (EAP-pwd or EAP-EKE) and TNC */
if (data->anon_provisioning &&
!eap_teap_allowed_anon_prov_phase2_method(vendor, type)) {
wpa_printf(MSG_INFO,
"EAP-TEAP: EAP type %u:%u not allowed during unauthenticated provisioning",
vendor, type);
return -1;
}
#ifdef EAP_TNC
if (vendor == EAP_VENDOR_IETF && type == EAP_TYPE_TNC) {
data->phase2_type.vendor = EAP_VENDOR_IETF;
data->phase2_type.method = EAP_TYPE_TNC;
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Selected Phase 2 EAP vendor %d method %d for TNC",
data->phase2_type.vendor,
data->phase2_type.method);
return 0;
}
#endif /* EAP_TNC */
for (i = 0; i < data->num_phase2_types; i++) {
if (data->phase2_types[i].vendor != vendor ||
data->phase2_types[i].method != type)
continue;
data->phase2_type.vendor = data->phase2_types[i].vendor;
data->phase2_type.method = data->phase2_types[i].method;
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Selected Phase 2 EAP vendor %d method %d",
data->phase2_type.vendor,
data->phase2_type.method);
break;
}
if (vendor != data->phase2_type.vendor ||
type != data->phase2_type.method ||
(vendor == EAP_VENDOR_IETF && type == EAP_TYPE_NONE))
return -1;
return 0;
}
static void eap_teap_deinit_inner_eap(struct eap_sm *sm,
struct eap_teap_data *data)
{
if (!data->phase2_priv || !data->phase2_method)
return;
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Phase 2 EAP sequence - deinitialize previous method");
data->phase2_method->deinit(sm, data->phase2_priv);
data->phase2_method = NULL;
data->phase2_priv = NULL;
data->phase2_type.vendor = EAP_VENDOR_IETF;
data->phase2_type.method = EAP_TYPE_NONE;
}
static int eap_teap_phase2_request(struct eap_sm *sm,
struct eap_teap_data *data,
struct eap_method_ret *ret,
struct eap_hdr *hdr,
struct wpabuf **resp)
{
size_t len = be_to_host16(hdr->length);
u8 *pos;
struct eap_method_ret iret;
struct eap_peer_config *config = eap_get_config(sm);
struct wpabuf msg;
int vendor = EAP_VENDOR_IETF;
enum eap_type method;
if (len <= sizeof(struct eap_hdr)) {
wpa_printf(MSG_INFO,
"EAP-TEAP: too short Phase 2 request (len=%lu)",
(unsigned long) len);
return -1;
}
pos = (u8 *) (hdr + 1);
method = *pos;
if (method == EAP_TYPE_EXPANDED) {
if (len < sizeof(struct eap_hdr) + 8) {
wpa_printf(MSG_INFO,
"EAP-TEAP: Too short Phase 2 request (expanded header) (len=%lu)",
(unsigned long) len);
return -1;
}
vendor = WPA_GET_BE24(pos + 1);
method = WPA_GET_BE32(pos + 4);
}
wpa_printf(MSG_DEBUG, "EAP-TEAP: Phase 2 Request: type=%u:%u",
vendor, method);
if (vendor == EAP_VENDOR_IETF && method == EAP_TYPE_IDENTITY) {
eap_teap_deinit_inner_eap(sm, data);
*resp = eap_sm_buildIdentity(sm, hdr->identifier, 1);
return 0;
}
if (data->phase2_priv && data->phase2_method &&
(vendor != data->phase2_type.vendor ||
method != data->phase2_type.method))
eap_teap_deinit_inner_eap(sm, data);
if (data->phase2_type.vendor == EAP_VENDOR_IETF &&
data->phase2_type.method == EAP_TYPE_NONE &&
eap_teap_select_phase2_method(data, vendor, method) < 0) {
if (eap_peer_tls_phase2_nak(data->phase2_types,
data->num_phase2_types,
hdr, resp))
return -1;
return 0;
}
if ((!data->phase2_priv && eap_teap_init_phase2_method(sm, data) < 0) ||
!data->phase2_method) {
wpa_printf(MSG_INFO,
"EAP-TEAP: Failed to initialize Phase 2 EAP method %u:%u",
vendor, method);
ret->methodState = METHOD_DONE;
ret->decision = DECISION_FAIL;
return -1;
}
os_memset(&iret, 0, sizeof(iret));
wpabuf_set(&msg, hdr, len);
*resp = data->phase2_method->process(sm, data->phase2_priv, &iret,
&msg);
if (iret.methodState == METHOD_DONE)
data->inner_method_done = 1;
if (!(*resp) ||
(iret.methodState == METHOD_DONE &&
iret.decision == DECISION_FAIL)) {
/* Wait for protected indication of failure */
ret->methodState = METHOD_MAY_CONT;
ret->decision = DECISION_FAIL;
} else if ((iret.methodState == METHOD_DONE ||
iret.methodState == METHOD_MAY_CONT) &&
(iret.decision == DECISION_UNCOND_SUCC ||
iret.decision == DECISION_COND_SUCC)) {
data->phase2_success = 1;
}
if (!(*resp) && config &&
(config->pending_req_identity || config->pending_req_password ||
config->pending_req_otp || config->pending_req_new_password ||
config->pending_req_sim)) {
wpabuf_free(data->pending_phase2_req);
data->pending_phase2_req = wpabuf_alloc_copy(hdr, len);
} else if (!(*resp))
return -1;
return 0;
}
static struct wpabuf * eap_teap_tlv_nak(int vendor_id, int tlv_type)
{
struct wpabuf *buf;
struct teap_tlv_nak *nak;
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Add NAK TLV (Vendor-Id %u NAK-Type %u)",
vendor_id, tlv_type);
buf = wpabuf_alloc(sizeof(*nak));
if (!buf)
return NULL;
nak = wpabuf_put(buf, sizeof(*nak));
nak->tlv_type = host_to_be16(TEAP_TLV_MANDATORY | TEAP_TLV_NAK);
nak->length = host_to_be16(6);
nak->vendor_id = host_to_be32(vendor_id);
nak->nak_type = host_to_be16(tlv_type);
return buf;
}
static struct wpabuf * eap_teap_tlv_pac_ack(void)
{
struct wpabuf *buf;
struct teap_tlv_result *res;
struct teap_tlv_pac_ack *ack;
buf = wpabuf_alloc(sizeof(*res) + sizeof(*ack));
if (!buf)
return NULL;
wpa_printf(MSG_DEBUG, "EAP-TEAP: Add PAC TLV (ack)");
ack = wpabuf_put(buf, sizeof(*ack));
ack->tlv_type = host_to_be16(TEAP_TLV_PAC | TEAP_TLV_MANDATORY);
ack->length = host_to_be16(sizeof(*ack) - sizeof(struct teap_tlv_hdr));
ack->pac_type = host_to_be16(PAC_TYPE_PAC_ACKNOWLEDGEMENT);
ack->pac_len = host_to_be16(2);
ack->result = host_to_be16(TEAP_STATUS_SUCCESS);
return buf;
}
static struct wpabuf * eap_teap_add_identity_type(struct eap_sm *sm,
struct wpabuf *msg)
{
struct wpabuf *tlv;
tlv = eap_teap_tlv_identity_type(sm->use_machine_cred ?
TEAP_IDENTITY_TYPE_MACHINE :
TEAP_IDENTITY_TYPE_USER);
return wpabuf_concat(msg, tlv);
}
static struct wpabuf * eap_teap_process_eap_payload_tlv(
struct eap_sm *sm, struct eap_teap_data *data,
struct eap_method_ret *ret,
u8 *eap_payload_tlv, size_t eap_payload_tlv_len,
enum teap_identity_types req_id_type)
{
struct eap_hdr *hdr;
struct wpabuf *resp = NULL;
if (eap_payload_tlv_len < sizeof(*hdr)) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: too short EAP Payload TLV (len=%lu)",
(unsigned long) eap_payload_tlv_len);
return NULL;
}
hdr = (struct eap_hdr *) eap_payload_tlv;
if (be_to_host16(hdr->length) > eap_payload_tlv_len) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: EAP packet overflow in EAP Payload TLV");
return NULL;
}
if (hdr->code != EAP_CODE_REQUEST) {
wpa_printf(MSG_INFO,
"EAP-TEAP: Unexpected code=%d in Phase 2 EAP header",
hdr->code);
return NULL;
}
if (eap_teap_phase2_request(sm, data, ret, hdr, &resp)) {
wpa_printf(MSG_INFO,
"EAP-TEAP: Phase 2 Request processing failed");
return NULL;
}
resp = eap_teap_tlv_eap_payload(resp);
if (req_id_type)
resp = eap_teap_add_identity_type(sm, resp);
return resp;
}
static struct wpabuf * eap_teap_process_basic_auth_req(
struct eap_sm *sm, struct eap_teap_data *data,
u8 *basic_auth_req, size_t basic_auth_req_len,
enum teap_identity_types req_id_type)
{
const u8 *identity, *password;
size_t identity_len, password_len, plen;
struct wpabuf *resp;
wpa_hexdump_ascii(MSG_DEBUG, "EAP-TEAP: Basic-Password-Auth-Req prompt",
basic_auth_req, basic_auth_req_len);
/* TODO: send over control interface */
identity = eap_get_config_identity(sm, &identity_len);
password = eap_get_config_password(sm, &password_len);
if (!identity || !password ||
identity_len > 255 || password_len > 255) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: No username/password suitable for Basic-Password-Auth");
return eap_teap_tlv_nak(0, TEAP_TLV_BASIC_PASSWORD_AUTH_REQ);
}
plen = 1 + identity_len + 1 + password_len;
resp = wpabuf_alloc(sizeof(struct teap_tlv_hdr) + plen);
if (!resp)
return NULL;
eap_teap_put_tlv_hdr(resp, TEAP_TLV_BASIC_PASSWORD_AUTH_RESP, plen);
wpabuf_put_u8(resp, identity_len);
wpabuf_put_data(resp, identity, identity_len);
wpabuf_put_u8(resp, password_len);
wpabuf_put_data(resp, password, password_len);
wpa_hexdump_buf_key(MSG_DEBUG, "EAP-TEAP: Basic-Password-Auth-Resp",
resp);
if (req_id_type)
resp = eap_teap_add_identity_type(sm, resp);
/* Assume this succeeds so that Result TLV(Success) from the server can
* be used to terminate TEAP. */
data->phase2_success = 1;
return resp;
}
static int
eap_teap_validate_crypto_binding(struct eap_teap_data *data,
const struct teap_tlv_crypto_binding *cb)
{
u8 flags, subtype;
subtype = cb->subtype & 0x0f;
flags = cb->subtype >> 4;
wpa_printf(MSG_DEBUG,
"EAP-TEAP: 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->received_version ||
subtype != TEAP_CRYPTO_BINDING_SUBTYPE_REQUEST ||
flags < 1 || flags > 3) {
wpa_printf(MSG_INFO,
"EAP-TEAP: Invalid Version/Flags/Sub-Type in Crypto-Binding TLV: Version %u Received Version %u Flags %u Sub-Type %u",
cb->version, cb->received_version, flags, subtype);
return -1;
}
if (cb->nonce[EAP_TEAP_NONCE_LEN - 1] & 0x01) {
wpa_printf(MSG_INFO,
"EAP-TEAP: Invalid Crypto-Binding TLV Nonce in request");
return -1;
}
return 0;
}
static int eap_teap_write_crypto_binding(
struct eap_teap_data *data,
struct teap_tlv_crypto_binding *rbind,
const struct teap_tlv_crypto_binding *cb,
const u8 *cmk_msk, const u8 *cmk_emsk)
{
u8 subtype, flags;
rbind->tlv_type = host_to_be16(TEAP_TLV_MANDATORY |
TEAP_TLV_CRYPTO_BINDING);
rbind->length = host_to_be16(sizeof(*rbind) -
sizeof(struct teap_tlv_hdr));
rbind->version = EAP_TEAP_VERSION;
rbind->received_version = data->received_version;
/* FIX: RFC 7170 is not clear on which Flags value to use when
* Crypto-Binding TLV is used with Basic-Password-Auth */
flags = cmk_emsk ? TEAP_CRYPTO_BINDING_EMSK_AND_MSK_CMAC :
TEAP_CRYPTO_BINDING_MSK_CMAC;
subtype = TEAP_CRYPTO_BINDING_SUBTYPE_RESPONSE;
rbind->subtype = (flags << 4) | subtype;
os_memcpy(rbind->nonce, cb->nonce, sizeof(cb->nonce));
inc_byte_array(rbind->nonce, sizeof(rbind->nonce));
os_memset(rbind->emsk_compound_mac, 0, EAP_TEAP_COMPOUND_MAC_LEN);
os_memset(rbind->msk_compound_mac, 0, EAP_TEAP_COMPOUND_MAC_LEN);
if (eap_teap_compound_mac(data->tls_cs, rbind, data->server_outer_tlvs,
data->peer_outer_tlvs, cmk_msk,
rbind->msk_compound_mac) < 0)
return -1;
if (cmk_emsk &&
eap_teap_compound_mac(data->tls_cs, rbind, data->server_outer_tlvs,
data->peer_outer_tlvs, cmk_emsk,
rbind->emsk_compound_mac) < 0)
return -1;
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Reply Crypto-Binding TLV: Version %u Received Version %u Flags %u SubType %u",
rbind->version, rbind->received_version, flags, subtype);
wpa_hexdump(MSG_MSGDUMP, "EAP-TEAP: Nonce",
rbind->nonce, sizeof(rbind->nonce));
wpa_hexdump(MSG_MSGDUMP, "EAP-TEAP: EMSK Compound MAC",
rbind->emsk_compound_mac, sizeof(rbind->emsk_compound_mac));
wpa_hexdump(MSG_MSGDUMP, "EAP-TEAP: MSK Compound MAC",
rbind->msk_compound_mac, sizeof(rbind->msk_compound_mac));
return 0;
}
static int eap_teap_get_cmk(struct eap_sm *sm, struct eap_teap_data *data,
u8 *cmk_msk, u8 *cmk_emsk)
{
u8 *msk = NULL, *emsk = NULL;
size_t msk_len = 0, emsk_len = 0;
int res;
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Determining CMK[%d] for Compound MAC calculation",
data->simck_idx + 1);
if (!data->phase2_method)
return eap_teap_derive_cmk_basic_pw_auth(data->tls_cs,
data->simck_msk,
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->isKeyAvailable &&
!data->phase2_method->isKeyAvailable(sm, data->phase2_priv)) {
wpa_printf(MSG_INFO,
"EAP-TEAP: Phase 2 key material not available");
return -1;
}
if (data->phase2_method->isKeyAvailable &&
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->isKeyAvailable &&
data->phase2_method->get_emsk) {
emsk = data->phase2_method->get_emsk(sm, data->phase2_priv,
&emsk_len);
}
res = eap_teap_derive_imck(data->tls_cs,
data->simck_msk, data->simck_emsk,
msk, msk_len, emsk, emsk_len,
data->simck_msk, cmk_msk,
data->simck_emsk, 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 res;
}
static int eap_teap_session_id(struct eap_teap_data *data)
{
const size_t max_id_len = 100;
int res;
os_free(data->session_id);
data->session_id = os_malloc(max_id_len);
if (!data->session_id)
return -1;
data->session_id[0] = EAP_TYPE_TEAP;
res = tls_get_tls_unique(data->ssl.conn, data->session_id + 1,
max_id_len - 1);
if (res < 0) {
os_free(data->session_id);
data->session_id = NULL;
wpa_printf(MSG_ERROR, "EAP-TEAP: Failed to derive Session-Id");
return -1;
}
data->id_len = 1 + res;
wpa_hexdump(MSG_DEBUG, "EAP-TEAP: Derived Session-Id",
data->session_id, data->id_len);
return 0;
}
static struct wpabuf * eap_teap_process_crypto_binding(
struct eap_sm *sm, struct eap_teap_data *data,
struct eap_method_ret *ret,
const struct teap_tlv_crypto_binding *cb, size_t bind_len)
{
struct wpabuf *resp;
u8 *pos;
u8 cmk_msk[EAP_TEAP_CMK_LEN];
u8 cmk_emsk[EAP_TEAP_CMK_LEN];
const u8 *cmk_emsk_ptr = NULL;
int res;
size_t len;
u8 flags;
if (eap_teap_validate_crypto_binding(data, cb) < 0 ||
eap_teap_get_cmk(sm, data, cmk_msk, cmk_emsk) < 0)
return NULL;
/* Validate received MSK/EMSK Compound MAC */
flags = cb->subtype >> 4;
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, cmk_msk,
msk_compound_mac) < 0)
return NULL;
res = os_memcmp_const(msk_compound_mac, cb->msk_compound_mac,
EAP_TEAP_COMPOUND_MAC_LEN);
wpa_hexdump(MSG_MSGDUMP, "EAP-TEAP: Received MSK Compound MAC",
cb->msk_compound_mac, EAP_TEAP_COMPOUND_MAC_LEN);
wpa_hexdump(MSG_MSGDUMP,
"EAP-TEAP: Calculated MSK Compound MAC",
msk_compound_mac, EAP_TEAP_COMPOUND_MAC_LEN);
if (res != 0) {
wpa_printf(MSG_INFO,
"EAP-TEAP: MSK Compound MAC did not match");
return NULL;
}
}
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, cmk_emsk,
emsk_compound_mac) < 0)
return NULL;
res = os_memcmp_const(emsk_compound_mac, cb->emsk_compound_mac,
EAP_TEAP_COMPOUND_MAC_LEN);
wpa_hexdump(MSG_MSGDUMP, "EAP-TEAP: Received EMSK Compound MAC",
cb->emsk_compound_mac, EAP_TEAP_COMPOUND_MAC_LEN);
wpa_hexdump(MSG_MSGDUMP,
"EAP-TEAP: Calculated EMSK Compound MAC",
emsk_compound_mac, EAP_TEAP_COMPOUND_MAC_LEN);
if (res != 0) {
wpa_printf(MSG_INFO,
"EAP-TEAP: EMSK Compound MAC did not match");
return NULL;
}
cmk_emsk_ptr = cmk_emsk;
}
if (flags == TEAP_CRYPTO_BINDING_EMSK_CMAC &&
!data->cmk_emsk_available) {
wpa_printf(MSG_INFO,
"EAP-TEAP: Server included only EMSK Compound MAC, but no locally generated inner EAP EMSK to validate this");
return NULL;
}
/*
* Compound MAC was valid, so authentication succeeded. Reply with
* crypto binding to allow server to complete authentication.
*/
len = sizeof(struct teap_tlv_crypto_binding);
resp = wpabuf_alloc(len);
if (!resp)
return NULL;
if (data->phase2_success && eap_teap_derive_msk(data) < 0) {
wpa_printf(MSG_INFO, "EAP-TEAP: Failed to generate MSK");
ret->methodState = METHOD_DONE;
ret->decision = DECISION_FAIL;
data->phase2_success = 0;
wpabuf_free(resp);
return NULL;
}
if (data->phase2_success && eap_teap_session_id(data) < 0) {
wpabuf_free(resp);
return NULL;
}
pos = wpabuf_put(resp, sizeof(struct teap_tlv_crypto_binding));
if (eap_teap_write_crypto_binding(
data, (struct teap_tlv_crypto_binding *) pos,
cb, cmk_msk, cmk_emsk_ptr) < 0) {
wpabuf_free(resp);
return NULL;
}
return resp;
}
static void eap_teap_parse_pac_tlv(struct eap_teap_pac *entry, int type,
u8 *pos, size_t len, int *pac_key_found)
{
switch (type & 0x7fff) {
case PAC_TYPE_PAC_KEY:
wpa_hexdump_key(MSG_DEBUG, "EAP-TEAP: PAC-Key", pos, len);
if (len != EAP_TEAP_PAC_KEY_LEN) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Invalid PAC-Key length %lu",
(unsigned long) len);
break;
}
*pac_key_found = 1;
os_memcpy(entry->pac_key, pos, len);
break;
case PAC_TYPE_PAC_OPAQUE:
wpa_hexdump(MSG_DEBUG, "EAP-TEAP: PAC-Opaque", pos, len);
entry->pac_opaque = pos;
entry->pac_opaque_len = len;
break;
case PAC_TYPE_PAC_INFO:
wpa_hexdump(MSG_DEBUG, "EAP-TEAP: PAC-Info", pos, len);
entry->pac_info = pos;
entry->pac_info_len = len;
break;
default:
wpa_printf(MSG_DEBUG, "EAP-TEAP: Ignored unknown PAC type %d",
type);
break;
}
}
static int eap_teap_process_pac_tlv(struct eap_teap_pac *entry,
u8 *pac, size_t pac_len)
{
struct pac_attr_hdr *hdr;
u8 *pos;
size_t left, len;
int type, pac_key_found = 0;
pos = pac;
left = pac_len;
while (left > sizeof(*hdr)) {
hdr = (struct pac_attr_hdr *) pos;
type = be_to_host16(hdr->type);
len = be_to_host16(hdr->len);
pos += sizeof(*hdr);
left -= sizeof(*hdr);
if (len > left) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: PAC TLV overrun (type=%d len=%lu left=%lu)",
type, (unsigned long) len,
(unsigned long) left);
return -1;
}
eap_teap_parse_pac_tlv(entry, type, pos, len, &pac_key_found);
pos += len;
left -= len;
}
if (!pac_key_found || !entry->pac_opaque || !entry->pac_info) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: PAC TLV does not include all the required fields");
return -1;
}
return 0;
}
static int eap_teap_parse_pac_info(struct eap_teap_pac *entry, int type,
u8 *pos, size_t len)
{
u16 pac_type;
u32 lifetime;
struct os_time now;
switch (type & 0x7fff) {
case PAC_TYPE_CRED_LIFETIME:
if (len != 4) {
wpa_hexdump(MSG_DEBUG,
"EAP-TEAP: PAC-Info - Invalid CRED_LIFETIME length - ignored",
pos, len);
return 0;
}
/*
* This is not currently saved separately in PAC files since
* the server can automatically initiate PAC update when
* needed. Anyway, the information is available from PAC-Info
* dump if it is needed for something in the future.
*/
lifetime = WPA_GET_BE32(pos);
os_get_time(&now);
wpa_printf(MSG_DEBUG,
"EAP-TEAP: PAC-Info - CRED_LIFETIME %d (%d days)",
lifetime, (lifetime - (u32) now.sec) / 86400);
break;
case PAC_TYPE_A_ID:
wpa_hexdump_ascii(MSG_DEBUG, "EAP-TEAP: PAC-Info - A-ID",
pos, len);
entry->a_id = pos;
entry->a_id_len = len;
break;
case PAC_TYPE_I_ID:
wpa_hexdump_ascii(MSG_DEBUG, "EAP-TEAP: PAC-Info - I-ID",
pos, len);
entry->i_id = pos;
entry->i_id_len = len;
break;
case PAC_TYPE_A_ID_INFO:
wpa_hexdump_ascii(MSG_DEBUG, "EAP-TEAP: PAC-Info - A-ID-Info",
pos, len);
entry->a_id_info = pos;
entry->a_id_info_len = len;
break;
case PAC_TYPE_PAC_TYPE:
/* RFC 7170, Section 4.2.12.6 - PAC-Type TLV */
if (len != 2) {
wpa_printf(MSG_INFO,
"EAP-TEAP: Invalid PAC-Type length %lu (expected 2)",
(unsigned long) len);
wpa_hexdump_ascii(MSG_DEBUG,
"EAP-TEAP: PAC-Info - PAC-Type",
pos, len);
return -1;
}
pac_type = WPA_GET_BE16(pos);
if (pac_type != PAC_TYPE_TUNNEL_PAC) {
wpa_printf(MSG_INFO,
"EAP-TEAP: Unsupported PAC Type %d",
pac_type);
return -1;
}
wpa_printf(MSG_DEBUG, "EAP-TEAP: PAC-Info - PAC-Type %d",
pac_type);
entry->pac_type = pac_type;
break;
default:
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Ignored unknown PAC-Info type %d", type);
break;
}
return 0;
}
static int eap_teap_process_pac_info(struct eap_teap_pac *entry)
{
struct pac_attr_hdr *hdr;
u8 *pos;
size_t left, len;
int type;
/* RFC 7170, Section 4.2.12.4 */
/* PAC-Type defaults to Tunnel PAC (Type 1) */
entry->pac_type = PAC_TYPE_TUNNEL_PAC;
pos = entry->pac_info;
left = entry->pac_info_len;
while (left > sizeof(*hdr)) {
hdr = (struct pac_attr_hdr *) pos;
type = be_to_host16(hdr->type);
len = be_to_host16(hdr->len);
pos += sizeof(*hdr);
left -= sizeof(*hdr);
if (len > left) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: PAC-Info overrun (type=%d len=%lu left=%lu)",
type, (unsigned long) len,
(unsigned long) left);
return -1;
}
if (eap_teap_parse_pac_info(entry, type, pos, len) < 0)
return -1;
pos += len;
left -= len;
}
if (!entry->a_id || !entry->a_id_info) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: PAC-Info does not include all the required fields");
return -1;
}
return 0;
}
static struct wpabuf * eap_teap_process_pac(struct eap_sm *sm,
struct eap_teap_data *data,
struct eap_method_ret *ret,
u8 *pac, size_t pac_len)
{
struct eap_peer_config *config = eap_get_config(sm);
struct eap_teap_pac entry;
os_memset(&entry, 0, sizeof(entry));
if (eap_teap_process_pac_tlv(&entry, pac, pac_len) ||
eap_teap_process_pac_info(&entry))
return NULL;
eap_teap_add_pac(&data->pac, &data->current_pac, &entry);
eap_teap_pac_list_truncate(data->pac, data->max_pac_list_len);
if (data->use_pac_binary_format)
eap_teap_save_pac_bin(sm, data->pac, config->pac_file);
else
eap_teap_save_pac(sm, data->pac, config->pac_file);
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Send PAC-Acknowledgement - %s initiated provisioning completed successfully",
data->provisioning ? "peer" : "server");
return eap_teap_tlv_pac_ack();
}
static int eap_teap_parse_decrypted(struct wpabuf *decrypted,
struct eap_teap_tlv_parse *tlv,
struct wpabuf **resp)
{
u16 tlv_type;
int mandatory, res;
size_t len;
u8 *pos, *end;
os_memset(tlv, 0, sizeof(*tlv));
/* Parse TLVs from the decrypted Phase 2 data */
pos = wpabuf_mhead(decrypted);
end = pos + wpabuf_len(decrypted);
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);
*resp = eap_teap_tlv_nak(0, tlv_type);
break;
}
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Ignore unknown optional TLV type %u",
tlv_type);
}
pos += len;
}
return 0;
}
static struct wpabuf * eap_teap_pac_request(void)
{
struct wpabuf *req;
struct teap_tlv_request_action *act;
struct teap_tlv_hdr *pac;
struct teap_attr_pac_type *type;
req = wpabuf_alloc(sizeof(*act) + sizeof(*pac) + sizeof(*type));
if (!req)
return NULL;
wpa_printf(MSG_DEBUG, "EAP-TEAP: Add Request Action TLV (Process TLV)");
act = wpabuf_put(req, sizeof(*act));
act->tlv_type = host_to_be16(TEAP_TLV_REQUEST_ACTION);
act->length = host_to_be16(2);
act->status = TEAP_STATUS_SUCCESS;
act->action = TEAP_REQUEST_ACTION_PROCESS_TLV;
wpa_printf(MSG_DEBUG, "EAP-TEAP: Add PAC TLV (PAC-Type = Tunnel)");
pac = wpabuf_put(req, sizeof(*pac));
pac->tlv_type = host_to_be16(TEAP_TLV_PAC);
pac->length = host_to_be16(sizeof(*type));
type = wpabuf_put(req, sizeof(*type));
type->type = host_to_be16(PAC_TYPE_PAC_TYPE);
type->length = host_to_be16(2);
type->pac_type = host_to_be16(PAC_TYPE_TUNNEL_PAC);
return req;
}
static int eap_teap_process_decrypted(struct eap_sm *sm,
struct eap_teap_data *data,
struct eap_method_ret *ret,
u8 identifier,
struct wpabuf *decrypted,
struct wpabuf **out_data)
{
struct wpabuf *resp = NULL, *tmp;
struct eap_teap_tlv_parse tlv;
int failed = 0;
enum teap_error_codes error = 0;
int iresult_added = 0;
if (eap_teap_parse_decrypted(decrypted, &tlv, &resp) < 0) {
/* Parsing failed - no response available */
return 0;
}
if (resp) {
/* Parsing rejected the message - send out an error response */
goto send_resp;
}
if (tlv.result == TEAP_STATUS_FAILURE) {
/* Server indicated failure - respond similarly per
* RFC 7170, 3.6.3. This authentication exchange cannot succeed
* and will be terminated with a cleartext EAP Failure. */
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Server rejected authentication");
resp = eap_teap_tlv_result(TEAP_STATUS_FAILURE, 0);
ret->methodState = METHOD_DONE;
ret->decision = DECISION_FAIL;
goto send_resp;
}
if (tlv.iresult == TEAP_STATUS_SUCCESS && !tlv.crypto_binding) {
/* Intermediate-Result TLV indicating success, but no
* Crypto-Binding TLV */
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Intermediate-Result TLV indicating success, but no Crypto-Binding TLV");
failed = 1;
error = TEAP_ERROR_TUNNEL_COMPROMISE_ERROR;
goto done;
}
if (!data->iresult_verified && !data->result_success_done &&
tlv.result == TEAP_STATUS_SUCCESS && !tlv.crypto_binding) {
/* Result TLV indicating success, but no Crypto-Binding TLV */
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Result TLV indicating success, but no Crypto-Binding TLV");
failed = 1;
error = TEAP_ERROR_TUNNEL_COMPROMISE_ERROR;
goto done;
}
if (tlv.iresult != TEAP_STATUS_SUCCESS &&
tlv.iresult != TEAP_STATUS_FAILURE &&
data->inner_method_done) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Inner EAP method exchange completed, but no Intermediate-Result TLV included");
failed = 1;
error = TEAP_ERROR_TUNNEL_COMPROMISE_ERROR;
goto done;
}
if (tlv.identity_type == TEAP_IDENTITY_TYPE_MACHINE) {
struct eap_peer_config *config = eap_get_config(sm);
sm->use_machine_cred = config && config->machine_identity &&
config->machine_identity_len;
} else if (tlv.identity_type) {
sm->use_machine_cred = 0;
}
if (tlv.identity_type) {
struct eap_peer_config *config = eap_get_config(sm);
os_free(data->phase2_types);
data->phase2_types = NULL;
data->num_phase2_types = 0;
if (config &&
eap_peer_select_phase2_methods(config, "auth=",
&data->phase2_types,
&data->num_phase2_types,
sm->use_machine_cred) < 0) {
wpa_printf(MSG_INFO,
"EAP-TEAP: Failed to update Phase 2 EAP types");
failed = 1;
goto done;
}
}
if (tlv.basic_auth_req) {
tmp = eap_teap_process_basic_auth_req(sm, data,
tlv.basic_auth_req,
tlv.basic_auth_req_len,
tlv.identity_type);
if (!tmp)
failed = 1;
resp = wpabuf_concat(resp, tmp);
} else if (tlv.eap_payload_tlv) {
tmp = eap_teap_process_eap_payload_tlv(sm, data, ret,
tlv.eap_payload_tlv,
tlv.eap_payload_tlv_len,
tlv.identity_type);
if (!tmp)
failed = 1;
resp = wpabuf_concat(resp, tmp);
if (tlv.iresult == TEAP_STATUS_SUCCESS ||
tlv.iresult == TEAP_STATUS_FAILURE) {
tmp = eap_teap_tlv_result(failed ?
TEAP_STATUS_FAILURE :
TEAP_STATUS_SUCCESS, 1);
resp = wpabuf_concat(resp, tmp);
if (tlv.iresult == TEAP_STATUS_FAILURE)
failed = 1;
iresult_added = 1;
}
}
if (tlv.crypto_binding) {
if (tlv.iresult != TEAP_STATUS_SUCCESS &&
tlv.result != TEAP_STATUS_SUCCESS) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Unexpected Crypto-Binding TLV without Result TLV or Intermediate-Result TLV indicating success");
failed = 1;
error = TEAP_ERROR_UNEXPECTED_TLVS_EXCHANGED;
goto done;
}
tmp = eap_teap_process_crypto_binding(sm, data, ret,
tlv.crypto_binding,
tlv.crypto_binding_len);
if (!tmp) {
failed = 1;
error = TEAP_ERROR_TUNNEL_COMPROMISE_ERROR;
} else {
resp = wpabuf_concat(resp, tmp);
if (tlv.result == TEAP_STATUS_SUCCESS && !failed)
data->result_success_done = 1;
if (tlv.iresult == TEAP_STATUS_SUCCESS && !failed) {
data->inner_method_done = 0;
data->iresult_verified = 1;
}
}
}
if (data->result_success_done && data->session_ticket_used &&
eap_teap_derive_msk(data) == 0) {
/* Assume the server might accept authentication without going
* through inner authentication. */
wpa_printf(MSG_DEBUG,
"EAP-TEAP: PAC used - server may decide to skip inner authentication");
ret->methodState = METHOD_MAY_CONT;
ret->decision = DECISION_COND_SUCC;
}
if (tlv.pac) {
if (tlv.result == TEAP_STATUS_SUCCESS) {
tmp = eap_teap_process_pac(sm, data, ret,
tlv.pac, tlv.pac_len);
resp = wpabuf_concat(resp, tmp);
} else {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: PAC TLV without Result TLV acknowledging success");
failed = 1;
error = TEAP_ERROR_UNEXPECTED_TLVS_EXCHANGED;
}
}
if (!data->current_pac && data->provisioning && !failed && !tlv.pac &&
tlv.crypto_binding &&
(!data->anon_provisioning ||
(data->phase2_success && data->phase2_method &&
data->phase2_method->vendor == 0 &&
eap_teap_allowed_anon_prov_cipher_suite(data->tls_cs) &&
eap_teap_allowed_anon_prov_phase2_method(
data->phase2_method->vendor,
data->phase2_method->method))) &&
(tlv.iresult == TEAP_STATUS_SUCCESS ||
tlv.result == TEAP_STATUS_SUCCESS)) {
/*
* Need to request Tunnel PAC when using authenticated
* provisioning.
*/
wpa_printf(MSG_DEBUG, "EAP-TEAP: Request Tunnel PAC");
tmp = eap_teap_pac_request();
resp = wpabuf_concat(resp, tmp);
}
done:
if (failed) {
tmp = eap_teap_tlv_result(TEAP_STATUS_FAILURE, 0);
resp = wpabuf_concat(tmp, resp);
if (error != 0) {
tmp = eap_teap_tlv_error(error);
resp = wpabuf_concat(tmp, resp);
}
ret->methodState = METHOD_DONE;
ret->decision = DECISION_FAIL;
} else if (tlv.result == TEAP_STATUS_SUCCESS) {
tmp = eap_teap_tlv_result(TEAP_STATUS_SUCCESS, 0);
resp = wpabuf_concat(tmp, resp);
}
if ((tlv.iresult == TEAP_STATUS_SUCCESS ||
tlv.iresult == TEAP_STATUS_FAILURE) && !iresult_added) {
tmp = eap_teap_tlv_result((!failed && data->phase2_success) ?
TEAP_STATUS_SUCCESS :
TEAP_STATUS_FAILURE, 1);
resp = wpabuf_concat(tmp, resp);
}
if (resp && tlv.result == TEAP_STATUS_SUCCESS && !failed &&
(tlv.crypto_binding || data->iresult_verified) &&
data->phase2_success) {
/* Successfully completed Phase 2 */
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Authentication completed successfully");
ret->methodState = METHOD_MAY_CONT;
data->on_tx_completion = data->provisioning ?
METHOD_MAY_CONT : METHOD_DONE;
ret->decision = DECISION_UNCOND_SUCC;
}
if (!resp) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: No recognized TLVs - send empty response packet");
resp = wpabuf_alloc(1);
}
send_resp:
if (!resp)
return 0;
wpa_hexdump_buf(MSG_DEBUG, "EAP-TEAP: Encrypting Phase 2 data", resp);
if (eap_peer_tls_encrypt(sm, &data->ssl, EAP_TYPE_TEAP,
data->teap_version, identifier,
resp, out_data)) {
wpa_printf(MSG_INFO,
"EAP-TEAP: Failed to encrypt a Phase 2 frame");
}
wpabuf_free(resp);
return 0;
}
static int eap_teap_decrypt(struct eap_sm *sm, struct eap_teap_data *data,
struct eap_method_ret *ret, u8 identifier,
const struct wpabuf *in_data,
struct wpabuf **out_data)
{
struct wpabuf *in_decrypted;
int res;
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Received %lu bytes encrypted data for Phase 2",
(unsigned long) wpabuf_len(in_data));
if (data->pending_phase2_req) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Pending Phase 2 request - skip decryption and use old data");
/* Clear TLS reassembly state. */
eap_peer_tls_reset_input(&data->ssl);
in_decrypted = data->pending_phase2_req;
data->pending_phase2_req = NULL;
goto continue_req;
}
if (wpabuf_len(in_data) == 0) {
/* Received TLS ACK - requesting more fragments */
res = eap_peer_tls_encrypt(sm, &data->ssl, EAP_TYPE_TEAP,
data->teap_version,
identifier, NULL, out_data);
if (res == 0 && !data->ssl.tls_out &&
data->on_tx_completion) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Mark authentication completed at full TX of fragments");
ret->methodState = data->on_tx_completion;
data->on_tx_completion = 0;
ret->decision = DECISION_UNCOND_SUCC;
}
return res;
}
res = eap_peer_tls_decrypt(sm, &data->ssl, in_data, &in_decrypted);
if (res)
return res;
continue_req:
wpa_hexdump_buf(MSG_MSGDUMP, "EAP-TEAP: Decrypted Phase 2 TLV(s)",
in_decrypted);
if (wpabuf_len(in_decrypted) < 4) {
wpa_printf(MSG_INFO,
"EAP-TEAP: Too short Phase 2 TLV frame (len=%lu)",
(unsigned long) wpabuf_len(in_decrypted));
wpabuf_free(in_decrypted);
return -1;
}
res = eap_teap_process_decrypted(sm, data, ret, identifier,
in_decrypted, out_data);
wpabuf_free(in_decrypted);
return res;
}
static void eap_teap_select_pac(struct eap_teap_data *data,
const u8 *a_id, size_t a_id_len)
{
if (!a_id)
return;
data->current_pac = eap_teap_get_pac(data->pac, a_id, a_id_len,
PAC_TYPE_TUNNEL_PAC);
if (data->current_pac) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: PAC found for this A-ID (PAC-Type %d)",
data->current_pac->pac_type);
wpa_hexdump_ascii(MSG_MSGDUMP, "EAP-TEAP: A-ID-Info",
data->current_pac->a_id_info,
data->current_pac->a_id_info_len);
}
}
static int eap_teap_use_pac_opaque(struct eap_sm *sm,
struct eap_teap_data *data,
struct eap_teap_pac *pac)
{
u8 *tlv;
size_t tlv_len, olen;
struct teap_tlv_hdr *ehdr;
wpa_printf(MSG_DEBUG, "EAP-TEAP: Add PAC-Opaque TLS extension");
olen = pac->pac_opaque_len;
tlv_len = sizeof(*ehdr) + olen;
tlv = os_malloc(tlv_len);
if (tlv) {
ehdr = (struct teap_tlv_hdr *) tlv;
ehdr->tlv_type = host_to_be16(PAC_TYPE_PAC_OPAQUE);
ehdr->length = host_to_be16(olen);
os_memcpy(ehdr + 1, pac->pac_opaque, olen);
}
if (!tlv ||
tls_connection_client_hello_ext(sm->ssl_ctx, data->ssl.conn,
TLS_EXT_PAC_OPAQUE,
tlv, tlv_len) < 0) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Failed to add PAC-Opaque TLS extension");
os_free(tlv);
return -1;
}
os_free(tlv);
return 0;
}
static int eap_teap_clear_pac_opaque_ext(struct eap_sm *sm,
struct eap_teap_data *data)
{
if (tls_connection_client_hello_ext(sm->ssl_ctx, data->ssl.conn,
TLS_EXT_PAC_OPAQUE, NULL, 0) < 0) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Failed to remove PAC-Opaque TLS extension");
return -1;
}
return 0;
}
static int eap_teap_process_start(struct eap_sm *sm,
struct eap_teap_data *data, u8 flags,
const u8 *pos, size_t left)
{
const u8 *a_id = NULL;
size_t a_id_len = 0;
/* TODO: Support (mostly theoretical) case of TEAP/Start request being
* fragmented */
/* EAP-TEAP version negotiation (RFC 7170, Section 3.2) */
data->received_version = flags & EAP_TLS_VERSION_MASK;
wpa_printf(MSG_DEBUG, "EAP-TEAP: Start (server ver=%u, own ver=%u)",
data->received_version, data->teap_version);
if (data->received_version < 1) {
/* Version 1 was the first defined version, so reject 0 */
wpa_printf(MSG_INFO,
"EAP-TEAP: Server used unknown TEAP version %u",
data->received_version);
return -1;
}
if (data->received_version < data->teap_version)
data->teap_version = data->received_version;
wpa_printf(MSG_DEBUG, "EAP-TEAP: Using TEAP version %d",
data->teap_version);
wpa_hexdump(MSG_MSGDUMP, "EAP-TEAP: Start message payload", pos, left);
/* Parse Authority-ID TLV from Outer TLVs, if present */
if (flags & EAP_TEAP_FLAGS_OUTER_TLV_LEN) {
const u8 *outer_pos, *outer_end;
u32 outer_tlv_len;
if (left < 4) {
wpa_printf(MSG_INFO,
"EAP-TEAP: Not enough room for the Outer TLV Length field");
return -1;
}
outer_tlv_len = WPA_GET_BE32(pos);
pos += 4;
left -= 4;
if (outer_tlv_len > left) {
wpa_printf(MSG_INFO,
"EAP-TEAP: Truncated Outer TLVs field (Outer TLV Length: %u; remaining buffer: %u)",
outer_tlv_len, (unsigned int) left);
return -1;
}
outer_pos = pos + left - outer_tlv_len;
outer_end = outer_pos + outer_tlv_len;
wpa_hexdump(MSG_MSGDUMP, "EAP-TEAP: Start message Outer TLVs",
outer_pos, outer_tlv_len);
wpabuf_free(data->server_outer_tlvs);
data->server_outer_tlvs = wpabuf_alloc_copy(outer_pos,
outer_tlv_len);
if (!data->server_outer_tlvs)
return -1;
left -= outer_tlv_len;
if (left > 0) {
wpa_hexdump(MSG_INFO,
"EAP-TEAP: Unexpected TLS Data in Start message",
pos, left);
return -1;
}
while (outer_pos < outer_end) {
u16 tlv_type, tlv_len;
if (outer_end - outer_pos < 4) {
wpa_printf(MSG_INFO,
"EAP-TEAP: Truncated Outer TLV header");
return -1;
}
tlv_type = WPA_GET_BE16(outer_pos);
outer_pos += 2;
tlv_len = WPA_GET_BE16(outer_pos);
outer_pos += 2;
/* Outer TLVs are required to be optional, so no need to
* check the M flag */
tlv_type &= TEAP_TLV_TYPE_MASK;
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Outer TLV: Type=%u Length=%u",
tlv_type, tlv_len);
if (outer_end - outer_pos < tlv_len) {
wpa_printf(MSG_INFO,
"EAP-TEAP: Truncated Outer TLV (Type %u)",
tlv_type);
return -1;
}
if (tlv_type == TEAP_TLV_AUTHORITY_ID) {
wpa_hexdump(MSG_DEBUG, "EAP-TEAP: Authority-ID",
outer_pos, tlv_len);
if (a_id) {
wpa_printf(MSG_INFO,
"EAP-TEAP: Multiple Authority-ID TLVs in TEAP/Start");
return -1;
}
a_id = outer_pos;
a_id_len = tlv_len;
} else {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Ignore unknown Outer TLV (Type %u)",
tlv_type);
}
outer_pos += tlv_len;
}
} else if (left > 0) {
wpa_hexdump(MSG_INFO,
"EAP-TEAP: Unexpected TLS Data in Start message",
pos, left);
return -1;
}
eap_teap_select_pac(data, a_id, a_id_len);
if (data->resuming && data->current_pac) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Trying to resume session - do not add PAC-Opaque to TLS ClientHello");
if (eap_teap_clear_pac_opaque_ext(sm, data) < 0)
return -1;
} else if (data->current_pac) {
/*
* PAC found for the A-ID and we are not resuming an old
* session, so add PAC-Opaque extension to ClientHello.
*/
if (eap_teap_use_pac_opaque(sm, data, data->current_pac) < 0)
return -1;
} else if (data->provisioning_allowed) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: No PAC found - starting provisioning");
if (eap_teap_clear_pac_opaque_ext(sm, data) < 0)
return -1;
data->provisioning = 1;
}
return 0;
}
#ifdef CONFIG_TESTING_OPTIONS
static struct wpabuf * eap_teap_add_dummy_outer_tlvs(struct eap_teap_data *data,
struct wpabuf *resp)
{
struct wpabuf *resp2;
u16 len;
const u8 *pos;
u8 flags;
wpabuf_free(data->peer_outer_tlvs);
data->peer_outer_tlvs = wpabuf_alloc(4 + 4);
if (!data->peer_outer_tlvs) {
wpabuf_free(resp);
return NULL;
}
/* Outer TLVs (dummy Vendor-Specific TLV for testing) */
wpabuf_put_be16(data->peer_outer_tlvs, TEAP_TLV_VENDOR_SPECIFIC);
wpabuf_put_be16(data->peer_outer_tlvs, 4);
wpabuf_put_be32(data->peer_outer_tlvs, EAP_VENDOR_HOSTAP);
wpa_hexdump_buf(MSG_DEBUG, "EAP-TEAP: TESTING - Add dummy Outer TLVs",
data->peer_outer_tlvs);
wpa_hexdump_buf(MSG_DEBUG,
"EAP-TEAP: TEAP/Start response before modification",
resp);
resp2 = wpabuf_alloc(wpabuf_len(resp) + 4 +
wpabuf_len(data->peer_outer_tlvs));
if (!resp2) {
wpabuf_free(resp);
return NULL;
}
pos = wpabuf_head(resp);
wpabuf_put_u8(resp2, *pos++); /* Code */
wpabuf_put_u8(resp2, *pos++); /* Identifier */
len = WPA_GET_BE16(pos);
pos += 2;
wpabuf_put_be16(resp2, len + 4 + wpabuf_len(data->peer_outer_tlvs));
wpabuf_put_u8(resp2, *pos++); /* Type */
/* Flags | Ver (with Outer TLV length included flag set to 1) */
flags = *pos++;
if (flags & (EAP_TEAP_FLAGS_OUTER_TLV_LEN |
EAP_TLS_FLAGS_LENGTH_INCLUDED)) {
wpa_printf(MSG_INFO,
"EAP-TEAP: Cannot add Outer TLVs for testing");
wpabuf_free(resp);
wpabuf_free(resp2);
return NULL;
}
flags |= EAP_TEAP_FLAGS_OUTER_TLV_LEN;
wpabuf_put_u8(resp2, flags);
/* Outer TLV Length */
wpabuf_put_be32(resp2, wpabuf_len(data->peer_outer_tlvs));
/* TLS Data */
wpabuf_put_data(resp2, pos, wpabuf_len(resp) - 6);
wpabuf_put_buf(resp2, data->peer_outer_tlvs); /* Outer TLVs */
wpabuf_free(resp);
wpa_hexdump_buf(MSG_DEBUG,
"EAP-TEAP: TEAP/Start response after modification",
resp2);
return resp2;
}
#endif /* CONFIG_TESTING_OPTIONS */
static struct wpabuf * eap_teap_process(struct eap_sm *sm, void *priv,
struct eap_method_ret *ret,
const struct wpabuf *reqData)
{
const struct eap_hdr *req;
size_t left;
int res;
u8 flags, id;
struct wpabuf *resp;
const u8 *pos;
struct eap_teap_data *data = priv;
struct wpabuf msg;
pos = eap_peer_tls_process_init(sm, &data->ssl, EAP_TYPE_TEAP, ret,
reqData, &left, &flags);
if (!pos)
return NULL;
req = wpabuf_head(reqData);
id = req->identifier;
if (flags & EAP_TLS_FLAGS_START) {
if (eap_teap_process_start(sm, data, flags, pos, left) < 0)
return NULL;
/* Outer TLVs are not used in further packet processing and
* there cannot be TLS Data in this TEAP/Start message, so
* enforce that by ignoring whatever data might remain in the
* buffer. */
left = 0;
} else if (flags & EAP_TEAP_FLAGS_OUTER_TLV_LEN) {
/* TODO: RFC 7170, Section 4.3.1 indicates that the unexpected
* Outer TLVs MUST be ignored instead of ignoring the full
* message. */
wpa_printf(MSG_INFO,
"EAP-TEAP: Outer TLVs present in non-Start message -> ignore message");
return NULL;
}
wpabuf_set(&msg, pos, left);
resp = NULL;
if (tls_connection_established(sm->ssl_ctx, data->ssl.conn) &&
!data->resuming) {
/* Process tunneled (encrypted) phase 2 data. */
res = eap_teap_decrypt(sm, data, ret, id, &msg, &resp);
if (res < 0) {
ret->methodState = METHOD_DONE;
ret->decision = DECISION_FAIL;
/*
* Ack possible Alert that may have caused failure in
* decryption.
*/
res = 1;
}
} else {
if (sm->waiting_ext_cert_check && data->pending_resp) {
struct eap_peer_config *config = eap_get_config(sm);
if (config->pending_ext_cert_check ==
EXT_CERT_CHECK_GOOD) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: External certificate check succeeded - continue handshake");
resp = data->pending_resp;
data->pending_resp = NULL;
sm->waiting_ext_cert_check = 0;
return resp;
}
if (config->pending_ext_cert_check ==
EXT_CERT_CHECK_BAD) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: External certificate check failed - force authentication failure");
ret->methodState = METHOD_DONE;
ret->decision = DECISION_FAIL;
sm->waiting_ext_cert_check = 0;
return NULL;
}
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Continuing to wait external server certificate validation");
return NULL;
}
/* Continue processing TLS handshake (phase 1). */
res = eap_peer_tls_process_helper(sm, &data->ssl,
EAP_TYPE_TEAP,
data->teap_version, id, &msg,
&resp);
if (res < 0) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: TLS processing failed");
ret->methodState = METHOD_DONE;
ret->decision = DECISION_FAIL;
return resp;
}
if (sm->waiting_ext_cert_check) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Waiting external server certificate validation");
wpabuf_free(data->pending_resp);
data->pending_resp = resp;
return NULL;
}
if (tls_connection_established(sm->ssl_ctx, data->ssl.conn)) {
char cipher[80];
wpa_printf(MSG_DEBUG,
"EAP-TEAP: TLS done, proceed to 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 (data->provisioning &&
(!(data->provisioning_allowed &
EAP_TEAP_PROV_AUTH) ||
tls_get_cipher(sm->ssl_ctx, data->ssl.conn,
cipher, sizeof(cipher)) < 0 ||
os_strstr(cipher, "ADH-") ||
os_strstr(cipher, "anon"))) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Using anonymous (unauthenticated) provisioning");
data->anon_provisioning = 1;
} else {
data->anon_provisioning = 0;
}
data->resuming = 0;
if (eap_teap_derive_key_auth(sm, data) < 0) {
wpa_printf(MSG_DEBUG,
"EAP-TEAP: Could not derive keys");
ret->methodState = METHOD_DONE;
ret->decision = DECISION_FAIL;
wpabuf_free(resp);
return NULL;
}
}
if (res == 2) {
/*
* Application data included in the handshake message.
*/
wpabuf_free(data->pending_phase2_req);
data->pending_phase2_req = resp;
resp = NULL;
res = eap_teap_decrypt(sm, data, ret, id, &msg, &resp);
}
}
if (res == 1) {
wpabuf_free(resp);
return eap_peer_tls_build_ack(id, EAP_TYPE_TEAP,
data->teap_version);
}
#ifdef CONFIG_TESTING_OPTIONS
if (data->test_outer_tlvs && res == 0 && resp &&
(flags & EAP_TLS_FLAGS_START) && wpabuf_len(resp) >= 6)
resp = eap_teap_add_dummy_outer_tlvs(data, resp);
#endif /* CONFIG_TESTING_OPTIONS */
return resp;
}
#if 0 /* TODO */
static Boolean eap_teap_has_reauth_data(struct eap_sm *sm, void *priv)
{
struct eap_teap_data *data = priv;
return tls_connection_established(sm->ssl_ctx, data->ssl.conn);
}
static void eap_teap_deinit_for_reauth(struct eap_sm *sm, void *priv)
{
struct eap_teap_data *data = priv;
if (data->phase2_priv && data->phase2_method &&
data->phase2_method->deinit_for_reauth)
data->phase2_method->deinit_for_reauth(sm, data->phase2_priv);
eap_teap_clear(data);
}
static void * eap_teap_init_for_reauth(struct eap_sm *sm, void *priv)
{
struct eap_teap_data *data = priv;
if (eap_peer_tls_reauth_init(sm, &data->ssl)) {
eap_teap_deinit(sm, data);
return NULL;
}
if (data->phase2_priv && data->phase2_method &&
data->phase2_method->init_for_reauth)
data->phase2_method->init_for_reauth(sm, data->phase2_priv);
data->phase2_success = 0;
data->inner_method_done = 0;
data->result_success_done = 0;
data->iresult_verified = 0;
data->done_on_tx_completion = 0;
data->resuming = 1;
data->provisioning = 0;
data->anon_provisioning = 0;
data->simck_idx = 0;
return priv;
}
#endif
static int eap_teap_get_status(struct eap_sm *sm, void *priv, char *buf,
size_t buflen, int verbose)
{
struct eap_teap_data *data = priv;
int len, ret;
len = eap_peer_tls_status(sm, &data->ssl, buf, buflen, verbose);
if (data->phase2_method) {
ret = os_snprintf(buf + len, buflen - len,
"EAP-TEAP Phase 2 method=%s\n",
data->phase2_method->name);
if (os_snprintf_error(buflen - len, ret))
return len;
len += ret;
}
return len;
}
static Boolean eap_teap_isKeyAvailable(struct eap_sm *sm, void *priv)
{
struct eap_teap_data *data = priv;
return data->success;
}
static u8 * eap_teap_getKey(struct eap_sm *sm, void *priv, size_t *len)
{
struct eap_teap_data *data = priv;
u8 *key;
if (!data->success)
return NULL;
key = os_memdup(data->key_data, EAP_TEAP_KEY_LEN);
if (!key)
return NULL;
*len = EAP_TEAP_KEY_LEN;
return key;
}
static u8 * eap_teap_get_session_id(struct eap_sm *sm, void *priv, size_t *len)
{
struct eap_teap_data *data = priv;
u8 *id;
if (!data->success || !data->session_id)
return NULL;
id = os_memdup(data->session_id, data->id_len);
if (!id)
return NULL;
*len = data->id_len;
return id;
}
static u8 * eap_teap_get_emsk(struct eap_sm *sm, void *priv, size_t *len)
{
struct eap_teap_data *data = priv;
u8 *key;
if (!data->success)
return NULL;
key = os_memdup(data->emsk, EAP_EMSK_LEN);
if (!key)
return NULL;
*len = EAP_EMSK_LEN;
return key;
}
int eap_peer_teap_register(void)
{
struct eap_method *eap;
eap = eap_peer_method_alloc(EAP_PEER_METHOD_INTERFACE_VERSION,
EAP_VENDOR_IETF, EAP_TYPE_TEAP, "TEAP");
if (!eap)
return -1;
eap->init = eap_teap_init;
eap->deinit = eap_teap_deinit;
eap->process = eap_teap_process;
eap->isKeyAvailable = eap_teap_isKeyAvailable;
eap->getKey = eap_teap_getKey;
eap->getSessionId = eap_teap_get_session_id;
eap->get_status = eap_teap_get_status;
#if 0 /* TODO */
eap->has_reauth_data = eap_teap_has_reauth_data;
eap->deinit_for_reauth = eap_teap_deinit_for_reauth;
eap->init_for_reauth = eap_teap_init_for_reauth;
#endif
eap->get_emsk = eap_teap_get_emsk;
return eap_peer_method_register(eap);
}