From: Alexander Clouter Date: Tue, 7 Jul 2020 11:13:23 +0000 (+0100) Subject: v3.0.x: TLSv1.3 support for EAP-{TLS,TTLS,PEAP} (#3516) X-Git-Tag: release_3_0_22~540 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e8b4d28e140cd354ec3f72619bb4dd9ed4f261cc;p=thirdparty%2Ffreeradius-server.git v3.0.x: TLSv1.3 support for EAP-{TLS,TTLS,PEAP} (#3516) * eap-tls: tls version 1.3 draft-ietf-emu-eap-tls13 (tls) and draft-ietf-emu-tls-eap-types (ttls/peap) * SQUASH: remove unneeded line, compilers complain about more readable code rather than sensibly optimise away https://github.com/FreeRADIUS/freeradius-server/pull/3516#pullrequestreview-443451513 * SQUASH: comment about early data * SQUASH: middleware boxen comment * SQUASH: method of using context is too confusing retain original behaviour --- diff --git a/raddb/mods-available/eap b/raddb/mods-available/eap index a89a7836635..7e06a846540 100644 --- a/raddb/mods-available/eap +++ b/raddb/mods-available/eap @@ -405,11 +405,6 @@ eap { # # Allowed values are "1.0", "1.1", "1.2", and "1.3". # - # Note that the server WILL NOT permit negotiation of - # TLS 1.3. The EAP-TLS standards for TLS 1.3 are NOT - # finished. It is therefore impossible for the server - # to negotiate EAP-TLS correctly with TLS 1.3. - # # The values must be in quotes. # tls_min_version = "1.2" diff --git a/src/include/tls-h b/src/include/tls-h index 62f57c4715a..93a6fbb7e4d 100644 --- a/src/include/tls-h +++ b/src/include/tls-h @@ -160,7 +160,7 @@ typedef struct _tls_session_t { void *opaque; void (*free_opaque)(void *opaque); - char const *prf_label; + char const *label; bool allow_session_resumption; //!< Whether session resumption is allowed. } tls_session_t; diff --git a/src/main/cb.c b/src/main/cb.c index 4ae14e575be..dc708472865 100644 --- a/src/main/cb.c +++ b/src/main/cb.c @@ -124,6 +124,12 @@ void cbtls_msg(int write_p, int msg_version, int content_type, state->info.alert_level = 0x00; state->info.alert_description = 0x00; +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + } else if (content_type == SSL3_RT_INNER_CONTENT_TYPE && buf[0] == SSL3_RT_APPLICATION_DATA) { + /* let tls_ack_handler set application_data */ + state->info.content_type = SSL3_RT_HANDSHAKE; +#endif + #ifdef SSL3_RT_HEARTBEAT } else if (content_type == TLS1_RT_HEARTBEAT) { uint8_t *p = buf; diff --git a/src/main/tls.c b/src/main/tls.c index 2db998b2abb..6e483fb0f86 100644 --- a/src/main/tls.c +++ b/src/main/tls.c @@ -858,10 +858,10 @@ int tls_handshake_send(REQUEST *request, tls_session_t *ssn) record_minus(&ssn->clean_in, NULL, written); /* Get the dirty data from Bio to send it */ - err = BIO_read(ssn->from_ssl, ssn->dirty_out.data, - sizeof(ssn->dirty_out.data)); + err = BIO_read(ssn->from_ssl, ssn->dirty_out.data + ssn->dirty_out.used, + sizeof(ssn->dirty_out.data) - ssn->dirty_out.used); if (err > 0) { - ssn->dirty_out.used = err; + ssn->dirty_out.used += err; } else { if (!tls_error_io_log(request, ssn, err, "Failed in " STRINGIFY(__FUNCTION__) " (SSL_write)")) { @@ -3091,6 +3091,18 @@ post_ca: ctx_options |= SSL_OP_NO_SSLv2; ctx_options |= SSL_OP_NO_SSLv3; + /* + * If set then dummy Change Cipher Spec (CCS) messages are sent in + * TLSv1.3. This has the effect of making TLSv1.3 look more like TLSv1.2 + * so that middleboxes that do not understand TLSv1.3 will not drop + * the connection. This isn't needed for EAP-TLS, so we disable it. + * + * EAP (hopefully) does not have middlebox deployments + */ +#ifdef SSL_OP_ENABLE_MIDDLEBOX_COMPAT + ctx_options &= ~SSL_OP_ENABLE_MIDDLEBOX_COMPAT; +#endif + /* * SSL_CTX_set_(min|max)_proto_version was included in OpenSSL 1.1.0 * @@ -3119,9 +3131,9 @@ post_ca: * Pick the maximum one we know about. */ #ifdef TLS1_4_VERSION - max_version = TLS1_2_VERSION; /* NOT a typo! EAP methods for TLS 1.4 are NOT finished */ + max_version = TLS1_3_VERSION; /* NOT a typo! EAP methods for TLS 1.4 are NOT finished */ #elif defined(TLS1_3_VERSION) - max_version = TLS1_2_VERSION; /* NOT a typo! EAP methods for TLS 1.3 are NOT finished */ + max_version = TLS1_3_VERSION; #elif defined(TLS1_2_VERSION) max_version = TLS1_2_VERSION; #elif defined(TLS1_1_VERSION) @@ -3294,6 +3306,20 @@ post_ca: SSL_CTX_set_options(ctx, ctx_options); + /* + * TLS 1.3 introduces the concept of early data (also known as zero + * round trip data or 0-RTT data). Early data allows a client to send + * data to a server in the first round trip of a connection, without + * waiting for the TLS handshake to complete if the client has spoken + * to the same server recently. This doesn't work for EAP, so we + * disable early data. + * + * OpenSSL 1.1.1 and later set this as the default + */ +#if OPENSSL_VERSION_NUMBER >= 0x10100000L && OPENSSL_VERSION_NUMBER < 0x10101000L + SSL_CTX_set_max_early_data(ctx, 0); +#endif + /* * TODO: Set the RSA & DH * SSL_CTX_set_tmp_rsa_callback(ctx, cbtls_rsa); diff --git a/src/modules/rlm_eap/libeap/eap_tls.c b/src/modules/rlm_eap/libeap/eap_tls.c index bc4bb0c9aba..9b73c482f83 100644 --- a/src/modules/rlm_eap/libeap/eap_tls.c +++ b/src/modules/rlm_eap/libeap/eap_tls.c @@ -160,15 +160,41 @@ int eaptls_success(eap_handler_t *handler, int peap_flag) /* * Automatically generate MPPE keying material. */ - if (tls_session->prf_label) { + if (tls_session->label) { + uint8_t const *context = NULL; + size_t context_size = 0; +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + uint8_t const context_tls13[] = { handler->type }; +#endif + + switch (tls_session->info.version) { +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + case TLS1_3_VERSION: + context = context_tls13; + context_size = sizeof(context_tls13); + break; +#endif + case TLS1_2_VERSION: + case TLS1_1_VERSION: + case TLS1_VERSION: + break; + case SSL2_VERSION: + case SSL3_VERSION: + default: + /* Should never happen */ + rad_assert(0); + return 0; + break; + } eaptls_gen_mppe_keys(handler->request, - tls_session->ssl, tls_session->prf_label); + tls_session->ssl, tls_session->label, + context, context_size); } else if (handler->type != PW_EAP_FAST) { RWDEBUG("Not adding MPPE keys because there is no PRF label"); } - eaptls_gen_eap_key(handler->request->reply, tls_session->ssl, - handler->type); + eaptls_gen_eap_key(handler->request->reply, tls_session->ssl, handler->type); + return 1; } @@ -724,6 +750,15 @@ static fr_tls_status_t eaptls_operation(fr_tls_status_t status, eap_handler_t *h return FR_TLS_FAIL; } +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + /* draft-ietf-emu-eap-tls13-10 section 2.5 */ + if (tls_session->is_init_finished && tls_session->info.version == TLS1_3_VERSION && handler->type == PW_EAP_TLS) { + RDEBUG("TLS send Commitment Message"); + tls_session->record_plus(&tls_session->clean_in, "\0", 1); + tls_handshake_send(request, tls_session); + } +#endif + /* * FIXME: return success/fail. * @@ -737,8 +772,7 @@ static fr_tls_status_t eaptls_operation(fr_tls_status_t status, eap_handler_t *h /* * If there is no data to send i.e * dirty_out.used <=0 and if the SSL - * handshake is finished, then return a - * EPTLS_SUCCESS + * handshake is finished. */ if (tls_session->is_init_finished) { diff --git a/src/modules/rlm_eap/libeap/eap_tls.h b/src/modules/rlm_eap/libeap/eap_tls.h index 73c7fdd53b5..e82b3711c89 100644 --- a/src/modules/rlm_eap/libeap/eap_tls.h +++ b/src/modules/rlm_eap/libeap/eap_tls.h @@ -62,11 +62,11 @@ int eaptls_fail(eap_handler_t *handler, int peap_flag) CC_HINT(nonnull); int eaptls_request(EAP_DS *eap_ds, tls_session_t *ssn) CC_HINT(nonnull); -void T_PRF(unsigned char const *secret, unsigned int secret_len, char const *prf_label, unsigned char const *seed, unsigned int seed_len, unsigned char *out, unsigned int out_len) CC_HINT(nonnull(1,3,6)); -void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *prf_label); +void T_PRF(unsigned char const *secret, unsigned int secret_len, char const *prf_label, unsigned char const *seed, unsigned int seed_len, unsigned char *out, unsigned int out_len) CC_HINT(nonnull(1,3,6)); +void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *label, uint8_t const *context, size_t context_size); void eapttls_gen_challenge(SSL *s, uint8_t *buffer, size_t size); void eaptls_gen_eap_key(RADIUS_PACKET *packet, SSL *s, uint32_t header); -void eap_fast_tls_gen_challenge(SSL *ssl, uint8_t *buffer, uint8_t *scratch, size_t size, char const *prf_label) CC_HINT(nonnull); +void eap_fast_tls_gen_challenge(SSL *ssl, uint8_t *buffer, uint8_t *scratch, size_t size, char const *prf_label) CC_HINT(nonnull); #define BUFFER_SIZE 1024 diff --git a/src/modules/rlm_eap/libeap/mppe_keys.c b/src/modules/rlm_eap/libeap/mppe_keys.c index 3a9e8641048..78b4ae9f005 100644 --- a/src/modules/rlm_eap/libeap/mppe_keys.c +++ b/src/modules/rlm_eap/libeap/mppe_keys.c @@ -150,38 +150,50 @@ static void PRF(unsigned char const *secret, unsigned int secret_len, /* * Generate keys according to RFC 2716 and add to reply */ -void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *prf_label) +void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *label, uint8_t const *context, UNUSED size_t context_size) { uint8_t out[4 * EAPTLS_MPPE_KEY_LEN]; uint8_t *p; - size_t prf_size; + size_t len; - prf_size = strlen(prf_label); + len = strlen(label); #if OPENSSL_VERSION_NUMBER >= 0x10001000L - if (SSL_export_keying_material(s, out, sizeof(out), prf_label, prf_size, NULL, 0, 0) != 1) { + if (SSL_export_keying_material(s, out, sizeof(out), label, len, context, context_size, context != NULL) != 1) { ERROR("Failed generating keying material"); return; } #else { - uint8_t seed[64 + (2 * SSL3_RANDOM_SIZE)]; + uint8_t seed[64 + (2 * SSL3_RANDOM_SIZE) + (context ? 2 + context_size : 0)]; uint8_t buf[4 * EAPTLS_MPPE_KEY_LEN]; p = seed; - memcpy(p, prf_label, prf_size); - p += prf_size; + memcpy(p, label, len); + p += len; memcpy(p, s->s3->client_random, SSL3_RANDOM_SIZE); p += SSL3_RANDOM_SIZE; - prf_size += SSL3_RANDOM_SIZE; + len += SSL3_RANDOM_SIZE; memcpy(p, s->s3->server_random, SSL3_RANDOM_SIZE); - prf_size += SSL3_RANDOM_SIZE; + p += SSL3_RANDOM_SIZE; + len += SSL3_RANDOM_SIZE; + + if (context) { + /* cloned and reversed FR_PUT_LE16 */ + p[0] = ((uint16_t) (context_size)) >> 8; + p[1] = ((uint16_t) (context_size)) & 0xff; + p += 2; + len += 2; + memcpy(p, context, context_size); + p += context_size; + len += context_size; + } PRF(s->session->master_key, s->session->master_key_length, - seed, prf_size, out, buf, sizeof(out)); + seed, len, out, buf, sizeof(out)); } #endif @@ -195,7 +207,7 @@ void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *prf_label) } -#define FR_TLS_PRF_CHALLENGE "ttls challenge" +#define FR_TLS_PRF_CHALLENGE "ttls challenge" /* * Generate the TTLS challenge @@ -206,9 +218,10 @@ void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *prf_label) void eapttls_gen_challenge(SSL *s, uint8_t *buffer, size_t size) { #if OPENSSL_VERSION_NUMBER >= 0x10001000L - SSL_export_keying_material(s, buffer, size, FR_TLS_PRF_CHALLENGE, - sizeof(FR_TLS_PRF_CHALLENGE) - 1, NULL, 0, 0); - + if (SSL_export_keying_material(s, buffer, size, FR_TLS_PRF_CHALLENGE, + sizeof(FR_TLS_PRF_CHALLENGE)-1, NULL, 0, 0) != 1) { + ERROR("Failed generating keying material"); + } #else uint8_t out[32], buf[32]; uint8_t seed[sizeof(FR_TLS_PRF_CHALLENGE)-1 + 2*SSL3_RANDOM_SIZE]; @@ -226,11 +239,13 @@ void eapttls_gen_challenge(SSL *s, uint8_t *buffer, size_t size) #endif } +#define FR_TLS_EXPORTER_METHOD_ID "EXPORTER_EAP_TLS_Method-Id" + /* * Actually generates EAP-Session-Id, which is an internal server * attribute. Not all systems want to send EAP-Key-Name. */ -void eaptls_gen_eap_key(RADIUS_PACKET *packet, SSL *ssl, uint32_t header) +void eaptls_gen_eap_key(RADIUS_PACKET *packet, SSL *s, uint32_t header) { VALUE_PAIR *vp; uint8_t *buff, *p; @@ -243,9 +258,22 @@ void eaptls_gen_eap_key(RADIUS_PACKET *packet, SSL *ssl, uint32_t header) *p++ = header & 0xff; - SSL_get_client_random(ssl, p, SSL3_RANDOM_SIZE); +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + { + uint8_t const context[] = { header }; + + if (SSL_export_keying_material(s, p, 2 * SSL3_RANDOM_SIZE, + FR_TLS_EXPORTER_METHOD_ID, sizeof(FR_TLS_EXPORTER_METHOD_ID)-1, + context, 1, 1) != 1) { + ERROR("Failed generating keying material"); + return; + } + } +#else + SSL_get_client_random(s, p, SSL3_RANDOM_SIZE); p += SSL3_RANDOM_SIZE; - SSL_get_server_random(ssl, p, SSL3_RANDOM_SIZE); + SSL_get_server_random(s, p, SSL3_RANDOM_SIZE); +#endif vp->vp_octets = buff; fr_pair_add(&packet->vps, vp); diff --git a/src/modules/rlm_eap/types/rlm_eap_peap/rlm_eap_peap.c b/src/modules/rlm_eap/types/rlm_eap_peap/rlm_eap_peap.c index 3d23322663c..d3be348ce3b 100644 --- a/src/modules/rlm_eap/types/rlm_eap_peap/rlm_eap_peap.c +++ b/src/modules/rlm_eap/types/rlm_eap_peap/rlm_eap_peap.c @@ -199,11 +199,6 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) handler->opaque = ((void *)ssn); - /* - * Set up type-specific information. - */ - ssn->prf_label = "client EAP encryption"; - /* * As it is a poorly designed protocol, PEAP uses * bits in the TLS header to indicate PEAP @@ -373,6 +368,25 @@ static int mod_process(void *arg, eap_handler_t *handler) RDEBUG2("No saved attributes in the original Access-Accept"); } + /* + * Set the label based on the TLS version negotiated in the handshake. + */ + switch (tls_session->info.version) { +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + case TLS1_3_VERSION: + tls_session->label = "EXPORTER_EAP_TLS_Key_Material"; + break; +#endif + case TLS1_2_VERSION: + case TLS1_1_VERSION: + case TLS1_VERSION: + tls_session->label = "client EAP encryption"; + break; + default: + break; + } + + /* * Success: Automatically return MPPE keys. */ diff --git a/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.c b/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.c index 4d41cd42e6e..305ba21cfcd 100644 --- a/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.c +++ b/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.c @@ -99,11 +99,6 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) handler->opaque = ((void *)ssn); - /* - * Set up type-specific information. - */ - ssn->prf_label = "client EAP encryption"; - /* * TLS session initialization is over. Now handle TLS * related handshaking or application data. @@ -195,6 +190,24 @@ static int CC_HINT(nonnull) mod_process(void *type_arg, eap_handler_t *handler) /* success */ } + /* + * Set the label based on the TLS version negotiated in the handshake. + */ + switch (tls_session->info.version) { +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + case TLS1_3_VERSION: + tls_session->label = "EXPORTER_EAP_TLS_Key_Material"; + break; +#endif + case TLS1_2_VERSION: + case TLS1_1_VERSION: + case TLS1_VERSION: + tls_session->label = "client EAP encryption"; + break; + default: + break; + } + /* * Success: Automatically return MPPE keys. */ diff --git a/src/modules/rlm_eap/types/rlm_eap_ttls/rlm_eap_ttls.c b/src/modules/rlm_eap/types/rlm_eap_ttls/rlm_eap_ttls.c index a3c575bceb5..e9b96b1c810 100644 --- a/src/modules/rlm_eap/types/rlm_eap_ttls/rlm_eap_ttls.c +++ b/src/modules/rlm_eap/types/rlm_eap_ttls/rlm_eap_ttls.c @@ -188,11 +188,6 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) handler->opaque = ((void *)ssn); - /* - * Set up type-specific information. - */ - ssn->prf_label = "ttls keying material"; - /* * TLS session initialization is over. Now handle TLS * related handshaking or application data. @@ -271,6 +266,24 @@ static int mod_process(void *arg, eap_handler_t *handler) } do_keys: + /* + * Set the label based on the TLS version negotiated in the handshake. + */ + switch (tls_session->info.version) { +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + case TLS1_3_VERSION: + tls_session->label = "EXPORTER_EAP_TLS_Key_Material"; + break; +#endif + case TLS1_2_VERSION: + case TLS1_1_VERSION: + case TLS1_VERSION: + tls_session->label = "ttls keying material"; + break; + default: + break; + } + /* * Success: Automatically return MPPE keys. */ @@ -342,8 +355,7 @@ static int mod_process(void *arg, eap_handler_t *handler) * Success: Automatically return MPPE keys. */ case PW_CODE_ACCESS_ACCEPT: - ret = eaptls_success(handler, 0); - goto done; + goto do_keys; /* * No response packet, MUST be proxying it. diff --git a/src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c b/src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c index 627d722ed7d..40d398a9be8 100644 --- a/src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c +++ b/src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c @@ -405,7 +405,7 @@ static VALUE_PAIR *diameter2vp(REQUEST *request, REQUEST *fake, SSL *ssl, */ if (((vp->da->vendor == 0) && (vp->da->attr == PW_CHAP_CHALLENGE)) || ((vp->da->vendor == VENDORPEC_MICROSOFT) && (vp->da->attr == PW_MSCHAP_CHALLENGE))) { - uint8_t challenge[16]; + uint8_t challenge[17]; if ((vp->vp_length < 8) || (vp->vp_length > 16)) { @@ -415,8 +415,11 @@ static VALUE_PAIR *diameter2vp(REQUEST *request, REQUEST *fake, SSL *ssl, return NULL; } - eapttls_gen_challenge(ssl, challenge, - sizeof(challenge)); + /* + * TLSv1.3 exports a different key depending on the length + * requested so ask for *exactly* what the spec requires + */ + eapttls_gen_challenge(ssl, challenge, vp->vp_length + 1); if (memcmp(challenge, vp->vp_octets, vp->vp_length) != 0) {