From 0ed57c5ea8cf1ec32698b1a876bb014ebfc1136f Mon Sep 17 00:00:00 2001 From: Jouni Malinen Date: Tue, 9 Jul 2019 16:56:02 +0300 Subject: [PATCH] EAP-TEAP server and peer implementation (RFC 7170) This adds support for a new EAP method: EAP-TEAP (Tunnel Extensible Authentication Protocol). This should be considered experimental since RFC 7170 has number of conflicting statements and missing details to allow unambiguous interpretation. As such, there may be interoperability issues with other implementations and this version should not be deployed for production purposes until those unclear areas are resolved. This does not yet support use of NewSessionTicket message to deliver a new PAC (either in the server or peer implementation). In other words, only the in-tunnel distribution of PAC-Opaque is supported for now. Use of the NewSessionTicket mechanism would require TLS library support to allow arbitrary data to be specified as the contents of the message. Signed-off-by: Jouni Malinen --- hostapd/Android.mk | 10 + hostapd/Makefile | 10 + hostapd/config_file.c | 14 + hostapd/defconfig | 10 + hostapd/eap_register.c | 5 + hostapd/hostapd.conf | 10 + src/ap/ap_config.h | 2 + src/ap/authsrv.c | 2 + src/ap/ieee802_1x.c | 2 + src/crypto/tls_openssl.c | 25 +- src/eap_common/eap_defs.h | 1 + src/eap_common/eap_teap_common.c | 698 ++++++++ src/eap_common/eap_teap_common.h | 218 +++ src/eap_peer/eap.c | 2 +- src/eap_peer/eap_config.h | 2 + src/eap_peer/eap_methods.h | 1 + src/eap_peer/eap_teap.c | 2021 ++++++++++++++++++++++++ src/eap_peer/eap_teap_pac.c | 931 +++++++++++ src/eap_peer/eap_teap_pac.h | 50 + src/eap_peer/eap_tls_common.c | 11 +- src/eap_peer/eap_tls_common.h | 4 +- src/eap_server/eap.h | 2 + src/eap_server/eap_i.h | 2 + src/eap_server/eap_methods.h | 1 + src/eap_server/eap_server.c | 2 + src/eap_server/eap_server_teap.c | 1947 +++++++++++++++++++++++ src/eap_server/eap_server_tls_common.c | 2 + src/eap_server/eap_tls_common.h | 1 + src/eapol_auth/eapol_auth_sm.c | 6 +- src/eapol_auth/eapol_auth_sm.h | 2 + src/radius/radius_server.c | 7 + src/radius/radius_server.h | 3 + wpa_supplicant/Android.mk | 17 + wpa_supplicant/Makefile | 20 +- wpa_supplicant/defconfig | 10 + wpa_supplicant/eap_register.c | 10 + 36 files changed, 6047 insertions(+), 14 deletions(-) create mode 100644 src/eap_common/eap_teap_common.c create mode 100644 src/eap_common/eap_teap_common.h create mode 100644 src/eap_peer/eap_teap.c create mode 100644 src/eap_peer/eap_teap_pac.c create mode 100644 src/eap_peer/eap_teap_pac.h create mode 100644 src/eap_server/eap_server_teap.c diff --git a/hostapd/Android.mk b/hostapd/Android.mk index e082cab82..79b8a4840 100644 --- a/hostapd/Android.mk +++ b/hostapd/Android.mk @@ -487,6 +487,16 @@ NEED_T_PRF=y NEED_AES_UNWRAP=y endif +ifdef CONFIG_EAP_TEAP +L_CFLAGS += -DEAP_SERVER_TEAP +OBJS += src/eap_server/eap_server_teap.c +OBJS += src/eap_common/eap_teap_common.c +TLS_FUNCS=y +NEED_T_PRF=y +NEED_SHA384=y +NEED_AES_UNWRAP=y +endif + ifdef CONFIG_WPS L_CFLAGS += -DCONFIG_WPS -DEAP_SERVER_WSC OBJS += src/utils/uuid.c diff --git a/hostapd/Makefile b/hostapd/Makefile index a8d77fed3..2a6bd7ac8 100644 --- a/hostapd/Makefile +++ b/hostapd/Makefile @@ -526,6 +526,16 @@ NEED_T_PRF=y NEED_AES_UNWRAP=y endif +ifdef CONFIG_EAP_TEAP +CFLAGS += -DEAP_SERVER_TEAP +OBJS += ../src/eap_server/eap_server_teap.o +OBJS += ../src/eap_common/eap_teap_common.o +TLS_FUNCS=y +NEED_T_PRF=y +NEED_SHA384=y +NEED_AES_UNWRAP=y +endif + ifdef CONFIG_WPS CFLAGS += -DCONFIG_WPS -DEAP_SERVER_WSC OBJS += ../src/utils/uuid.o diff --git a/hostapd/config_file.c b/hostapd/config_file.c index c4106c128..3a294386f 100644 --- a/hostapd/config_file.c +++ b/hostapd/config_file.c @@ -2675,6 +2675,20 @@ static int hostapd_config_fill(struct hostapd_config *conf, } else if (os_strcmp(buf, "pac_key_refresh_time") == 0) { bss->pac_key_refresh_time = atoi(pos); #endif /* EAP_SERVER_FAST */ +#ifdef EAP_SERVER_TEAP + } else if (os_strcmp(buf, "eap_teap_auth") == 0) { + int val = atoi(pos); + + if (val < 0 || val > 1) { + wpa_printf(MSG_ERROR, + "Line %d: Invalid eap_teap_auth value", + line); + return 1; + } + bss->eap_teap_auth = val; + } else if (os_strcmp(buf, "eap_teap_pac_no_inner") == 0) { + bss->eap_teap_pac_no_inner = atoi(pos); +#endif /* EAP_SERVER_TEAP */ #ifdef EAP_SERVER_SIM } else if (os_strcmp(buf, "eap_sim_db") == 0) { os_free(bss->eap_sim_db); diff --git a/hostapd/defconfig b/hostapd/defconfig index 891ed6816..01871c951 100644 --- a/hostapd/defconfig +++ b/hostapd/defconfig @@ -110,6 +110,16 @@ CONFIG_EAP_TTLS=y # EAP-FAST for the integrated EAP server #CONFIG_EAP_FAST=y +# EAP-TEAP for the integrated EAP server +# Note: The current EAP-TEAP implementation is experimental and should not be +# enabled for production use. The IETF RFC 7170 that defines EAP-TEAP has number +# of conflicting statements and missing details and the implementation has +# vendor specific workarounds for those and as such, may not interoperate with +# any other implementation. This should not be used for anything else than +# experimentation and interoperability testing until those issues has been +# resolved. +#CONFIG_EAP_TEAP=y + # Wi-Fi Protected Setup (WPS) #CONFIG_WPS=y # Enable UPnP support for external WPS Registrars diff --git a/hostapd/eap_register.c b/hostapd/eap_register.c index 8477c2154..3e870c7f0 100644 --- a/hostapd/eap_register.c +++ b/hostapd/eap_register.c @@ -121,6 +121,11 @@ int eap_server_register_methods(void) ret = eap_server_fast_register(); #endif /* EAP_SERVER_FAST */ +#ifdef EAP_SERVER_TEAP + if (ret == 0) + ret = eap_server_teap_register(); +#endif /* EAP_SERVER_TEAP */ + #ifdef EAP_SERVER_WSC if (ret == 0) ret = eap_server_wsc_register(); diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf index 71e577a89..d67a40573 100644 --- a/hostapd/hostapd.conf +++ b/hostapd/hostapd.conf @@ -1174,6 +1174,16 @@ eap_server=0 # (or fewer) of the lifetime remains. #pac_key_refresh_time=86400 +# EAP-TEAP authentication type +# 0 = inner EAP (default) +# 1 = Basic-Password-Auth +#eap_teap_auth=0 + +# EAP-TEAP authentication behavior when using PAC +# 0 = perform inner authentication (default) +# 1 = skip inner authentication (inner EAP/Basic-Password-Auth) +#eap_teap_pac_no_inner=0 + # EAP-SIM and EAP-AKA protected success/failure indication using AT_RESULT_IND # (default: 0 = disabled). #eap_sim_aka_result_ind=1 diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h index fc6524c9e..b1007622c 100644 --- a/src/ap/ap_config.h +++ b/src/ap/ap_config.h @@ -423,6 +423,8 @@ struct hostapd_bss_config { int eap_fast_prov; int pac_key_lifetime; int pac_key_refresh_time; + int eap_teap_auth; + int eap_teap_pac_no_inner; int eap_sim_aka_result_ind; int tnc; int fragment_size; diff --git a/src/ap/authsrv.c b/src/ap/authsrv.c index eced6c7c6..3dd6b7bee 100644 --- a/src/ap/authsrv.c +++ b/src/ap/authsrv.c @@ -120,6 +120,8 @@ static int hostapd_setup_radius_srv(struct hostapd_data *hapd) srv.eap_fast_prov = conf->eap_fast_prov; srv.pac_key_lifetime = conf->pac_key_lifetime; srv.pac_key_refresh_time = conf->pac_key_refresh_time; + srv.eap_teap_auth = conf->eap_teap_auth; + srv.eap_teap_pac_no_inner = conf->eap_teap_pac_no_inner; srv.eap_sim_aka_result_ind = conf->eap_sim_aka_result_ind; srv.tnc = conf->tnc; srv.wps = hapd->wps; diff --git a/src/ap/ieee802_1x.c b/src/ap/ieee802_1x.c index fe0d3d9c9..d62864136 100644 --- a/src/ap/ieee802_1x.c +++ b/src/ap/ieee802_1x.c @@ -2371,6 +2371,8 @@ int ieee802_1x_init(struct hostapd_data *hapd) conf.eap_fast_prov = hapd->conf->eap_fast_prov; conf.pac_key_lifetime = hapd->conf->pac_key_lifetime; conf.pac_key_refresh_time = hapd->conf->pac_key_refresh_time; + conf.eap_teap_auth = hapd->conf->eap_teap_auth; + conf.eap_teap_pac_no_inner = hapd->conf->eap_teap_pac_no_inner; conf.eap_sim_aka_result_ind = hapd->conf->eap_sim_aka_result_ind; conf.tnc = hapd->conf->tnc; conf.wps = hapd->wps; diff --git a/src/crypto/tls_openssl.c b/src/crypto/tls_openssl.c index ff6f72bab..48193a543 100644 --- a/src/crypto/tls_openssl.c +++ b/src/crypto/tls_openssl.c @@ -44,6 +44,13 @@ #define OPENSSL_NEED_EAP_FAST_PRF #endif +#if defined(EAP_FAST) || defined(EAP_FAST_DYNAMIC) || \ + defined(EAP_SERVER_FAST) || defined(EAP_TEAP) || \ + defined(EAP_SERVER_TEAP) +#define EAP_FAST_OR_TEAP +#endif + + #if defined(OPENSSL_IS_BORINGSSL) /* stack_index_t is the return type of OpenSSL's sk_XXX_num() functions. */ typedef size_t stack_index_t; @@ -4476,7 +4483,7 @@ int tls_connection_set_cipher_list(void *tls_ctx, struct tls_connection *conn, wpa_printf(MSG_DEBUG, "OpenSSL: cipher suites: %s", buf + 1); #if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) -#if defined(EAP_FAST) || defined(EAP_FAST_DYNAMIC) || defined(EAP_SERVER_FAST) +#ifdef EAP_FAST_OR_TEAP if (os_strstr(buf, ":ADH-")) { /* * Need to drop to security level 0 to allow anonymous @@ -4487,7 +4494,7 @@ int tls_connection_set_cipher_list(void *tls_ctx, struct tls_connection *conn, /* Force at least security level 1 */ SSL_set_security_level(conn->ssl, 1); } -#endif /* EAP_FAST || EAP_FAST_DYNAMIC || EAP_SERVER_FAST */ +#endif /* EAP_FAST_OR_TEAP */ #endif if (SSL_set_cipher_list(conn->ssl, buf + 1) != 1) { @@ -4541,7 +4548,7 @@ int tls_connection_enable_workaround(void *ssl_ctx, } -#if defined(EAP_FAST) || defined(EAP_FAST_DYNAMIC) || defined(EAP_SERVER_FAST) +#ifdef EAP_FAST_OR_TEAP /* ClientHello TLS extensions require a patch to openssl, so this function is * commented out unless explicitly needed for EAP-FAST in order to be able to * build this file with unmodified openssl. */ @@ -4558,7 +4565,7 @@ int tls_connection_client_hello_ext(void *ssl_ctx, struct tls_connection *conn, return 0; } -#endif /* EAP_FAST || EAP_FAST_DYNAMIC || EAP_SERVER_FAST */ +#endif /* EAP_FAST_OR_TEAP */ int tls_connection_get_failed(void *ssl_ctx, struct tls_connection *conn) @@ -5176,7 +5183,7 @@ int tls_global_set_params(void *tls_ctx, } -#if defined(EAP_FAST) || defined(EAP_FAST_DYNAMIC) || defined(EAP_SERVER_FAST) +#ifdef EAP_FAST_OR_TEAP /* Pre-shared secred requires a patch to openssl, so this function is * commented out unless explicitly needed for EAP-FAST in order to be able to * build this file with unmodified openssl. */ @@ -5257,7 +5264,7 @@ static int tls_session_ticket_ext_cb(SSL *s, const unsigned char *data, return 1; } -#endif /* EAP_FAST || EAP_FAST_DYNAMIC || EAP_SERVER_FAST */ +#endif /* EAP_FAST_OR_TEAP */ int tls_connection_set_session_ticket_cb(void *tls_ctx, @@ -5265,7 +5272,7 @@ int tls_connection_set_session_ticket_cb(void *tls_ctx, tls_session_ticket_cb cb, void *ctx) { -#if defined(EAP_FAST) || defined(EAP_FAST_DYNAMIC) || defined(EAP_SERVER_FAST) +#ifdef EAP_FAST_OR_TEAP conn->session_ticket_cb = cb; conn->session_ticket_cb_ctx = ctx; @@ -5282,9 +5289,9 @@ int tls_connection_set_session_ticket_cb(void *tls_ctx, } return 0; -#else /* EAP_FAST || EAP_FAST_DYNAMIC || EAP_SERVER_FAST */ +#else /* EAP_FAST_OR_TEAP */ return -1; -#endif /* EAP_FAST || EAP_FAST_DYNAMIC || EAP_SERVER_FAST */ +#endif /* EAP_FAST_OR_TEAP */ } diff --git a/src/eap_common/eap_defs.h b/src/eap_common/eap_defs.h index 54f26ca38..bc3047c79 100644 --- a/src/eap_common/eap_defs.h +++ b/src/eap_common/eap_defs.h @@ -92,6 +92,7 @@ typedef enum { EAP_TYPE_GPSK = 51 /* RFC 5433 */, EAP_TYPE_PWD = 52 /* RFC 5931 */, EAP_TYPE_EKE = 53 /* RFC 6124 */, + EAP_TYPE_TEAP = 55 /* RFC 7170 */, EAP_TYPE_EXPANDED = 254 /* RFC 3748 */ } EapType; diff --git a/src/eap_common/eap_teap_common.c b/src/eap_common/eap_teap_common.c new file mode 100644 index 000000000..fbca1b5e4 --- /dev/null +++ b/src/eap_common/eap_teap_common.c @@ -0,0 +1,698 @@ +/* + * EAP-TEAP common helper functions (RFC 7170) + * Copyright (c) 2008-2019, Jouni Malinen + * + * 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/sha256.h" +#include "crypto/sha384.h" +#include "crypto/tls.h" +#include "eap_defs.h" +#include "eap_teap_common.h" + + +void eap_teap_put_tlv_hdr(struct wpabuf *buf, u16 type, u16 len) +{ + struct teap_tlv_hdr hdr; + + hdr.tlv_type = host_to_be16(type); + hdr.length = host_to_be16(len); + wpabuf_put_data(buf, &hdr, sizeof(hdr)); +} + + +void eap_teap_put_tlv(struct wpabuf *buf, u16 type, const void *data, u16 len) +{ + eap_teap_put_tlv_hdr(buf, type, len); + wpabuf_put_data(buf, data, len); +} + + +void eap_teap_put_tlv_buf(struct wpabuf *buf, u16 type, + const struct wpabuf *data) +{ + eap_teap_put_tlv_hdr(buf, type, wpabuf_len(data)); + wpabuf_put_buf(buf, data); +} + + +struct wpabuf * eap_teap_tlv_eap_payload(struct wpabuf *buf) +{ + struct wpabuf *e; + + if (!buf) + return NULL; + + /* Encapsulate EAP packet in EAP-Payload TLV */ + wpa_printf(MSG_DEBUG, "EAP-TEAP: Add EAP-Payload TLV"); + e = wpabuf_alloc(sizeof(struct teap_tlv_hdr) + wpabuf_len(buf)); + if (!e) { + wpa_printf(MSG_ERROR, + "EAP-TEAP: Failed to allocate memory for TLV encapsulation"); + wpabuf_free(buf); + return NULL; + } + eap_teap_put_tlv_buf(e, TEAP_TLV_MANDATORY | TEAP_TLV_EAP_PAYLOAD, buf); + wpabuf_free(buf); + + /* TODO: followed by optional TLVs associated with the EAP packet */ + + return e; +} + + +static int eap_teap_tls_prf(const u8 *secret, size_t secret_len, + const char *label, const u8 *seed, size_t seed_len, + u8 *out, size_t outlen) +{ + /* TODO: TLS-PRF for TLSv1.3 */ + return tls_prf_sha256(secret, secret_len, label, seed, seed_len, + out, outlen); +} + + +int eap_teap_derive_eap_msk(const u8 *simck, u8 *msk) +{ + /* + * RFC 7170, Section 5.4: EAP Master Session Key Generation + * MSK = TLS-PRF(S-IMCK[j], "Session Key Generating Function", 64) + */ + + if (eap_teap_tls_prf(simck, EAP_TEAP_SIMCK_LEN, + "Session Key Generating Function", (u8 *) "", 0, + msk, EAP_TEAP_KEY_LEN) < 0) + return -1; + wpa_hexdump_key(MSG_DEBUG, "EAP-TEAP: Derived key (MSK)", + msk, EAP_TEAP_KEY_LEN); + return 0; +} + + +int eap_teap_derive_eap_emsk(const u8 *simck, u8 *emsk) +{ + /* + * RFC 7170, Section 5.4: EAP Master Session Key Generation + * EMSK = TLS-PRF(S-IMCK[j], + * "Extended Session Key Generating Function", 64) + */ + + if (eap_teap_tls_prf(simck, EAP_TEAP_SIMCK_LEN, + "Extended Session Key Generating Function", + (u8 *) "", 0, emsk, EAP_EMSK_LEN) < 0) + return -1; + wpa_hexdump_key(MSG_DEBUG, "EAP-TEAP: Derived key (EMSK)", + emsk, EAP_EMSK_LEN); + return 0; +} + + +int eap_teap_derive_cmk_basic_pw_auth(const u8 *s_imck_msk, u8 *cmk) +{ + u8 imsk[32], imck[EAP_TEAP_IMCK_LEN]; + int res; + + /* FIX: The Basic-Password-Auth (i.e., no inner EAP) case is + * not fully defined in RFC 7170, so this CMK derivation may + * need to be changed if a fixed definition is eventually + * published. For now, derive CMK[0] based on S-IMCK[0] and + * IMSK of 32 octets of zeros. */ + os_memset(imsk, 0, 32); + res = eap_teap_tls_prf(s_imck_msk, EAP_TEAP_SIMCK_LEN, + "Inner Methods Compound Keys", + imsk, 32, imck, sizeof(imck)); + if (res < 0) + return -1; + os_memcpy(cmk, &imck[EAP_TEAP_SIMCK_LEN], EAP_TEAP_CMK_LEN); + wpa_hexdump_key(MSG_DEBUG, "EAP-TEAP: CMK[no-inner-EAP]", + cmk, EAP_TEAP_CMK_LEN); + forced_memzero(imck, sizeof(imck)); + return 0; +} + + +int eap_teap_derive_imck(const u8 *prev_s_imck_msk, const u8 *prev_s_imck_emsk, + const u8 *msk, size_t msk_len, + const u8 *emsk, size_t emsk_len, + u8 *s_imck_msk, u8 *cmk_msk, + u8 *s_imck_emsk, u8 *cmk_emsk) +{ + u8 imsk[64], imck[EAP_TEAP_IMCK_LEN]; + int res; + + /* + * RFC 7170, Section 5.2: + * IMSK = First 32 octets of TLS-PRF(EMSK, "TEAPbindkey@ietf.org" | + * "\0" | 64) + * (if EMSK is not available, MSK is used instead; if neither is + * available, IMSK is 32 octets of zeros; MSK is truncated to 32 octets + * or padded to 32 octets, if needed) + * (64 is encoded as a 2-octet field in network byte order) + * + * S-IMCK[0] = session_key_seed + * IMCK[j] = TLS-PRF(S-IMCK[j-1], "Inner Methods Compound Keys", + * IMSK[j], 60) + * S-IMCK[j] = first 40 octets of IMCK[j] + * CMK[j] = last 20 octets of IMCK[j] + */ + + wpa_hexdump_key(MSG_DEBUG, "EAP-TEAP: MSK[j]", msk, msk_len); + wpa_hexdump_key(MSG_DEBUG, "EAP-TEAP: EMSK[j]", emsk, emsk_len); + + if (emsk && emsk_len > 0) { + u8 context[3]; + + context[0] = 0; + context[1] = 0; + context[2] = 64; + if (eap_teap_tls_prf(emsk, emsk_len, "TEAPbindkey@ietf.org", + context, sizeof(context), imsk, 64) < 0) + return -1; + + wpa_hexdump_key(MSG_DEBUG, "EAP-TEAP: IMSK from EMSK", + imsk, 32); + + res = eap_teap_tls_prf(prev_s_imck_emsk, EAP_TEAP_SIMCK_LEN, + "Inner Methods Compound Keys", + imsk, 32, imck, EAP_TEAP_IMCK_LEN); + forced_memzero(imsk, sizeof(imsk)); + if (res < 0) + return -1; + + os_memcpy(s_imck_emsk, imck, EAP_TEAP_SIMCK_LEN); + wpa_hexdump_key(MSG_DEBUG, "EAP-TEAP: EMSK S-IMCK[j]", + s_imck_emsk, EAP_TEAP_SIMCK_LEN); + os_memcpy(cmk_emsk, &imck[EAP_TEAP_SIMCK_LEN], + EAP_TEAP_CMK_LEN); + wpa_hexdump_key(MSG_DEBUG, "EAP-TEAP: EMSK CMK[j]", + cmk_emsk, EAP_TEAP_CMK_LEN); + forced_memzero(imck, EAP_TEAP_IMCK_LEN); + } + + if (msk && msk_len > 0) { + size_t copy_len = msk_len; + + os_memset(imsk, 0, 32); /* zero pad, if needed */ + if (copy_len > 32) + copy_len = 32; + os_memcpy(imsk, msk, copy_len); + wpa_hexdump_key(MSG_DEBUG, "EAP-TEAP: IMSK from MSK", imsk, 32); + } else { + os_memset(imsk, 0, 32); + wpa_hexdump_key(MSG_DEBUG, "EAP-TEAP: Zero IMSK", imsk, 32); + } + + res = eap_teap_tls_prf(prev_s_imck_msk, EAP_TEAP_SIMCK_LEN, + "Inner Methods Compound Keys", + imsk, 32, imck, EAP_TEAP_IMCK_LEN); + forced_memzero(imsk, sizeof(imsk)); + if (res < 0) + return -1; + + os_memcpy(s_imck_msk, imck, EAP_TEAP_SIMCK_LEN); + wpa_hexdump_key(MSG_DEBUG, "EAP-TEAP: MSK S-IMCK[j]", + s_imck_msk, EAP_TEAP_SIMCK_LEN); + os_memcpy(cmk_msk, &imck[EAP_TEAP_SIMCK_LEN], EAP_TEAP_CMK_LEN); + wpa_hexdump_key(MSG_DEBUG, "EAP-TEAP: MSK CMK[j]", + cmk_msk, EAP_TEAP_CMK_LEN); + forced_memzero(imck, EAP_TEAP_IMCK_LEN); + + return 0; +} + + +static int tls_cipher_suite_match(const u16 *list, size_t count, u16 cs) +{ + size_t i; + + for (i = 0; i < count; i++) { + if (list[i] == cs) + return 1; + } + + return 0; +} + + +static int tls_cipher_suite_mac_sha1(u16 cs) +{ + static const u16 sha1_cs[] = { + 0x0005, 0x0007, 0x000a, 0x000d, 0x0010, 0x0013, 0x0016, 0x001b, + 0x002f, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, + 0x0037, 0x0038, 0x0039, 0x003a, 0x0041, 0x0042, 0x0043, 0x0044, + 0x0045, 0x0046, 0x0084, 0x0085, 0x0086, 0x0087, 0x0088, 0x0089, + 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, 0x0090, 0x0091, + 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, 0x0098, 0x0099, + 0x009a, 0x009b, + 0xc002, 0xc003, 0xc004, 0xc005, 0xc007, 0xc008, 0xc009, 0xc009, + 0xc00a, 0xc00c, 0xc00d, 0xc00e, 0xc00f, 0xc011, 0xc012, 0xc013, + 0xc014, 0xc016, 0xc017, 0xc018, 0xc019, 0xc01a, 0xc01b, 0xc01c, + 0xc014, 0xc01e, 0xc01f, 0xc020, 0xc021, 0xc022, 0xc033, 0xc034, + 0xc035, 0xc036 + }; + + return tls_cipher_suite_match(sha1_cs, ARRAY_SIZE(sha1_cs), cs); +} + + +static int tls_cipher_suite_mac_sha256(u16 cs) +{ + static const u16 sha256_cs[] = { + 0x003c, 0x003d, 0x003e, 0x003f, 0x0040, 0x0067, 0x0068, 0x0069, + 0x006a, 0x006b, 0x006c, 0x006d, 0x009c, 0x009e, 0x00a0, 0x00a2, + 0x00a4, 0x00a6, 0x00a8, 0x00aa, 0x00ac, 0x00ae, 0x00b2, 0x00b6, + 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bd, 0x00be, 0x00be, + 0x00bf, 0x00bf, 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, + 0x1301, 0x1303, 0x1304, 0x1305, + 0xc023, 0xc025, 0xc027, 0xc029, 0xc02b, 0xc02d, 0xc02f, 0xc031, + 0xc037, 0xc03c, 0xc03e, 0xc040, 0xc040, 0xc042, 0xc044, 0xc046, + 0xc048, 0xc04a, 0xc04c, 0xc04e, 0xc050, 0xc052, 0xc054, 0xc056, + 0xc058, 0xc05a, 0xc05c, 0xc05e, 0xc060, 0xc062, 0xc064, 0xc066, + 0xc068, 0xc06a, 0xc06c, 0xc06e, 0xc070, 0xc072, 0xc074, 0xc076, + 0xc078, 0xc07a, 0xc07c, 0xc07e, 0xc080, 0xc082, 0xc084, 0xc086, + 0xc088, 0xc08a, 0xc08c, 0xc08e, 0xc090, 0xc092, 0xc094, 0xc096, + 0xc098, 0xc09a, 0xc0b0, 0xc0b2, 0xc0b4, + 0xcca8, 0xcca9, 0xccaa, 0xccab, 0xccac, 0xccad, 0xccae, + 0xd001, 0xd003, 0xd005 + }; + + return tls_cipher_suite_match(sha256_cs, ARRAY_SIZE(sha256_cs), cs); +} + + +static int tls_cipher_suite_mac_sha384(u16 cs) +{ + static const u16 sha384_cs[] = { + 0x009d, 0x009f, 0x00a1, 0x00a3, 0x00a5, 0x00a7, 0x00a9, 0x00ab, + 0x00ad, 0x00af, 0x00b3, 0x00b7, 0x1302, + 0xc024, 0xc026, 0xc028, 0xc02a, 0xc02c, 0xc02e, 0xc030, 0xc032, + 0xc038, 0xc03d, 0xc03f, 0xc041, 0xc043, 0xc045, 0xc047, 0xc049, + 0xc04b, 0xc04d, 0xc04f, 0xc051, 0xc053, 0xc055, 0xc057, 0xc059, + 0xc05b, 0xc05d, 0xc05f, 0xc061, 0xc063, 0xc065, 0xc067, 0xc069, + 0xc06b, 0xc06d, 0xc06f, 0xc071, 0xc073, 0xc075, 0xc077, 0xc079, + 0xc07b, 0xc07d, 0xc07f, 0xc081, 0xc083, 0xc085, 0xc087, 0xc089, + 0xc08b, 0xc08d, 0xc08f, 0xc091, 0xc093, 0xc095, 0xc097, 0xc099, + 0xc09b, 0xc0b1, 0xc0b3, 0xc0b5, + 0xd002 + }; + + return tls_cipher_suite_match(sha384_cs, ARRAY_SIZE(sha384_cs), cs); +} + + +static int eap_teap_tls_mac(u16 tls_cs, const u8 *cmk, size_t cmk_len, + const u8 *buffer, size_t buffer_len, + u8 *mac, size_t mac_len) +{ + int res; + u8 tmp[48]; + + os_memset(tmp, 0, sizeof(tmp)); + os_memset(mac, 0, mac_len); + + if (tls_cipher_suite_mac_sha1(tls_cs)) { + wpa_printf(MSG_DEBUG, "EAP-TEAP: MAC algorithm: HMAC-SHA1"); + res = hmac_sha1(cmk, cmk_len, buffer, buffer_len, tmp); + } else if (tls_cipher_suite_mac_sha256(tls_cs)) { + wpa_printf(MSG_DEBUG, "EAP-TEAP: MAC algorithm: HMAC-SHA256"); + res = hmac_sha256(cmk, cmk_len, buffer, buffer_len, tmp); + } else if (tls_cipher_suite_mac_sha384(tls_cs)) { + wpa_printf(MSG_DEBUG, "EAP-TEAP: MAC algorithm: HMAC-SHA384"); + res = hmac_sha384(cmk, cmk_len, buffer, buffer_len, tmp); + } else { + wpa_printf(MSG_INFO, + "EAP-TEAP: Unsupported TLS cipher suite 0x%04x", + tls_cs); + res = -1; + } + if (res < 0) + return res; + + /* FIX: RFC 7170 does not describe how to handle truncation of the + * Compound MAC or if the fields are supposed to be of variable length + * based on the negotiated TLS cipher suite (they are defined as having + * fixed size of 20 octets in the TLV description) */ + if (mac_len > sizeof(tmp)) + mac_len = sizeof(tmp); + os_memcpy(mac, tmp, mac_len); + return 0; +} + + +int eap_teap_compound_mac(u16 tls_cs, const struct teap_tlv_crypto_binding *cb, + const struct wpabuf *server_outer_tlvs, + const struct wpabuf *peer_outer_tlvs, + const u8 *cmk, u8 *compound_mac) +{ + u8 *pos, *buffer; + size_t bind_len, buffer_len; + struct teap_tlv_crypto_binding *tmp_cb; + int res; + + /* RFC 7170, Section 5.3 */ + bind_len = sizeof(struct teap_tlv_hdr) + be_to_host16(cb->length); + buffer_len = bind_len + 1; + if (server_outer_tlvs) + buffer_len += wpabuf_len(server_outer_tlvs); + if (peer_outer_tlvs) + buffer_len += wpabuf_len(peer_outer_tlvs); + buffer = os_malloc(buffer_len); + if (!buffer) + return -1; + + pos = buffer; + /* 1. The entire Crypto-Binding TLV attribute with both the EMSK and MSK + * Compound MAC fields zeroed out. */ + os_memcpy(pos, cb, bind_len); + pos += bind_len; + tmp_cb = (struct teap_tlv_crypto_binding *) buffer; + os_memset(tmp_cb->emsk_compound_mac, 0, EAP_TEAP_COMPOUND_MAC_LEN); + os_memset(tmp_cb->msk_compound_mac, 0, EAP_TEAP_COMPOUND_MAC_LEN); + + /* 2. The EAP Type sent by the other party in the first TEAP message. */ + /* This is supposed to be the EAP Type sent by the other party in the + * first TEAP message, but since we cannot get here without having + * successfully negotiated use of TEAP, this can only be the fixed EAP + * Type of TEAP. */ + *pos++ = EAP_TYPE_TEAP; + + /* 3. All the Outer TLVs from the first TEAP message sent by EAP server + * to peer. */ + if (server_outer_tlvs) { + os_memcpy(pos, wpabuf_head(server_outer_tlvs), + wpabuf_len(server_outer_tlvs)); + pos += wpabuf_len(server_outer_tlvs); + } + + /* 4. All the Outer TLVs from the first TEAP message sent by the peer to + * the EAP server. */ + if (peer_outer_tlvs) { + os_memcpy(pos, wpabuf_head(peer_outer_tlvs), + wpabuf_len(peer_outer_tlvs)); + pos += wpabuf_len(peer_outer_tlvs); + } + + buffer_len = pos - buffer; + + wpa_hexdump_key(MSG_MSGDUMP, + "EAP-TEAP: CMK for Compound MAC calculation", + cmk, EAP_TEAP_CMK_LEN); + wpa_hexdump(MSG_MSGDUMP, + "EAP-TEAP: BUFFER for Compound MAC calculation", + buffer, buffer_len); + res = eap_teap_tls_mac(tls_cs, cmk, EAP_TEAP_CMK_LEN, + buffer, buffer_len, + compound_mac, EAP_TEAP_COMPOUND_MAC_LEN); + os_free(buffer); + + return res; +} + + +int eap_teap_parse_tlv(struct eap_teap_tlv_parse *tlv, + int tlv_type, u8 *pos, size_t len) +{ + switch (tlv_type) { + case TEAP_TLV_RESULT: + wpa_hexdump(MSG_MSGDUMP, "EAP-TEAP: Result TLV", pos, len); + if (tlv->result) { + wpa_printf(MSG_INFO, + "EAP-TEAP: More than one Result TLV in the message"); + tlv->result = TEAP_STATUS_FAILURE; + return -2; + } + if (len < 2) { + wpa_printf(MSG_INFO, "EAP-TEAP: Too short Result TLV"); + tlv->result = TEAP_STATUS_FAILURE; + break; + } + tlv->result = WPA_GET_BE16(pos); + if (tlv->result != TEAP_STATUS_SUCCESS && + tlv->result != TEAP_STATUS_FAILURE) { + wpa_printf(MSG_INFO, "EAP-TEAP: Unknown Result %d", + tlv->result); + tlv->result = TEAP_STATUS_FAILURE; + } + wpa_printf(MSG_DEBUG, "EAP-TEAP: Result: %s", + tlv->result == TEAP_STATUS_SUCCESS ? + "Success" : "Failure"); + break; + case TEAP_TLV_NAK: + wpa_hexdump(MSG_MSGDUMP, "EAP-TEAP: NAK TLV", pos, len); + if (len < 6) { + wpa_printf(MSG_INFO, "EAP-TEAP: Too short NAK TLV"); + tlv->result = TEAP_STATUS_FAILURE; + break; + } + tlv->nak = pos; + tlv->nak_len = len; + break; + case TEAP_TLV_REQUEST_ACTION: + wpa_hexdump(MSG_MSGDUMP, "EAP-TEAP: Request-Action TLV", + pos, len); + if (tlv->request_action) { + wpa_printf(MSG_INFO, + "EAP-TEAP: More than one Request-Action TLV in the message"); + tlv->iresult = TEAP_STATUS_FAILURE; + return -2; + } + if (len < 2) { + wpa_printf(MSG_INFO, + "EAP-TEAP: Too short Request-Action TLV"); + tlv->iresult = TEAP_STATUS_FAILURE; + break; + } + tlv->request_action_status = pos[0]; + tlv->request_action = pos[1]; + wpa_printf(MSG_DEBUG, + "EAP-TEAP: Request-Action: Status=%u Action=%u", + tlv->request_action_status, tlv->request_action); + break; + case TEAP_TLV_EAP_PAYLOAD: + wpa_hexdump(MSG_MSGDUMP, "EAP-TEAP: EAP-Payload TLV", + pos, len); + if (tlv->eap_payload_tlv) { + wpa_printf(MSG_INFO, + "EAP-TEAP: More than one EAP-Payload TLV in the message"); + tlv->iresult = TEAP_STATUS_FAILURE; + return -2; + } + tlv->eap_payload_tlv = pos; + tlv->eap_payload_tlv_len = len; + break; + case TEAP_TLV_INTERMEDIATE_RESULT: + wpa_hexdump(MSG_MSGDUMP, "EAP-TEAP: Intermediate-Result TLV", + pos, len); + if (len < 2) { + wpa_printf(MSG_INFO, + "EAP-TEAP: Too short Intermediate-Result TLV"); + tlv->iresult = TEAP_STATUS_FAILURE; + break; + } + if (tlv->iresult) { + wpa_printf(MSG_INFO, + "EAP-TEAP: More than one Intermediate-Result TLV in the message"); + tlv->iresult = TEAP_STATUS_FAILURE; + return -2; + } + tlv->iresult = WPA_GET_BE16(pos); + if (tlv->iresult != TEAP_STATUS_SUCCESS && + tlv->iresult != TEAP_STATUS_FAILURE) { + wpa_printf(MSG_INFO, + "EAP-TEAP: Unknown Intermediate Result %d", + tlv->iresult); + tlv->iresult = TEAP_STATUS_FAILURE; + } + wpa_printf(MSG_DEBUG, "EAP-TEAP: Intermediate Result: %s", + tlv->iresult == TEAP_STATUS_SUCCESS ? + "Success" : "Failure"); + break; + case TEAP_TLV_PAC: + wpa_hexdump(MSG_MSGDUMP, "EAP-TEAP: PAC TLV", pos, len); + if (tlv->pac) { + wpa_printf(MSG_INFO, + "EAP-TEAP: More than one PAC TLV in the message"); + tlv->iresult = TEAP_STATUS_FAILURE; + return -2; + } + tlv->pac = pos; + tlv->pac_len = len; + break; + case TEAP_TLV_CRYPTO_BINDING: + wpa_hexdump(MSG_MSGDUMP, "EAP-TEAP: Crypto-Binding TLV", + pos, len); + if (tlv->crypto_binding) { + wpa_printf(MSG_INFO, + "EAP-TEAP: More than one Crypto-Binding TLV in the message"); + tlv->iresult = TEAP_STATUS_FAILURE; + return -2; + } + tlv->crypto_binding_len = sizeof(struct teap_tlv_hdr) + len; + if (tlv->crypto_binding_len < sizeof(*tlv->crypto_binding)) { + wpa_printf(MSG_INFO, + "EAP-TEAP: Too short Crypto-Binding TLV"); + tlv->iresult = TEAP_STATUS_FAILURE; + return -2; + } + tlv->crypto_binding = (struct teap_tlv_crypto_binding *) + (pos - sizeof(struct teap_tlv_hdr)); + break; + case TEAP_TLV_BASIC_PASSWORD_AUTH_REQ: + wpa_hexdump_ascii(MSG_MSGDUMP, + "EAP-TEAP: Basic-Password-Auth-Req TLV", + pos, len); + if (tlv->basic_auth_req) { + wpa_printf(MSG_INFO, + "EAP-TEAP: More than one Basic-Password-Auth-Req TLV in the message"); + tlv->iresult = TEAP_STATUS_FAILURE; + return -2; + } + tlv->basic_auth_req = pos; + tlv->basic_auth_req_len = len; + break; + case TEAP_TLV_BASIC_PASSWORD_AUTH_RESP: + wpa_hexdump_ascii(MSG_MSGDUMP, + "EAP-TEAP: Basic-Password-Auth-Resp TLV", + pos, len); + if (tlv->basic_auth_resp) { + wpa_printf(MSG_INFO, + "EAP-TEAP: More than one Basic-Password-Auth-Resp TLV in the message"); + tlv->iresult = TEAP_STATUS_FAILURE; + return -2; + } + tlv->basic_auth_resp = pos; + tlv->basic_auth_resp_len = len; + break; + default: + /* Unknown TLV */ + return -1; + } + + return 0; +} + + +const char * eap_teap_tlv_type_str(enum teap_tlv_types type) +{ + switch (type) { + case TEAP_TLV_AUTHORITY_ID: + return "Authority-ID"; + case TEAP_TLV_IDENTITY_TYPE: + return "Identity-Type"; + case TEAP_TLV_RESULT: + return "Result"; + case TEAP_TLV_NAK: + return "NAK"; + case TEAP_TLV_ERROR: + return "Error"; + case TEAP_TLV_CHANNEL_BINDING: + return "Channel-Binding"; + case TEAP_TLV_VENDOR_SPECIFIC: + return "Vendor-Specific"; + case TEAP_TLV_REQUEST_ACTION: + return "Request-Action"; + case TEAP_TLV_EAP_PAYLOAD: + return "EAP-Payload"; + case TEAP_TLV_INTERMEDIATE_RESULT: + return "Intermediate-Result"; + case TEAP_TLV_PAC: + return "PAC"; + case TEAP_TLV_CRYPTO_BINDING: + return "Crypto-Binding"; + case TEAP_TLV_BASIC_PASSWORD_AUTH_REQ: + return "Basic-Password-Auth-Req"; + case TEAP_TLV_BASIC_PASSWORD_AUTH_RESP: + return "Basic-Password-Auth-Resp"; + case TEAP_TLV_PKCS7: + return "PKCS#7"; + case TEAP_TLV_PKCS10: + return "PKCS#10"; + case TEAP_TLV_TRUSTED_SERVER_ROOT: + return "Trusted-Server-Root"; + } + + return "?"; +} + + +struct wpabuf * eap_teap_tlv_result(int status, int intermediate) +{ + struct wpabuf *buf; + struct teap_tlv_result *result; + + if (status != TEAP_STATUS_FAILURE && status != TEAP_STATUS_SUCCESS) + return NULL; + + buf = wpabuf_alloc(sizeof(*result)); + if (!buf) + return NULL; + wpa_printf(MSG_DEBUG, "EAP-TEAP: Add %sResult TLV(status=%s)", + intermediate ? "Intermediate-" : "", + status == TEAP_STATUS_SUCCESS ? "Success" : "Failure"); + result = wpabuf_put(buf, sizeof(*result)); + result->tlv_type = host_to_be16(TEAP_TLV_MANDATORY | + (intermediate ? + TEAP_TLV_INTERMEDIATE_RESULT : + TEAP_TLV_RESULT)); + result->length = host_to_be16(2); + result->status = host_to_be16(status); + return buf; +} + + +struct wpabuf * eap_teap_tlv_error(enum teap_error_codes error) +{ + struct wpabuf *buf; + + buf = wpabuf_alloc(4 + 4); + if (!buf) + return NULL; + wpa_printf(MSG_DEBUG, "EAP-TEAP: Add Error TLV(Error Code=%d)", + error); + wpabuf_put_be16(buf, TEAP_TLV_MANDATORY | TEAP_TLV_ERROR); + wpabuf_put_be16(buf, 4); + wpabuf_put_be32(buf, error); + return buf; +} + + +int eap_teap_allowed_anon_prov_phase2_method(u8 type) +{ + /* RFC 7170, Section 3.8.3: MUST provide mutual authentication, + * provide key generation, and be resistant to dictionary attack. + * Section 3.8 also mentions requirement for using EMSK Compound MAC. */ + return type == EAP_TYPE_PWD || type == EAP_TYPE_EKE; +} + + +int eap_teap_allowed_anon_prov_cipher_suite(u16 cs) +{ + /* RFC 7170, Section 3.8.3: anonymous ciphersuites MAY be supported as + * long as the TLS pre-master secret is generated form contribution from + * both peers. Accept the recommended TLS_DH_anon_WITH_AES_128_CBC_SHA + * cipher suite and other ciphersuites that use DH in some form, have + * SHA-1 or stronger MAC function, and use reasonable strong cipher. */ + static const u16 ok_cs[] = { + /* DH-anon */ + 0x0034, 0x003a, 0x006c, 0x006d, 0x00a6, 0x00a7, + /* DHE-RSA */ + 0x0033, 0x0039, 0x0067, 0x006b, 0x009e, 0x009f, + /* ECDH-anon */ + 0xc018, 0xc019, + /* ECDH-RSA */ + 0xc003, 0xc00f, 0xc029, 0xc02a, 0xc031, 0xc032, + /* ECDH-ECDSA */ + 0xc004, 0xc005, 0xc025, 0xc026, 0xc02d, 0xc02e, + /* ECDHE-RSA */ + 0xc013, 0xc014, 0xc027, 0xc028, 0xc02f, 0xc030, + /* ECDHE-ECDSA */ + 0xc009, 0xc00a, 0xc023, 0xc024, 0xc02b, 0xc02c, + }; + + return tls_cipher_suite_match(ok_cs, ARRAY_SIZE(ok_cs), cs); +} diff --git a/src/eap_common/eap_teap_common.h b/src/eap_common/eap_teap_common.h new file mode 100644 index 000000000..585ec7c2f --- /dev/null +++ b/src/eap_common/eap_teap_common.h @@ -0,0 +1,218 @@ +/* + * EAP-TEAP definitions (RFC 7170) + * Copyright (c) 2004-2019, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef EAP_TEAP_H +#define EAP_TEAP_H + +#define EAP_TEAP_VERSION 1 +#define EAP_TEAP_KEY_LEN 64 +#define EAP_TEAP_IMCK_LEN 60 +#define EAP_TEAP_SIMCK_LEN 40 +#define EAP_TEAP_CMK_LEN 20 +#define EAP_TEAP_COMPOUND_MAC_LEN 20 +#define EAP_TEAP_NONCE_LEN 32 + +#define TEAP_TLS_EXPORTER_LABEL_SKS "EXPORTER: teap session key seed" + +#define TLS_EXT_PAC_OPAQUE 35 + +/* + * RFC 7170: Section 4.2.12.1 - Formats for PAC Attributes + * Note: bit 0x8000 (Mandatory) and bit 0x4000 (Reserved) are also defined + * in the general TLV format (Section 4.2.1). + */ +#define PAC_TYPE_PAC_KEY 1 +#define PAC_TYPE_PAC_OPAQUE 2 +#define PAC_TYPE_CRED_LIFETIME 3 +#define PAC_TYPE_A_ID 4 +#define PAC_TYPE_I_ID 5 +/* 6 - Reserved */ +#define PAC_TYPE_A_ID_INFO 7 +#define PAC_TYPE_PAC_ACKNOWLEDGEMENT 8 +#define PAC_TYPE_PAC_INFO 9 +#define PAC_TYPE_PAC_TYPE 10 + +#ifdef _MSC_VER +#pragma pack(push, 1) +#endif /* _MSC_VER */ + +struct pac_attr_hdr { + be16 type; + be16 len; +} STRUCT_PACKED; + +struct teap_tlv_hdr { + be16 tlv_type; + be16 length; +} STRUCT_PACKED; + +/* Result TLV and Intermediate-Result TLV */ +struct teap_tlv_result { + be16 tlv_type; + be16 length; + be16 status; + /* for Intermediate-Result TLV, followed by optional TLVs */ +} STRUCT_PACKED; + +struct teap_tlv_nak { + be16 tlv_type; + be16 length; + be32 vendor_id; + be16 nak_type; + /* followed by optional TLVs */ +} STRUCT_PACKED; + +struct teap_tlv_crypto_binding { + be16 tlv_type; /* TLV Type[14b] and M/R flags */ + be16 length; + u8 reserved; + u8 version; + u8 received_version; + u8 subtype; /* Flags[4b] and Sub-Type[4b] */ + u8 nonce[EAP_TEAP_NONCE_LEN]; + u8 emsk_compound_mac[EAP_TEAP_COMPOUND_MAC_LEN]; + u8 msk_compound_mac[EAP_TEAP_COMPOUND_MAC_LEN]; +} STRUCT_PACKED; + +struct teap_tlv_request_action { + be16 tlv_type; + be16 length; + u8 status; + u8 action; + /* followed by optional TLVs */ +} STRUCT_PACKED; + +enum teap_request_action { + TEAP_REQUEST_ACTION_PROCESS_TLV = 1, + TEAP_REQUEST_ACTION_NEGOTIATE_EAP = 2, +}; + +/* PAC TLV with PAC-Acknowledgement TLV attribute */ +struct teap_tlv_pac_ack { + be16 tlv_type; + be16 length; + be16 pac_type; + be16 pac_len; + be16 result; +} STRUCT_PACKED; + +struct teap_attr_pac_type { + be16 type; /* PAC_TYPE_PAC_TYPE */ + be16 length; /* 2 */ + be16 pac_type; +} STRUCT_PACKED; + +#ifdef _MSC_VER +#pragma pack(pop) +#endif /* _MSC_VER */ + +#define TEAP_CRYPTO_BINDING_SUBTYPE_REQUEST 0 +#define TEAP_CRYPTO_BINDING_SUBTYPE_RESPONSE 1 + +#define TEAP_CRYPTO_BINDING_EMSK_CMAC 1 +#define TEAP_CRYPTO_BINDING_MSK_CMAC 2 +#define TEAP_CRYPTO_BINDING_EMSK_AND_MSK_CMAC 3 + + +#define EAP_TEAP_PAC_KEY_LEN 48 + +/* RFC 7170: 4.2.12.6 PAC-Type TLV */ +#define PAC_TYPE_TUNNEL_PAC 1 + + +/* RFC 7170, 4.2.1: General TLV Format */ +enum teap_tlv_types { + TEAP_TLV_AUTHORITY_ID = 1, + TEAP_TLV_IDENTITY_TYPE = 2, + TEAP_TLV_RESULT = 3, + TEAP_TLV_NAK = 4, + TEAP_TLV_ERROR = 5, + TEAP_TLV_CHANNEL_BINDING = 6, + TEAP_TLV_VENDOR_SPECIFIC = 7, + TEAP_TLV_REQUEST_ACTION = 8, + TEAP_TLV_EAP_PAYLOAD = 9, + TEAP_TLV_INTERMEDIATE_RESULT = 10, + TEAP_TLV_PAC = 11, + TEAP_TLV_CRYPTO_BINDING = 12, + TEAP_TLV_BASIC_PASSWORD_AUTH_REQ = 13, + TEAP_TLV_BASIC_PASSWORD_AUTH_RESP = 14, + TEAP_TLV_PKCS7 = 15, + TEAP_TLV_PKCS10 = 16, + TEAP_TLV_TRUSTED_SERVER_ROOT = 17, +}; + +enum teap_tlv_result_status { + TEAP_STATUS_SUCCESS = 1, + TEAP_STATUS_FAILURE = 2 +}; + +#define TEAP_TLV_MANDATORY 0x8000 +#define TEAP_TLV_TYPE_MASK 0x3fff + +/* RFC 7170, 4.2.6: Error TLV */ +enum teap_error_codes { + TEAP_ERROR_INNER_METHOD = 1001, + TEAP_ERROR_UNSPEC_AUTH_INFRA_PROBLEM = 1002, + TEAP_ERROR_UNSPEC_AUTHENTICATION_FAILURE = 1003, + TEAP_ERROR_UNSPEC_AUTHORIZATION_FAILURE = 1004, + TEAP_ERROR_USER_ACCOUNT_CRED_UNAVAILABLE = 1005, + TEAP_ERROR_USER_ACCOUNT_EXPIRED = 1006, + TEAP_ERROR_USER_ACCOUNT_LOCKED_TRY_AGAIN_LATER = 1007, + TEAP_ERROR_USER_ACCOUNT_LOCKED_ADMIN_REQ = 1008, + TEAP_ERROR_TUNNEL_COMPROMISE_ERROR = 2001, + TEAP_ERROR_UNEXPECTED_TLVS_EXCHANGED = 2002, +}; + +struct wpabuf; +struct tls_connection; + +struct eap_teap_tlv_parse { + u8 *eap_payload_tlv; + size_t eap_payload_tlv_len; + struct teap_tlv_crypto_binding *crypto_binding; + size_t crypto_binding_len; + int iresult; + int result; + u8 *nak; + size_t nak_len; + u8 request_action; + u8 request_action_status; + u8 *pac; + size_t pac_len; + u8 *basic_auth_req; + size_t basic_auth_req_len; + u8 *basic_auth_resp; + size_t basic_auth_resp_len; +}; + +void eap_teap_put_tlv_hdr(struct wpabuf *buf, u16 type, u16 len); +void eap_teap_put_tlv(struct wpabuf *buf, u16 type, const void *data, u16 len); +void eap_teap_put_tlv_buf(struct wpabuf *buf, u16 type, + const struct wpabuf *data); +struct wpabuf * eap_teap_tlv_eap_payload(struct wpabuf *buf); +int eap_teap_derive_eap_msk(const u8 *simck, u8 *msk); +int eap_teap_derive_eap_emsk(const u8 *simck, u8 *emsk); +int eap_teap_derive_cmk_basic_pw_auth(const u8 *s_imck_msk, u8 *cmk); +int eap_teap_derive_imck(const u8 *prev_s_imck_msk, const u8 *prev_s_imck_emsk, + const u8 *msk, size_t msk_len, + const u8 *emsk, size_t emsk_len, + u8 *s_imck_msk, u8 *cmk_msk, + u8 *s_imck_emsk, u8 *cmk_emsk); +int eap_teap_compound_mac(u16 tls_cs, const struct teap_tlv_crypto_binding *cb, + const struct wpabuf *server_outer_tlvs, + const struct wpabuf *peer_outer_tlvs, + const u8 *cmk, u8 *compound_mac); +int eap_teap_parse_tlv(struct eap_teap_tlv_parse *tlv, + int tlv_type, u8 *pos, size_t len); +const char * eap_teap_tlv_type_str(enum teap_tlv_types type); +struct wpabuf * eap_teap_tlv_result(int status, int intermediate); +struct wpabuf * eap_teap_tlv_error(enum teap_error_codes error); +int eap_teap_allowed_anon_prov_phase2_method(u8 type); +int eap_teap_allowed_anon_prov_cipher_suite(u16 cs); + +#endif /* EAP_TEAP_H */ diff --git a/src/eap_peer/eap.c b/src/eap_peer/eap.c index b35040a00..ac15e0e50 100644 --- a/src/eap_peer/eap.c +++ b/src/eap_peer/eap.c @@ -2603,7 +2603,7 @@ static int eap_allowed_phase2_type(int vendor, int type) if (vendor != EAP_VENDOR_IETF) return 0; return type != EAP_TYPE_PEAP && type != EAP_TYPE_TTLS && - type != EAP_TYPE_FAST; + type != EAP_TYPE_FAST && type != EAP_TYPE_TEAP; } diff --git a/src/eap_peer/eap_config.h b/src/eap_peer/eap_config.h index 3a88f2abd..148c9066d 100644 --- a/src/eap_peer/eap_config.h +++ b/src/eap_peer/eap_config.h @@ -816,6 +816,8 @@ struct eap_peer_config { EXT_CERT_CHECK_GOOD, EXT_CERT_CHECK_BAD, } pending_ext_cert_check; + + int teap_anon_dh; }; diff --git a/src/eap_peer/eap_methods.h b/src/eap_peer/eap_methods.h index b96b211de..09e08d3cf 100644 --- a/src/eap_peer/eap_methods.h +++ b/src/eap_peer/eap_methods.h @@ -97,6 +97,7 @@ int eap_peer_psk_register(void); int eap_peer_aka_register(void); int eap_peer_aka_prime_register(void); int eap_peer_fast_register(void); +int eap_peer_teap_register(void); int eap_peer_pax_register(void); int eap_peer_sake_register(void); int eap_peer_gpsk_register(void); diff --git a/src/eap_peer/eap_teap.c b/src/eap_peer/eap_teap.c new file mode 100644 index 000000000..eea7d6ef0 --- /dev/null +++ b/src/eap_peer/eap_teap.c @@ -0,0 +1,2021 @@ +/* + * EAP peer method: EAP-TEAP (RFC 7170) + * Copyright (c) 2004-2019, Jouni Malinen + * + * 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 result_success_done; + + 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->ca_cert && !config->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) { + 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->simck_msk, data->key_data) < 0 || + eap_teap_derive_eap_emsk(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->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, u8 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(type)) { + wpa_printf(MSG_INFO, + "EAP-TEAP: EAP type %u not allowed during unauthenticated provisioning", + type); + return -1; + } + +#ifdef EAP_TNC + if (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 != EAP_VENDOR_IETF || + 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 (type != data->phase2_type.method || type == EAP_TYPE_NONE) + return -1; + + return 0; +} + + +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; + + 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); + wpa_printf(MSG_DEBUG, "EAP-TEAP: Phase 2 Request: type=%d", *pos); + if (*pos == EAP_TYPE_IDENTITY) { + *resp = eap_sm_buildIdentity(sm, hdr->identifier, 1); + return 0; + } + + if (data->phase2_priv && data->phase2_method && + *pos != data->phase2_type.method) { + 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; + } + + if (data->phase2_type.vendor == EAP_VENDOR_IETF && + data->phase2_type.method == EAP_TYPE_NONE && + eap_teap_select_phase2_method(data, *pos) < 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 %d", + *pos); + 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)) { + ret->methodState = METHOD_DONE; + 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_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) +{ + 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; + } + + return eap_teap_tlv_eap_payload(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) +{ + 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); + + /* 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->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->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; + + 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 || + (!data->result_success_done && + tlv.result == TEAP_STATUS_SUCCESS)) && + !tlv.crypto_binding) { + /* Result TLV or Intermediate-Result TLV indicating success, + * but no Crypto-Binding TLV */ + wpa_printf(MSG_DEBUG, + "EAP-TEAP: Result TLV or Intermediate-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.basic_auth_req) { + tmp = eap_teap_process_basic_auth_req(sm, data, + tlv.basic_auth_req, + tlv.basic_auth_req_len); + 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); + 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; + } + } + + 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; + } + } + + 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->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 (resp && tlv.result == TEAP_STATUS_SUCCESS && !failed && + tlv.crypto_binding && data->phase2_success) { + /* Successfully completed Phase 2 */ + wpa_printf(MSG_DEBUG, + "EAP-TEAP: Authentication completed successfully"); + ret->methodState = 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 */ + return eap_peer_tls_encrypt(sm, &data->ssl, EAP_TYPE_TEAP, + data->teap_version, + identifier, NULL, out_data); + } + + 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->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); +} diff --git a/src/eap_peer/eap_teap_pac.c b/src/eap_peer/eap_teap_pac.c new file mode 100644 index 000000000..34a274356 --- /dev/null +++ b/src/eap_peer/eap_teap_pac.c @@ -0,0 +1,931 @@ +/* + * EAP peer method: EAP-TEAP PAC file processing + * Copyright (c) 2004-2019, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "includes.h" + +#include "common.h" +#include "eap_config.h" +#include "eap_i.h" +#include "eap_teap_pac.h" + +/* TODO: encrypt PAC-Key in the PAC file */ + + +/* Text data format */ +static const char *pac_file_hdr = + "wpa_supplicant EAP-TEAP PAC file - version 1"; + +/* + * Binary data format + * 4-octet magic value: 6A E4 92 1C + * 2-octet version (big endian) + * + * + * version=0: + * Sequence of PAC entries: + * 2-octet PAC-Type (big endian) + * 32-octet PAC-Key + * 2-octet PAC-Opaque length (big endian) + * PAC-Opaque data (length bytes) + * 2-octet PAC-Info length (big endian) + * PAC-Info data (length bytes) + */ + +#define EAP_TEAP_PAC_BINARY_MAGIC 0x6ae4921c +#define EAP_TEAP_PAC_BINARY_FORMAT_VERSION 0 + + +/** + * eap_teap_free_pac - Free PAC data + * @pac: Pointer to the PAC entry + * + * Note that the PAC entry must not be in a list since this function does not + * remove the list links. + */ +void eap_teap_free_pac(struct eap_teap_pac *pac) +{ + os_free(pac->pac_opaque); + os_free(pac->pac_info); + os_free(pac->a_id); + os_free(pac->i_id); + os_free(pac->a_id_info); + os_free(pac); +} + + +/** + * eap_teap_get_pac - Get a PAC entry based on A-ID + * @pac_root: Pointer to root of the PAC list + * @a_id: A-ID to search for + * @a_id_len: Length of A-ID + * @pac_type: PAC-Type to search for + * Returns: Pointer to the PAC entry, or %NULL if A-ID not found + */ +struct eap_teap_pac * eap_teap_get_pac(struct eap_teap_pac *pac_root, + const u8 *a_id, size_t a_id_len, + u16 pac_type) +{ + struct eap_teap_pac *pac = pac_root; + + while (pac) { + if (pac->pac_type == pac_type && pac->a_id_len == a_id_len && + os_memcmp(pac->a_id, a_id, a_id_len) == 0) { + return pac; + } + pac = pac->next; + } + return NULL; +} + + +static void eap_teap_remove_pac(struct eap_teap_pac **pac_root, + struct eap_teap_pac **pac_current, + const u8 *a_id, size_t a_id_len, u16 pac_type) +{ + struct eap_teap_pac *pac, *prev; + + pac = *pac_root; + prev = NULL; + + while (pac) { + if (pac->pac_type == pac_type && pac->a_id_len == a_id_len && + os_memcmp(pac->a_id, a_id, a_id_len) == 0) { + if (!prev) + *pac_root = pac->next; + else + prev->next = pac->next; + if (*pac_current == pac) + *pac_current = NULL; + eap_teap_free_pac(pac); + break; + } + prev = pac; + pac = pac->next; + } +} + + +static int eap_teap_copy_buf(u8 **dst, size_t *dst_len, + const u8 *src, size_t src_len) +{ + if (src) { + *dst = os_memdup(src, src_len); + if (!(*dst)) + return -1; + *dst_len = src_len; + } + return 0; +} + + +/** + * eap_teap_add_pac - Add a copy of a PAC entry to a list + * @pac_root: Pointer to PAC list root pointer + * @pac_current: Pointer to the current PAC pointer + * @entry: New entry to clone and add to the list + * Returns: 0 on success, -1 on failure + * + * This function makes a clone of the given PAC entry and adds this copied + * entry to the list (pac_root). If an old entry for the same A-ID is found, + * it will be removed from the PAC list and in this case, pac_current entry + * is set to %NULL if it was the removed entry. + */ +int eap_teap_add_pac(struct eap_teap_pac **pac_root, + struct eap_teap_pac **pac_current, + struct eap_teap_pac *entry) +{ + struct eap_teap_pac *pac; + + if (!entry || !entry->a_id) + return -1; + + /* Remove a possible old entry for the matching A-ID. */ + eap_teap_remove_pac(pac_root, pac_current, + entry->a_id, entry->a_id_len, entry->pac_type); + + /* Allocate a new entry and add it to the list of PACs. */ + pac = os_zalloc(sizeof(*pac)); + if (!pac) + return -1; + + pac->pac_type = entry->pac_type; + os_memcpy(pac->pac_key, entry->pac_key, EAP_TEAP_PAC_KEY_LEN); + if (eap_teap_copy_buf(&pac->pac_opaque, &pac->pac_opaque_len, + entry->pac_opaque, entry->pac_opaque_len) < 0 || + eap_teap_copy_buf(&pac->pac_info, &pac->pac_info_len, + entry->pac_info, entry->pac_info_len) < 0 || + eap_teap_copy_buf(&pac->a_id, &pac->a_id_len, + entry->a_id, entry->a_id_len) < 0 || + eap_teap_copy_buf(&pac->i_id, &pac->i_id_len, + entry->i_id, entry->i_id_len) < 0 || + eap_teap_copy_buf(&pac->a_id_info, &pac->a_id_info_len, + entry->a_id_info, entry->a_id_info_len) < 0) { + eap_teap_free_pac(pac); + return -1; + } + + pac->next = *pac_root; + *pac_root = pac; + + return 0; +} + + +struct eap_teap_read_ctx { + FILE *f; + const char *pos; + const char *end; + int line; + char *buf; + size_t buf_len; +}; + +static int eap_teap_read_line(struct eap_teap_read_ctx *rc, char **value) +{ + char *pos; + + rc->line++; + if (rc->f) { + if (fgets(rc->buf, rc->buf_len, rc->f) == NULL) + return -1; + } else { + const char *l_end; + size_t len; + + if (rc->pos >= rc->end) + return -1; + l_end = rc->pos; + while (l_end < rc->end && *l_end != '\n') + l_end++; + len = l_end - rc->pos; + if (len >= rc->buf_len) + len = rc->buf_len - 1; + os_memcpy(rc->buf, rc->pos, len); + rc->buf[len] = '\0'; + rc->pos = l_end + 1; + } + + rc->buf[rc->buf_len - 1] = '\0'; + pos = rc->buf; + while (*pos != '\0') { + if (*pos == '\n' || *pos == '\r') { + *pos = '\0'; + break; + } + pos++; + } + + pos = os_strchr(rc->buf, '='); + if (pos) + *pos++ = '\0'; + *value = pos; + + return 0; +} + + +static u8 * eap_teap_parse_hex(const char *value, size_t *len) +{ + int hlen; + u8 *buf; + + if (!value) + return NULL; + hlen = os_strlen(value); + if (hlen & 1) + return NULL; + *len = hlen / 2; + buf = os_malloc(*len); + if (!buf) + return NULL; + if (hexstr2bin(value, buf, *len)) { + os_free(buf); + return NULL; + } + return buf; +} + + +static int eap_teap_init_pac_data(struct eap_sm *sm, const char *pac_file, + struct eap_teap_read_ctx *rc) +{ + os_memset(rc, 0, sizeof(*rc)); + + rc->buf_len = 2048; + rc->buf = os_malloc(rc->buf_len); + if (!rc->buf) + return -1; + + if (os_strncmp(pac_file, "blob://", 7) == 0) { + const struct wpa_config_blob *blob; + + blob = eap_get_config_blob(sm, pac_file + 7); + if (!blob) { + wpa_printf(MSG_INFO, + "EAP-TEAP: No PAC blob '%s' - assume no PAC entries have been provisioned", + pac_file + 7); + os_free(rc->buf); + return -1; + } + rc->pos = (char *) blob->data; + rc->end = (char *) blob->data + blob->len; + } else { + rc->f = fopen(pac_file, "rb"); + if (!rc->f) { + wpa_printf(MSG_INFO, + "EAP-TEAP: No PAC file '%s' - assume no PAC entries have been provisioned", + pac_file); + os_free(rc->buf); + return -1; + } + } + + return 0; +} + + +static void eap_teap_deinit_pac_data(struct eap_teap_read_ctx *rc) +{ + os_free(rc->buf); + if (rc->f) + fclose(rc->f); +} + + +static const char * eap_teap_parse_start(struct eap_teap_pac **pac) +{ + if (*pac) + return "START line without END"; + + *pac = os_zalloc(sizeof(struct eap_teap_pac)); + if (!(*pac)) + return "No memory for PAC entry"; + (*pac)->pac_type = PAC_TYPE_TUNNEL_PAC; + return NULL; +} + + +static const char * eap_teap_parse_end(struct eap_teap_pac **pac_root, + struct eap_teap_pac **pac) +{ + if (!(*pac)) + return "END line without START"; + if (*pac_root) { + struct eap_teap_pac *end = *pac_root; + + while (end->next) + end = end->next; + end->next = *pac; + } else + *pac_root = *pac; + + *pac = NULL; + return NULL; +} + + +static const char * eap_teap_parse_pac_type(struct eap_teap_pac *pac, + char *pos) +{ + if (!pos) + return "Cannot parse pac type"; + pac->pac_type = atoi(pos); + if (pac->pac_type != PAC_TYPE_TUNNEL_PAC) + return "Unrecognized PAC-Type"; + + return NULL; +} + + +static const char * eap_teap_parse_pac_key(struct eap_teap_pac *pac, char *pos) +{ + u8 *key; + size_t key_len; + + key = eap_teap_parse_hex(pos, &key_len); + if (!key || key_len != EAP_TEAP_PAC_KEY_LEN) { + os_free(key); + return "Invalid PAC-Key"; + } + + os_memcpy(pac->pac_key, key, EAP_TEAP_PAC_KEY_LEN); + os_free(key); + + return NULL; +} + + +static const char * eap_teap_parse_pac_opaque(struct eap_teap_pac *pac, + char *pos) +{ + os_free(pac->pac_opaque); + pac->pac_opaque = eap_teap_parse_hex(pos, &pac->pac_opaque_len); + if (!pac->pac_opaque) + return "Invalid PAC-Opaque"; + return NULL; +} + + +static const char * eap_teap_parse_a_id(struct eap_teap_pac *pac, char *pos) +{ + os_free(pac->a_id); + pac->a_id = eap_teap_parse_hex(pos, &pac->a_id_len); + if (!pac->a_id) + return "Invalid A-ID"; + return NULL; +} + + +static const char * eap_teap_parse_i_id(struct eap_teap_pac *pac, char *pos) +{ + os_free(pac->i_id); + pac->i_id = eap_teap_parse_hex(pos, &pac->i_id_len); + if (!pac->i_id) + return "Invalid I-ID"; + return NULL; +} + + +static const char * eap_teap_parse_a_id_info(struct eap_teap_pac *pac, + char *pos) +{ + os_free(pac->a_id_info); + pac->a_id_info = eap_teap_parse_hex(pos, &pac->a_id_info_len); + if (!pac->a_id_info) + return "Invalid A-ID-Info"; + return NULL; +} + + +/** + * eap_teap_load_pac - Load PAC entries (text format) + * @sm: Pointer to EAP state machine allocated with eap_peer_sm_init() + * @pac_root: Pointer to root of the PAC list (to be filled) + * @pac_file: Name of the PAC file/blob to load + * Returns: 0 on success, -1 on failure + */ +int eap_teap_load_pac(struct eap_sm *sm, struct eap_teap_pac **pac_root, + const char *pac_file) +{ + struct eap_teap_read_ctx rc; + struct eap_teap_pac *pac = NULL; + int count = 0; + char *pos; + const char *err = NULL; + + if (!pac_file) + return -1; + + if (eap_teap_init_pac_data(sm, pac_file, &rc) < 0) + return 0; + + if (eap_teap_read_line(&rc, &pos) < 0) { + /* empty file - assume it is fine to overwrite */ + eap_teap_deinit_pac_data(&rc); + return 0; + } + if (os_strcmp(pac_file_hdr, rc.buf) != 0) + err = "Unrecognized header line"; + + while (!err && eap_teap_read_line(&rc, &pos) == 0) { + if (os_strcmp(rc.buf, "START") == 0) + err = eap_teap_parse_start(&pac); + else if (os_strcmp(rc.buf, "END") == 0) { + err = eap_teap_parse_end(pac_root, &pac); + count++; + } else if (!pac) + err = "Unexpected line outside START/END block"; + else if (os_strcmp(rc.buf, "PAC-Type") == 0) + err = eap_teap_parse_pac_type(pac, pos); + else if (os_strcmp(rc.buf, "PAC-Key") == 0) + err = eap_teap_parse_pac_key(pac, pos); + else if (os_strcmp(rc.buf, "PAC-Opaque") == 0) + err = eap_teap_parse_pac_opaque(pac, pos); + else if (os_strcmp(rc.buf, "A-ID") == 0) + err = eap_teap_parse_a_id(pac, pos); + else if (os_strcmp(rc.buf, "I-ID") == 0) + err = eap_teap_parse_i_id(pac, pos); + else if (os_strcmp(rc.buf, "A-ID-Info") == 0) + err = eap_teap_parse_a_id_info(pac, pos); + } + + if (pac) { + if (!err) + err = "PAC block not terminated with END"; + eap_teap_free_pac(pac); + } + + eap_teap_deinit_pac_data(&rc); + + if (err) { + wpa_printf(MSG_INFO, "EAP-TEAP: %s in '%s:%d'", + err, pac_file, rc.line); + return -1; + } + + wpa_printf(MSG_DEBUG, "EAP-TEAP: Read %d PAC entries from '%s'", + count, pac_file); + + return 0; +} + + +static void eap_teap_write(char **buf, char **pos, size_t *buf_len, + const char *field, const u8 *data, + size_t len, int txt) +{ + size_t i, need; + int ret; + char *end; + + if (!data || !buf || !(*buf) || !pos || !(*pos) || *pos < *buf) + return; + + need = os_strlen(field) + len * 2 + 30; + if (txt) + need += os_strlen(field) + len + 20; + + if (*pos - *buf + need > *buf_len) { + char *nbuf = os_realloc(*buf, *buf_len + need); + + if (!nbuf) { + os_free(*buf); + *buf = NULL; + return; + } + *pos = nbuf + (*pos - *buf); + *buf = nbuf; + *buf_len += need; + } + end = *buf + *buf_len; + + ret = os_snprintf(*pos, end - *pos, "%s=", field); + if (os_snprintf_error(end - *pos, ret)) + return; + *pos += ret; + *pos += wpa_snprintf_hex(*pos, end - *pos, data, len); + ret = os_snprintf(*pos, end - *pos, "\n"); + if (os_snprintf_error(end - *pos, ret)) + return; + *pos += ret; + + if (txt) { + ret = os_snprintf(*pos, end - *pos, "%s-txt=", field); + if (os_snprintf_error(end - *pos, ret)) + return; + *pos += ret; + for (i = 0; i < len; i++) { + ret = os_snprintf(*pos, end - *pos, "%c", data[i]); + if (os_snprintf_error(end - *pos, ret)) + return; + *pos += ret; + } + ret = os_snprintf(*pos, end - *pos, "\n"); + if (os_snprintf_error(end - *pos, ret)) + return; + *pos += ret; + } +} + + +static int eap_teap_write_pac(struct eap_sm *sm, const char *pac_file, + char *buf, size_t len) +{ + if (os_strncmp(pac_file, "blob://", 7) == 0) { + struct wpa_config_blob *blob; + + blob = os_zalloc(sizeof(*blob)); + if (!blob) + return -1; + blob->data = (u8 *) buf; + blob->len = len; + buf = NULL; + blob->name = os_strdup(pac_file + 7); + if (!blob->name) { + os_free(blob); + return -1; + } + eap_set_config_blob(sm, blob); + } else { + FILE *f; + + f = fopen(pac_file, "wb"); + if (!f) { + wpa_printf(MSG_INFO, + "EAP-TEAP: Failed to open PAC file '%s' for writing", + pac_file); + return -1; + } + if (fwrite(buf, 1, len, f) != len) { + wpa_printf(MSG_INFO, + "EAP-TEAP: Failed to write all PACs into '%s'", + pac_file); + fclose(f); + return -1; + } + os_free(buf); + fclose(f); + } + + return 0; +} + + +static int eap_teap_add_pac_data(struct eap_teap_pac *pac, char **buf, + char **pos, size_t *buf_len) +{ + int ret; + + ret = os_snprintf(*pos, *buf + *buf_len - *pos, + "START\nPAC-Type=%d\n", pac->pac_type); + if (os_snprintf_error(*buf + *buf_len - *pos, ret)) + return -1; + + *pos += ret; + eap_teap_write(buf, pos, buf_len, "PAC-Key", + pac->pac_key, EAP_TEAP_PAC_KEY_LEN, 0); + eap_teap_write(buf, pos, buf_len, "PAC-Opaque", + pac->pac_opaque, pac->pac_opaque_len, 0); + eap_teap_write(buf, pos, buf_len, "PAC-Info", + pac->pac_info, pac->pac_info_len, 0); + eap_teap_write(buf, pos, buf_len, "A-ID", + pac->a_id, pac->a_id_len, 0); + eap_teap_write(buf, pos, buf_len, "I-ID", + pac->i_id, pac->i_id_len, 1); + eap_teap_write(buf, pos, buf_len, "A-ID-Info", + pac->a_id_info, pac->a_id_info_len, 1); + if (!(*buf)) { + wpa_printf(MSG_DEBUG, "EAP-TEAP: No memory for PAC data"); + return -1; + } + ret = os_snprintf(*pos, *buf + *buf_len - *pos, "END\n"); + if (os_snprintf_error(*buf + *buf_len - *pos, ret)) + return -1; + *pos += ret; + + return 0; +} + + +/** + * eap_teap_save_pac - Save PAC entries (text format) + * @sm: Pointer to EAP state machine allocated with eap_peer_sm_init() + * @pac_root: Root of the PAC list + * @pac_file: Name of the PAC file/blob + * Returns: 0 on success, -1 on failure + */ +int eap_teap_save_pac(struct eap_sm *sm, struct eap_teap_pac *pac_root, + const char *pac_file) +{ + struct eap_teap_pac *pac; + int ret, count = 0; + char *buf, *pos; + size_t buf_len; + + if (!pac_file) + return -1; + + buf_len = 1024; + pos = buf = os_malloc(buf_len); + if (!buf) + return -1; + + ret = os_snprintf(pos, buf + buf_len - pos, "%s\n", pac_file_hdr); + if (os_snprintf_error(buf + buf_len - pos, ret)) { + os_free(buf); + return -1; + } + pos += ret; + + pac = pac_root; + while (pac) { + if (eap_teap_add_pac_data(pac, &buf, &pos, &buf_len)) { + os_free(buf); + return -1; + } + count++; + pac = pac->next; + } + + if (eap_teap_write_pac(sm, pac_file, buf, pos - buf)) { + os_free(buf); + return -1; + } + + wpa_printf(MSG_DEBUG, "EAP-TEAP: Wrote %d PAC entries into '%s'", + count, pac_file); + + return 0; +} + + +/** + * eap_teap_pac_list_truncate - Truncate a PAC list to the given length + * @pac_root: Root of the PAC list + * @max_len: Maximum length of the list (>= 1) + * Returns: Number of PAC entries removed + */ +size_t eap_teap_pac_list_truncate(struct eap_teap_pac *pac_root, + size_t max_len) +{ + struct eap_teap_pac *pac, *prev; + size_t count; + + pac = pac_root; + prev = NULL; + count = 0; + + while (pac) { + count++; + if (count > max_len) + break; + prev = pac; + pac = pac->next; + } + + if (count <= max_len || !prev) + return 0; + + count = 0; + prev->next = NULL; + + while (pac) { + prev = pac; + pac = pac->next; + eap_teap_free_pac(prev); + count++; + } + + return count; +} + + +static void eap_teap_pac_get_a_id(struct eap_teap_pac *pac) +{ + u8 *pos, *end; + u16 type, len; + + pos = pac->pac_info; + end = pos + pac->pac_info_len; + + while (end - pos > 4) { + type = WPA_GET_BE16(pos); + pos += 2; + len = WPA_GET_BE16(pos); + pos += 2; + if (len > (unsigned int) (end - pos)) + break; + + if (type == PAC_TYPE_A_ID) { + os_free(pac->a_id); + pac->a_id = os_memdup(pos, len); + if (!pac->a_id) + break; + pac->a_id_len = len; + } + + if (type == PAC_TYPE_A_ID_INFO) { + os_free(pac->a_id_info); + pac->a_id_info = os_memdup(pos, len); + if (!pac->a_id_info) + break; + pac->a_id_info_len = len; + } + + pos += len; + } +} + + +/** + * eap_teap_load_pac_bin - Load PAC entries (binary format) + * @sm: Pointer to EAP state machine allocated with eap_peer_sm_init() + * @pac_root: Pointer to root of the PAC list (to be filled) + * @pac_file: Name of the PAC file/blob to load + * Returns: 0 on success, -1 on failure + */ +int eap_teap_load_pac_bin(struct eap_sm *sm, struct eap_teap_pac **pac_root, + const char *pac_file) +{ + const struct wpa_config_blob *blob = NULL; + u8 *buf, *end, *pos; + size_t len, count = 0; + struct eap_teap_pac *pac, *prev; + + *pac_root = NULL; + + if (!pac_file) + return -1; + + if (os_strncmp(pac_file, "blob://", 7) == 0) { + blob = eap_get_config_blob(sm, pac_file + 7); + if (!blob) { + wpa_printf(MSG_INFO, + "EAP-TEAP: No PAC blob '%s' - assume no PAC entries have been provisioned", + pac_file + 7); + return 0; + } + buf = blob->data; + len = blob->len; + } else { + buf = (u8 *) os_readfile(pac_file, &len); + if (!buf) { + wpa_printf(MSG_INFO, + "EAP-TEAP: No PAC file '%s' - assume no PAC entries have been provisioned", + pac_file); + return 0; + } + } + + if (len == 0) { + if (!blob) + os_free(buf); + return 0; + } + + if (len < 6 || WPA_GET_BE32(buf) != EAP_TEAP_PAC_BINARY_MAGIC || + WPA_GET_BE16(buf + 4) != EAP_TEAP_PAC_BINARY_FORMAT_VERSION) { + wpa_printf(MSG_INFO, "EAP-TEAP: Invalid PAC file '%s' (bin)", + pac_file); + if (!blob) + os_free(buf); + return -1; + } + + pac = prev = NULL; + pos = buf + 6; + end = buf + len; + while (pos < end) { + u16 val; + + if (end - pos < 2 + EAP_TEAP_PAC_KEY_LEN + 2 + 2) { + pac = NULL; + goto parse_fail; + } + + pac = os_zalloc(sizeof(*pac)); + if (!pac) + goto parse_fail; + + pac->pac_type = WPA_GET_BE16(pos); + pos += 2; + os_memcpy(pac->pac_key, pos, EAP_TEAP_PAC_KEY_LEN); + pos += EAP_TEAP_PAC_KEY_LEN; + val = WPA_GET_BE16(pos); + pos += 2; + if (val > end - pos) + goto parse_fail; + pac->pac_opaque_len = val; + pac->pac_opaque = os_memdup(pos, pac->pac_opaque_len); + if (!pac->pac_opaque) + goto parse_fail; + pos += pac->pac_opaque_len; + if (end - pos < 2) + goto parse_fail; + val = WPA_GET_BE16(pos); + pos += 2; + if (val > end - pos) + goto parse_fail; + pac->pac_info_len = val; + pac->pac_info = os_memdup(pos, pac->pac_info_len); + if (!pac->pac_info) + goto parse_fail; + pos += pac->pac_info_len; + eap_teap_pac_get_a_id(pac); + + count++; + if (prev) + prev->next = pac; + else + *pac_root = pac; + prev = pac; + } + + if (!blob) + os_free(buf); + + wpa_printf(MSG_DEBUG, "EAP-TEAP: Read %lu PAC entries from '%s' (bin)", + (unsigned long) count, pac_file); + + return 0; + +parse_fail: + wpa_printf(MSG_INFO, "EAP-TEAP: Failed to parse PAC file '%s' (bin)", + pac_file); + if (!blob) + os_free(buf); + if (pac) + eap_teap_free_pac(pac); + return -1; +} + + +/** + * eap_teap_save_pac_bin - Save PAC entries (binary format) + * @sm: Pointer to EAP state machine allocated with eap_peer_sm_init() + * @pac_root: Root of the PAC list + * @pac_file: Name of the PAC file/blob + * Returns: 0 on success, -1 on failure + */ +int eap_teap_save_pac_bin(struct eap_sm *sm, struct eap_teap_pac *pac_root, + const char *pac_file) +{ + size_t len, count = 0; + struct eap_teap_pac *pac; + u8 *buf, *pos; + + len = 6; + pac = pac_root; + while (pac) { + if (pac->pac_opaque_len > 65535 || + pac->pac_info_len > 65535) + return -1; + len += 2 + EAP_TEAP_PAC_KEY_LEN + 2 + pac->pac_opaque_len + + 2 + pac->pac_info_len; + pac = pac->next; + } + + buf = os_malloc(len); + if (!buf) + return -1; + + pos = buf; + WPA_PUT_BE32(pos, EAP_TEAP_PAC_BINARY_MAGIC); + pos += 4; + WPA_PUT_BE16(pos, EAP_TEAP_PAC_BINARY_FORMAT_VERSION); + pos += 2; + + pac = pac_root; + while (pac) { + WPA_PUT_BE16(pos, pac->pac_type); + pos += 2; + os_memcpy(pos, pac->pac_key, EAP_TEAP_PAC_KEY_LEN); + pos += EAP_TEAP_PAC_KEY_LEN; + WPA_PUT_BE16(pos, pac->pac_opaque_len); + pos += 2; + os_memcpy(pos, pac->pac_opaque, pac->pac_opaque_len); + pos += pac->pac_opaque_len; + WPA_PUT_BE16(pos, pac->pac_info_len); + pos += 2; + os_memcpy(pos, pac->pac_info, pac->pac_info_len); + pos += pac->pac_info_len; + + pac = pac->next; + count++; + } + + if (eap_teap_write_pac(sm, pac_file, (char *) buf, len)) { + os_free(buf); + return -1; + } + + wpa_printf(MSG_DEBUG, "EAP-TEAP: Wrote %lu PAC entries into '%s' (bin)", + (unsigned long) count, pac_file); + + return 0; +} diff --git a/src/eap_peer/eap_teap_pac.h b/src/eap_peer/eap_teap_pac.h new file mode 100644 index 000000000..edf4c5763 --- /dev/null +++ b/src/eap_peer/eap_teap_pac.h @@ -0,0 +1,50 @@ +/* + * EAP peer method: EAP-TEAP PAC file processing + * Copyright (c) 2004-2019, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef EAP_TEAP_PAC_H +#define EAP_TEAP_PAC_H + +#include "eap_common/eap_teap_common.h" + +struct eap_teap_pac { + struct eap_teap_pac *next; + + u8 pac_key[EAP_TEAP_PAC_KEY_LEN]; + u8 *pac_opaque; + size_t pac_opaque_len; + u8 *pac_info; + size_t pac_info_len; + u8 *a_id; + size_t a_id_len; + u8 *i_id; + size_t i_id_len; + u8 *a_id_info; + size_t a_id_info_len; + u16 pac_type; +}; + + +void eap_teap_free_pac(struct eap_teap_pac *pac); +struct eap_teap_pac * eap_teap_get_pac(struct eap_teap_pac *pac_root, + const u8 *a_id, size_t a_id_len, + u16 pac_type); +int eap_teap_add_pac(struct eap_teap_pac **pac_root, + struct eap_teap_pac **pac_current, + struct eap_teap_pac *entry); +int eap_teap_load_pac(struct eap_sm *sm, struct eap_teap_pac **pac_root, + const char *pac_file); +int eap_teap_save_pac(struct eap_sm *sm, struct eap_teap_pac *pac_root, + const char *pac_file); +size_t eap_teap_pac_list_truncate(struct eap_teap_pac *pac_root, + size_t max_len); +int eap_teap_load_pac_bin(struct eap_sm *sm, struct eap_teap_pac **pac_root, + const char *pac_file); +int eap_teap_save_pac_bin(struct eap_sm *sm, struct eap_teap_pac *pac_root, + const char *pac_file); + +#endif /* EAP_TEAP_PAC_H */ diff --git a/src/eap_peer/eap_tls_common.c b/src/eap_peer/eap_tls_common.c index cb94c452e..f0d580dfa 100644 --- a/src/eap_peer/eap_tls_common.c +++ b/src/eap_peer/eap_tls_common.c @@ -159,7 +159,8 @@ static int eap_tls_params_from_conf(struct eap_sm *sm, struct eap_peer_config *config, int phase2) { os_memset(params, 0, sizeof(*params)); - if (sm->workaround && data->eap_type != EAP_TYPE_FAST) { + if (sm->workaround && data->eap_type != EAP_TYPE_FAST && + data->eap_type != EAP_TYPE_TEAP) { /* * Some deployed authentication servers seem to be unable to * handle the TLS Session Ticket extension (they are supposed @@ -171,7 +172,15 @@ static int eap_tls_params_from_conf(struct eap_sm *sm, */ params->flags |= TLS_CONN_DISABLE_SESSION_TICKET; } + if (data->eap_type == EAP_TYPE_TEAP) { + /* RFC 7170 requires TLS v1.2 or newer to be used with TEAP */ + params->flags |= TLS_CONN_DISABLE_TLSv1_0 | + TLS_CONN_DISABLE_TLSv1_1; + if (config->teap_anon_dh) + params->flags |= TLS_CONN_TEAP_ANON_DH; + } if (data->eap_type == EAP_TYPE_FAST || + data->eap_type == EAP_TYPE_TEAP || data->eap_type == EAP_TYPE_TTLS || data->eap_type == EAP_TYPE_PEAP) { /* The current EAP peer implementation is not yet ready for the diff --git a/src/eap_peer/eap_tls_common.h b/src/eap_peer/eap_tls_common.h index 5f825294d..d96eff1c8 100644 --- a/src/eap_peer/eap_tls_common.h +++ b/src/eap_peer/eap_tls_common.h @@ -70,7 +70,8 @@ struct eap_ssl_data { void *ssl_ctx; /** - * eap_type - EAP method used in Phase 1 (EAP_TYPE_TLS/PEAP/TTLS/FAST) + * eap_type - EAP method used in Phase 1 + * (EAP_TYPE_TLS/PEAP/TTLS/FAST/TEAP) */ u8 eap_type; @@ -85,6 +86,7 @@ struct eap_ssl_data { #define EAP_TLS_FLAGS_LENGTH_INCLUDED 0x80 #define EAP_TLS_FLAGS_MORE_FRAGMENTS 0x40 #define EAP_TLS_FLAGS_START 0x20 +#define EAP_TEAP_FLAGS_OUTER_TLV_LEN 0x10 #define EAP_TLS_VERSION_MASK 0x07 /* could be up to 128 bytes, but only the first 64 bytes are used */ diff --git a/src/eap_server/eap.h b/src/eap_server/eap.h index b130368b6..a32c8835c 100644 --- a/src/eap_server/eap.h +++ b/src/eap_server/eap.h @@ -121,6 +121,8 @@ struct eap_config { int eap_fast_prov; int pac_key_lifetime; int pac_key_refresh_time; + int eap_teap_auth; + int eap_teap_pac_no_inner; int eap_sim_aka_result_ind; int tnc; struct wps_context *wps; diff --git a/src/eap_server/eap_i.h b/src/eap_server/eap_i.h index 1cade10be..8e6ac4649 100644 --- a/src/eap_server/eap_i.h +++ b/src/eap_server/eap_i.h @@ -190,6 +190,8 @@ struct eap_sm { } eap_fast_prov; int pac_key_lifetime; int pac_key_refresh_time; + int eap_teap_auth; + int eap_teap_pac_no_inner; int eap_sim_aka_result_ind; int tnc; u16 pwd_group; diff --git a/src/eap_server/eap_methods.h b/src/eap_server/eap_methods.h index 3bf1495f7..fdbea7a7e 100644 --- a/src/eap_server/eap_methods.h +++ b/src/eap_server/eap_methods.h @@ -41,6 +41,7 @@ int eap_server_sake_register(void); int eap_server_gpsk_register(void); int eap_server_vendor_test_register(void); int eap_server_fast_register(void); +int eap_server_teap_register(void); int eap_server_wsc_register(void); int eap_server_ikev2_register(void); int eap_server_tnc_register(void); diff --git a/src/eap_server/eap_server.c b/src/eap_server/eap_server.c index e8b36e133..724ec154f 100644 --- a/src/eap_server/eap_server.c +++ b/src/eap_server/eap_server.c @@ -1869,6 +1869,8 @@ struct eap_sm * eap_server_sm_init(void *eapol_ctx, sm->eap_fast_prov = conf->eap_fast_prov; sm->pac_key_lifetime = conf->pac_key_lifetime; sm->pac_key_refresh_time = conf->pac_key_refresh_time; + sm->eap_teap_auth = conf->eap_teap_auth; + sm->eap_teap_pac_no_inner = conf->eap_teap_pac_no_inner; sm->eap_sim_aka_result_ind = conf->eap_sim_aka_result_ind; sm->tnc = conf->tnc; sm->wps = conf->wps; diff --git a/src/eap_server/eap_server_teap.c b/src/eap_server/eap_server_teap.c new file mode 100644 index 000000000..d8e5414d4 --- /dev/null +++ b/src/eap_server/eap_server_teap.c @@ -0,0 +1,1947 @@ +/* + * EAP-TEAP server (RFC 7170) + * Copyright (c) 2004-2019, Jouni Malinen + * + * 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); +} diff --git a/src/eap_server/eap_server_tls_common.c b/src/eap_server/eap_server_tls_common.c index 0eca0ff77..281376f01 100644 --- a/src/eap_server/eap_server_tls_common.c +++ b/src/eap_server/eap_server_tls_common.c @@ -373,6 +373,8 @@ static int eap_server_tls_reassemble(struct eap_ssl_data *data, u8 flags, unsigned int tls_msg_len = 0; const u8 *end = *pos + *left; + wpa_hexdump(MSG_MSGDUMP, "SSL: Received data", *pos, *left); + if (flags & EAP_TLS_FLAGS_LENGTH_INCLUDED) { if (*left < 4) { wpa_printf(MSG_INFO, "SSL: Short frame with TLS " diff --git a/src/eap_server/eap_tls_common.h b/src/eap_server/eap_tls_common.h index 0b04983ad..74b1c728d 100644 --- a/src/eap_server/eap_tls_common.h +++ b/src/eap_server/eap_tls_common.h @@ -62,6 +62,7 @@ struct eap_ssl_data { #define EAP_TLS_FLAGS_LENGTH_INCLUDED 0x80 #define EAP_TLS_FLAGS_MORE_FRAGMENTS 0x40 #define EAP_TLS_FLAGS_START 0x20 +#define EAP_TEAP_FLAGS_OUTER_TLV_LEN 0x10 #define EAP_TLS_VERSION_MASK 0x07 /* could be up to 128 bytes, but only the first 64 bytes are used */ diff --git a/src/eapol_auth/eapol_auth_sm.c b/src/eapol_auth/eapol_auth_sm.c index 36074d3e0..b7423d135 100644 --- a/src/eapol_auth/eapol_auth_sm.c +++ b/src/eapol_auth/eapol_auth_sm.c @@ -300,7 +300,7 @@ SM_STATE(AUTH_PAE, AUTHENTICATED) if (sm->auth_pae_state == AUTH_PAE_AUTHENTICATING && sm->authSuccess) sm->authAuthSuccessesWhileAuthenticating++; - + SM_ENTRY_MA(AUTH_PAE, AUTHENTICATED, auth_pae); sm->authPortStatus = Authorized; @@ -835,6 +835,8 @@ eapol_auth_alloc(struct eapol_authenticator *eapol, const u8 *addr, eap_conf.eap_fast_prov = eapol->conf.eap_fast_prov; eap_conf.pac_key_lifetime = eapol->conf.pac_key_lifetime; eap_conf.pac_key_refresh_time = eapol->conf.pac_key_refresh_time; + eap_conf.eap_teap_auth = eapol->conf.eap_teap_auth; + eap_conf.eap_teap_pac_no_inner = eapol->conf.eap_teap_pac_no_inner; eap_conf.eap_sim_aka_result_ind = eapol->conf.eap_sim_aka_result_ind; eap_conf.tnc = eapol->conf.tnc; eap_conf.wps = eapol->conf.wps; @@ -1231,6 +1233,8 @@ static int eapol_auth_conf_clone(struct eapol_auth_config *dst, dst->eap_fast_prov = src->eap_fast_prov; dst->pac_key_lifetime = src->pac_key_lifetime; dst->pac_key_refresh_time = src->pac_key_refresh_time; + dst->eap_teap_auth = src->eap_teap_auth; + dst->eap_teap_pac_no_inner = src->eap_teap_pac_no_inner; dst->eap_sim_aka_result_ind = src->eap_sim_aka_result_ind; dst->tnc = src->tnc; dst->wps = src->wps; diff --git a/src/eapol_auth/eapol_auth_sm.h b/src/eapol_auth/eapol_auth_sm.h index 44f3f31cc..41b6b1b1a 100644 --- a/src/eapol_auth/eapol_auth_sm.h +++ b/src/eapol_auth/eapol_auth_sm.h @@ -36,6 +36,8 @@ struct eapol_auth_config { int eap_fast_prov; int pac_key_lifetime; int pac_key_refresh_time; + int eap_teap_auth; + int eap_teap_pac_no_inner; int eap_sim_aka_result_ind; int tnc; struct wps_context *wps; diff --git a/src/radius/radius_server.c b/src/radius/radius_server.c index e0c0d8230..1b605c7f0 100644 --- a/src/radius/radius_server.c +++ b/src/radius/radius_server.c @@ -238,6 +238,9 @@ struct radius_server_data { */ int pac_key_refresh_time; + int eap_teap_auth; + int eap_teap_pac_no_inner; + /** * eap_sim_aka_result_ind - EAP-SIM/AKA protected success indication * @@ -792,6 +795,8 @@ radius_server_get_new_session(struct radius_server_data *data, eap_conf.eap_fast_prov = data->eap_fast_prov; eap_conf.pac_key_lifetime = data->pac_key_lifetime; eap_conf.pac_key_refresh_time = data->pac_key_refresh_time; + eap_conf.eap_teap_auth = data->eap_teap_auth; + eap_conf.eap_teap_pac_no_inner = data->eap_teap_pac_no_inner; eap_conf.eap_sim_aka_result_ind = data->eap_sim_aka_result_ind; eap_conf.tnc = data->tnc; eap_conf.wps = data->wps; @@ -2384,6 +2389,8 @@ radius_server_init(struct radius_server_conf *conf) data->eap_fast_prov = conf->eap_fast_prov; data->pac_key_lifetime = conf->pac_key_lifetime; data->pac_key_refresh_time = conf->pac_key_refresh_time; + data->eap_teap_auth = conf->eap_teap_auth; + data->eap_teap_pac_no_inner = conf->eap_teap_pac_no_inner; data->get_eap_user = conf->get_eap_user; data->eap_sim_aka_result_ind = conf->eap_sim_aka_result_ind; data->tnc = conf->tnc; diff --git a/src/radius/radius_server.h b/src/radius/radius_server.h index 53728f9d7..88c22db86 100644 --- a/src/radius/radius_server.h +++ b/src/radius/radius_server.h @@ -128,6 +128,9 @@ struct radius_server_conf { */ int pac_key_refresh_time; + int eap_teap_auth; + int eap_teap_pac_no_inner; + /** * eap_sim_aka_result_ind - EAP-SIM/AKA protected success indication * diff --git a/wpa_supplicant/Android.mk b/wpa_supplicant/Android.mk index c0d68b807..b5d982de3 100644 --- a/wpa_supplicant/Android.mk +++ b/wpa_supplicant/Android.mk @@ -644,6 +644,23 @@ CONFIG_IEEE8021X_EAPOL=y NEED_T_PRF=y endif +ifdef CONFIG_EAP_TEAP +# EAP-TEAP +ifeq ($(CONFIG_EAP_TEAP), dyn) +L_CFLAGS += -DEAP_YEAP_DYNAMIC +EAPDYN += src/eap_peer/eap_teap.so +EAPDYN += src/eap_common/eap_teap_common.c +else +L_CFLAGS += -DEAP_TEAP +OBJS += src/eap_peer/eap_teap.c src/eap_peer/eap_teap_pac.c +OBJS += src/eap_common/eap_teap_common.c +endif +TLS_FUNCS=y +CONFIG_IEEE8021X_EAPOL=y +NEED_T_PRF=y +NEED_SHA384=y +endif + ifdef CONFIG_EAP_PAX # EAP-PAX ifeq ($(CONFIG_EAP_PAX), dyn) diff --git a/wpa_supplicant/Makefile b/wpa_supplicant/Makefile index 348f9015c..f1384d5fa 100644 --- a/wpa_supplicant/Makefile +++ b/wpa_supplicant/Makefile @@ -671,6 +671,23 @@ CONFIG_IEEE8021X_EAPOL=y NEED_T_PRF=y endif +ifdef CONFIG_EAP_TEAP +# EAP-TEAP +ifeq ($(CONFIG_EAP_TEAP), dyn) +CFLAGS += -DEAP_TEAP_DYNAMIC +EAPDYN += ../src/eap_peer/eap_teap.so +EAPDYN += ../src/eap_common/eap_teap_common.o +else +CFLAGS += -DEAP_TEAP +OBJS += ../src/eap_peer/eap_teap.o ../src/eap_peer/eap_teap_pac.o +OBJS += ../src/eap_common/eap_teap_common.o +endif +TLS_FUNCS=y +CONFIG_IEEE8021X_EAPOL=y +NEED_T_PRF=y +NEED_SHA384=y +endif + ifdef CONFIG_EAP_PAX # EAP-PAX ifeq ($(CONFIG_EAP_PAX), dyn) @@ -1044,7 +1061,8 @@ endif ifdef TLS_FUNCS NEED_DES=y -# Shared TLS functions (needed for EAP_TLS, EAP_PEAP, EAP_TTLS, and EAP_FAST) +# Shared TLS functions (needed for EAP_TLS, EAP_PEAP, EAP_TTLS, EAP_FAST, and +# EAP_TEAP) OBJS += ../src/eap_peer/eap_tls_common.o ifndef CONFIG_FIPS NEED_TLS_PRF=y diff --git a/wpa_supplicant/defconfig b/wpa_supplicant/defconfig index 9cceda4b2..cdfb1974d 100644 --- a/wpa_supplicant/defconfig +++ b/wpa_supplicant/defconfig @@ -111,6 +111,16 @@ CONFIG_EAP_TTLS=y # EAP-FAST CONFIG_EAP_FAST=y +# EAP-TEAP +# Note: The current EAP-TEAP implementation is experimental and should not be +# enabled for production use. The IETF RFC 7170 that defines EAP-TEAP has number +# of conflicting statements and missing details and the implementation has +# vendor specific workarounds for those and as such, may not interoperate with +# any other implementation. This should not be used for anything else than +# experimentation and interoperability testing until those issues has been +# resolved. +#CONFIG_EAP_TEAP=y + # EAP-GTC CONFIG_EAP_GTC=y diff --git a/wpa_supplicant/eap_register.c b/wpa_supplicant/eap_register.c index ece57166c..3f018c4b3 100644 --- a/wpa_supplicant/eap_register.c +++ b/wpa_supplicant/eap_register.c @@ -102,6 +102,11 @@ int eap_register_methods(void) ret = eap_peer_fast_register(); #endif /* EAP_FAST */ +#ifdef EAP_TEAP + if (ret == 0) + ret = eap_peer_teap_register(); +#endif /* EAP_TEAP */ + #ifdef EAP_PAX if (ret == 0) ret = eap_peer_pax_register(); @@ -237,6 +242,11 @@ int eap_register_methods(void) ret = eap_server_fast_register(); #endif /* EAP_SERVER_FAST */ +#ifdef EAP_SERVER_TEAP + if (ret == 0) + ret = eap_server_teap_register(); +#endif /* EAP_SERVER_TEAP */ + #ifdef EAP_SERVER_WSC if (ret == 0) ret = eap_server_wsc_register();