hostapd/src/eap_server/eap_server_peap.c
Jouni Malinen 814f43cff5 EAP server: Simplify EAP method registration call
Free the allocated structure in error cases to remove need for each EAP
method to handle the error cases separately. Each registration function
can simply do "return eap_server_method_register(eap);" in the end of
the function.

Signed-off-by: Jouni Malinen <jouni@qca.qualcomm.com>
2016-01-13 23:35:53 +02:00

1383 lines
35 KiB
C

/*
* hostapd / EAP-PEAP (draft-josefsson-pppext-eap-tls-eap-10.txt)
* Copyright (c) 2004-2008, Jouni Malinen <j@w1.fi>
*
* This software may be distributed under the terms of the BSD license.
* See README for more details.
*/
#include "includes.h"
#include "common.h"
#include "crypto/sha1.h"
#include "crypto/tls.h"
#include "crypto/random.h"
#include "eap_i.h"
#include "eap_tls_common.h"
#include "eap_common/eap_tlv_common.h"
#include "eap_common/eap_peap_common.h"
#include "tncs.h"
/* Maximum supported PEAP version
* 0 = Microsoft's PEAP version 0; draft-kamath-pppext-peapv0-00.txt
* 1 = draft-josefsson-ppext-eap-tls-eap-05.txt
*/
#define EAP_PEAP_VERSION 1
static void eap_peap_reset(struct eap_sm *sm, void *priv);
struct eap_peap_data {
struct eap_ssl_data ssl;
enum {
START, PHASE1, PHASE1_ID2, PHASE2_START, PHASE2_ID,
PHASE2_METHOD, PHASE2_SOH,
PHASE2_TLV, SUCCESS_REQ, FAILURE_REQ, SUCCESS, FAILURE
} state;
int peap_version;
int recv_version;
const struct eap_method *phase2_method;
void *phase2_priv;
int force_version;
struct wpabuf *pending_phase2_resp;
enum { TLV_REQ_NONE, TLV_REQ_SUCCESS, TLV_REQ_FAILURE } tlv_request;
int crypto_binding_sent;
int crypto_binding_used;
enum { NO_BINDING, OPTIONAL_BINDING, REQUIRE_BINDING } crypto_binding;
u8 binding_nonce[32];
u8 ipmk[40];
u8 cmk[20];
u8 *phase2_key;
size_t phase2_key_len;
struct wpabuf *soh_response;
};
static const char * eap_peap_state_txt(int state)
{
switch (state) {
case START:
return "START";
case PHASE1:
return "PHASE1";
case PHASE1_ID2:
return "PHASE1_ID2";
case PHASE2_START:
return "PHASE2_START";
case PHASE2_ID:
return "PHASE2_ID";
case PHASE2_METHOD:
return "PHASE2_METHOD";
case PHASE2_SOH:
return "PHASE2_SOH";
case PHASE2_TLV:
return "PHASE2_TLV";
case SUCCESS_REQ:
return "SUCCESS_REQ";
case FAILURE_REQ:
return "FAILURE_REQ";
case SUCCESS:
return "SUCCESS";
case FAILURE:
return "FAILURE";
default:
return "Unknown?!";
}
}
static void eap_peap_state(struct eap_peap_data *data, int state)
{
wpa_printf(MSG_DEBUG, "EAP-PEAP: %s -> %s",
eap_peap_state_txt(data->state),
eap_peap_state_txt(state));
data->state = state;
if (state == FAILURE || state == FAILURE_REQ)
tls_connection_remove_session(data->ssl.conn);
}
static void eap_peap_valid_session(struct eap_sm *sm,
struct eap_peap_data *data)
{
struct wpabuf *buf;
if (!sm->tls_session_lifetime ||
tls_connection_resumed(sm->ssl_ctx, data->ssl.conn))
return;
buf = wpabuf_alloc(1 + 1 + sm->identity_len);
if (!buf)
return;
wpabuf_put_u8(buf, EAP_TYPE_PEAP);
if (sm->identity) {
u8 id_len;
if (sm->identity_len <= 255)
id_len = sm->identity_len;
else
id_len = 255;
wpabuf_put_u8(buf, id_len);
wpabuf_put_data(buf, sm->identity, id_len);
} else {
wpabuf_put_u8(buf, 0);
}
tls_connection_set_success_data(data->ssl.conn, buf);
}
static void eap_peap_req_success(struct eap_sm *sm,
struct eap_peap_data *data)
{
if (data->state == FAILURE || data->state == FAILURE_REQ) {
eap_peap_state(data, FAILURE);
return;
}
if (data->peap_version == 0) {
data->tlv_request = TLV_REQ_SUCCESS;
eap_peap_state(data, PHASE2_TLV);
} else {
eap_peap_state(data, SUCCESS_REQ);
}
}
static void eap_peap_req_failure(struct eap_sm *sm,
struct eap_peap_data *data)
{
if (data->state == FAILURE || data->state == FAILURE_REQ ||
data->state == SUCCESS_REQ || data->tlv_request != TLV_REQ_NONE) {
eap_peap_state(data, FAILURE);
return;
}
if (data->peap_version == 0) {
data->tlv_request = TLV_REQ_FAILURE;
eap_peap_state(data, PHASE2_TLV);
} else {
eap_peap_state(data, FAILURE_REQ);
}
}
static void * eap_peap_init(struct eap_sm *sm)
{
struct eap_peap_data *data;
data = os_zalloc(sizeof(*data));
if (data == NULL)
return NULL;
data->peap_version = EAP_PEAP_VERSION;
data->force_version = -1;
if (sm->user && sm->user->force_version >= 0) {
data->force_version = sm->user->force_version;
wpa_printf(MSG_DEBUG, "EAP-PEAP: forcing version %d",
data->force_version);
data->peap_version = data->force_version;
}
data->state = START;
data->crypto_binding = OPTIONAL_BINDING;
if (eap_server_tls_ssl_init(sm, &data->ssl, 0, EAP_TYPE_PEAP)) {
wpa_printf(MSG_INFO, "EAP-PEAP: Failed to initialize SSL.");
eap_peap_reset(sm, data);
return NULL;
}
return data;
}
static void eap_peap_reset(struct eap_sm *sm, void *priv)
{
struct eap_peap_data *data = priv;
if (data == NULL)
return;
if (data->phase2_priv && data->phase2_method)
data->phase2_method->reset(sm, data->phase2_priv);
eap_server_tls_ssl_deinit(sm, &data->ssl);
wpabuf_free(data->pending_phase2_resp);
os_free(data->phase2_key);
wpabuf_free(data->soh_response);
bin_clear_free(data, sizeof(*data));
}
static struct wpabuf * eap_peap_build_start(struct eap_sm *sm,
struct eap_peap_data *data, u8 id)
{
struct wpabuf *req;
req = eap_msg_alloc(EAP_VENDOR_IETF, EAP_TYPE_PEAP, 1,
EAP_CODE_REQUEST, id);
if (req == NULL) {
wpa_printf(MSG_ERROR, "EAP-PEAP: Failed to allocate memory for"
" request");
eap_peap_state(data, FAILURE);
return NULL;
}
wpabuf_put_u8(req, EAP_TLS_FLAGS_START | data->peap_version);
eap_peap_state(data, PHASE1);
return req;
}
static struct wpabuf * eap_peap_build_phase2_req(struct eap_sm *sm,
struct eap_peap_data *data,
u8 id)
{
struct wpabuf *buf, *encr_req, msgbuf;
const u8 *req;
size_t req_len;
if (data->phase2_method == NULL || data->phase2_priv == NULL) {
wpa_printf(MSG_DEBUG, "EAP-PEAP: Phase 2 method not ready");
return NULL;
}
buf = data->phase2_method->buildReq(sm, data->phase2_priv, id);
if (buf == NULL)
return NULL;
req = wpabuf_head(buf);
req_len = wpabuf_len(buf);
wpa_hexdump_key(MSG_DEBUG, "EAP-PEAP: Encrypting Phase 2 data",
req, req_len);
if (data->peap_version == 0 &&
data->phase2_method->method != EAP_TYPE_TLV) {
req += sizeof(struct eap_hdr);
req_len -= sizeof(struct eap_hdr);
}
wpabuf_set(&msgbuf, req, req_len);
encr_req = eap_server_tls_encrypt(sm, &data->ssl, &msgbuf);
wpabuf_free(buf);
return encr_req;
}
#ifdef EAP_SERVER_TNC
static struct wpabuf * eap_peap_build_phase2_soh(struct eap_sm *sm,
struct eap_peap_data *data,
u8 id)
{
struct wpabuf *buf1, *buf, *encr_req, msgbuf;
const u8 *req;
size_t req_len;
buf1 = tncs_build_soh_request();
if (buf1 == NULL)
return NULL;
buf = eap_msg_alloc(EAP_VENDOR_MICROSOFT, 0x21, wpabuf_len(buf1),
EAP_CODE_REQUEST, id);
if (buf == NULL) {
wpabuf_free(buf1);
return NULL;
}
wpabuf_put_buf(buf, buf1);
wpabuf_free(buf1);
req = wpabuf_head(buf);
req_len = wpabuf_len(buf);
wpa_hexdump_key(MSG_DEBUG, "EAP-PEAP: Encrypting Phase 2 SOH data",
req, req_len);
req += sizeof(struct eap_hdr);
req_len -= sizeof(struct eap_hdr);
wpabuf_set(&msgbuf, req, req_len);
encr_req = eap_server_tls_encrypt(sm, &data->ssl, &msgbuf);
wpabuf_free(buf);
return encr_req;
}
#endif /* EAP_SERVER_TNC */
static void eap_peap_get_isk(struct eap_peap_data *data,
u8 *isk, size_t isk_len)
{
size_t key_len;
os_memset(isk, 0, isk_len);
if (data->phase2_key == NULL)
return;
key_len = data->phase2_key_len;
if (key_len > isk_len)
key_len = isk_len;
os_memcpy(isk, data->phase2_key, key_len);
}
static int eap_peap_derive_cmk(struct eap_sm *sm, struct eap_peap_data *data)
{
u8 *tk;
u8 isk[32], imck[60];
/*
* Tunnel key (TK) is the first 60 octets of the key generated by
* phase 1 of PEAP (based on TLS).
*/
tk = eap_server_tls_derive_key(sm, &data->ssl, "client EAP encryption",
EAP_TLS_KEY_LEN);
if (tk == NULL)
return -1;
wpa_hexdump_key(MSG_DEBUG, "EAP-PEAP: TK", tk, 60);
if (tls_connection_resumed(sm->ssl_ctx, data->ssl.conn)) {
/* Fast-connect: IPMK|CMK = TK */
os_memcpy(data->ipmk, tk, 40);
wpa_hexdump_key(MSG_DEBUG, "EAP-PEAP: IPMK from TK",
data->ipmk, 40);
os_memcpy(data->cmk, tk + 40, 20);
wpa_hexdump_key(MSG_DEBUG, "EAP-PEAP: CMK from TK",
data->cmk, 20);
os_free(tk);
return 0;
}
eap_peap_get_isk(data, isk, sizeof(isk));
wpa_hexdump_key(MSG_DEBUG, "EAP-PEAP: ISK", isk, sizeof(isk));
/*
* IPMK Seed = "Inner Methods Compound Keys" | ISK
* TempKey = First 40 octets of TK
* IPMK|CMK = PRF+(TempKey, IPMK Seed, 60)
* (note: draft-josefsson-pppext-eap-tls-eap-10.txt includes a space
* in the end of the label just before ISK; is that just a typo?)
*/
wpa_hexdump_key(MSG_DEBUG, "EAP-PEAP: TempKey", tk, 40);
if (peap_prfplus(data->peap_version, tk, 40,
"Inner Methods Compound Keys",
isk, sizeof(isk), imck, sizeof(imck)) < 0) {
os_free(tk);
return -1;
}
wpa_hexdump_key(MSG_DEBUG, "EAP-PEAP: IMCK (IPMKj)",
imck, sizeof(imck));
os_free(tk);
os_memcpy(data->ipmk, imck, 40);
wpa_hexdump_key(MSG_DEBUG, "EAP-PEAP: IPMK (S-IPMKj)", data->ipmk, 40);
os_memcpy(data->cmk, imck + 40, 20);
wpa_hexdump_key(MSG_DEBUG, "EAP-PEAP: CMK (CMKj)", data->cmk, 20);
return 0;
}
static struct wpabuf * eap_peap_build_phase2_tlv(struct eap_sm *sm,
struct eap_peap_data *data,
u8 id)
{
struct wpabuf *buf, *encr_req;
size_t mlen;
mlen = 6; /* Result TLV */
if (data->peap_version == 0 && data->tlv_request == TLV_REQ_SUCCESS &&
data->crypto_binding != NO_BINDING) {
mlen += 60; /* Cryptobinding TLV */
#ifdef EAP_SERVER_TNC
if (data->soh_response)
mlen += wpabuf_len(data->soh_response);
#endif /* EAP_SERVER_TNC */
}
buf = eap_msg_alloc(EAP_VENDOR_IETF, EAP_TYPE_TLV, mlen,
EAP_CODE_REQUEST, id);
if (buf == NULL)
return NULL;
wpabuf_put_u8(buf, 0x80); /* Mandatory */
wpabuf_put_u8(buf, EAP_TLV_RESULT_TLV);
/* Length */
wpabuf_put_be16(buf, 2);
/* Status */
wpabuf_put_be16(buf, data->tlv_request == TLV_REQ_SUCCESS ?
EAP_TLV_RESULT_SUCCESS : EAP_TLV_RESULT_FAILURE);
if (data->peap_version == 0 && data->tlv_request == TLV_REQ_SUCCESS &&
data->crypto_binding != NO_BINDING) {
u8 *mac;
u8 eap_type = EAP_TYPE_PEAP;
const u8 *addr[2];
size_t len[2];
u16 tlv_type;
#ifdef EAP_SERVER_TNC
if (data->soh_response) {
wpa_printf(MSG_DEBUG, "EAP-PEAP: Adding MS-SOH "
"Response TLV");
wpabuf_put_buf(buf, data->soh_response);
wpabuf_free(data->soh_response);
data->soh_response = NULL;
}
#endif /* EAP_SERVER_TNC */
if (eap_peap_derive_cmk(sm, data) < 0 ||
random_get_bytes(data->binding_nonce, 32)) {
wpabuf_free(buf);
return NULL;
}
/* Compound_MAC: HMAC-SHA1-160(cryptobinding TLV | EAP type) */
addr[0] = wpabuf_put(buf, 0);
len[0] = 60;
addr[1] = &eap_type;
len[1] = 1;
tlv_type = EAP_TLV_CRYPTO_BINDING_TLV;
wpabuf_put_be16(buf, tlv_type);
wpabuf_put_be16(buf, 56);
wpabuf_put_u8(buf, 0); /* Reserved */
wpabuf_put_u8(buf, data->peap_version); /* Version */
wpabuf_put_u8(buf, data->recv_version); /* RecvVersion */
wpabuf_put_u8(buf, 0); /* SubType: 0 = Request, 1 = Response */
wpabuf_put_data(buf, data->binding_nonce, 32); /* Nonce */
mac = wpabuf_put(buf, 20); /* Compound_MAC */
wpa_hexdump(MSG_MSGDUMP, "EAP-PEAP: Compound_MAC CMK",
data->cmk, 20);
wpa_hexdump(MSG_MSGDUMP, "EAP-PEAP: Compound_MAC data 1",
addr[0], len[0]);
wpa_hexdump(MSG_MSGDUMP, "EAP-PEAP: Compound_MAC data 2",
addr[1], len[1]);
hmac_sha1_vector(data->cmk, 20, 2, addr, len, mac);
wpa_hexdump(MSG_MSGDUMP, "EAP-PEAP: Compound_MAC",
mac, SHA1_MAC_LEN);
data->crypto_binding_sent = 1;
}
wpa_hexdump_buf_key(MSG_DEBUG, "EAP-PEAP: Encrypting Phase 2 TLV data",
buf);
encr_req = eap_server_tls_encrypt(sm, &data->ssl, buf);
wpabuf_free(buf);
return encr_req;
}
static struct wpabuf * eap_peap_build_phase2_term(struct eap_sm *sm,
struct eap_peap_data *data,
u8 id, int success)
{
struct wpabuf *encr_req, msgbuf;
size_t req_len;
struct eap_hdr *hdr;
req_len = sizeof(*hdr);
hdr = os_zalloc(req_len);
if (hdr == NULL)
return NULL;
hdr->code = success ? EAP_CODE_SUCCESS : EAP_CODE_FAILURE;
hdr->identifier = id;
hdr->length = host_to_be16(req_len);
wpa_hexdump_key(MSG_DEBUG, "EAP-PEAP: Encrypting Phase 2 data",
(u8 *) hdr, req_len);
wpabuf_set(&msgbuf, hdr, req_len);
encr_req = eap_server_tls_encrypt(sm, &data->ssl, &msgbuf);
os_free(hdr);
return encr_req;
}
static struct wpabuf * eap_peap_buildReq(struct eap_sm *sm, void *priv, u8 id)
{
struct eap_peap_data *data = priv;
if (data->ssl.state == FRAG_ACK) {
return eap_server_tls_build_ack(id, EAP_TYPE_PEAP,
data->peap_version);
}
if (data->ssl.state == WAIT_FRAG_ACK) {
return eap_server_tls_build_msg(&data->ssl, EAP_TYPE_PEAP,
data->peap_version, id);
}
switch (data->state) {
case START:
return eap_peap_build_start(sm, data, id);
case PHASE1:
case PHASE1_ID2:
if (tls_connection_established(sm->ssl_ctx, data->ssl.conn)) {
wpa_printf(MSG_DEBUG, "EAP-PEAP: Phase1 done, "
"starting Phase2");
eap_peap_state(data, PHASE2_START);
}
break;
case PHASE2_ID:
case PHASE2_METHOD:
wpabuf_free(data->ssl.tls_out);
data->ssl.tls_out_pos = 0;
data->ssl.tls_out = eap_peap_build_phase2_req(sm, data, id);
break;
#ifdef EAP_SERVER_TNC
case PHASE2_SOH:
wpabuf_free(data->ssl.tls_out);
data->ssl.tls_out_pos = 0;
data->ssl.tls_out = eap_peap_build_phase2_soh(sm, data, id);
break;
#endif /* EAP_SERVER_TNC */
case PHASE2_TLV:
wpabuf_free(data->ssl.tls_out);
data->ssl.tls_out_pos = 0;
data->ssl.tls_out = eap_peap_build_phase2_tlv(sm, data, id);
break;
case SUCCESS_REQ:
wpabuf_free(data->ssl.tls_out);
data->ssl.tls_out_pos = 0;
data->ssl.tls_out = eap_peap_build_phase2_term(sm, data, id,
1);
break;
case FAILURE_REQ:
wpabuf_free(data->ssl.tls_out);
data->ssl.tls_out_pos = 0;
data->ssl.tls_out = eap_peap_build_phase2_term(sm, data, id,
0);
break;
default:
wpa_printf(MSG_DEBUG, "EAP-PEAP: %s - unexpected state %d",
__func__, data->state);
return NULL;
}
return eap_server_tls_build_msg(&data->ssl, EAP_TYPE_PEAP,
data->peap_version, id);
}
static Boolean eap_peap_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_PEAP, respData, &len);
if (pos == NULL || len < 1) {
wpa_printf(MSG_INFO, "EAP-PEAP: Invalid frame");
return TRUE;
}
return FALSE;
}
static int eap_peap_phase2_init(struct eap_sm *sm, struct eap_peap_data *data,
int vendor, 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(vendor, 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 0;
}
static int eap_tlv_validate_cryptobinding(struct eap_sm *sm,
struct eap_peap_data *data,
const u8 *crypto_tlv,
size_t crypto_tlv_len)
{
u8 buf[61], mac[SHA1_MAC_LEN];
const u8 *pos;
if (crypto_tlv_len != 4 + 56) {
wpa_printf(MSG_DEBUG, "EAP-PEAP: Invalid cryptobinding TLV "
"length %d", (int) crypto_tlv_len);
return -1;
}
pos = crypto_tlv;
pos += 4; /* TLV header */
if (pos[1] != data->peap_version) {
wpa_printf(MSG_DEBUG, "EAP-PEAP: Cryptobinding TLV Version "
"mismatch (was %d; expected %d)",
pos[1], data->peap_version);
return -1;
}
if (pos[3] != 1) {
wpa_printf(MSG_DEBUG, "EAP-PEAP: Unexpected Cryptobinding TLV "
"SubType %d", pos[3]);
return -1;
}
pos += 4;
pos += 32; /* Nonce */
/* Compound_MAC: HMAC-SHA1-160(cryptobinding TLV | EAP type) */
os_memcpy(buf, crypto_tlv, 60);
os_memset(buf + 4 + 4 + 32, 0, 20); /* Compound_MAC */
buf[60] = EAP_TYPE_PEAP;
hmac_sha1(data->cmk, 20, buf, sizeof(buf), mac);
if (os_memcmp_const(mac, pos, SHA1_MAC_LEN) != 0) {
wpa_printf(MSG_DEBUG, "EAP-PEAP: Invalid Compound_MAC in "
"cryptobinding TLV");
wpa_hexdump_key(MSG_DEBUG, "EAP-PEAP: CMK", data->cmk, 20);
wpa_hexdump(MSG_DEBUG, "EAP-PEAP: Cryptobinding seed data",
buf, 61);
return -1;
}
wpa_printf(MSG_DEBUG, "EAP-PEAP: Valid cryptobinding TLV received");
return 0;
}
static void eap_peap_process_phase2_tlv(struct eap_sm *sm,
struct eap_peap_data *data,
struct wpabuf *in_data)
{
const u8 *pos;
size_t left;
const u8 *result_tlv = NULL, *crypto_tlv = NULL;
size_t result_tlv_len = 0, crypto_tlv_len = 0;
int tlv_type, mandatory, tlv_len;
pos = eap_hdr_validate(EAP_VENDOR_IETF, EAP_TYPE_TLV, in_data, &left);
if (pos == NULL) {
wpa_printf(MSG_DEBUG, "EAP-PEAP: Invalid EAP-TLV header");
return;
}
/* Parse TLVs */
wpa_hexdump(MSG_DEBUG, "EAP-PEAP: Received TLVs", pos, left);
while (left >= 4) {
mandatory = !!(pos[0] & 0x80);
tlv_type = pos[0] & 0x3f;
tlv_type = (tlv_type << 8) | pos[1];
tlv_len = ((int) pos[2] << 8) | pos[3];
pos += 4;
left -= 4;
if ((size_t) tlv_len > left) {
wpa_printf(MSG_DEBUG, "EAP-PEAP: TLV underrun "
"(tlv_len=%d left=%lu)", tlv_len,
(unsigned long) left);
eap_peap_state(data, FAILURE);
return;
}
switch (tlv_type) {
case EAP_TLV_RESULT_TLV:
result_tlv = pos;
result_tlv_len = tlv_len;
break;
case EAP_TLV_CRYPTO_BINDING_TLV:
crypto_tlv = pos;
crypto_tlv_len = tlv_len;
break;
default:
wpa_printf(MSG_DEBUG, "EAP-PEAP: Unsupported TLV Type "
"%d%s", tlv_type,
mandatory ? " (mandatory)" : "");
if (mandatory) {
eap_peap_state(data, FAILURE);
return;
}
/* Ignore this TLV, but process other TLVs */
break;
}
pos += tlv_len;
left -= tlv_len;
}
if (left) {
wpa_printf(MSG_DEBUG, "EAP-PEAP: Last TLV too short in "
"Request (left=%lu)", (unsigned long) left);
eap_peap_state(data, FAILURE);
return;
}
/* Process supported TLVs */
if (crypto_tlv && data->crypto_binding_sent) {
wpa_hexdump(MSG_DEBUG, "EAP-PEAP: Cryptobinding TLV",
crypto_tlv, crypto_tlv_len);
if (eap_tlv_validate_cryptobinding(sm, data, crypto_tlv - 4,
crypto_tlv_len + 4) < 0) {
eap_peap_state(data, FAILURE);
return;
}
data->crypto_binding_used = 1;
} else if (!crypto_tlv && data->crypto_binding_sent &&
data->crypto_binding == REQUIRE_BINDING) {
wpa_printf(MSG_DEBUG, "EAP-PEAP: No cryptobinding TLV");
eap_peap_state(data, FAILURE);
return;
}
if (result_tlv) {
int status;
const char *requested;
wpa_hexdump(MSG_DEBUG, "EAP-PEAP: Result TLV",
result_tlv, result_tlv_len);
if (result_tlv_len < 2) {
wpa_printf(MSG_INFO, "EAP-PEAP: Too short Result TLV "
"(len=%lu)",
(unsigned long) result_tlv_len);
eap_peap_state(data, FAILURE);
return;
}
requested = data->tlv_request == TLV_REQ_SUCCESS ? "Success" :
"Failure";
status = WPA_GET_BE16(result_tlv);
if (status == EAP_TLV_RESULT_SUCCESS) {
wpa_printf(MSG_INFO, "EAP-PEAP: TLV Result - Success "
"- requested %s", requested);
if (data->tlv_request == TLV_REQ_SUCCESS) {
eap_peap_state(data, SUCCESS);
eap_peap_valid_session(sm, data);
} else {
eap_peap_state(data, FAILURE);
}
} else if (status == EAP_TLV_RESULT_FAILURE) {
wpa_printf(MSG_INFO, "EAP-PEAP: TLV Result - Failure "
"- requested %s", requested);
eap_peap_state(data, FAILURE);
} else {
wpa_printf(MSG_INFO, "EAP-PEAP: Unknown TLV Result "
"Status %d", status);
eap_peap_state(data, FAILURE);
}
}
}
#ifdef EAP_SERVER_TNC
static void eap_peap_process_phase2_soh(struct eap_sm *sm,
struct eap_peap_data *data,
struct wpabuf *in_data)
{
const u8 *pos, *vpos;
size_t left;
const u8 *soh_tlv = NULL;
size_t soh_tlv_len = 0;
int tlv_type, mandatory, tlv_len, vtlv_len;
u32 next_type;
u32 vendor_id;
pos = eap_hdr_validate(EAP_VENDOR_MICROSOFT, 0x21, in_data, &left);
if (pos == NULL) {
wpa_printf(MSG_DEBUG, "EAP-PEAP: Not a valid SoH EAP "
"Extensions Method header - skip TNC");
goto auth_method;
}
/* Parse TLVs */
wpa_hexdump(MSG_DEBUG, "EAP-PEAP: Received TLVs (SoH)", pos, left);
while (left >= 4) {
mandatory = !!(pos[0] & 0x80);
tlv_type = pos[0] & 0x3f;
tlv_type = (tlv_type << 8) | pos[1];
tlv_len = ((int) pos[2] << 8) | pos[3];
pos += 4;
left -= 4;
if ((size_t) tlv_len > left) {
wpa_printf(MSG_DEBUG, "EAP-PEAP: TLV underrun "
"(tlv_len=%d left=%lu)", tlv_len,
(unsigned long) left);
eap_peap_state(data, FAILURE);
return;
}
switch (tlv_type) {
case EAP_TLV_VENDOR_SPECIFIC_TLV:
if (tlv_len < 4) {
wpa_printf(MSG_DEBUG, "EAP-PEAP: Too short "
"vendor specific TLV (len=%d)",
(int) tlv_len);
eap_peap_state(data, FAILURE);
return;
}
vendor_id = WPA_GET_BE32(pos);
if (vendor_id != EAP_VENDOR_MICROSOFT) {
if (mandatory) {
eap_peap_state(data, FAILURE);
return;
}
break;
}
vpos = pos + 4;
mandatory = !!(vpos[0] & 0x80);
tlv_type = vpos[0] & 0x3f;
tlv_type = (tlv_type << 8) | vpos[1];
vtlv_len = ((int) vpos[2] << 8) | vpos[3];
vpos += 4;
if (vpos + vtlv_len > pos + left) {
wpa_printf(MSG_DEBUG, "EAP-PEAP: Vendor TLV "
"underrun");
eap_peap_state(data, FAILURE);
return;
}
if (tlv_type == 1) {
soh_tlv = vpos;
soh_tlv_len = vtlv_len;
break;
}
wpa_printf(MSG_DEBUG, "EAP-PEAP: Unsupported MS-TLV "
"Type %d%s", tlv_type,
mandatory ? " (mandatory)" : "");
if (mandatory) {
eap_peap_state(data, FAILURE);
return;
}
/* Ignore this TLV, but process other TLVs */
break;
default:
wpa_printf(MSG_DEBUG, "EAP-PEAP: Unsupported TLV Type "
"%d%s", tlv_type,
mandatory ? " (mandatory)" : "");
if (mandatory) {
eap_peap_state(data, FAILURE);
return;
}
/* Ignore this TLV, but process other TLVs */
break;
}
pos += tlv_len;
left -= tlv_len;
}
if (left) {
wpa_printf(MSG_DEBUG, "EAP-PEAP: Last TLV too short in "
"Request (left=%lu)", (unsigned long) left);
eap_peap_state(data, FAILURE);
return;
}
/* Process supported TLVs */
if (soh_tlv) {
int failure = 0;
wpabuf_free(data->soh_response);
data->soh_response = tncs_process_soh(soh_tlv, soh_tlv_len,
&failure);
if (failure) {
eap_peap_state(data, FAILURE);
return;
}
} else {
wpa_printf(MSG_DEBUG, "EAP-PEAP: No SoH TLV received");
eap_peap_state(data, FAILURE);
return;
}
auth_method:
eap_peap_state(data, PHASE2_METHOD);
next_type = sm->user->methods[0].method;
sm->user_eap_method_index = 1;
wpa_printf(MSG_DEBUG, "EAP-PEAP: try EAP vendor %d type %d",
sm->user->methods[0].vendor, next_type);
eap_peap_phase2_init(sm, data, sm->user->methods[0].vendor, next_type);
}
#endif /* EAP_SERVER_TNC */
static void eap_peap_process_phase2_response(struct eap_sm *sm,
struct eap_peap_data *data,
struct wpabuf *in_data)
{
int next_vendor = EAP_VENDOR_IETF;
u32 next_type = EAP_TYPE_NONE;
const struct eap_hdr *hdr;
const u8 *pos;
size_t left;
if (data->state == PHASE2_TLV) {
eap_peap_process_phase2_tlv(sm, data, in_data);
return;
}
#ifdef EAP_SERVER_TNC
if (data->state == PHASE2_SOH) {
eap_peap_process_phase2_soh(sm, data, in_data);
return;
}
#endif /* EAP_SERVER_TNC */
if (data->phase2_priv == NULL) {
wpa_printf(MSG_DEBUG, "EAP-PEAP: %s - Phase2 not "
"initialized?!", __func__);
return;
}
hdr = wpabuf_head(in_data);
pos = (const u8 *) (hdr + 1);
if (wpabuf_len(in_data) > sizeof(*hdr) && *pos == EAP_TYPE_NAK) {
left = wpabuf_len(in_data) - sizeof(*hdr);
wpa_hexdump(MSG_DEBUG, "EAP-PEAP: Phase2 type Nak'ed; "
"allowed types", pos + 1, left - 1);
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].vendor !=
EAP_VENDOR_IETF ||
sm->user->methods[sm->user_eap_method_index].method !=
EAP_TYPE_NONE)) {
next_vendor = sm->user->methods[
sm->user_eap_method_index].vendor;
next_type = sm->user->methods[
sm->user_eap_method_index++].method;
wpa_printf(MSG_DEBUG,
"EAP-PEAP: try EAP vendor %d type 0x%x",
next_vendor, next_type);
} else {
eap_peap_req_failure(sm, data);
next_vendor = EAP_VENDOR_IETF;
next_type = EAP_TYPE_NONE;
}
eap_peap_phase2_init(sm, data, next_vendor, next_type);
return;
}
if (data->phase2_method->check(sm, data->phase2_priv, in_data)) {
wpa_printf(MSG_DEBUG, "EAP-PEAP: Phase2 check() asked to "
"ignore the packet");
return;
}
data->phase2_method->process(sm, data->phase2_priv, in_data);
if (sm->method_pending == METHOD_PENDING_WAIT) {
wpa_printf(MSG_DEBUG, "EAP-PEAP: Phase2 method is in "
"pending wait state - save decrypted response");
wpabuf_free(data->pending_phase2_resp);
data->pending_phase2_resp = wpabuf_dup(in_data);
}
if (!data->phase2_method->isDone(sm, data->phase2_priv))
return;
if (!data->phase2_method->isSuccess(sm, data->phase2_priv)) {
wpa_printf(MSG_DEBUG, "EAP-PEAP: Phase2 method failed");
eap_peap_req_failure(sm, data);
next_vendor = EAP_VENDOR_IETF;
next_type = EAP_TYPE_NONE;
eap_peap_phase2_init(sm, data, next_vendor, next_type);
return;
}
os_free(data->phase2_key);
if (data->phase2_method->getKey) {
data->phase2_key = data->phase2_method->getKey(
sm, data->phase2_priv, &data->phase2_key_len);
if (data->phase2_key == NULL) {
wpa_printf(MSG_DEBUG, "EAP-PEAP: Phase2 getKey "
"failed");
eap_peap_req_failure(sm, data);
eap_peap_phase2_init(sm, data, EAP_VENDOR_IETF,
EAP_TYPE_NONE);
return;
}
}
switch (data->state) {
case PHASE1_ID2:
case PHASE2_ID:
case PHASE2_SOH:
if (eap_user_get(sm, sm->identity, sm->identity_len, 1) != 0) {
wpa_hexdump_ascii(MSG_DEBUG, "EAP_PEAP: Phase2 "
"Identity not found in the user "
"database",
sm->identity, sm->identity_len);
eap_peap_req_failure(sm, data);
next_vendor = EAP_VENDOR_IETF;
next_type = EAP_TYPE_NONE;
break;
}
#ifdef EAP_SERVER_TNC
if (data->state != PHASE2_SOH && sm->tnc &&
data->peap_version == 0) {
eap_peap_state(data, PHASE2_SOH);
wpa_printf(MSG_DEBUG, "EAP-PEAP: Try to initialize "
"TNC (NAP SOH)");
next_vendor = EAP_VENDOR_IETF;
next_type = EAP_TYPE_NONE;
break;
}
#endif /* EAP_SERVER_TNC */
eap_peap_state(data, PHASE2_METHOD);
next_vendor = sm->user->methods[0].vendor;
next_type = sm->user->methods[0].method;
sm->user_eap_method_index = 1;
wpa_printf(MSG_DEBUG, "EAP-PEAP: try EAP vendor %d type 0x%x",
next_vendor, next_type);
break;
case PHASE2_METHOD:
eap_peap_req_success(sm, data);
next_vendor = EAP_VENDOR_IETF;
next_type = EAP_TYPE_NONE;
break;
case FAILURE:
break;
default:
wpa_printf(MSG_DEBUG, "EAP-PEAP: %s - unexpected state %d",
__func__, data->state);
break;
}
eap_peap_phase2_init(sm, data, next_vendor, next_type);
}
static void eap_peap_process_phase2(struct eap_sm *sm,
struct eap_peap_data *data,
const struct wpabuf *respData,
struct wpabuf *in_buf)
{
struct wpabuf *in_decrypted;
const struct eap_hdr *hdr;
size_t len;
wpa_printf(MSG_DEBUG, "EAP-PEAP: received %lu bytes encrypted data for"
" Phase 2", (unsigned long) wpabuf_len(in_buf));
if (data->pending_phase2_resp) {
wpa_printf(MSG_DEBUG, "EAP-PEAP: Pending Phase 2 response - "
"skip decryption and use old data");
eap_peap_process_phase2_response(sm, data,
data->pending_phase2_resp);
wpabuf_free(data->pending_phase2_resp);
data->pending_phase2_resp = NULL;
return;
}
in_decrypted = tls_connection_decrypt(sm->ssl_ctx, data->ssl.conn,
in_buf);
if (in_decrypted == NULL) {
wpa_printf(MSG_INFO, "EAP-PEAP: Failed to decrypt Phase 2 "
"data");
eap_peap_state(data, FAILURE);
return;
}
wpa_hexdump_buf_key(MSG_DEBUG, "EAP-PEAP: Decrypted Phase 2 EAP",
in_decrypted);
if (data->peap_version == 0 && data->state != PHASE2_TLV) {
const struct eap_hdr *resp;
struct eap_hdr *nhdr;
struct wpabuf *nbuf =
wpabuf_alloc(sizeof(struct eap_hdr) +
wpabuf_len(in_decrypted));
if (nbuf == NULL) {
wpabuf_free(in_decrypted);
return;
}
resp = wpabuf_head(respData);
nhdr = wpabuf_put(nbuf, sizeof(*nhdr));
nhdr->code = resp->code;
nhdr->identifier = resp->identifier;
nhdr->length = host_to_be16(sizeof(struct eap_hdr) +
wpabuf_len(in_decrypted));
wpabuf_put_buf(nbuf, in_decrypted);
wpabuf_free(in_decrypted);
in_decrypted = nbuf;
}
hdr = wpabuf_head(in_decrypted);
if (wpabuf_len(in_decrypted) < (int) sizeof(*hdr)) {
wpa_printf(MSG_INFO, "EAP-PEAP: Too short Phase 2 "
"EAP frame (len=%lu)",
(unsigned long) wpabuf_len(in_decrypted));
wpabuf_free(in_decrypted);
eap_peap_req_failure(sm, data);
return;
}
len = be_to_host16(hdr->length);
if (len > wpabuf_len(in_decrypted)) {
wpa_printf(MSG_INFO, "EAP-PEAP: Length mismatch in "
"Phase 2 EAP frame (len=%lu hdr->length=%lu)",
(unsigned long) wpabuf_len(in_decrypted),
(unsigned long) len);
wpabuf_free(in_decrypted);
eap_peap_req_failure(sm, data);
return;
}
wpa_printf(MSG_DEBUG, "EAP-PEAP: received Phase 2: code=%d "
"identifier=%d length=%lu", hdr->code, hdr->identifier,
(unsigned long) len);
switch (hdr->code) {
case EAP_CODE_RESPONSE:
eap_peap_process_phase2_response(sm, data, in_decrypted);
break;
case EAP_CODE_SUCCESS:
wpa_printf(MSG_DEBUG, "EAP-PEAP: Phase 2 Success");
if (data->state == SUCCESS_REQ) {
eap_peap_state(data, SUCCESS);
eap_peap_valid_session(sm, data);
}
break;
case EAP_CODE_FAILURE:
wpa_printf(MSG_DEBUG, "EAP-PEAP: Phase 2 Failure");
eap_peap_state(data, FAILURE);
break;
default:
wpa_printf(MSG_INFO, "EAP-PEAP: Unexpected code=%d in "
"Phase 2 EAP header", hdr->code);
break;
}
wpabuf_free(in_decrypted);
}
static int eap_peap_process_version(struct eap_sm *sm, void *priv,
int peer_version)
{
struct eap_peap_data *data = priv;
data->recv_version = peer_version;
if (data->force_version >= 0 && peer_version != data->force_version) {
wpa_printf(MSG_INFO, "EAP-PEAP: peer did not select the forced"
" version (forced=%d peer=%d) - reject",
data->force_version, peer_version);
return -1;
}
if (peer_version < data->peap_version) {
wpa_printf(MSG_DEBUG, "EAP-PEAP: peer ver=%d, own ver=%d; "
"use version %d",
peer_version, data->peap_version, peer_version);
data->peap_version = peer_version;
}
return 0;
}
static void eap_peap_process_msg(struct eap_sm *sm, void *priv,
const struct wpabuf *respData)
{
struct eap_peap_data *data = priv;
switch (data->state) {
case PHASE1:
if (eap_server_tls_phase1(sm, &data->ssl) < 0) {
eap_peap_state(data, FAILURE);
break;
}
break;
case PHASE2_START:
eap_peap_state(data, PHASE2_ID);
eap_peap_phase2_init(sm, data, EAP_VENDOR_IETF,
EAP_TYPE_IDENTITY);
break;
case PHASE1_ID2:
case PHASE2_ID:
case PHASE2_METHOD:
case PHASE2_SOH:
case PHASE2_TLV:
eap_peap_process_phase2(sm, data, respData, data->ssl.tls_in);
break;
case SUCCESS_REQ:
eap_peap_state(data, SUCCESS);
eap_peap_valid_session(sm, data);
break;
case FAILURE_REQ:
eap_peap_state(data, FAILURE);
break;
default:
wpa_printf(MSG_DEBUG, "EAP-PEAP: Unexpected state %d in %s",
data->state, __func__);
break;
}
}
static void eap_peap_process(struct eap_sm *sm, void *priv,
struct wpabuf *respData)
{
struct eap_peap_data *data = priv;
const struct wpabuf *buf;
const u8 *pos;
u8 id_len;
if (eap_server_tls_process(sm, &data->ssl, respData, data,
EAP_TYPE_PEAP, eap_peap_process_version,
eap_peap_process_msg) < 0) {
eap_peap_state(data, FAILURE);
return;
}
if (data->state == SUCCESS ||
!tls_connection_established(sm->ssl_ctx, data->ssl.conn) ||
!tls_connection_resumed(sm->ssl_ctx, data->ssl.conn))
return;
buf = tls_connection_get_success_data(data->ssl.conn);
if (!buf || wpabuf_len(buf) < 2) {
wpa_printf(MSG_DEBUG,
"EAP-PEAP: No success data in resumed session - reject attempt");
eap_peap_state(data, FAILURE);
return;
}
pos = wpabuf_head(buf);
if (*pos != EAP_TYPE_PEAP) {
wpa_printf(MSG_DEBUG,
"EAP-PEAP: Resumed session for another EAP type (%u) - reject attempt",
*pos);
eap_peap_state(data, FAILURE);
return;
}
pos++;
id_len = *pos++;
wpa_hexdump_ascii(MSG_DEBUG, "EAP-PEAP: Identity from cached session",
pos, id_len);
os_free(sm->identity);
sm->identity = os_malloc(id_len ? id_len : 1);
if (!sm->identity) {
sm->identity_len = 0;
eap_peap_state(data, FAILURE);
return;
}
os_memcpy(sm->identity, pos, id_len);
sm->identity_len = id_len;
if (eap_user_get(sm, sm->identity, sm->identity_len, 1) != 0) {
wpa_hexdump_ascii(MSG_DEBUG, "EAP-PEAP: Phase2 Identity not found in the user database",
sm->identity, sm->identity_len);
eap_peap_state(data, FAILURE);
return;
}
wpa_printf(MSG_DEBUG,
"EAP-PEAP: Resuming previous session - skip Phase2");
eap_peap_req_success(sm, data);
if (data->state == SUCCESS_REQ)
tls_connection_set_success_data_resumed(data->ssl.conn);
}
static Boolean eap_peap_isDone(struct eap_sm *sm, void *priv)
{
struct eap_peap_data *data = priv;
return data->state == SUCCESS || data->state == FAILURE;
}
static u8 * eap_peap_getKey(struct eap_sm *sm, void *priv, size_t *len)
{
struct eap_peap_data *data = priv;
u8 *eapKeyData;
if (data->state != SUCCESS)
return NULL;
if (data->crypto_binding_used) {
u8 csk[128];
/*
* Note: It looks like Microsoft implementation requires null
* termination for this label while the one used for deriving
* IPMK|CMK did not use null termination.
*/
if (peap_prfplus(data->peap_version, data->ipmk, 40,
"Session Key Generating Function",
(u8 *) "\00", 1, csk, sizeof(csk)) < 0)
return NULL;
wpa_hexdump_key(MSG_DEBUG, "EAP-PEAP: CSK", csk, sizeof(csk));
eapKeyData = os_malloc(EAP_TLS_KEY_LEN);
if (eapKeyData) {
os_memcpy(eapKeyData, csk, EAP_TLS_KEY_LEN);
*len = EAP_TLS_KEY_LEN;
wpa_hexdump(MSG_DEBUG, "EAP-PEAP: Derived key",
eapKeyData, EAP_TLS_KEY_LEN);
} else {
wpa_printf(MSG_DEBUG, "EAP-PEAP: Failed to derive "
"key");
}
return eapKeyData;
}
/* TODO: PEAPv1 - different label in some cases */
eapKeyData = eap_server_tls_derive_key(sm, &data->ssl,
"client EAP encryption",
EAP_TLS_KEY_LEN);
if (eapKeyData) {
*len = EAP_TLS_KEY_LEN;
wpa_hexdump(MSG_DEBUG, "EAP-PEAP: Derived key",
eapKeyData, EAP_TLS_KEY_LEN);
} else {
wpa_printf(MSG_DEBUG, "EAP-PEAP: Failed to derive key");
}
return eapKeyData;
}
static Boolean eap_peap_isSuccess(struct eap_sm *sm, void *priv)
{
struct eap_peap_data *data = priv;
return data->state == SUCCESS;
}
static u8 * eap_peap_get_session_id(struct eap_sm *sm, void *priv, size_t *len)
{
struct eap_peap_data *data = priv;
if (data->state != SUCCESS)
return NULL;
return eap_server_tls_derive_session_id(sm, &data->ssl, EAP_TYPE_PEAP,
len);
}
int eap_server_peap_register(void)
{
struct eap_method *eap;
eap = eap_server_method_alloc(EAP_SERVER_METHOD_INTERFACE_VERSION,
EAP_VENDOR_IETF, EAP_TYPE_PEAP, "PEAP");
if (eap == NULL)
return -1;
eap->init = eap_peap_init;
eap->reset = eap_peap_reset;
eap->buildReq = eap_peap_buildReq;
eap->check = eap_peap_check;
eap->process = eap_peap_process;
eap->isDone = eap_peap_isDone;
eap->getKey = eap_peap_getKey;
eap->isSuccess = eap_peap_isSuccess;
eap->getSessionId = eap_peap_get_session_id;
return eap_server_method_register(eap);
}