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();