From: sftcd Date: Wed, 20 Nov 2024 14:10:30 +0000 (+0000) Subject: ECH client side X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=85eb508640a2cfa12c1e7bf93a186b2b64cf75fb;p=thirdparty%2Fopenssl.git ECH client side Reviewed-by: Tomas Mraz Reviewed-by: Matt Caswell (Merged from https://github.com/openssl/openssl/pull/26011) --- diff --git a/apps/s_client.c b/apps/s_client.c index c2bda406b8f..85943c96752 100644 --- a/apps/s_client.c +++ b/apps/s_client.c @@ -107,6 +107,9 @@ static int keymatexportlen = 20; static BIO *bio_c_out = NULL; static int c_quiet = 0; static char *sess_out = NULL; +# ifndef OPENSSL_NO_ECH +static char *ech_config_list = NULL; +# endif static SSL_SESSION *psksess = NULL; static void print_stuff(BIO *berr, SSL *con, int full); @@ -517,6 +520,9 @@ typedef enum OPTION_choice { OPT_ENABLE_CLIENT_RPK, OPT_SCTP_LABEL_BUG, OPT_KTLS, +# ifndef OPENSSL_NO_ECH + OPT_ECHCONFIGLIST, +# endif OPT_R_ENUM, OPT_PROV_ENUM } OPTION_CHOICE; @@ -709,6 +715,10 @@ const OPTIONS s_client_options[] = { {"enable_pha", OPT_ENABLE_PHA, '-', "Enable post-handshake-authentication"}, {"enable_server_rpk", OPT_ENABLE_SERVER_RPK, '-', "Enable raw public keys (RFC7250) from the server"}, {"enable_client_rpk", OPT_ENABLE_CLIENT_RPK, '-', "Enable raw public keys (RFC7250) from the client"}, +# ifndef OPENSSL_NO_ECH + {"ech_config_list", OPT_ECHCONFIGLIST, 's', + "Set ECHConfigList, value is base 64 encoded ECHConfigList"}, +# endif #ifndef OPENSSL_NO_SRTP {"use_srtp", OPT_USE_SRTP, 's', "Offer SRTP key management with a colon-separated profile list"}, @@ -1502,6 +1512,11 @@ int s_client_main(int argc, char **argv) case OPT_SERVERNAME: servername = opt_arg(); break; +# ifndef OPENSSL_NO_ECH + case OPT_ECHCONFIGLIST: + ech_config_list = opt_arg(); + break; +# endif case OPT_NOSERVERNAME: noservername = 1; break; @@ -2104,6 +2119,13 @@ int s_client_main(int argc, char **argv) } } +# ifndef OPENSSL_NO_ECH + if (ech_config_list != NULL + && SSL_set1_ech_config_list(con, (unsigned char *)ech_config_list, + strlen(ech_config_list)) != 1) + goto end; +# endif + if (dane_tlsa_domain != NULL) { if (SSL_dane_enable(con, dane_tlsa_domain) <= 0) { BIO_printf(bio_err, "%s: Error enabling DANE TLSA " @@ -3375,6 +3397,101 @@ static void print_cert_key_info(BIO *bio, X509 *cert) OPENSSL_free(curve); } +# ifndef OPENSSL_NO_ECH +static void print_ech_retry_configs(BIO *bio, SSL *s) +{ + int ind, cnt = 0, has_priv, for_retry; + OSSL_ECHSTORE *es = NULL; + time_t secs = 0; + char *pn = NULL, *ec = NULL; + size_t rtlen = 0; + unsigned char *rtval = NULL; + BIO *biom = NULL; + + if (SSL_ech_get1_retry_config(s, &rtval, &rtlen) != 1) { + BIO_printf(bio, "ECH: Error getting retry-configs\n"); + return; + } + /* + * print nicely, note that any non-supported versions + * sent by server will have been filtered out by now + */ + if ((biom = BIO_new(BIO_s_mem())) == NULL + || BIO_write(biom, rtval, rtlen) <= 0 + || (es = OSSL_ECHSTORE_new(NULL, NULL)) == NULL + || OSSL_ECHSTORE_read_echconfiglist(es, biom) != 1) { + BIO_printf(bio, "ECH: Error loading retry-configs\n"); + goto end; + } + if (OSSL_ECHSTORE_num_entries(es, &cnt) != 1) + goto end; + BIO_printf(bio, "ECH: Got %d retry-configs\n", cnt); + for (ind = 0; ind != cnt; ind++) { + if (OSSL_ECHSTORE_get1_info(es, ind, &secs, &pn, &ec, + &has_priv, &for_retry) != 1) { + BIO_printf(bio, "ECH: Error getting retry-config %d\n", ind); + goto end; + } + BIO_printf(bio, "ECH: entry: %d public_name: %s age: %d%s\n", + ind, pn, (int)secs, has_priv ? " (has private key)" : ""); + BIO_printf(bio, "ECH: \t%s\n", ec); + OPENSSL_free(pn); + pn = NULL; + OPENSSL_free(ec); + ec = NULL; + } +end: + BIO_free_all(biom); + OPENSSL_free(rtval); + OPENSSL_free(pn); + OPENSSL_free(ec); + OSSL_ECHSTORE_free(es); + return; +} + +static void print_ech_status(BIO *bio, SSL *s, int estat) +{ + switch (estat) { + case SSL_ECH_STATUS_NOT_TRIED: + BIO_printf(bio, "ECH: not tried: %d\n", estat); + break; + case SSL_ECH_STATUS_FAILED: + BIO_printf(bio, "ECH: tried but failed: %d\n", estat); + break; + case SSL_ECH_STATUS_FAILED_ECH: + BIO_printf(bio, "ECH: failed+retry-configs: %d\n", estat); + break; + case SSL_ECH_STATUS_SUCCESS: + BIO_printf(bio, "ECH: success: %d\n", estat); + break; + case SSL_ECH_STATUS_GREASE_ECH: + BIO_printf(bio, "ECH: GREASE+retry-configs%d\n", estat); + break; + case SSL_ECH_STATUS_BACKEND: + BIO_printf(bio, "ECH: BACKEND: %d\n", estat); + break; + case SSL_ECH_STATUS_GREASE: + BIO_printf(bio, "ECH: GREASE: %d\n", estat); + break; + case SSL_ECH_STATUS_BAD_CALL: + BIO_printf(bio, "ECH: BAD CALL: %d\n", estat); + break; + case SSL_ECH_STATUS_BAD_NAME: + BIO_printf(bio, "ECH: BAD NAME: %d\n", estat); + break; + case SSL_ECH_STATUS_NOT_CONFIGURED: + BIO_printf(bio, "ECH: NOT CONFIGURED: %d\n", estat); + break; + case SSL_ECH_STATUS_FAILED_ECH_BAD_NAME: + BIO_printf(bio, "ECH: failed+retry-configs: %d\n", estat); + break; + default: + BIO_printf(bio, "ECH: unexpected status: %d\n", estat); + } + return; +} +# endif + static void print_stuff(BIO *bio, SSL *s, int full) { X509 *peer = NULL; @@ -3618,6 +3735,26 @@ static void print_stuff(BIO *bio, SSL *s, int full) OPENSSL_free(exportedkeymat); } BIO_printf(bio, "---\n"); +# ifndef OPENSSL_NO_ECH + { + char *inner = NULL, *outer = NULL; + int estat = 0; + + estat = SSL_ech_get1_status(s, &inner, &outer); + print_ech_status(bio, s, estat); + if (estat == SSL_ECH_STATUS_SUCCESS) { + BIO_printf(bio, "ECH: inner: %s\n", inner); + BIO_printf(bio, "ECH: outer: %s\n", outer); + } + if (estat == SSL_ECH_STATUS_FAILED_ECH + || estat == SSL_ECH_STATUS_FAILED_ECH_BAD_NAME) + print_ech_retry_configs(bio, s); + OPENSSL_free(inner); + OPENSSL_free(outer); + } + BIO_printf(bio, "---\n"); +# endif + /* flush, or debugging output gets mixed with http response */ (void)BIO_flush(bio); } diff --git a/doc/man1/openssl-s_client.pod.in b/doc/man1/openssl-s_client.pod.in index 82c5917f608..06c3f2ed68c 100644 --- a/doc/man1/openssl-s_client.pod.in +++ b/doc/man1/openssl-s_client.pod.in @@ -122,6 +122,7 @@ B B [B<-enable_server_rpk>] [B<-enable_client_rpk>] [I:I] +[B<-ech_config_list>] =head1 DESCRIPTION @@ -176,6 +177,15 @@ specified with this flag and issues an HTTP CONNECT command to connect to the desired server. If the host string is an IPv6 address, it must be enclosed in C<[> and C<]>. +=item B<-ech_config_list> I + +Specifies the ECHConfigList value to use for Encrypted Client Hello (ECH) for +the TLS session. The value must be a base64 encoded ECHConfigList. + +The ECHConfigList structure is defined in RFC XXXX. (That's currently in +L) +=for comment TODO(ECH): replace XXXX when RFC published. + =item B<-proxy_user> I When used with the B<-proxy> flag, the program will attempt to authenticate diff --git a/include/internal/ech_helpers.h b/include/internal/ech_helpers.h new file mode 100644 index 00000000000..b71029857b1 --- /dev/null +++ b/include/internal/ech_helpers.h @@ -0,0 +1,27 @@ +/* + * Copyright 2024 The OpenSSL Project Authors. All Rights Reserved. + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +/* + * These functions are ECH helpers that are used within the library but + * also by ECH test code. + */ + +#ifndef OPENSSL_ECH_HELPERS_H +# define OPENSSL_ECH_HELPERS_H +# pragma once + +# ifndef OPENSSL_NO_ECH + +int ossl_ech_get_sh_offsets(const unsigned char *sh, size_t sh_len, + size_t *exts, size_t *echoffset, + uint16_t *echtype); +int ossl_ech_make_enc_info(unsigned char *encoding, size_t encoding_length, + unsigned char *info, size_t *info_len); + +# endif +#endif diff --git a/ssl/ech/ech_helper.c b/ssl/ech/ech_helper.c index bbe62eee21b..6f900408ef5 100644 --- a/ssl/ech/ech_helper.c +++ b/ssl/ech/ech_helper.c @@ -11,5 +11,141 @@ #include #include "../ssl_local.h" #include "ech_local.h" +#include "internal/ech_helpers.h" -/* TODO(ECH): move code that's used by internals and test here */ +/* TODO(ECH): move more code that's used by internals and test here */ + +/* used in ECH crypto derivations (odd format for EBCDIC goodness) */ +/* "tls ech" */ +static const char OSSL_ECH_CONTEXT_STRING[] = "\x74\x6c\x73\x20\x65\x63\x68"; + +/* + * Given a SH (or HRR) find the offsets of the ECH (if any) + * sh is the SH buffer + * sh_len is the length of the SH + * exts points to offset of extensions + * echoffset points to offset of ECH + * echtype points to the ext type of the ECH + * return 1 for success, zero otherwise + * + * Offsets are returned to the type or length field in question. + * Offsets are set to zero if relevant thing not found. + * + * Note: input here is untrusted! + */ +int ossl_ech_get_sh_offsets(const unsigned char *sh, size_t sh_len, + size_t *exts, size_t *echoffset, + uint16_t *echtype) +{ + unsigned int etype = 0, pi_tmp = 0; + const unsigned char *pp_tmp = NULL, *shstart = NULL; + PACKET pkt, session_id, extpkt, oneext; + size_t extlens = 0; + int done = 0; +#ifdef OSSL_ECH_SUPERVERBOSE + size_t echlen = 0; /* length of ECH, including type & ECH-internal length */ + size_t sessid_offset = 0, sessid_len = 0; +#endif + + if (sh == NULL || sh_len == 0 || exts == NULL || echoffset == NULL + || echtype == NULL) + return 0; + *exts = *echoffset = *echtype = 0; + if (!PACKET_buf_init(&pkt, sh, sh_len)) + return 0; + shstart = PACKET_data(&pkt); + if (!PACKET_get_net_2(&pkt, &pi_tmp)) + return 0; + /* + * TODO(ECH): we've had a TLSv1.2 test in the past where we add an + * ECH to a TLSv1.2 CH to ensure server code ignores that properly. + * We might or might not keep that, if we don't then the test below + * should allow TLSv1.3 only. + */ + /* if we're not TLSv1.2+ then we can bail, but it's not an error */ + if (pi_tmp != TLS1_2_VERSION && pi_tmp != TLS1_3_VERSION) + return 1; + if (!PACKET_get_bytes(&pkt, &pp_tmp, SSL3_RANDOM_SIZE) +#ifdef OSSL_ECH_SUPERVERBOSE + || (sessid_offset = PACKET_data(&pkt) - shstart) == 0 +#endif + || !PACKET_get_length_prefixed_1(&pkt, &session_id) +#ifdef OSSL_ECH_SUPERVERBOSE + || (sessid_len = PACKET_remaining(&session_id)) == 0 +#endif + || !PACKET_get_net_2(&pkt, &pi_tmp) /* ciphersuite */ + || !PACKET_get_1(&pkt, &pi_tmp) /* compression */ + || (*exts = PACKET_data(&pkt) - shstart) == 0 + || !PACKET_as_length_prefixed_2(&pkt, &extpkt) + || PACKET_remaining(&pkt) != 0) + return 0; + extlens = PACKET_remaining(&extpkt); + if (extlens == 0) /* not an error, in theory */ + return 1; + while (PACKET_remaining(&extpkt) > 0 && done < 1) { + if (!PACKET_get_net_2(&extpkt, &etype) + || !PACKET_get_length_prefixed_2(&extpkt, &oneext)) + return 0; + if (etype == TLSEXT_TYPE_ech) { + if (PACKET_remaining(&oneext) != 8) + return 0; + *echoffset = PACKET_data(&oneext) - shstart - 4; + *echtype = etype; +#ifdef OSSL_ECH_SUPERVERBOSE + echlen = PACKET_remaining(&oneext) + 4; /* type/length included */ +#endif + done++; + } + } +#ifdef OSSL_ECH_SUPERVERBOSE + OSSL_TRACE_BEGIN(TLS) { + BIO_printf(trc_out, "orig SH/ECH type: %4x\n", *echtype); + } OSSL_TRACE_END(TLS); + ossl_ech_pbuf("orig SH", (unsigned char *)sh, sh_len); + ossl_ech_pbuf("orig SH session_id", (unsigned char *)sh + sessid_offset, + sessid_len); + ossl_ech_pbuf("orig SH exts", (unsigned char *)sh + *exts, extlens); + ossl_ech_pbuf("orig SH/ECH ", (unsigned char *)sh + *echoffset, echlen); +#endif + return 1; +} + +/* + * make up HPKE "info" input as per spec + * encoding is the ECHconfig being used + * encodinglen is the length of ECHconfig being used + * info is a caller-allocated buffer for results + * info_len is the buffer size on input, used-length on output + * return 1 for success, zero otherwise + */ +int ossl_ech_make_enc_info(unsigned char *encoding, size_t encoding_length, + unsigned char *info, size_t *info_len) +{ + WPACKET ipkt = { 0 }; + BUF_MEM *ipkt_mem = NULL; + + if (encoding == NULL || info == NULL || info_len == NULL) + return 0; + if (*info_len < (sizeof(OSSL_ECH_CONTEXT_STRING) + encoding_length)) + return 0; + if ((ipkt_mem = BUF_MEM_new()) == NULL + || !WPACKET_init(&ipkt, ipkt_mem) + || !WPACKET_memcpy(&ipkt, OSSL_ECH_CONTEXT_STRING, + sizeof(OSSL_ECH_CONTEXT_STRING) - 1) + /* + * the zero valued octet is required by the spec, section 7.1 so + * a tiny bit better to add it explicitly rather than depend on + * the context string being NUL terminated + */ + || !WPACKET_put_bytes_u8(&ipkt, 0) + || !WPACKET_memcpy(&ipkt, encoding, encoding_length)) { + WPACKET_cleanup(&ipkt); + BUF_MEM_free(ipkt_mem); + return 0; + } + *info_len = sizeof(OSSL_ECH_CONTEXT_STRING) + encoding_length; + memcpy(info, WPACKET_get_curr(&ipkt) - *info_len, *info_len); + WPACKET_cleanup(&ipkt); + BUF_MEM_free(ipkt_mem); + return 1; +} diff --git a/ssl/ech/ech_internal.c b/ssl/ech/ech_internal.c index 403beb66de5..a8d60102046 100644 --- a/ssl/ech/ech_internal.c +++ b/ssl/ech/ech_internal.c @@ -11,11 +11,65 @@ #include #include "../ssl_local.h" #include "ech_local.h" +#include +#include +#include #ifndef OPENSSL_NO_ECH +/* + * Strings used in ECH crypto derivations (odd format for EBCDIC goodness) + */ +/* "ech accept confirmation" */ +static const char OSSL_ECH_ACCEPT_CONFIRM_STRING[] = "\x65\x63\x68\x20\x61\x63\x63\x65\x70\x74\x20\x63\x6f\x6e\x66\x69\x72\x6d\x61\x74\x69\x6f\x6e"; +/* "hrr ech accept confirmation" */ +static const char OSSL_ECH_HRR_CONFIRM_STRING[] = "\x68\x72\x72\x20\x65\x63\x68\x20\x61\x63\x63\x65\x70\x74\x20\x63\x6f\x6e\x66\x69\x72\x6d\x61\x74\x69\x6f\x6e"; + /* ECH internal API functions */ +# ifdef OSSL_ECH_SUPERVERBOSE +/* ascii-hex print a buffer nicely for debug/interop purposes */ +void ossl_ech_pbuf(const char *msg, const unsigned char *buf, const size_t blen) +{ + OSSL_TRACE_BEGIN(TLS) { + if (msg == NULL) { + BIO_printf(trc_out, "msg is NULL\n"); + } else if (buf == NULL || blen == 0) { + BIO_printf(trc_out, "%s: buf is %p\n", msg, (void *)buf); + BIO_printf(trc_out, "%s: blen is %lu\n", msg, (unsigned long)blen); + } else { + BIO_printf(trc_out, "%s (%lu)\n", msg, (unsigned long)blen); + BIO_dump_indent(trc_out, buf, blen, 4); + } + } OSSL_TRACE_END(TLS); + return; +} + +/* trace out transcript */ +void ossl_ech_ptranscript(SSL_CONNECTION *s, const char *msg) +{ + OSSL_TRACE_BEGIN(TLS) { + size_t hdatalen = 0; + unsigned char *hdata = NULL; + unsigned char ddata[EVP_MAX_MD_SIZE]; + size_t ddatalen; + + if (s == NULL) + return; + hdatalen = BIO_get_mem_data(s->s3.handshake_buffer, &hdata); + ossl_ech_pbuf(msg, hdata, hdatalen); + if (s->s3.handshake_dgst != NULL) { + if (ssl_handshake_hash(s, ddata, sizeof(ddata), &ddatalen) == 0) + BIO_printf(trc_out, "ssl_handshake_hash failed\n"); + ossl_ech_pbuf(msg, ddata, ddatalen); + } else { + BIO_printf(trc_out, "handshake_dgst is NULL\n"); + } + } OSSL_TRACE_END(TLS); + return; +} +# endif + static OSSL_ECHSTORE_ENTRY *ossl_echstore_entry_dup(const OSSL_ECHSTORE_ENTRY *orig) { OSSL_ECHSTORE_ENTRY *ret = NULL; @@ -148,7 +202,7 @@ int ossl_ech_conn_init(SSL_CONNECTION *s, SSL_CTX *ctx, s->ext.ech.attempted_cid = OSSL_ECH_config_id_unset; if (s->ext.ech.es != NULL) s->ext.ech.attempted = 1; - if (ctx->options & SSL_OP_ECH_GREASE) + if ((ctx->options & SSL_OP_ECH_GREASE) != 0) s->options |= SSL_OP_ECH_GREASE; return 1; err: @@ -160,4 +214,1194 @@ err: return 0; } +/* + * Assemble the set of ECHConfig values to return as retry-configs. + * The caller (stoc ECH extension handler) needs to OPENSSL_free the rcfgs + * The rcfgs itself is missing the outer length to make it an ECHConfigList + * so the caller adds that using WPACKET functions + */ +int ossl_ech_get_retry_configs(SSL_CONNECTION *s, unsigned char **rcfgs, + size_t *rcfgslen) +{ + OSSL_ECHSTORE *es = NULL; + OSSL_ECHSTORE_ENTRY *ee = NULL; + int i, num = 0; + size_t retslen = 0, encilen = 0; + unsigned char *tmp = NULL, *enci = NULL, *rets = NULL; + + if (s == NULL || rcfgs == NULL || rcfgslen == NULL) + return 0; + es = s->ext.ech.es; + if (es != NULL && es->entries != NULL) + num = sk_OSSL_ECHSTORE_ENTRY_num(es->entries); + for (i = 0; i != num; i++) { + ee = sk_OSSL_ECHSTORE_ENTRY_value(es->entries, i); + if (ee != NULL && ee->for_retry == OSSL_ECH_FOR_RETRY) { + encilen = ee->encoded_len; + if (encilen < 2) + goto err; + encilen -= 2; + enci = ee->encoded + 2; + tmp = (unsigned char *)OPENSSL_realloc(rets, retslen + encilen); + if (tmp == NULL) + goto err; + rets = tmp; + memcpy(rets + retslen, enci, encilen); + retslen += encilen; + } + } + *rcfgs = rets; + *rcfgslen = retslen; + return 1; +err: + OPENSSL_free(rets); + *rcfgs = NULL; + *rcfgslen = 0; + return 0; +} + +/* GREASEy constants */ +# define OSSL_ECH_MAX_GREASE_PUB 0x100 /* buffer size for 'enc' values */ +# define OSSL_ECH_MAX_GREASE_CT 0x200 /* max GREASEy ciphertext we'll emit */ + +/* + * Send a random value that looks like a real ECH. + * + * TODO(ECH): the "best" thing to do here is not yet known. For now, we do + * GREASEing as currently (20241102) done by chrome: + * - always HKDF-SHA256 + * - always AES-128-GCM + * - random config ID, even for requests to same server in same session + * - random enc + * - random looking payload, randomly 144, 176, 208, 240 bytes, no correlation with server + */ +int ossl_ech_send_grease(SSL_CONNECTION *s, WPACKET *pkt) +{ + OSSL_HPKE_SUITE hpke_suite_in = OSSL_HPKE_SUITE_DEFAULT; + OSSL_HPKE_SUITE *hpke_suite_in_p = NULL; + OSSL_HPKE_SUITE hpke_suite = OSSL_HPKE_SUITE_DEFAULT; + size_t pp_at_start = 0, pp_at_end = 0; + size_t senderpub_len = OSSL_ECH_MAX_GREASE_PUB; + size_t cipher_len = 0, cipher_len_jitter = 0; + unsigned char cid, senderpub[OSSL_ECH_MAX_GREASE_PUB]; + unsigned char cipher[OSSL_ECH_MAX_GREASE_CT]; + unsigned char *pp = WPACKET_get_curr(pkt); + + if (s == NULL) + return 0; + if (s->ssl.ctx == NULL) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return 0; + } + WPACKET_get_total_written(pkt, &pp_at_start); + /* randomly select cipher_len to be one of 144, 176, 208, 244 */ + if (RAND_bytes_ex(s->ssl.ctx->libctx, &cid, 1, + RAND_DRBG_STRENGTH) <= 0) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return 0; + } + cipher_len_jitter = cid % 4; + cipher_len = 144; + cipher_len += 32 * cipher_len_jitter; + /* generate a random (1 octet) client id */ + if (RAND_bytes_ex(s->ssl.ctx->libctx, &cid, 1, + RAND_DRBG_STRENGTH) <= 0) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return 0; + } + s->ext.ech.attempted_cid = cid; + hpke_suite_in_p = &hpke_suite; + if (s->ext.ech.grease_suite != NULL) { + if (OSSL_HPKE_str2suite(s->ext.ech.grease_suite, &hpke_suite_in) != 1) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return 0; + } + hpke_suite_in_p = &hpke_suite_in; + } + if (OSSL_HPKE_get_grease_value(hpke_suite_in_p, &hpke_suite, + senderpub, &senderpub_len, + cipher, cipher_len, + s->ssl.ctx->libctx, NULL) != 1) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return 0; + } + if (!WPACKET_put_bytes_u16(pkt, s->ext.ech.attempted_type) + || !WPACKET_start_sub_packet_u16(pkt) + || !WPACKET_put_bytes_u8(pkt, OSSL_ECH_OUTER_CH_TYPE) + || !WPACKET_put_bytes_u16(pkt, hpke_suite.kdf_id) + || !WPACKET_put_bytes_u16(pkt, hpke_suite.aead_id) + || !WPACKET_put_bytes_u8(pkt, cid) + || !WPACKET_sub_memcpy_u16(pkt, senderpub, senderpub_len) + || !WPACKET_sub_memcpy_u16(pkt, cipher, cipher_len) + || !WPACKET_close(pkt) + ) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return 0; + } + /* record the ECH sent so we can re-tx same if we hit an HRR */ + OPENSSL_free(s->ext.ech.sent); + WPACKET_get_total_written(pkt, &pp_at_end); + s->ext.ech.sent_len = pp_at_end - pp_at_start; + s->ext.ech.sent = OPENSSL_malloc(s->ext.ech.sent_len); + if (s->ext.ech.sent == NULL) { + s->ext.ech.sent_len = 0; + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return 0; + } + memcpy(s->ext.ech.sent, pp, s->ext.ech.sent_len); + s->ext.ech.grease = OSSL_ECH_IS_GREASE; + OSSL_TRACE_BEGIN(TLS) { + BIO_printf(trc_out, "ECH - sending GREASE\n"); + } OSSL_TRACE_END(TLS); + return 1; +} + +/* + * Search the ECH store for one that's a match. If no outer_name was set via + * API then we just take the 1st match where we locally support the HPKE suite. + * If OTOH, an outer_name was provided via API then we prefer the first that + * matches that. Name comparison is via case-insensitive exact matches. + */ +int ossl_ech_pick_matching_cfg(SSL_CONNECTION *s, OSSL_ECHSTORE_ENTRY **ee, + OSSL_HPKE_SUITE *suite) +{ + int namematch = 0, nameoverride = 0, suitematch = 0, num, cind = 0; + unsigned int csuite = 0, tsuite = 0, hnlen = 0; + OSSL_ECHSTORE_ENTRY *lee = NULL, *tee = NULL; + OSSL_ECHSTORE *es = NULL; + char *hn = NULL; + + if (s == NULL || s->ext.ech.es == NULL || ee == NULL || suite == NULL) + return 0; + es = s->ext.ech.es; + if (es->entries == NULL) + return 0; + num = sk_OSSL_ECHSTORE_ENTRY_num(es->entries); + /* allow API-set pref to override */ + hn = s->ext.ech.outer_hostname; + hnlen = (hn == NULL ? 0 : strlen(hn)); + if (hnlen != 0) + nameoverride = 1; + if (s->ext.ech.no_outer == 1) { + hn = NULL; + hnlen = 0; + nameoverride = 1; + } + for (cind = 0; cind != num && (suitematch == 0 || namematch == 0); cind++) { + lee = sk_OSSL_ECHSTORE_ENTRY_value(es->entries, cind); + if (lee == NULL || lee->version != OSSL_ECH_RFCXXXX_VERSION) + continue; + if (nameoverride == 1 && hnlen == 0) { + namematch = 1; + } else { + namematch = 0; + if (hnlen == 0 + || (lee->public_name != NULL + && strlen(lee->public_name) == hnlen + && !OPENSSL_strncasecmp(hn, (char *)lee->public_name, + hnlen))) + namematch = 1; + } + suitematch = 0; + for (csuite = 0; csuite != lee->nsuites && suitematch == 0; csuite++) { + if (OSSL_HPKE_suite_check(lee->suites[csuite]) == 1) { + if (tee == NULL) { /* remember 1st suite match for override */ + tee = lee; + tsuite = csuite; + } + suitematch = 1; + if (namematch == 1) { /* pick this one if both "fit" */ + *suite = lee->suites[csuite]; + *ee = lee; + break; + } + } + } + } + if (nameoverride == 1 && (namematch == 0 || suitematch == 0)) { + *suite = tee->suites[tsuite]; + *ee = tee; + } else if (namematch == 0 || suitematch == 0) { + /* no joy */ + return 0; + } + if (*ee == NULL || (*ee)->pub_len == 0 || (*ee)->pub == NULL) + return 0; + return 1; +} + +/* Make up the ClientHelloInner and EncodedClientHelloInner buffers */ +int ossl_ech_encode_inner(SSL_CONNECTION *s) +{ + int rv = 0; + size_t nraws = 0, ind = 0, innerlen = 0; + unsigned char *innerch_full = NULL; + WPACKET inner = { 0 }; /* "fake" pkt for inner */ + BUF_MEM *inner_mem = NULL; + RAW_EXTENSION *raws = NULL; + + /* basic checks */ + if (s == NULL) + return 0; + if (s->ext.ech.es == NULL || s->clienthello == NULL) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return 0; + } + if ((inner_mem = BUF_MEM_new()) == NULL + || !WPACKET_init(&inner, inner_mem) + /* We don't add the type and 3-octet header as usually done */ + /* Add ver/rnd/sess-id/suites to buffer */ + || !WPACKET_put_bytes_u16(&inner, s->client_version) + || !WPACKET_memcpy(&inner, s->ext.ech.client_random, SSL3_RANDOM_SIZE) + /* Session ID is forced to zero in the encoded inner */ + || !WPACKET_sub_memcpy_u8(&inner, NULL, 0) + /* Ciphers supported */ + || !WPACKET_start_sub_packet_u16(&inner) + || !ssl_cipher_list_to_bytes(s, SSL_get_ciphers(&s->ssl), &inner) + || !WPACKET_close(&inner) + /* COMPRESSION */ + || !WPACKET_start_sub_packet_u8(&inner) + /* Add the NULL compression method */ + || !WPACKET_put_bytes_u8(&inner, 0) + || !WPACKET_close(&inner)) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + /* Now handle extensions */ + if (!WPACKET_start_sub_packet_u16(&inner)) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + /* Grab a pointer to the already constructed extensions */ + raws = s->clienthello->pre_proc_exts; + nraws = s->clienthello->pre_proc_exts_len; + if (raws == NULL || nraws < TLSEXT_IDX_num_builtins) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + /* We put ECH-compressed stuff first (if any), because we can */ + if (s->ext.ech.n_outer_only > 0) { + if (!WPACKET_put_bytes_u16(&inner, TLSEXT_TYPE_outer_extensions) + || !WPACKET_start_sub_packet_u16(&inner) + /* redundant encoding of more-or-less the same thing */ + || !WPACKET_start_sub_packet_u8(&inner)) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + /* add the types for each of the compressed extensions now */ + for (ind = 0; ind != s->ext.ech.n_outer_only; ind++) { + if (!WPACKET_put_bytes_u16(&inner, s->ext.ech.outer_only[ind])) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + } + /* close the 2 sub-packets with the compressed types */ + if (!WPACKET_close(&inner) || !WPACKET_close(&inner)) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + } + /* now copy the rest, as "proper" exts, into encoded inner */ + for (ind = 0; ind < TLSEXT_IDX_num_builtins; ind++) { + if (raws[ind].present == 0 || ossl_ech_2bcompressed(ind) == 1) + continue; + if (!WPACKET_put_bytes_u16(&inner, raws[ind].type) + || !WPACKET_sub_memcpy_u16(&inner, PACKET_data(&raws[ind].data), + PACKET_remaining(&raws[ind].data))) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + } + if (!WPACKET_close(&inner) /* close the encoded inner packet */ + || !WPACKET_get_length(&inner, &innerlen)) { /* len for inner CH */ + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + innerch_full = OPENSSL_malloc(innerlen); + if (innerch_full == NULL) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + memcpy(innerch_full, inner_mem->data, innerlen); + OPENSSL_free(s->ext.ech.encoded_innerch); + s->ext.ech.encoded_innerch = innerch_full; + s->ext.ech.encoded_innerch_len = innerlen; + /* and clean up */ + rv = 1; +err: + WPACKET_cleanup(&inner); + BUF_MEM_free(inner_mem); + return rv; +} + +/* + * Find ECH acceptance signal in a SH + * hrr is 1 if this is for an HRR, otherwise for SH + * acbuf is (a preallocated) 8 octet buffer + * shbuf is a pointer to the SH buffer + * shlen is the length of the SH buf + * return: 1 for success, 0 otherwise + */ +int ossl_ech_find_confirm(SSL_CONNECTION *s, int hrr, + unsigned char acbuf[OSSL_ECH_SIGNAL_LEN], + const unsigned char *shbuf, const size_t shlen) +{ + PACKET pkt; + const unsigned char *acp = NULL, *pp_tmp; + unsigned int pi_tmp, etype, elen; + int done = 0; + + if (hrr == 0) { + acp = s->s3.server_random + SSL3_RANDOM_SIZE - OSSL_ECH_SIGNAL_LEN; + memcpy(acbuf, acp, OSSL_ECH_SIGNAL_LEN); + return 1; + } else { + if (!PACKET_buf_init(&pkt, shbuf, shlen) + || !PACKET_get_net_2(&pkt, &pi_tmp) + || !PACKET_get_bytes(&pkt, &pp_tmp, SSL3_RANDOM_SIZE) + || !PACKET_get_1(&pkt, &pi_tmp) /* sessid len */ + || !PACKET_get_bytes(&pkt, &pp_tmp, pi_tmp) /* sessid */ + || !PACKET_get_net_2(&pkt, &pi_tmp) /* ciphersuite */ + || !PACKET_get_1(&pkt, &pi_tmp) /* compression */ + || !PACKET_get_net_2(&pkt, &pi_tmp)) /* len(extensions) */ + return 0; + while (PACKET_remaining(&pkt) > 0 && done < 1) { + if (!PACKET_get_net_2(&pkt, &etype) + || !PACKET_get_net_2(&pkt, &elen)) + return 0; + if (etype == TLSEXT_TYPE_ech) { + if (elen != OSSL_ECH_SIGNAL_LEN + || !PACKET_get_bytes(&pkt, &acp, elen)) + return 0; + memcpy(acbuf, acp, elen); + done++; + } else { + if (!PACKET_get_bytes(&pkt, &pp_tmp, elen)) + return 0; + } + } + return done; + } + return 0; +} + +/* + * make up a buffer to use to reset transcript + * for_hrr is 1 if we've just seen HRR, 0 otherwise + * shbuf is the output buffer + * shlen is the length of that buffer + * tbuf is the output buffer + * tlen is the length of that buffer + * chend returns the offset of the end of the last CH in the buffer + * fixedshbuf_len returns the fixed up length of the SH + * return 1 for good, 0 otherwise + */ +int ossl_ech_make_transcript_buffer(SSL_CONNECTION *s, int for_hrr, + const unsigned char *shbuf, size_t shlen, + unsigned char **tbuf, size_t *tlen, + size_t *chend, size_t *fixedshbuf_len) +{ + unsigned char *fixedshbuf = NULL, *hashin = NULL, hashval[EVP_MAX_MD_SIZE]; + unsigned int hashlen = 0, hashin_len = 0; + EVP_MD_CTX *ctx = NULL; + EVP_MD *md = NULL; + WPACKET tpkt = { 0 }, shpkt = { 0 }; + BUF_MEM *tpkt_mem = NULL, *shpkt_mem = NULL; + + /* + * SH preamble has bad length at this point on server + * and is missing on client so we'll fix + */ + if ((shpkt_mem = BUF_MEM_new()) == NULL + || !WPACKET_init(&shpkt, shpkt_mem)) { + BUF_MEM_free(shpkt_mem); + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + if (!WPACKET_put_bytes_u8(&shpkt, SSL3_MT_SERVER_HELLO) + || (s->server == 1 && !WPACKET_put_bytes_u24(&shpkt, shlen - 4)) + || (s->server == 1 && !WPACKET_memcpy(&shpkt, shbuf + 4, shlen -4)) + || (s->server == 0 && !WPACKET_put_bytes_u24(&shpkt, shlen)) + || (s->server == 0 && !WPACKET_memcpy(&shpkt, shbuf, shlen)) + || !WPACKET_get_length(&shpkt, fixedshbuf_len)) { + BUF_MEM_free(shpkt_mem); + WPACKET_cleanup(&shpkt); + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + fixedshbuf = (unsigned char *)shpkt_mem->data; + WPACKET_cleanup(&shpkt); +# ifdef OSSL_ECH_SUPERVERBOSE + ossl_ech_pbuf("cx: fixed sh buf", fixedshbuf, *fixedshbuf_len); +# endif + if ((tpkt_mem = BUF_MEM_new()) == NULL + || !WPACKET_init(&tpkt, tpkt_mem)) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + if (s->hello_retry_request == SSL_HRR_NONE) { + if (!WPACKET_memcpy(&tpkt, s->ext.ech.innerch, + s->ext.ech.innerch_len) + || !WPACKET_get_length(&tpkt, chend) + || !WPACKET_memcpy(&tpkt, fixedshbuf, *fixedshbuf_len) + || !WPACKET_get_length(&tpkt, tlen)) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + *tbuf = OPENSSL_malloc(*tlen); + if (*tbuf == NULL) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + memcpy(*tbuf, WPACKET_get_curr(&tpkt) - *tlen, *tlen); + BUF_MEM_free(shpkt_mem); + WPACKET_cleanup(&tpkt); + BUF_MEM_free(tpkt_mem); + return 1; + } + /* everything below only applies if we're at some stage in doing HRR */ + if ((md = (EVP_MD *)ssl_handshake_md(s)) == NULL) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + if (for_hrr == 0) { /* after 2nd SH rx'd */ + hashin = s->ext.ech.innerch1; + hashin_len = s->ext.ech.innerch1_len; + } else { /* after HRR rx'd */ + hashin = s->ext.ech.innerch; + hashin_len = s->ext.ech.innerch_len; + OPENSSL_free(s->ext.ech.kepthrr); + /* stash this SH/HRR for later */ + s->ext.ech.kepthrr = OPENSSL_malloc(*fixedshbuf_len); + if (s->ext.ech.kepthrr == NULL) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + memcpy(s->ext.ech.kepthrr, fixedshbuf, *fixedshbuf_len); + s->ext.ech.kepthrr_len = *fixedshbuf_len; + } +# ifdef OSSL_ECH_SUPERVERBOSE + ossl_ech_pbuf("cx: ch2hash", hashin, hashin_len); +# endif + if ((ctx = EVP_MD_CTX_new()) == NULL + || EVP_DigestInit_ex(ctx, md, NULL) <= 0 + || EVP_DigestUpdate(ctx, hashin, hashin_len) <= 0 + || EVP_DigestFinal_ex(ctx, hashval, &hashlen) <= 0) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + EVP_MD_CTX_free(ctx); + ctx = NULL; + if (!WPACKET_put_bytes_u8(&tpkt, SSL3_MT_MESSAGE_HASH) + || !WPACKET_put_bytes_u24(&tpkt, hashlen) + || !WPACKET_memcpy(&tpkt, hashval, hashlen)) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + if (for_hrr == 0) { /* after 2nd SH */ + if (!WPACKET_memcpy(&tpkt, s->ext.ech.kepthrr, + s->ext.ech.kepthrr_len) + || !WPACKET_memcpy(&tpkt, s->ext.ech.innerch, + s->ext.ech.innerch_len)) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + } + if (!WPACKET_get_length(&tpkt, chend) + || !WPACKET_memcpy(&tpkt, fixedshbuf, *fixedshbuf_len) + || !WPACKET_get_length(&tpkt, tlen)) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + *tbuf = OPENSSL_malloc(*tlen); + if (*tbuf == NULL) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + memcpy(*tbuf, WPACKET_get_curr(&tpkt) - *tlen, *tlen); + BUF_MEM_free(shpkt_mem); + WPACKET_cleanup(&tpkt); + BUF_MEM_free(tpkt_mem); + return 1; +err: + BUF_MEM_free(shpkt_mem); + BUF_MEM_free(tpkt_mem); + WPACKET_cleanup(&tpkt); + EVP_MD_CTX_free(ctx); + return 0; +} + +/* + * reset the handshake buffer for transcript after ECH is good + * buf is the data to put into the transcript (inner CH if no HRR) + * blen is the length of buf + * return 1 for success + */ +int ossl_ech_reset_hs_buffer(SSL_CONNECTION *s, const unsigned char *buf, + size_t blen) +{ +# ifdef OSSL_ECH_SUPERVERBOSE + ossl_ech_pbuf("RESET transcript to", buf, blen); +# endif + if (s->s3.handshake_buffer != NULL) { + (void)BIO_set_close(s->s3.handshake_buffer, BIO_CLOSE); + BIO_free(s->s3.handshake_buffer); + s->s3.handshake_buffer = NULL; + } + EVP_MD_CTX_free(s->s3.handshake_dgst); + s->s3.handshake_dgst = NULL; + s->s3.handshake_buffer = BIO_new(BIO_s_mem()); + if (s->s3.handshake_buffer == NULL) + return 0; + /* providing nothing at all is a real use (mid-HRR) */ + if (buf != NULL && blen > 0) + BIO_write(s->s3.handshake_buffer, (void *)buf, (int)blen); + return 1; +} + +/* + * To control the number of zeros added after an EncodedClientHello - we pad + * to a target number of octets or, if there are naturally more, to a number + * divisible by the defined increment (we also do the spec-recommended SNI + * padding thing first) + */ +# define OSSL_ECH_PADDING_TARGET 128 /* ECH cleartext padded to at least this */ +# define OSSL_ECH_PADDING_INCREMENT 32 /* ECH padded to a multiple of this */ + +/* + * figure out how much padding for cleartext (on client) + * ee is the chosen ECHConfig + * return overall length to use including padding or zero on error + * + * "Recommended" inner SNI padding scheme as per spec (section 6.1.3) + * Might remove the mnl stuff later - overall message padding seems + * better really, BUT... we might want to keep this if others (e.g. + * browsers) do it so as to not stand out compared to them. + * + * The "+ 9" constant below is from the specifiation and is the + * expansion comparing a string length to an encoded SNI extension. + * Same is true of the 31/32 formula below. + * + * Note that the AEAD tag will be added later, so if we e.g. have + * a padded cleartext of 128 octets, the ciphertext will be 144 + * octets. + */ +static size_t ech_calc_padding(SSL_CONNECTION *s, OSSL_ECHSTORE_ENTRY *ee) +{ + int length_of_padding = 0, length_with_snipadding = 0; + int innersnipadding = 0, length_with_padding = 0; + size_t mnl = 0, clear_len = 0, isnilen = 0; + + if (s == NULL || ee == NULL) + return 0; + mnl = ee->max_name_length; + if (mnl != 0) { + /* do weirder padding if SNI present in inner */ + if (s->ext.hostname != NULL) { + isnilen = strlen(s->ext.hostname) + 9; + innersnipadding = (mnl > isnilen) ? mnl - isnilen : 0; + } else { + innersnipadding = mnl + 9; + } + } + /* padding is after the inner client hello has been encoded */ + length_with_snipadding = innersnipadding + s->ext.ech.encoded_innerch_len; + length_of_padding = 31 - ((length_with_snipadding - 1) % 32); + length_with_padding = s->ext.ech.encoded_innerch_len + + length_of_padding + innersnipadding; + /* + * Finally - make sure final result is longer than padding target + * and a multiple of our padding increment. + * TODO(ECH): This is a local addition - we might take it out if + * it makes us stick out; or if we take out the above more (uselessly:-) + * complicated scheme, we may only need this in the end. + */ + if (length_with_padding % OSSL_ECH_PADDING_INCREMENT) + length_with_padding += OSSL_ECH_PADDING_INCREMENT + - (length_with_padding % OSSL_ECH_PADDING_INCREMENT); + while (length_with_padding < OSSL_ECH_PADDING_TARGET) + length_with_padding += OSSL_ECH_PADDING_INCREMENT; + clear_len = length_with_padding; + OSSL_TRACE_BEGIN(TLS) { + BIO_printf(trc_out, "EAAE: padding: mnl: %zu, lws: %d " + "lop: %d, lwp: %d, clear_len: %zu, orig: %zu\n", + mnl, length_with_snipadding, length_of_padding, + length_with_padding, clear_len, + s->ext.ech.encoded_innerch_len); + } OSSL_TRACE_END(TLS); + return clear_len; +} + +/* + * Calculate AAD and do ECH encryption + * pkt is the packet to send + * return 1 for success, other otherwise + * + * 1. Make up the AAD: the encoded outer, with ECH ciphertext octets zero'd + * 2. Do the encryption + * 3. Put the ECH back into the encoding + * 4. Encode the outer (again!) + */ +int ossl_ech_aad_and_encrypt(SSL_CONNECTION *s, WPACKET *pkt) +{ + int rv = 0, hpke_mode = OSSL_HPKE_MODE_BASE; + OSSL_ECHSTORE_ENTRY *ee = NULL; + OSSL_HPKE_SUITE hpke_suite = OSSL_HPKE_SUITE_DEFAULT; + unsigned char config_id_to_use = 0x00, info[SSL3_RT_MAX_PLAIN_LENGTH]; + unsigned char *clear = NULL, *cipher = NULL, *aad = NULL, *mypub = NULL; + size_t cipherlen = 0, aad_len = 0, lenclen = 0, mypub_len = 0; + size_t info_len = SSL3_RT_MAX_PLAIN_LENGTH, clear_len = 0; + + if (s == NULL) + return 0; + if (s->ext.ech.es == NULL || s->ext.ech.es->entries == NULL + || pkt == NULL || s->ssl.ctx == NULL) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + rv = ossl_ech_pick_matching_cfg(s, &ee, &hpke_suite); + if (rv != 1 || ee == NULL) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + s->ext.ech.attempted_type = ee->version; + OSSL_TRACE_BEGIN(TLS) { + BIO_printf(trc_out, "EAAE: selected: version: %4x, config %2x\n", + ee->version, ee->config_id); + } OSSL_TRACE_END(TLS); + config_id_to_use = ee->config_id; /* if requested, use a random config_id instead */ + if ((s->ssl.ctx->options & SSL_OP_ECH_IGNORE_CID) != 0 + || (s->options & SSL_OP_ECH_IGNORE_CID) != 0) { + if (RAND_bytes_ex(s->ssl.ctx->libctx, &config_id_to_use, 1, + RAND_DRBG_STRENGTH) <= 0) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return 0; + } +# ifdef OSSL_ECH_SUPERVERBOSE + ossl_ech_pbuf("EAAE: random config_id", &config_id_to_use, 1); +# endif + } + s->ext.ech.attempted_cid = config_id_to_use; +# ifdef OSSL_ECH_SUPERVERBOSE + ossl_ech_pbuf("EAAE: peer pub", ee->pub, ee->pub_len); + ossl_ech_pbuf("EAAE: clear", s->ext.ech.encoded_innerch, + s->ext.ech.encoded_innerch_len); + ossl_ech_pbuf("EAAE: ECHConfig", ee->encoded, ee->encoded_len); +# endif + /* + * The AAD is the full outer client hello but with the correct number of + * zeros for where the ECH ciphertext octets will later be placed. So we + * add the ECH extension to the |pkt| but with zeros for ciphertext, that + * forms up the AAD, then after we've encrypted, we'll splice in the actual + * ciphertext. + * Watch out for the "4" offsets that remove the type and 3-octet length + * from the encoded CH as per the spec. + */ + clear_len = ech_calc_padding(s, ee); + if (clear_len == 0) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + lenclen = OSSL_HPKE_get_public_encap_size(hpke_suite); + if (s->ext.ech.hpke_ctx == NULL) { /* 1st CH */ + if (ossl_ech_make_enc_info(ee->encoded, ee->encoded_len, + info, &info_len) != 1) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } +# ifdef OSSL_ECH_SUPERVERBOSE + ossl_ech_pbuf("EAAE info", info, info_len); +# endif + s->ext.ech.hpke_ctx = OSSL_HPKE_CTX_new(hpke_mode, hpke_suite, + OSSL_HPKE_ROLE_SENDER, + NULL, NULL); + if (s->ext.ech.hpke_ctx == NULL) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + mypub = OPENSSL_malloc(lenclen); + if (mypub == NULL) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + mypub_len = lenclen; + rv = OSSL_HPKE_encap(s->ext.ech.hpke_ctx, mypub, &mypub_len, + ee->pub, ee->pub_len, info, info_len); + if (rv != 1) { + OPENSSL_free(mypub); + mypub = NULL; + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + s->ext.ech.pub = mypub; + s->ext.ech.pub_len = mypub_len; + } else { /* HRR - retrieve public */ + mypub = s->ext.ech.pub; + mypub_len = s->ext.ech.pub_len; + if (mypub == NULL || mypub_len == 0) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + } +# ifdef OSSL_ECH_SUPERVERBOSE + ossl_ech_pbuf("EAAE: mypub", mypub, mypub_len); + WPACKET_get_total_written(pkt, &aad_len); /* use aad_len for tracing */ + ossl_ech_pbuf("EAAE pkt b4", WPACKET_get_curr(pkt) - aad_len, aad_len); +# endif + cipherlen = OSSL_HPKE_get_ciphertext_size(hpke_suite, clear_len); + if (cipherlen <= clear_len || cipherlen > OSSL_ECH_MAX_PAYLOAD_LEN) { + SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION); + goto err; + } + cipher = OPENSSL_zalloc(cipherlen); + if (cipher == NULL) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + if (!WPACKET_put_bytes_u16(pkt, TLSEXT_TYPE_ech) + || !WPACKET_start_sub_packet_u16(pkt) + || !WPACKET_put_bytes_u8(pkt, OSSL_ECH_OUTER_CH_TYPE) + || !WPACKET_put_bytes_u16(pkt, hpke_suite.kdf_id) + || !WPACKET_put_bytes_u16(pkt, hpke_suite.aead_id) + || !WPACKET_put_bytes_u8(pkt, config_id_to_use) + || (s->hello_retry_request == SSL_HRR_PENDING + && !WPACKET_put_bytes_u16(pkt, 0x00)) /* no pub */ + || (s->hello_retry_request != SSL_HRR_PENDING + && !WPACKET_sub_memcpy_u16(pkt, mypub, mypub_len)) + || !WPACKET_sub_memcpy_u16(pkt, cipher, cipherlen) + || !WPACKET_close(pkt) + || !WPACKET_get_total_written(pkt, &aad_len) + || aad_len < 4) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + aad_len -= 4; /* aad starts after type + 3-octet len */ + aad = WPACKET_get_curr(pkt) - aad_len; + /* + * close the extensions of the CH - we skipped doing this + * earlier when encoding extensions, to allow for adding the + * ECH here (when doing ECH) - see tls_construct_extensions() + * towards the end + */ + if (!WPACKET_close(pkt)) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } +# ifdef OSSL_ECH_SUPERVERBOSE + ossl_ech_pbuf("EAAE: aad", aad, aad_len); +# endif + clear = OPENSSL_zalloc(clear_len); /* zeros incl. padding */ + if (clear == NULL) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + memcpy(clear, s->ext.ech.encoded_innerch, s->ext.ech.encoded_innerch_len); +# ifdef OSSL_ECH_SUPERVERBOSE + ossl_ech_pbuf("EAAE: padded clear", clear, clear_len); +# endif + rv = OSSL_HPKE_seal(s->ext.ech.hpke_ctx, cipher, &cipherlen, + aad, aad_len, clear, clear_len); + OPENSSL_free(clear); + if (rv != 1) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } +# ifdef OSSL_ECH_SUPERVERBOSE + ossl_ech_pbuf("EAAE: cipher", cipher, cipherlen); + ossl_ech_pbuf("EAAE: hpke mypub", mypub, mypub_len); +# endif + /* splice real ciphertext back in now */ + memcpy(aad + aad_len - cipherlen, cipher, cipherlen); +# ifdef OSSL_ECH_SUPERVERBOSE + /* re-use aad_len for tracing */ + WPACKET_get_total_written(pkt, &aad_len); + ossl_ech_pbuf("EAAE pkt aftr", WPACKET_get_curr(pkt) - aad_len, aad_len); +# endif + OPENSSL_free(cipher); + return 1; +err: + OPENSSL_free(cipher); + return 0; +} + +/* + * print info about the ECH-status of an SSL connection + * out is the BIO to use (e.g. stdout/whatever) + * selector OSSL_ECH_SELECT_ALL or just one of the SSL_ECH values + */ +static void ech_status_print(BIO *out, SSL_CONNECTION *s, int selector) +{ + int num = 0, i, has_priv, for_retry; + size_t j; + time_t secs = 0; + char *pn = NULL, *ec = NULL; + OSSL_ECHSTORE *es = NULL; + +# ifdef OSSL_ECH_SUPERVERBOSE + BIO_printf(out, "ech_status_print\n"); + BIO_printf(out, "s=%p\n", (void *)s); +# endif + BIO_printf(out, "ech_attempted=%d\n", s->ext.ech.attempted); + BIO_printf(out, "ech_attempted_type=0x%4x\n", + s->ext.ech.attempted_type); + if (s->ext.ech.attempted_cid == OSSL_ECH_config_id_unset) + BIO_printf(out, "ech_atttempted_cid is unset\n"); + else + BIO_printf(out, "ech_atttempted_cid=0x%02x\n", + s->ext.ech.attempted_cid); + BIO_printf(out, "ech_done=%d\n", s->ext.ech.done); + BIO_printf(out, "ech_grease=%d\n", s->ext.ech.grease); +# ifdef OSSL_ECH_SUPERVERBOSE + BIO_printf(out, "HRR=%d\n", s->hello_retry_request); +# endif + BIO_printf(out, "ech_backend=%d\n", s->ext.ech.backend); + BIO_printf(out, "ech_success=%d\n", s->ext.ech.success); + es = s->ext.ech.es; + if (es == NULL || es->entries == NULL) { + BIO_printf(out, "ECH cfg=NONE\n"); + } else { + num = sk_OSSL_ECHSTORE_ENTRY_num(es->entries); + BIO_printf(out, "%d ECHConfig values loaded\n", num); + for (i = 0; i != num; i++) { + if (selector != OSSL_ECHSTORE_ALL && selector != i) + continue; + BIO_printf(out, "cfg(%d): ", i); + if (OSSL_ECHSTORE_get1_info(es, i, &secs, &pn, &ec, + &has_priv, &for_retry) != 1) { + OPENSSL_free(pn); /* just in case */ + OPENSSL_free(ec); + continue; + } + BIO_printf(out, "ECH entry: %d public_name: %s age: %d%s\n", + i, pn, (int)secs, has_priv ? " (has private key)" : ""); + BIO_printf(out, "\t%s\n", ec); + OPENSSL_free(pn); + OPENSSL_free(ec); + } + } + if (s->ext.ech.returned) { + BIO_printf(out, "ret="); + for (j = 0; j != s->ext.ech.returned_len; j++) { + if (j != 0 && j % 16 == 0) + BIO_printf(out, "\n "); + BIO_printf(out, "%02x:", (unsigned)(s->ext.ech.returned[j])); + } + BIO_printf(out, "\n"); + } + return; +} + +/* size of string buffer returned via ECH callback */ +# define OSSL_ECH_PBUF_SIZE 8 * 1024 + +/* + * Swap the inner and outer after ECH success on the client + * return 0 for error, 1 for success + */ +int ossl_ech_swaperoo(SSL_CONNECTION *s) +{ + unsigned char *curr_buf = NULL, *new_buf = NULL; + size_t curr_buflen = 0, new_buflen = 0, outer_chlen = 0, other_octets = 0; + + if (s == NULL) + return 0; +# ifdef OSSL_ECH_SUPERVERBOSE + ossl_ech_ptranscript(s, "ech_swaperoo, b4"); +# endif + /* un-stash inner key share */ + if (s->ext.ech.tmp_pkey == NULL) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return 0; + } + EVP_PKEY_free(s->s3.tmp.pkey); + s->s3.tmp.pkey = s->ext.ech.tmp_pkey; + s->s3.group_id = s->ext.ech.group_id; + s->ext.ech.tmp_pkey = NULL; + /* + * TODO(ECH): I suggest re-factoring transcript handling (which + * is probably needed) after/with the PR that includes the server- + * side ECH code. That should be much easier as at that point the + * full set of tests can be run, whereas for now, we're limited + * to testing the client side really works via bodged s_client + * scripts, so there'd be a bigger risk of breaking something + * subtly if we try re-factor now. + * + * When not doing HRR... fix up the transcript to reflect the inner CH. + * If there's a client hello at the start of the buffer, then that's + * the outer CH and we want to replace that with the inner. We need to + * be careful that there could be a server hello following and can't + * lose that. + * + * For HRR... HRR processing code has already done the necessary. + */ + if (s->hello_retry_request == SSL_HRR_NONE) { + curr_buflen = BIO_get_mem_data(s->s3.handshake_buffer, + &curr_buf); + if (curr_buflen > 4 && curr_buf[0] == SSL3_MT_CLIENT_HELLO) { + outer_chlen = 1 + curr_buf[1] * 256 * 256 + + curr_buf[2] * 256 + curr_buf[3]; + if (outer_chlen > curr_buflen) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return 0; + } + other_octets = curr_buflen - outer_chlen; + if (other_octets > 0) { + new_buflen = s->ext.ech.innerch_len + other_octets; + new_buf = OPENSSL_malloc(new_buflen); + if (new_buf == NULL) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return 0; + } + if (s->ext.ech.innerch != NULL) /* asan check added */ + memcpy(new_buf, s->ext.ech.innerch, + s->ext.ech.innerch_len); + memcpy(new_buf + s->ext.ech.innerch_len, + &curr_buf[outer_chlen], other_octets); + } else { + new_buf = s->ext.ech.innerch; + new_buflen = s->ext.ech.innerch_len; + } + } else { + new_buf = s->ext.ech.innerch; + new_buflen = s->ext.ech.innerch_len; + } + /* + * And now reset the handshake transcript to our buffer + * Note ssl3_finish_mac isn't that great a name - that one just + * adds to the transcript but doesn't actually "finish" anything + */ + if (ssl3_init_finished_mac(s) == 0) { + OPENSSL_free(new_buf); + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return 0; + } + if (ssl3_finish_mac(s, new_buf, new_buflen) == 0) { + OPENSSL_free(new_buf); + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return 0; + } + OPENSSL_free(new_buf); + } +# ifdef OSSL_ECH_SUPERVERBOSE + ossl_ech_ptranscript(s, "ech_swaperoo, after"); +# endif + /* Declare victory! */ + s->ext.ech.attempted = 1; + s->ext.ech.success = 1; + s->ext.ech.done = 1; + s->ext.ech.grease = OSSL_ECH_NOT_GREASE; + /* time to call an ECH callback, if there's one */ + if (s->ext.ech.es != NULL && s->ext.ech.done == 1 + && s->hello_retry_request != SSL_HRR_PENDING + && s->ext.ech.cb != NULL) { + char pstr[OSSL_ECH_PBUF_SIZE + 1] = { 0 }; + BIO *biom = BIO_new(BIO_s_mem()); + unsigned int cbrv = 0; + + if (biom == NULL) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return 0; + } + ech_status_print(biom, s, OSSL_ECHSTORE_ALL); + BIO_read(biom, pstr, OSSL_ECH_PBUF_SIZE); + cbrv = s->ext.ech.cb(&s->ssl, pstr); + BIO_free(biom); + if (cbrv != 1) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return 0; + } + } + return 1; +} + +/* + * do the HKDF for ECH acceptance checking + * md is the h/s hash + * for_hrr is 1 if we're doing a HRR + * hashval/hashlen is the transcript hash + * hoval is the output, with the ECH acceptance signal + * return 1 for good, 0 for error + */ +static int ech_hkdf_extract_wrap(SSL_CONNECTION *s, EVP_MD *md, int for_hrr, + unsigned char *hashval, size_t hashlen, + unsigned char hoval[OSSL_ECH_SIGNAL_LEN]) +{ + int rv = 0; + unsigned char notsecret[EVP_MAX_MD_SIZE], zeros[EVP_MAX_MD_SIZE]; + size_t retlen = 0, labellen = 0; + EVP_PKEY_CTX *pctx = NULL; + const char *label = NULL; + unsigned char *p = NULL; + + if (for_hrr == 1) { + label = OSSL_ECH_HRR_CONFIRM_STRING; + } else { + label = OSSL_ECH_ACCEPT_CONFIRM_STRING; + } + labellen = strlen(label); +# ifdef OSSL_ECH_SUPERVERBOSE + ossl_ech_pbuf("cc: label", (unsigned char *)label, labellen); +# endif + memset(zeros, 0, EVP_MAX_MD_SIZE); + /* We don't seem to have an hkdf-extract that's exposed by libcrypto */ + pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); + if (pctx == NULL + || EVP_PKEY_derive_init(pctx) != 1 + || EVP_PKEY_CTX_hkdf_mode(pctx, + EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY) != 1 + || EVP_PKEY_CTX_hkdf_mode(pctx, + EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY) != 1 + || EVP_PKEY_CTX_set_hkdf_md(pctx, md) != 1) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + /* pick correct client_random */ + if (s->server) + p = s->s3.client_random; + else + p = s->ext.ech.client_random; +# ifdef OSSL_ECH_SUPERVERBOSE + ossl_ech_pbuf("cc: client_random", p, SSL3_RANDOM_SIZE); +# endif + if (EVP_PKEY_CTX_set1_hkdf_key(pctx, p, SSL3_RANDOM_SIZE) != 1 + || EVP_PKEY_CTX_set1_hkdf_salt(pctx, zeros, hashlen) != 1 + || EVP_PKEY_derive(pctx, NULL, &retlen) != 1 + || hashlen != retlen + || EVP_PKEY_derive(pctx, notsecret, &retlen) != 1) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } +# ifdef OSSL_ECH_SUPERVERBOSE + ossl_ech_pbuf("cc: notsecret", notsecret, hashlen); +# endif + if (hashlen < OSSL_ECH_SIGNAL_LEN + || !tls13_hkdf_expand(s, md, notsecret, + (const unsigned char *)label, labellen, + hashval, hashlen, hoval, + OSSL_ECH_SIGNAL_LEN, 1)) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + rv = 1; +err: + EVP_PKEY_CTX_free(pctx); + return rv; +} + +/* + * ECH accept_confirmation calculation + * for_hrr is 1 if this is for an HRR, otherwise for SH + * ac is (a caller allocated) 8 octet buffer + * shbuf is a pointer to the SH buffer (incl. the type+3-octet length) + * shlen is the length of the SH buf + * return: 1 for success, 0 otherwise + * + * This is a magic value in the ServerHello.random lower 8 octets + * that is used to signal that the inner worked. + * + * As per spec: + * + * accept_confirmation = HKDF-Expand-Label( + * HKDF-Extract(0, ClientHelloInner.random), + * "ech accept confirmation", + * transcript_ech_conf, + * 8) + * + * transcript_ech_conf = ClientHelloInner..ServerHello + * with last 8 octets of ServerHello.random==0x00 + * + * and with differences due to HRR + */ +int ossl_ech_calc_confirm(SSL_CONNECTION *s, int for_hrr, + unsigned char acbuf[OSSL_ECH_SIGNAL_LEN], + const unsigned char *shbuf, const size_t shlen) +{ + int rv = 0; + EVP_MD_CTX *ctx = NULL; + EVP_MD *md = NULL; + unsigned char *tbuf = NULL, *conf_loc = NULL; + unsigned char *fixedshbuf = NULL; + size_t fixedshbuf_len = 0, tlen = 0, chend = 0; + size_t shoffset = 6 + 24, extoffset = 0, echoffset = 0; + uint16_t echtype; + unsigned int hashlen = 0; + unsigned char hashval[EVP_MAX_MD_SIZE], hoval[EVP_MAX_MD_SIZE]; + + if ((md = (EVP_MD *)ssl_handshake_md(s)) == NULL) + goto err; + if (ossl_ech_make_transcript_buffer(s, for_hrr, shbuf, shlen, &tbuf, &tlen, + &chend, &fixedshbuf_len) != 1) + goto err; /* SSLfatal called already */ +# ifdef OSSL_ECH_SUPERVERBOSE + ossl_ech_pbuf("cx: tbuf b4", tbuf, tlen); +# endif + /* put zeros in correct place */ + if (for_hrr == 0) { /* zap magic octets at fixed place for SH */ + conf_loc = tbuf + chend + shoffset; + } else { + if (s->server == 1) { /* we get to say where we put ECH:-) */ + conf_loc = tbuf + tlen - OSSL_ECH_SIGNAL_LEN; + } else { + if (ossl_ech_get_sh_offsets(shbuf, shlen, &extoffset, + &echoffset, &echtype) != 1) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_R_ECH_REQUIRED); + goto err; + } + if (echoffset == 0 || extoffset == 0 || echtype == 0 + || tlen < (chend + 4 + echoffset + 4 + OSSL_ECH_SIGNAL_LEN)) { + /* No ECH found so we'll exit, but set random output */ + if (RAND_bytes_ex(s->ssl.ctx->libctx, acbuf, + OSSL_ECH_SIGNAL_LEN, + RAND_DRBG_STRENGTH) <= 0) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_R_ECH_REQUIRED); + goto err; + } + rv = 1; + goto err; + } + conf_loc = tbuf + chend + 4 + echoffset + 4; + } + } + memset(conf_loc, 0, OSSL_ECH_SIGNAL_LEN); +# ifdef OSSL_ECH_SUPERVERBOSE + ossl_ech_pbuf("cx: tbuf after", tbuf, tlen); +# endif + if ((ctx = EVP_MD_CTX_new()) == NULL + || EVP_DigestInit_ex(ctx, md, NULL) <= 0 + || EVP_DigestUpdate(ctx, tbuf, tlen) <= 0 + || EVP_DigestFinal_ex(ctx, hashval, &hashlen) <= 0) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + EVP_MD_CTX_free(ctx); + ctx = NULL; +# ifdef OSSL_ECH_SUPERVERBOSE + ossl_ech_pbuf("cx: hashval", hashval, hashlen); +# endif + if (ech_hkdf_extract_wrap(s, md, for_hrr, hashval, hashlen, hoval) != 1) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + memcpy(acbuf, hoval, OSSL_ECH_SIGNAL_LEN); /* Finally, set the output */ +# ifdef OSSL_ECH_SUPERVERBOSE + ossl_ech_pbuf("cx: result", acbuf, OSSL_ECH_SIGNAL_LEN); +# endif + /* put confirm value back into transcript vars */ + if (s->hello_retry_request != SSL_HRR_NONE && s->ext.ech.kepthrr != NULL + && for_hrr == 1 && s->server == 1) + memcpy(s->ext.ech.kepthrr + s->ext.ech.kepthrr_len + - OSSL_ECH_SIGNAL_LEN, acbuf, OSSL_ECH_SIGNAL_LEN); + memcpy(conf_loc, acbuf, OSSL_ECH_SIGNAL_LEN); + /* on a server, we need to reset the hs buffer now */ + if (s->server && s->hello_retry_request == SSL_HRR_NONE) + ossl_ech_reset_hs_buffer(s, s->ext.ech.innerch, s->ext.ech.innerch_len); + if (s->server && s->hello_retry_request == SSL_HRR_COMPLETE) + ossl_ech_reset_hs_buffer(s, tbuf, tlen - fixedshbuf_len); + rv = 1; +err: + OPENSSL_free(fixedshbuf); + OPENSSL_free(tbuf); + EVP_MD_CTX_free(ctx); + return rv; +} #endif diff --git a/ssl/ech/ech_local.h b/ssl/ech/ech_local.h index 387fe713b60..fdf7d45b0f6 100644 --- a/ssl/ech/ech_local.h +++ b/ssl/ech/ech_local.h @@ -41,7 +41,22 @@ /* value for not yet set ECH config_id */ # define OSSL_ECH_config_id_unset -1 +# define OSSL_ECH_OUTER_CH_TYPE 0 /* outer ECHClientHello enum */ +# define OSSL_ECH_INNER_CH_TYPE 1 /* inner ECHClientHello enum */ + # define OSSL_ECH_CIPHER_LEN 4 /* ECHCipher length (2 for kdf, 2 for aead) */ + +# define OSSL_ECH_SIGNAL_LEN 8 /* length of ECH acceptance signal */ + +# ifndef CLIENT_VERSION_LEN +/* + * This is the legacy version length, i.e. len(0x0303). The same + * label is used in e.g. test/sslapitest.c and elsewhere but not + * defined in a header file I could find. + */ +# define CLIENT_VERSION_LEN 2 +# endif + /* * Reminder of what goes in DNS for ECH RFC XXXX * @@ -84,7 +99,7 @@ typedef struct ossl_echext_st { DEFINE_STACK_OF(OSSL_ECHEXT) typedef struct ossl_echstore_entry_st { - uint16_t version; /* 0xff0d for draft-13 */ + uint16_t version; /* 0xfe0d for RFC XXXX */ char *public_name; size_t pub_len; unsigned char *pub; @@ -133,14 +148,19 @@ typedef struct ossl_ech_conn_st { */ char *former_inner; /* - * TODO(ECH): The next 4 buffers (and lengths) may change later - * if a better way to handle the mutiple transcripts needed is - * suggested/invented. I'd suggest we review these when that code - * is part of a PR (which won't be for a few PR's yet.) + * TODO(ECH): The next 4 buffers (and lengths) may change if a + * better way to handle the mutiple transcripts needed is + * suggested/invented. I suggest re-factoring transcript handling + * (which is probably needed) after/with the PR that includes the + * server-side ECH code. That should be much easier as at that point + * the full set of tests can be run, whereas for now, we're limited + * to testing the client side really works via bodged s_client + * scripts, so there'd be a bigger risk of breaking something + * subtly if we try re-factor now. */ /* * encoded inner ClientHello before/after ECH compression, which` - * is nitty/complex (to avoid repeating the same extenstion value + * is nitty/complex (to avoid repeating the same extension value * in outer and inner, thus saving bandwidth) but (re-)calculating * the compression is a pain, so we'll store those as we make them */ @@ -174,7 +194,7 @@ typedef struct ossl_ech_conn_st { * to avoid the need to change a couple of extension APIs. * TODO(ECH): check if there's another way to get that value */ - size_t ext_ind; + int ext_ind; /* ECH status vars */ int ch_depth; /* set during CH creation, 0: doing outer, 1: doing inner */ int attempted; /* 1 if ECH was or is being attempted, 0 otherwise */ @@ -207,6 +227,39 @@ typedef struct ossl_ech_conn_st { unsigned char client_random[SSL3_RANDOM_SIZE]; /* CH random */ } OSSL_ECH_CONN; +/* Return values from ossl_ech_same_ext */ +# define OSSL_ECH_SAME_EXT_ERR 0 /* bummer something wrong */ +# define OSSL_ECH_SAME_EXT_DONE 1 /* proceed with same value in inner/outer */ +# define OSSL_ECH_SAME_EXT_CONTINUE 2 /* generate a new value for outer CH */ + +/* + * During extension construction (in extensions_clnt.c and surprisingly also in + * extensions.c), we need to handle inner/outer CH cloning - ossl_ech_same_ext + * will (depending on compile time handling options) copy the value from + * CH.inner to CH.outer or else processing will continue, for a 2nd call, + * likely generating a fresh value for the outer CH. The fresh value could well + * be the same as in the inner. + * + * This macro should be called in each _ctos_ function that doesn't explicitly + * have special ECH handling. + * + * Note that the placement of this macro needs a bit of thought - it has to go + * after declarations (to keep the ansi-c compile happy) and also after any + * checks that result in the extension not being sent but before any relevant + * state changes that would affect a possible 2nd call to the constructor. + * Luckily, that's usually not too hard, but it's not mechanical. + */ +# define ECH_SAME_EXT(s, pkt) \ + if (s->ext.ech.es != NULL && s->ext.ech.grease == 0) { \ + int ech_iosame_rv = ossl_ech_same_ext(s, pkt); \ + \ + if (ech_iosame_rv == OSSL_ECH_SAME_EXT_ERR) \ + return EXT_RETURN_FAIL; \ + if (ech_iosame_rv == OSSL_ECH_SAME_EXT_DONE) \ + return EXT_RETURN_SENT; \ + /* otherwise continue as normal */ \ + } + /* Internal ECH APIs */ OSSL_ECHSTORE *ossl_echstore_dup(const OSSL_ECHSTORE *old); @@ -217,6 +270,36 @@ int ossl_ech_conn_init(SSL_CONNECTION *s, SSL_CTX *ctx, void ossl_ech_conn_clear(OSSL_ECH_CONN *ec); void ossl_echext_free(OSSL_ECHEXT *e); OSSL_ECHEXT *ossl_echext_dup(const OSSL_ECHEXT *src); +# ifdef OSSL_ECH_SUPERVERBOSE +void ossl_ech_pbuf(const char *msg, + const unsigned char *buf, const size_t blen); +void ossl_ech_ptranscript(SSL_CONNECTION *s, const char *msg); +# endif +int ossl_ech_get_retry_configs(SSL_CONNECTION *s, unsigned char **rcfgs, + size_t *rcfgslen); +int ossl_ech_send_grease(SSL_CONNECTION *s, WPACKET *pkt); +int ossl_ech_pick_matching_cfg(SSL_CONNECTION *s, OSSL_ECHSTORE_ENTRY **ee, + OSSL_HPKE_SUITE *suite); +int ossl_ech_encode_inner(SSL_CONNECTION *s); +int ossl_ech_find_confirm(SSL_CONNECTION *s, int hrr, + unsigned char acbuf[OSSL_ECH_SIGNAL_LEN], + const unsigned char *shbuf, const size_t shlen); +int ossl_ech_make_transcript_buffer(SSL_CONNECTION *s, int for_hrr, + const unsigned char *shbuf, size_t shlen, + unsigned char **tbuf, size_t *tlen, + size_t *chend, size_t *fixedshbuf_len); +int ossl_ech_reset_hs_buffer(SSL_CONNECTION *s, const unsigned char *buf, + size_t blen); +int ossl_ech_aad_and_encrypt(SSL_CONNECTION *s, WPACKET *pkt); +int ossl_ech_swaperoo(SSL_CONNECTION *s); +int ossl_ech_calc_confirm(SSL_CONNECTION *s, int for_hrr, + unsigned char acbuf[OSSL_ECH_SIGNAL_LEN], + const unsigned char *shbuf, const size_t shlen); + +/* these are internal but located in ssl/statem/extensions.c */ +int ossl_ech_same_ext(SSL_CONNECTION *s, WPACKET *pkt); +int ossl_ech_same_key_share(void); +int ossl_ech_2bcompressed(int ind); # endif #endif diff --git a/ssl/ech/ech_store.c b/ssl/ech/ech_store.c index 653b99f3494..5d2781fbf4b 100644 --- a/ssl/ech/ech_store.c +++ b/ssl/ech/ech_store.c @@ -1124,7 +1124,7 @@ int OSSL_ECHSTORE_flush_keys(OSSL_ECHSTORE *es, time_t age) ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT); return 0; } - if (ee->keyshare != NULL && ((ee->loadtime + age) > now)) { + if (ee->keyshare != NULL && ee->loadtime + age >= now) { ossl_echstore_entry_free(ee); sk_OSSL_ECHSTORE_ENTRY_delete(es->entries, i); } diff --git a/ssl/ssl_ciph.c b/ssl/ssl_ciph.c index 6127cb7a4b4..86357ee7767 100644 --- a/ssl/ssl_ciph.c +++ b/ssl/ssl_ciph.c @@ -2250,3 +2250,107 @@ const char *OSSL_default_ciphersuites(void) "TLS_CHACHA20_POLY1305_SHA256:" "TLS_AES_128_GCM_SHA256"; } + +int ssl_cipher_list_to_bytes(SSL_CONNECTION *s, STACK_OF(SSL_CIPHER) *sk, + WPACKET *pkt) +{ + int i; + size_t totlen = 0, len, maxlen, maxverok = 0; + int empty_reneg_info_scsv = !s->renegotiate + && !SSL_CONNECTION_IS_DTLS(s) + && ssl_security(s, SSL_SECOP_VERSION, 0, TLS1_VERSION, NULL) + && s->min_proto_version <= TLS1_VERSION; + SSL *ssl = SSL_CONNECTION_GET_SSL(s); + + /* Set disabled masks for this session */ + if (!ssl_set_client_disabled(s)) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_R_NO_PROTOCOLS_AVAILABLE); + return 0; + } + + if (sk == NULL) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return 0; + } + +#ifdef OPENSSL_MAX_TLS1_2_CIPHER_LENGTH +# if OPENSSL_MAX_TLS1_2_CIPHER_LENGTH < 6 +# error Max cipher length too short +# endif + /* + * Some servers hang if client hello > 256 bytes as hack workaround + * chop number of supported ciphers to keep it well below this if we + * use TLS v1.2 + */ + if (TLS1_get_version(ssl) >= TLS1_2_VERSION) + maxlen = OPENSSL_MAX_TLS1_2_CIPHER_LENGTH & ~1; + else +#endif + /* Maximum length that can be stored in 2 bytes. Length must be even */ + maxlen = 0xfffe; + + if (empty_reneg_info_scsv) + maxlen -= 2; + if (s->mode & SSL_MODE_SEND_FALLBACK_SCSV) + maxlen -= 2; + + for (i = 0; i < sk_SSL_CIPHER_num(sk) && totlen < maxlen; i++) { + const SSL_CIPHER *c; + + c = sk_SSL_CIPHER_value(sk, i); + /* Skip disabled ciphers */ + if (ssl_cipher_disabled(s, c, SSL_SECOP_CIPHER_SUPPORTED, 0)) + continue; + + if (!ssl->method->put_cipher_by_char(c, pkt, &len)) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return 0; + } + + /* Sanity check that the maximum version we offer has ciphers enabled */ + if (!maxverok) { + int minproto = SSL_CONNECTION_IS_DTLS(s) ? c->min_dtls : c->min_tls; + int maxproto = SSL_CONNECTION_IS_DTLS(s) ? c->max_dtls : c->max_tls; + + if (ssl_version_cmp(s, maxproto, s->s3.tmp.max_ver) >= 0 + && ssl_version_cmp(s, minproto, s->s3.tmp.max_ver) <= 0) + maxverok = 1; + } + + totlen += len; + } + + if (totlen == 0 || !maxverok) { + const char *maxvertext = + !maxverok + ? "No ciphers enabled for max supported SSL/TLS version" + : NULL; + + SSLfatal_data(s, SSL_AD_INTERNAL_ERROR, SSL_R_NO_CIPHERS_AVAILABLE, + maxvertext); + return 0; + } + + if (totlen != 0) { + if (empty_reneg_info_scsv) { + static const SSL_CIPHER scsv = { + 0, NULL, NULL, SSL3_CK_SCSV, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + if (!ssl->method->put_cipher_by_char(&scsv, pkt, &len)) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return 0; + } + } + if (s->mode & SSL_MODE_SEND_FALLBACK_SCSV) { + static const SSL_CIPHER scsv = { + 0, NULL, NULL, SSL3_CK_FALLBACK_SCSV, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + if (!ssl->method->put_cipher_by_char(&scsv, pkt, &len)) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return 0; + } + } + } + + return 1; +} diff --git a/ssl/ssl_local.h b/ssl/ssl_local.h index 5c0b21678d6..d33b98f3ec0 100644 --- a/ssl/ssl_local.h +++ b/ssl/ssl_local.h @@ -691,6 +691,8 @@ typedef enum tlsext_index_en { TLSEXT_IDX_compress_certificate, TLSEXT_IDX_early_data, TLSEXT_IDX_certificate_authorities, + TLSEXT_IDX_ech, + TLSEXT_IDX_outer_extensions, TLSEXT_IDX_padding, TLSEXT_IDX_psk, /* Dummy index - must always be the last entry */ @@ -2626,6 +2628,8 @@ __owur STACK_OF(SSL_CIPHER) *ssl_get_ciphers_by_id(SSL_CONNECTION *sc); __owur int ssl_x509err2alert(int type); void ssl_sort_cipher_list(void); int ssl_load_ciphers(SSL_CTX *ctx); +int ssl_cipher_list_to_bytes(SSL_CONNECTION *s, STACK_OF(SSL_CIPHER) *sk, + WPACKET *pkt); __owur int ssl_setup_sigalgs(SSL_CTX *ctx); int ssl_load_groups(SSL_CTX *ctx); int ssl_load_sigalgs(SSL_CTX *ctx); diff --git a/ssl/ssl_stat.c b/ssl/ssl_stat.c index d6ba000c65d..01e0fcd3b05 100644 --- a/ssl/ssl_stat.c +++ b/ssl/ssl_stat.c @@ -333,6 +333,10 @@ const char *SSL_alert_desc_string(int value) return "BH"; case TLS1_AD_UNKNOWN_PSK_IDENTITY: return "UP"; +#ifndef OPENSSL_NO_ECH + case TLS1_AD_ECH_REQUIRED: + return "RR"; +#endif default: return "UK"; } @@ -403,6 +407,10 @@ const char *SSL_alert_desc_string_long(int value) return "unknown PSK identity"; case TLS1_AD_NO_APPLICATION_PROTOCOL: return "no application protocol"; +#ifndef OPENSSL_NO_ECH + case TLS1_AD_ECH_REQUIRED: + return "ECH required"; +#endif default: return "unknown"; } diff --git a/ssl/statem/extensions.c b/ssl/statem/extensions.c index 4d3445c6f22..87f3dccb987 100644 --- a/ssl/statem/extensions.c +++ b/ssl/statem/extensions.c @@ -19,6 +19,40 @@ #include "../ssl_local.h" #include "statem_local.h" +/* + * values for ext_defs ech_handling field + * exceptionally, we don't conditionally compile that field to avoid a pile of + * fndefs all over the ext_defs values + */ +#define OSSL_ECH_HANDLING_CALL_BOTH 1 /* call constructor both times */ +#define OSSL_ECH_HANDLING_COMPRESS 2 /* compress outer value into inner */ +#define OSSL_ECH_HANDLING_DUPLICATE 3 /* same value in inner and outer */ +/* + * DUPLICATE isn't really useful other than to show we can, + * and for debugging/tests/coverage so may disappear. Changes mostly + * won't affect the outer CH size, due to padding, but might for some + * larger extensions. + * + * Note there is a co-dependency with test/recipies/75-test_quicapi.t: + * If you change an |ech_handling| value, that may well affect the order + * of extensions in a ClientHello, which is reflected in the test data + * in test/recipies/75-test_quicapi_data/\*.txt files. To fix, you need + * to look in test-runs/test_quicapi for the "new" files and then edit + * (replacing actual octets with "?" in relevant places), and copy the + * result back over to test/recipies/75-test_quicapi_data/. The reason + * this happens is the ECH COMPRESS'd extensions need to be contiguous + * in the ClientHello, so changes to/from COMPRESS affect extension + * order, in inner and outer CH. There doesn't seem to be an easy, + * generic, way to reconcile these compile-time changes with having + * fixed value test files. Likely the best option is to decide on the + * disposition of ECH COMPRESS or not and consider that an at least + * medium-term thing. (But still allow other builds to vary at + * compile time if they need something different.) + */ +#ifndef OPENSSL_NO_ECH +static int init_ech(SSL_CONNECTION *s, unsigned int context); +#endif /* OPENSSL_NO_ECH */ + static int final_renegotiate(SSL_CONNECTION *s, unsigned int context, int sent); static int init_server_name(SSL_CONNECTION *s, unsigned int context); static int final_server_name(SSL_CONNECTION *s, unsigned int context, int sent); @@ -84,6 +118,11 @@ typedef struct extensions_definition_st { * protocol versions */ unsigned int context; + /* + * exceptionally, we don't conditionally compile this field to avoid a pile of + * fndefs all over the ext_defs values + */ + int ech_handling; /* how to handle ECH for this extension type */ /* * Initialise extension before parsing. Always called for relevant contexts * even if extension not present @@ -138,12 +177,14 @@ typedef struct extensions_definition_st { * NOTE: WebSphere Application Server 7+ cannot handle empty extensions at * the end, keep these extensions before signature_algorithm. */ -#define INVALID_EXTENSION { TLSEXT_TYPE_invalid, 0, NULL, NULL, NULL, NULL, NULL, NULL } +#define INVALID_EXTENSION { TLSEXT_TYPE_invalid, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL } + static const EXTENSION_DEFINITION ext_defs[] = { { TLSEXT_TYPE_renegotiate, SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_2_SERVER_HELLO | SSL_EXT_SSL3_ALLOWED | SSL_EXT_TLS1_2_AND_BELOW_ONLY, + OSSL_ECH_HANDLING_COMPRESS, NULL, tls_parse_ctos_renegotiate, tls_parse_stoc_renegotiate, tls_construct_stoc_renegotiate, tls_construct_ctos_renegotiate, final_renegotiate @@ -152,6 +193,7 @@ static const EXTENSION_DEFINITION ext_defs[] = { TLSEXT_TYPE_server_name, SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_2_SERVER_HELLO | SSL_EXT_TLS1_3_ENCRYPTED_EXTENSIONS, + OSSL_ECH_HANDLING_CALL_BOTH, init_server_name, tls_parse_ctos_server_name, tls_parse_stoc_server_name, tls_construct_stoc_server_name, tls_construct_ctos_server_name, @@ -161,6 +203,7 @@ static const EXTENSION_DEFINITION ext_defs[] = { TLSEXT_TYPE_max_fragment_length, SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_2_SERVER_HELLO | SSL_EXT_TLS1_3_ENCRYPTED_EXTENSIONS, + OSSL_ECH_HANDLING_COMPRESS, NULL, tls_parse_ctos_maxfragmentlen, tls_parse_stoc_maxfragmentlen, tls_construct_stoc_maxfragmentlen, tls_construct_ctos_maxfragmentlen, final_maxfragmentlen @@ -169,6 +212,7 @@ static const EXTENSION_DEFINITION ext_defs[] = { { TLSEXT_TYPE_srp, SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_2_AND_BELOW_ONLY, + OSSL_ECH_HANDLING_COMPRESS, init_srp, tls_parse_ctos_srp, NULL, NULL, tls_construct_ctos_srp, NULL }, #else @@ -178,6 +222,7 @@ static const EXTENSION_DEFINITION ext_defs[] = { TLSEXT_TYPE_ec_point_formats, SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_2_SERVER_HELLO | SSL_EXT_TLS1_2_AND_BELOW_ONLY, + OSSL_ECH_HANDLING_COMPRESS, init_ec_point_formats, tls_parse_ctos_ec_pt_formats, tls_parse_stoc_ec_pt_formats, tls_construct_stoc_ec_pt_formats, tls_construct_ctos_ec_pt_formats, final_ec_pt_formats @@ -211,6 +256,7 @@ static const EXTENSION_DEFINITION ext_defs[] = { TLSEXT_TYPE_supported_groups, SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_3_ENCRYPTED_EXTENSIONS | SSL_EXT_TLS1_2_SERVER_HELLO, + OSSL_ECH_HANDLING_COMPRESS, NULL, tls_parse_ctos_supported_groups, NULL, tls_construct_stoc_supported_groups, tls_construct_ctos_supported_groups, NULL @@ -219,6 +265,7 @@ static const EXTENSION_DEFINITION ext_defs[] = { TLSEXT_TYPE_session_ticket, SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_2_SERVER_HELLO | SSL_EXT_TLS1_2_AND_BELOW_ONLY, + OSSL_ECH_HANDLING_COMPRESS, init_session_ticket, tls_parse_ctos_session_ticket, tls_parse_stoc_session_ticket, tls_construct_stoc_session_ticket, tls_construct_ctos_session_ticket, NULL @@ -228,6 +275,7 @@ static const EXTENSION_DEFINITION ext_defs[] = { TLSEXT_TYPE_status_request, SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_2_SERVER_HELLO | SSL_EXT_TLS1_3_CERTIFICATE | SSL_EXT_TLS1_3_CERTIFICATE_REQUEST, + OSSL_ECH_HANDLING_COMPRESS, init_status_request, tls_parse_ctos_status_request, tls_parse_stoc_status_request, tls_construct_stoc_status_request, tls_construct_ctos_status_request, NULL @@ -240,6 +288,7 @@ static const EXTENSION_DEFINITION ext_defs[] = { TLSEXT_TYPE_next_proto_neg, SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_2_SERVER_HELLO | SSL_EXT_TLS1_2_AND_BELOW_ONLY, + OSSL_ECH_HANDLING_COMPRESS, init_npn, tls_parse_ctos_npn, tls_parse_stoc_npn, tls_construct_stoc_next_proto_neg, tls_construct_ctos_npn, NULL }, @@ -254,6 +303,7 @@ static const EXTENSION_DEFINITION ext_defs[] = { TLSEXT_TYPE_application_layer_protocol_negotiation, SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_2_SERVER_HELLO | SSL_EXT_TLS1_3_ENCRYPTED_EXTENSIONS, + OSSL_ECH_HANDLING_CALL_BOTH, init_alpn, tls_parse_ctos_alpn, tls_parse_stoc_alpn, tls_construct_stoc_alpn, tls_construct_ctos_alpn, final_alpn }, @@ -262,6 +312,7 @@ static const EXTENSION_DEFINITION ext_defs[] = { TLSEXT_TYPE_use_srtp, SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_2_SERVER_HELLO | SSL_EXT_TLS1_3_ENCRYPTED_EXTENSIONS | SSL_EXT_DTLS_ONLY, + OSSL_ECH_HANDLING_COMPRESS, init_srtp, tls_parse_ctos_use_srtp, tls_parse_stoc_use_srtp, tls_construct_stoc_use_srtp, tls_construct_ctos_use_srtp, NULL }, @@ -272,6 +323,15 @@ static const EXTENSION_DEFINITION ext_defs[] = { TLSEXT_TYPE_encrypt_then_mac, SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_2_SERVER_HELLO | SSL_EXT_TLS1_2_AND_BELOW_ONLY, + /* + * If you want to demonstrate/exercise duplicate, then + * this does that and has no effect on sizes, but it + * will break the quicapi test (see above). Probably + * best done in local tests and not comitted to any + * upstream. + * OSSL_ECH_HANDLING_DUPLICATE, + */ + OSSL_ECH_HANDLING_COMPRESS, init_etm, tls_parse_ctos_etm, tls_parse_stoc_etm, tls_construct_stoc_etm, tls_construct_ctos_etm, NULL }, @@ -280,6 +340,7 @@ static const EXTENSION_DEFINITION ext_defs[] = { TLSEXT_TYPE_signed_certificate_timestamp, SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_2_SERVER_HELLO | SSL_EXT_TLS1_3_CERTIFICATE | SSL_EXT_TLS1_3_CERTIFICATE_REQUEST, + OSSL_ECH_HANDLING_COMPRESS, NULL, /* * No server side support for this, but can be provided by a custom @@ -295,12 +356,14 @@ static const EXTENSION_DEFINITION ext_defs[] = { TLSEXT_TYPE_extended_master_secret, SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_2_SERVER_HELLO | SSL_EXT_TLS1_2_AND_BELOW_ONLY, + OSSL_ECH_HANDLING_COMPRESS, init_ems, tls_parse_ctos_ems, tls_parse_stoc_ems, tls_construct_stoc_ems, tls_construct_ctos_ems, final_ems }, { TLSEXT_TYPE_signature_algorithms_cert, SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_3_CERTIFICATE_REQUEST, + OSSL_ECH_HANDLING_COMPRESS, init_sig_algs_cert, tls_parse_ctos_sig_algs_cert, tls_parse_ctos_sig_algs_cert, /* We do not generate signature_algorithms_cert at present. */ @@ -309,6 +372,7 @@ static const EXTENSION_DEFINITION ext_defs[] = { { TLSEXT_TYPE_post_handshake_auth, SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_3_ONLY, + OSSL_ECH_HANDLING_COMPRESS, init_post_handshake_auth, tls_parse_ctos_post_handshake_auth, NULL, NULL, tls_construct_ctos_post_handshake_auth, @@ -318,6 +382,7 @@ static const EXTENSION_DEFINITION ext_defs[] = { TLSEXT_TYPE_client_cert_type, SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_3_ENCRYPTED_EXTENSIONS | SSL_EXT_TLS1_2_SERVER_HELLO, + OSSL_ECH_HANDLING_CALL_BOTH, init_client_cert_type, tls_parse_ctos_client_cert_type, tls_parse_stoc_client_cert_type, tls_construct_stoc_client_cert_type, tls_construct_ctos_client_cert_type, @@ -327,6 +392,7 @@ static const EXTENSION_DEFINITION ext_defs[] = { TLSEXT_TYPE_server_cert_type, SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_3_ENCRYPTED_EXTENSIONS | SSL_EXT_TLS1_2_SERVER_HELLO, + OSSL_ECH_HANDLING_CALL_BOTH, init_server_cert_type, tls_parse_ctos_server_cert_type, tls_parse_stoc_server_cert_type, tls_construct_stoc_server_cert_type, tls_construct_ctos_server_cert_type, @@ -335,6 +401,7 @@ static const EXTENSION_DEFINITION ext_defs[] = { { TLSEXT_TYPE_signature_algorithms, SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_3_CERTIFICATE_REQUEST, + OSSL_ECH_HANDLING_COMPRESS, init_sig_algs, tls_parse_ctos_sig_algs, tls_parse_ctos_sig_algs, tls_construct_ctos_sig_algs, tls_construct_ctos_sig_algs, final_sig_algs @@ -343,6 +410,7 @@ static const EXTENSION_DEFINITION ext_defs[] = { TLSEXT_TYPE_supported_versions, SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_3_SERVER_HELLO | SSL_EXT_TLS1_3_HELLO_RETRY_REQUEST | SSL_EXT_TLS_IMPLEMENTATION_ONLY, + OSSL_ECH_HANDLING_COMPRESS, NULL, /* Processed inline as part of version selection */ NULL, tls_parse_stoc_supported_versions, @@ -353,6 +421,7 @@ static const EXTENSION_DEFINITION ext_defs[] = { TLSEXT_TYPE_psk_kex_modes, SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS_IMPLEMENTATION_ONLY | SSL_EXT_TLS1_3_ONLY, + OSSL_ECH_HANDLING_COMPRESS, init_psk_kex_modes, tls_parse_ctos_psk_kex_modes, NULL, NULL, tls_construct_ctos_psk_kex_modes, NULL }, @@ -365,6 +434,7 @@ static const EXTENSION_DEFINITION ext_defs[] = { SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_3_SERVER_HELLO | SSL_EXT_TLS1_3_HELLO_RETRY_REQUEST | SSL_EXT_TLS_IMPLEMENTATION_ONLY | SSL_EXT_TLS1_3_ONLY, + OSSL_ECH_HANDLING_COMPRESS, NULL, tls_parse_ctos_key_share, tls_parse_stoc_key_share, tls_construct_stoc_key_share, tls_construct_ctos_key_share, final_key_share @@ -374,6 +444,7 @@ static const EXTENSION_DEFINITION ext_defs[] = { TLSEXT_TYPE_cookie, SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_3_HELLO_RETRY_REQUEST | SSL_EXT_TLS_IMPLEMENTATION_ONLY | SSL_EXT_TLS1_3_ONLY, + OSSL_ECH_HANDLING_COMPRESS, NULL, tls_parse_ctos_cookie, tls_parse_stoc_cookie, tls_construct_stoc_cookie, tls_construct_ctos_cookie, NULL }, @@ -386,12 +457,14 @@ static const EXTENSION_DEFINITION ext_defs[] = { TLSEXT_TYPE_cryptopro_bug, SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_2_SERVER_HELLO | SSL_EXT_TLS1_2_AND_BELOW_ONLY, + OSSL_ECH_HANDLING_COMPRESS, NULL, NULL, NULL, tls_construct_stoc_cryptopro_bug, NULL, NULL }, { TLSEXT_TYPE_compress_certificate, SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_3_CERTIFICATE_REQUEST | SSL_EXT_TLS_IMPLEMENTATION_ONLY | SSL_EXT_TLS1_3_ONLY, + OSSL_ECH_HANDLING_COMPRESS, tls_init_compress_certificate, tls_parse_compress_certificate, tls_parse_compress_certificate, tls_construct_compress_certificate, tls_construct_compress_certificate, @@ -401,6 +474,7 @@ static const EXTENSION_DEFINITION ext_defs[] = { TLSEXT_TYPE_early_data, SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_3_ENCRYPTED_EXTENSIONS | SSL_EXT_TLS1_3_NEW_SESSION_TICKET | SSL_EXT_TLS1_3_ONLY, + OSSL_ECH_HANDLING_CALL_BOTH, NULL, tls_parse_ctos_early_data, tls_parse_stoc_early_data, tls_construct_stoc_early_data, tls_construct_ctos_early_data, final_early_data @@ -409,15 +483,47 @@ static const EXTENSION_DEFINITION ext_defs[] = { TLSEXT_TYPE_certificate_authorities, SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_3_CERTIFICATE_REQUEST | SSL_EXT_TLS1_3_ONLY, + OSSL_ECH_HANDLING_COMPRESS, init_certificate_authorities, tls_parse_certificate_authorities, tls_parse_certificate_authorities, tls_construct_certificate_authorities, tls_construct_certificate_authorities, NULL, }, +#ifndef OPENSSL_NO_ECH + { + TLSEXT_TYPE_ech, + SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_3_ONLY | + SSL_EXT_TLS1_3_ENCRYPTED_EXTENSIONS | + SSL_EXT_TLS1_3_HELLO_RETRY_REQUEST, + OSSL_ECH_HANDLING_CALL_BOTH, + init_ech, + /* + * TODO(ECH): add server calls as per below in a bit + * tls_parse_ctos_ech, tls_parse_stoc_ech, + * tls_construct_stoc_ech, tls_construct_ctos_ech, + */ + NULL, tls_parse_stoc_ech, + NULL, tls_construct_ctos_ech, + NULL + }, + { + TLSEXT_TYPE_outer_extensions, + SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_3_ONLY, + OSSL_ECH_HANDLING_CALL_BOTH, + NULL, + NULL, NULL, + NULL, NULL, + NULL + }, +#else /* OPENSSL_NO_ECH */ + INVALID_EXTENSION, + INVALID_EXTENSION, +#endif /* END_OPENSSL_NO_ECH */ { /* Must be immediately before pre_shared_key */ TLSEXT_TYPE_padding, SSL_EXT_CLIENT_HELLO, + OSSL_ECH_HANDLING_CALL_BOTH, NULL, /* We send this, but don't read it */ NULL, NULL, NULL, tls_construct_ctos_padding, NULL @@ -427,11 +533,136 @@ static const EXTENSION_DEFINITION ext_defs[] = { TLSEXT_TYPE_psk, SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_3_SERVER_HELLO | SSL_EXT_TLS_IMPLEMENTATION_ONLY | SSL_EXT_TLS1_3_ONLY, + OSSL_ECH_HANDLING_CALL_BOTH, NULL, tls_parse_ctos_psk, tls_parse_stoc_psk, tls_construct_stoc_psk, tls_construct_ctos_psk, final_psk } }; +#ifndef OPENSSL_NO_ECH +/* + * Copy an inner extension value to outer. + * inner CH must have been pre-decoded into s->clienthello->pre_proc_exts + * already. + */ +static int ech_copy_inner2outer(SSL_CONNECTION *s, uint16_t ext_type, + int ind, WPACKET *pkt) +{ + RAW_EXTENSION *myext = NULL, *raws = NULL; + + if (s == NULL || s->clienthello == NULL) + return OSSL_ECH_SAME_EXT_ERR; + raws = s->clienthello->pre_proc_exts; + if (raws == NULL) + return OSSL_ECH_SAME_EXT_ERR; + myext = &raws[ind]; + OSSL_TRACE_BEGIN(TLS) { + BIO_printf(trc_out, "inner2outer: Copying ext type %d to outer\n", + ext_type); + } OSSL_TRACE_END(TLS); + /* + * This one wasn't in inner, so re-do processing. We don't + * actually do this currently, but could. + */ + if (myext == NULL) + return OSSL_ECH_SAME_EXT_CONTINUE; + /* copy inner value to outer */ + if (PACKET_data(&myext->data) != NULL + && PACKET_remaining(&myext->data) > 0) { + if (!WPACKET_put_bytes_u16(pkt, ext_type) + || !WPACKET_sub_memcpy_u16(pkt, PACKET_data(&myext->data), + PACKET_remaining(&myext->data))) + return OSSL_ECH_SAME_EXT_ERR; + } else { + /* empty extension */ + if (!WPACKET_put_bytes_u16(pkt, ext_type) + || !WPACKET_put_bytes_u16(pkt, 0)) + return OSSL_ECH_SAME_EXT_ERR; + } + return 1; +} + +/* + * DUPEMALL is useful for testing - this turns off compression and + * causes two calls to each extension constructor, which'd be the same + * as making all entries in ext_tab use the CALL_BOTH value + */ +# undef DUPEMALL + +/* + * Check if we're using the same/different key shares + * return 1 if same key share in inner and outer, 0 otherwise + */ +int ossl_ech_same_key_share(void) +{ +# ifdef DUPEMALL + return 0; +# endif + return ext_defs[TLSEXT_IDX_key_share].ech_handling + != OSSL_ECH_HANDLING_CALL_BOTH; +} + +/* + * say if extension at index |ind| in ext_defs is to be ECH compressed + * return 1 if this one is to be compressed, 0 if not, -1 for error + */ +int ossl_ech_2bcompressed(int ind) +{ + const int nexts = OSSL_NELEM(ext_defs); + +# ifdef DUPEMALL + return 0; +# endif + if (ind < 0 || ind >= nexts) + return -1; + return ext_defs[ind].ech_handling == OSSL_ECH_HANDLING_COMPRESS; +} + +/* as needed, repeat extension from inner in outer handling compression */ +int ossl_ech_same_ext(SSL_CONNECTION *s, WPACKET *pkt) +{ + unsigned int type = 0; + int tind = 0, nexts = OSSL_NELEM(ext_defs); + +# ifdef DUPEMALL + return OSSL_ECH_SAME_EXT_CONTINUE; +# endif + if (s == NULL || s->ext.ech.es == NULL) + return OSSL_ECH_SAME_EXT_CONTINUE; /* nothing to do */ + tind = s->ext.ech.ext_ind; + /* If this index'd extension won't be compressed, we're done */ + if (tind < 0 || tind >= nexts) + return OSSL_ECH_SAME_EXT_ERR; + type = ext_defs[tind].type; + if (s->ext.ech.ch_depth == 1) { + /* inner CH - just note compression as configured */ + if (ext_defs[tind].ech_handling != OSSL_ECH_HANDLING_COMPRESS) + return OSSL_ECH_SAME_EXT_CONTINUE; + /* mark this one to be "compressed" */ + if (s->ext.ech.n_outer_only >= OSSL_ECH_OUTERS_MAX) + return OSSL_ECH_SAME_EXT_ERR; + s->ext.ech.outer_only[s->ext.ech.n_outer_only] = type; + s->ext.ech.n_outer_only++; + OSSL_TRACE_BEGIN(TLS) { + BIO_printf(trc_out, "ech_same_ext: Marking (type %u, ind %d " + "tot-comp %d) for compression\n", type, tind, + (int) s->ext.ech.n_outer_only); + } OSSL_TRACE_END(TLS); + return OSSL_ECH_SAME_EXT_CONTINUE; + } else { + /* Copy value from inner to outer, or indicate a new value needed */ + if (s->clienthello == NULL || pkt == NULL) + return OSSL_ECH_SAME_EXT_ERR; + if (ext_defs[tind].ech_handling == OSSL_ECH_HANDLING_CALL_BOTH) + return OSSL_ECH_SAME_EXT_CONTINUE; + else + return ech_copy_inner2outer(s, type, tind, pkt); + } + /* just in case - shouldn't happen */ + return OSSL_ECH_SAME_EXT_ERR; +} +#endif + /* Returns a TLSEXT_TYPE for the given index */ unsigned int ossl_get_extension_type(size_t idx) { @@ -856,6 +1087,9 @@ int tls_construct_extensions(SSL_CONNECTION *s, WPACKET *pkt, int min_version, max_version = 0, reason; const EXTENSION_DEFINITION *thisexd; int for_comp = (context & SSL_EXT_TLS1_3_CERTIFICATE_COMPRESSION) != 0; +#ifndef OPENSSL_NO_ECH + int pass; +#endif if (!WPACKET_start_sub_packet_u16(pkt) /* @@ -891,40 +1125,77 @@ int tls_construct_extensions(SSL_CONNECTION *s, WPACKET *pkt, return 0; } - for (i = 0, thisexd = ext_defs; i < OSSL_NELEM(ext_defs); i++, thisexd++) { - EXT_RETURN (*construct)(SSL_CONNECTION *s, WPACKET *pkt, - unsigned int context, - X509 *x, size_t chainidx); - EXT_RETURN ret; +#ifndef OPENSSL_NO_ECH + /* + * Two passes - we first construct the to-be-ECH-compressed + * extensions, and then go around again constructing those that + * aren't to be ECH-compressed. We need to ensure this ordering + * so that all the ECH-compressed extensions are contiguous + * in the encoding. The actual compression happens later in + * ech_encode_inner(). + */ + for (pass = 0; pass <= 1; pass++) +#endif - /* Skip if not relevant for our context */ - if (!should_add_extension(s, thisexd->context, context, max_version)) - continue; + for (i = 0, thisexd = ext_defs; i < OSSL_NELEM(ext_defs); + i++, thisexd++) { + EXT_RETURN (*construct)(SSL_CONNECTION *s, WPACKET *pkt, + unsigned int context, + X509 *x, size_t chainidx); + EXT_RETURN ret; + +#ifndef OPENSSL_NO_ECH + /* do compressed in pass 0, non-compressed in pass 1 */ + if (ossl_ech_2bcompressed(i) == pass) + continue; + /* stash index - needed for COMPRESS ECH handling */ + s->ext.ech.ext_ind = i; +#endif + /* Skip if not relevant for our context */ + if (!should_add_extension(s, thisexd->context, context, max_version)) + continue; - construct = s->server ? thisexd->construct_stoc - : thisexd->construct_ctos; + construct = s->server ? thisexd->construct_stoc + : thisexd->construct_ctos; - if (construct == NULL) - continue; + if (construct == NULL) + continue; - ret = construct(s, pkt, context, x, chainidx); - if (ret == EXT_RETURN_FAIL) { - /* SSLfatal() already called */ + ret = construct(s, pkt, context, x, chainidx); + if (ret == EXT_RETURN_FAIL) { + /* SSLfatal() already called */ + return 0; + } + if (ret == EXT_RETURN_SENT + && (context & (SSL_EXT_CLIENT_HELLO + | SSL_EXT_TLS1_3_CERTIFICATE_REQUEST + | SSL_EXT_TLS1_3_NEW_SESSION_TICKET)) != 0) + s->ext.extflags[i] |= SSL_EXT_FLAG_SENT; + } + +#ifndef OPENSSL_NO_ECH + /* + * don't close yet if client in the middle of doing ECH, we'll + * eventually close this in ech_aad_and_encrypt() after we add + * the real ECH extension value + */ + if (s->server + || s->ext.ech.attempted == 0 + || s->ext.ech.ch_depth == 1 + || s->ext.ech.grease == OSSL_ECH_IS_GREASE) { + if (!WPACKET_close(pkt)) { + if (!for_comp) + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); return 0; } - if (ret == EXT_RETURN_SENT - && (context & (SSL_EXT_CLIENT_HELLO - | SSL_EXT_TLS1_3_CERTIFICATE_REQUEST - | SSL_EXT_TLS1_3_NEW_SESSION_TICKET)) != 0) - s->ext.extflags[i] |= SSL_EXT_FLAG_SENT; } - +#else if (!WPACKET_close(pkt)) { if (!for_comp) SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); return 0; } - +#endif return 1; } @@ -987,6 +1258,26 @@ static int init_server_name(SSL_CONNECTION *s, unsigned int context) return 1; } +#ifndef OPENSSL_NO_ECH +/* + * Just note that ech is not yet done + * return 1 for good, 0 otherwise + */ +static int init_ech(SSL_CONNECTION *s, unsigned int context) +{ + const int nexts = OSSL_NELEM(ext_defs); + + /* we don't need this assert everywhere - anywhere is fine */ + if (!ossl_assert(TLSEXT_IDX_num_builtins == nexts)) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return 0; + } + if ((context & SSL_EXT_CLIENT_HELLO) != 0) + s->ext.ech.done = 0; + return 1; +} +#endif /* OPENSSL_NO_ECH */ + static int final_server_name(SSL_CONNECTION *s, unsigned int context, int sent) { int ret = SSL_TLSEXT_ERR_NOACK; @@ -1048,7 +1339,7 @@ static int final_server_name(SSL_CONNECTION *s, unsigned int context, int sent) && was_ticket && (SSL_get_options(ssl) & SSL_OP_NO_TICKET) != 0) { s->ext.ticket_expected = 0; if (!s->hit) { - SSL_SESSION* ss = SSL_get_session(ssl); + SSL_SESSION *ss = SSL_get_session(ssl); if (ss != NULL) { OPENSSL_free(ss->ext.tick); @@ -1532,6 +1823,13 @@ int tls_psk_do_binder(SSL_CONNECTION *s, const EVP_MD *md, int ret = -1; int usepskfored = 0; SSL_CTX *sctx = SSL_CONNECTION_GET_CTX(s); +#ifndef OPENSSL_NO_ECH + unsigned char hashval[EVP_MAX_MD_SIZE]; + unsigned int hashlen = 0; + EVP_MD_CTX *ctx = NULL; + WPACKET tpkt; + BUF_MEM *tpkt_mem = NULL; +#endif /* Ensure cast to size_t is safe */ if (!ossl_assert(hashsizei > 0)) { @@ -1613,12 +1911,47 @@ int tls_psk_do_binder(SSL_CONNECTION *s, const EVP_MD *md, long hdatalen_l; void *hdata; - hdatalen = hdatalen_l = - BIO_get_mem_data(s->s3.handshake_buffer, &hdata); - if (hdatalen_l <= 0) { - SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_R_BAD_HANDSHAKE_LENGTH); - goto err; +#ifndef OPENSSL_NO_ECH + /* handle the hashing as per ECH needs (on client) */ + if (s->ext.ech.attempted == 1 && s->ext.ech.ch_depth == 1) { + if ((tpkt_mem = BUF_MEM_new()) == NULL + || !BUF_MEM_grow(tpkt_mem, SSL3_RT_MAX_PLAIN_LENGTH) + || !WPACKET_init(&tpkt, tpkt_mem)) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + hashlen = EVP_MD_size(md); + if ((ctx = EVP_MD_CTX_new()) == NULL + || EVP_DigestInit_ex(ctx, md, NULL) <= 0 + || EVP_DigestUpdate(ctx, s->ext.ech.innerch1, + s->ext.ech.innerch1_len) <= 0 + || EVP_DigestFinal_ex(ctx, hashval, &hashlen) <= 0) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + EVP_MD_CTX_free(ctx); + ctx = NULL; + if (!WPACKET_put_bytes_u8(&tpkt, SSL3_MT_MESSAGE_HASH) + || !WPACKET_put_bytes_u24(&tpkt, hashlen) + || !WPACKET_memcpy(&tpkt, hashval, hashlen) + || !WPACKET_memcpy(&tpkt, s->ext.ech.kepthrr, + s->ext.ech.kepthrr_len) + || !WPACKET_get_length(&tpkt, &hdatalen)) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + hdata = WPACKET_get_curr(&tpkt) - hdatalen; + } else { +#endif + hdatalen = hdatalen_l = + BIO_get_mem_data(s->s3.handshake_buffer, &hdata); + if (hdatalen_l <= 0) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_R_BAD_HANDSHAKE_LENGTH); + goto err; + } +#ifndef OPENSSL_NO_ECH } +#endif /* * For servers the handshake buffer data will include the second @@ -1686,6 +2019,13 @@ int tls_psk_do_binder(SSL_CONNECTION *s, const EVP_MD *md, OPENSSL_cleanse(finishedkey, sizeof(finishedkey)); EVP_PKEY_free(mackey); EVP_MD_CTX_free(mctx); +#ifndef OPENSSL_NO_ECH + EVP_MD_CTX_free(ctx); + if (tpkt_mem != NULL) { + WPACKET_cleanup(&tpkt); + BUF_MEM_free(tpkt_mem); + } +#endif return ret; } @@ -1813,6 +2153,9 @@ static EXT_RETURN tls_construct_compress_certificate(SSL_CONNECTION *sc, WPACKET if (sc->cert_comp_prefs[0] == TLSEXT_comp_cert_none) return EXT_RETURN_NOT_SENT; +# ifndef OPENSSL_NO_ECH + ECH_SAME_EXT(sc, pkt); +# endif if (!WPACKET_put_bytes_u16(pkt, TLSEXT_TYPE_compress_certificate) || !WPACKET_start_sub_packet_u16(pkt) diff --git a/ssl/statem/extensions_clnt.c b/ssl/statem/extensions_clnt.c index baa7c47b3cd..5866fa73216 100644 --- a/ssl/statem/extensions_clnt.c +++ b/ssl/statem/extensions_clnt.c @@ -12,6 +12,9 @@ #include "internal/cryptlib.h" #include "internal/ssl_unwrap.h" #include "statem_local.h" +#ifndef OPENSSL_NO_ECH +# include +#endif EXT_RETURN tls_construct_ctos_renegotiate(SSL_CONNECTION *s, WPACKET *pkt, unsigned int context, X509 *x, @@ -35,6 +38,9 @@ EXT_RETURN tls_construct_ctos_renegotiate(SSL_CONNECTION *s, WPACKET *pkt, return EXT_RETURN_NOT_SENT; } +#ifndef OPENSSL_NO_ECH + ECH_SAME_EXT(s, pkt) +#endif if (!WPACKET_put_bytes_u16(pkt, TLSEXT_TYPE_renegotiate) || !WPACKET_start_sub_packet_u16(pkt) @@ -47,6 +53,10 @@ EXT_RETURN tls_construct_ctos_renegotiate(SSL_CONNECTION *s, WPACKET *pkt, return EXT_RETURN_SENT; } +#ifndef OPENSSL_NO_ECH + ECH_SAME_EXT(s, pkt) +#endif + /* Add a complete RI extension if renegotiating */ if (!WPACKET_put_bytes_u16(pkt, TLSEXT_TYPE_renegotiate) || !WPACKET_start_sub_packet_u16(pkt) @@ -64,6 +74,43 @@ EXT_RETURN tls_construct_ctos_server_name(SSL_CONNECTION *s, WPACKET *pkt, unsigned int context, X509 *x, size_t chainidx) { +#ifndef OPENSSL_NO_ECH + char *chosen = s->ext.hostname; + OSSL_HPKE_SUITE suite; + OSSL_ECHSTORE_ENTRY *ee = NULL; + + if (s->ext.ech.es != NULL) { + if (ossl_ech_pick_matching_cfg(s, &ee, &suite) != 1) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return EXT_RETURN_NOT_SENT; + } + /* Don't send outer SNI if external API says so */ + if (s->ext.ech.ch_depth == 0 && s->ext.ech.no_outer == 1) + return EXT_RETURN_NOT_SENT; + if (s->ext.ech.ch_depth == 1) /* inner */ + chosen = s->ext.hostname; + if (s->ext.ech.ch_depth == 0) { /* outer */ + if (s->ext.ech.outer_hostname != NULL) /* prefer API */ + chosen = s->ext.ech.outer_hostname; + else /* use name from ECHConfig */ + chosen = ee->public_name; + } + } + if (chosen == NULL) + return EXT_RETURN_NOT_SENT; + /* Add TLS extension servername to the Client Hello message */ + if (!WPACKET_put_bytes_u16(pkt, TLSEXT_TYPE_server_name) + || !WPACKET_start_sub_packet_u16(pkt) + || !WPACKET_start_sub_packet_u16(pkt) + || !WPACKET_put_bytes_u8(pkt, TLSEXT_NAMETYPE_host_name) + || !WPACKET_sub_memcpy_u16(pkt, chosen, strlen(chosen)) + || !WPACKET_close(pkt) + || !WPACKET_close(pkt)) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return EXT_RETURN_FAIL; + } + return EXT_RETURN_SENT; +#else if (s->ext.hostname == NULL) return EXT_RETURN_NOT_SENT; @@ -83,6 +130,7 @@ EXT_RETURN tls_construct_ctos_server_name(SSL_CONNECTION *s, WPACKET *pkt, } return EXT_RETURN_SENT; +#endif } /* Push a Max Fragment Len extension into ClientHello */ @@ -92,6 +140,9 @@ EXT_RETURN tls_construct_ctos_maxfragmentlen(SSL_CONNECTION *s, WPACKET *pkt, { if (s->ext.max_fragment_len_mode == TLSEXT_max_fragment_length_DISABLED) return EXT_RETURN_NOT_SENT; +#ifndef OPENSSL_NO_ECH + ECH_SAME_EXT(s, pkt) +#endif /* Add Max Fragment Length extension if client enabled it. */ /*- @@ -118,6 +169,9 @@ EXT_RETURN tls_construct_ctos_srp(SSL_CONNECTION *s, WPACKET *pkt, /* Add SRP username if there is one */ if (s->srp_ctx.login == NULL) return EXT_RETURN_NOT_SENT; +# ifndef OPENSSL_NO_ECH + ECH_SAME_EXT(s, pkt) +# endif if (!WPACKET_put_bytes_u16(pkt, TLSEXT_TYPE_srp) /* Sub-packet for SRP extension */ @@ -196,6 +250,9 @@ EXT_RETURN tls_construct_ctos_ec_pt_formats(SSL_CONNECTION *s, WPACKET *pkt, } if (!use_ecc(s, min_version, max_version)) return EXT_RETURN_NOT_SENT; +#ifndef OPENSSL_NO_ECH + ECH_SAME_EXT(s, pkt) +#endif /* Add TLS extension ECPointFormats to the ClientHello message */ tls1_get_formatlist(s, &pformats, &num_formats); @@ -233,6 +290,9 @@ EXT_RETURN tls_construct_ctos_supported_groups(SSL_CONNECTION *s, WPACKET *pkt, if (!use_ecc(s, min_version, max_version) && (SSL_CONNECTION_IS_DTLS(s) || max_version < TLS1_3_VERSION)) return EXT_RETURN_NOT_SENT; +#ifndef OPENSSL_NO_ECH + ECH_SAME_EXT(s, pkt) +#endif /* * Add TLS extension supported_groups to the ClientHello message @@ -289,6 +349,9 @@ EXT_RETURN tls_construct_ctos_session_ticket(SSL_CONNECTION *s, WPACKET *pkt, if (!tls_use_ticket(s)) return EXT_RETURN_NOT_SENT; +#ifndef OPENSSL_NO_ECH + ECH_SAME_EXT(s, pkt) +#endif if (!s->new_session && s->session != NULL && s->session->ext.tick != NULL @@ -346,6 +409,10 @@ EXT_RETURN tls_construct_ctos_sig_algs(SSL_CONNECTION *s, WPACKET *pkt, return EXT_RETURN_NOT_SENT; } +#ifndef OPENSSL_NO_ECH + ECH_SAME_EXT(s, pkt) +#endif + salglen = tls12_get_psigalgs(s, 1, &salg); if (!WPACKET_put_bytes_u16(pkt, TLSEXT_TYPE_signature_algorithms) /* Sub-packet for sig-algs extension */ @@ -375,6 +442,9 @@ EXT_RETURN tls_construct_ctos_status_request(SSL_CONNECTION *s, WPACKET *pkt, if (s->ext.status_type != TLSEXT_STATUSTYPE_ocsp) return EXT_RETURN_NOT_SENT; +# ifndef OPENSSL_NO_ECH + ECH_SAME_EXT(s, pkt) +# endif if (!WPACKET_put_bytes_u16(pkt, TLSEXT_TYPE_status_request) /* Sub-packet for status request extension */ @@ -435,6 +505,9 @@ EXT_RETURN tls_construct_ctos_npn(SSL_CONNECTION *s, WPACKET *pkt, if (SSL_CONNECTION_GET_CTX(s)->ext.npn_select_cb == NULL || !SSL_IS_FIRST_HANDSHAKE(s)) return EXT_RETURN_NOT_SENT; +# ifndef OPENSSL_NO_ECH + ECH_SAME_EXT(s, pkt) +# endif /* * The client advertises an empty extension to indicate its support @@ -454,8 +527,46 @@ EXT_RETURN tls_construct_ctos_alpn(SSL_CONNECTION *s, WPACKET *pkt, unsigned int context, X509 *x, size_t chainidx) { - s->s3.alpn_sent = 0; +#ifndef OPENSSL_NO_ECH + unsigned char *aval = NULL; + size_t alen = 0; +#endif + s->s3.alpn_sent = 0; +#ifndef OPENSSL_NO_ECH + /* + * If we have different alpn and alpn_outer values, then we set + * the appropriate one for inner and outer. + * If no alpn is set (for inner or outer), we don't send any. + * If only an inner is set then we send the same in both. + * Logic above is on the basis that alpn's aren't that sensitive, + * usually, so special action is needed to do better. + * We also don't support a way to send alpn only in the inner. + * If you don't want the inner value in the outer, you have to + * pick what to send in the outer and send that. + */ + if (!SSL_IS_FIRST_HANDSHAKE(s)) + return EXT_RETURN_NOT_SENT; + aval = s->ext.alpn; + alen = s->ext.alpn_len; + if (s->ext.ech.ch_depth == 1 && s->ext.alpn == NULL) /* inner */ + return EXT_RETURN_NOT_SENT; + if (s->ext.ech.ch_depth == 0 && s->ext.alpn == NULL + && s->ext.ech.alpn_outer == NULL) /* outer */ + return EXT_RETURN_NOT_SENT; + if (s->ext.ech.ch_depth == 0 && s->ext.ech.alpn_outer != NULL) { + aval = s->ext.ech.alpn_outer; + alen = s->ext.ech.alpn_outer_len; + } + if (!WPACKET_put_bytes_u16(pkt, + TLSEXT_TYPE_application_layer_protocol_negotiation) + || !WPACKET_start_sub_packet_u16(pkt) + || !WPACKET_sub_memcpy_u16(pkt, aval, alen) + || !WPACKET_close(pkt)) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return EXT_RETURN_FAIL; + } +#else if (s->ext.alpn == NULL || !SSL_IS_FIRST_HANDSHAKE(s)) return EXT_RETURN_NOT_SENT; @@ -468,6 +579,7 @@ EXT_RETURN tls_construct_ctos_alpn(SSL_CONNECTION *s, WPACKET *pkt, SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); return EXT_RETURN_FAIL; } +#endif s->s3.alpn_sent = 1; return EXT_RETURN_SENT; @@ -485,6 +597,9 @@ EXT_RETURN tls_construct_ctos_use_srtp(SSL_CONNECTION *s, WPACKET *pkt, if (clnt == NULL) return EXT_RETURN_NOT_SENT; +# ifndef OPENSSL_NO_ECH + ECH_SAME_EXT(s, pkt) +# endif if (!WPACKET_put_bytes_u16(pkt, TLSEXT_TYPE_use_srtp) /* Sub-packet for SRTP extension */ @@ -523,6 +638,9 @@ EXT_RETURN tls_construct_ctos_etm(SSL_CONNECTION *s, WPACKET *pkt, { if (s->options & SSL_OP_NO_ENCRYPT_THEN_MAC) return EXT_RETURN_NOT_SENT; +#ifndef OPENSSL_NO_ECH + ECH_SAME_EXT(s, pkt) +#endif if (!WPACKET_put_bytes_u16(pkt, TLSEXT_TYPE_encrypt_then_mac) || !WPACKET_put_bytes_u16(pkt, 0)) { @@ -544,6 +662,9 @@ EXT_RETURN tls_construct_ctos_sct(SSL_CONNECTION *s, WPACKET *pkt, /* Not defined for client Certificates */ if (x != NULL) return EXT_RETURN_NOT_SENT; +# ifndef OPENSSL_NO_ECH + ECH_SAME_EXT(s, pkt) +# endif if (!WPACKET_put_bytes_u16(pkt, TLSEXT_TYPE_signed_certificate_timestamp) || !WPACKET_put_bytes_u16(pkt, 0)) { @@ -561,6 +682,9 @@ EXT_RETURN tls_construct_ctos_ems(SSL_CONNECTION *s, WPACKET *pkt, { if (s->options & SSL_OP_NO_EXTENDED_MASTER_SECRET) return EXT_RETURN_NOT_SENT; +#ifndef OPENSSL_NO_ECH + ECH_SAME_EXT(s, pkt) +#endif if (!WPACKET_put_bytes_u16(pkt, TLSEXT_TYPE_extended_master_secret) || !WPACKET_put_bytes_u16(pkt, 0)) { @@ -589,6 +713,9 @@ EXT_RETURN tls_construct_ctos_supported_versions(SSL_CONNECTION *s, WPACKET *pkt */ if (max_version < TLS1_3_VERSION) return EXT_RETURN_NOT_SENT; +#ifndef OPENSSL_NO_ECH + ECH_SAME_EXT(s, pkt) +#endif if (!WPACKET_put_bytes_u16(pkt, TLSEXT_TYPE_supported_versions) || !WPACKET_start_sub_packet_u16(pkt) @@ -621,6 +748,10 @@ EXT_RETURN tls_construct_ctos_psk_kex_modes(SSL_CONNECTION *s, WPACKET *pkt, #ifndef OPENSSL_NO_TLS1_3 int nodhe = s->options & SSL_OP_ALLOW_NO_DHE_KEX; +# ifndef OPENSSL_NO_ECH + ECH_SAME_EXT(s, pkt) +# endif + if (!WPACKET_put_bytes_u16(pkt, TLSEXT_TYPE_psk_kex_modes) || !WPACKET_start_sub_packet_u16(pkt) || !WPACKET_start_sub_packet_u8(pkt) @@ -680,6 +811,15 @@ static int add_key_share(SSL_CONNECTION *s, WPACKET *pkt, unsigned int group_id, goto err; } +# ifndef OPENSSL_NO_ECH + if (s->ext.ech.ch_depth == 1) { /* stash inner */ + EVP_PKEY_up_ref(key_share_key); + EVP_PKEY_free(s->ext.ech.tmp_pkey); + s->ext.ech.tmp_pkey = key_share_key; + s->ext.ech.group_id = group_id; + } +# endif + /* For backward compatibility, we use the first valid group to add a key share */ if (loop_num == 0) { s->s3.tmp.pkey = key_share_key; @@ -713,6 +853,10 @@ EXT_RETURN tls_construct_ctos_key_share(SSL_CONNECTION *s, WPACKET *pkt, int add_only_one = 0; size_t valid_keyshare = 0; +# ifndef OPENSSL_NO_ECH + ECH_SAME_EXT(s, pkt) +# endif + /* key_share extension */ if (!WPACKET_put_bytes_u16(pkt, TLSEXT_TYPE_key_share) /* Extension data sub-packet */ @@ -792,6 +936,9 @@ EXT_RETURN tls_construct_ctos_cookie(SSL_CONNECTION *s, WPACKET *pkt, /* Should only be set if we've had an HRR */ if (s->ext.tls13_cookie_len == 0) return EXT_RETURN_NOT_SENT; +#ifndef OPENSSL_NO_ECH + ECH_SAME_EXT(s, pkt) +#endif if (!WPACKET_put_bytes_u16(pkt, TLSEXT_TYPE_cookie) /* Extension data sub-packet */ @@ -826,6 +973,33 @@ EXT_RETURN tls_construct_ctos_early_data(SSL_CONNECTION *s, WPACKET *pkt, const EVP_MD *handmd = NULL; SSL *ussl = SSL_CONNECTION_GET_USER_SSL(s); +#ifndef OPENSSL_NO_ECH + /* + * If we're attempting ECH and processing the outer CH + * then we only need to check if the extension is to be + * sent or not - any other processing (with side effects) + * happened already for the inner CH. + */ + if (s->ext.ech.es != NULL && s->ext.ech.ch_depth == 0) { + /* + * if we called this for inner and did send then + * the following two things should be set, if so, + * then send again in the outer CH. + */ + if (s->ext.early_data == SSL_EARLY_DATA_REJECTED + && s->ext.early_data_ok == 1) { + if (!WPACKET_put_bytes_u16(pkt, TLSEXT_TYPE_early_data) + || !WPACKET_start_sub_packet_u16(pkt) + || !WPACKET_close(pkt)) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return EXT_RETURN_FAIL; + } + return EXT_RETURN_SENT; + } else { + return EXT_RETURN_NOT_SENT; + } + } +#endif if (s->hello_retry_request == SSL_HRR_PENDING) handmd = ssl_handshake_md(s); @@ -1206,6 +1380,89 @@ EXT_RETURN tls_construct_ctos_psk(SSL_CONNECTION *s, WPACKET *pkt, return EXT_RETURN_FAIL; } +# ifndef OPENSSL_NO_ECH + /* + * For ECH if we're processing the outer CH and the inner CH + * has a PSK, then we want to send a GREASE PSK in the outer. + * We'll do that by just replacing the ticket value itself + * with random values of the same length. + */ + if (s->ext.ech.es != NULL && s->ext.ech.ch_depth == 0) { + unsigned char *rndbuf = NULL; + size_t totalrndsize = 0; + + if (s->session == NULL) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return EXT_RETURN_FAIL; + } + totalrndsize = s->session->ext.ticklen + + 4 /* agems */ + + s->psksession_id_len + + reshashsize + + pskhashsize; + rndbuf = OPENSSL_malloc(totalrndsize); + if (rndbuf == NULL) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return EXT_RETURN_FAIL; + } + /* outer CH allocate a similar sized random value */ + if (RAND_bytes_ex(s->ssl.ctx->libctx, rndbuf, totalrndsize, + RAND_DRBG_STRENGTH) <= 0) { + OPENSSL_free(rndbuf); + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return EXT_RETURN_FAIL; + } + /* set agems from random buffer */ + agems = *((uint32_t *)(rndbuf + s->session->ext.ticklen)); + if (dores != 0) { + if (!WPACKET_sub_memcpy_u16(pkt, rndbuf, + s->session->ext.ticklen) + || !WPACKET_put_bytes_u32(pkt, agems)) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + OPENSSL_free(rndbuf); + return EXT_RETURN_FAIL; + } + } + if (s->psksession != NULL) { + if (!WPACKET_sub_memcpy_u16(pkt, + rndbuf + s->session->ext.ticklen + 4, + s->psksession_id_len) + || !WPACKET_put_bytes_u32(pkt, 0)) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + OPENSSL_free(rndbuf); + return EXT_RETURN_FAIL; + } + } + if (!WPACKET_close(pkt) + || !WPACKET_get_total_written(pkt, &binderoffset) + || !WPACKET_start_sub_packet_u16(pkt) + || (dores == 1 + && !WPACKET_sub_memcpy_u8(pkt, + rndbuf + s->session->ext.ticklen + + 4 + s->psksession_id_len, + reshashsize)) + || (s->psksession != NULL + && !WPACKET_sub_memcpy_u8(pkt, + rndbuf + s->session->ext.ticklen + + 4 + s->psksession_id_len + + reshashsize, + pskhashsize)) + || !WPACKET_close(pkt) + || !WPACKET_close(pkt) + || !WPACKET_get_total_written(pkt, &msglen) + /* + * We need to fill in all the sub-packet lengths now so we can + * calculate the HMAC of the message up to the binders + */ + || !WPACKET_fill_lengths(pkt)) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + OPENSSL_free(rndbuf); + return EXT_RETURN_FAIL; + } + OPENSSL_free(rndbuf); + return EXT_RETURN_SENT; + } +# endif /* OPENSSL_NO_ECH */ if (dores) { if (!WPACKET_sub_memcpy_u16(pkt, s->session->ext.tick, s->session->ext.ticklen) @@ -1274,6 +1531,9 @@ EXT_RETURN tls_construct_ctos_post_handshake_auth(SSL_CONNECTION *s, WPACKET *pk #ifndef OPENSSL_NO_TLS1_3 if (!s->pha_enabled) return EXT_RETURN_NOT_SENT; +# ifndef OPENSSL_NO_ECH + ECH_SAME_EXT(s, pkt) +# endif /* construct extension - 0 length, no contents */ if (!WPACKET_put_bytes_u16(pkt, TLSEXT_TYPE_post_handshake_auth) @@ -1393,6 +1653,32 @@ int tls_parse_stoc_server_name(SSL_CONNECTION *s, PACKET *pkt, unsigned int context, X509 *x, size_t chainidx) { +#ifndef OPENSSL_NO_ECH + char *eff_sni = s->ext.hostname; + + /* if we tried ECH and failed, the outer is what's expected */ + if (s->ext.ech.es != NULL && s->ext.ech.success == 0) + eff_sni = s->ext.ech.outer_hostname; + if (eff_sni == NULL) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return 0; + } + if (PACKET_remaining(pkt) > 0) { + SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION); + return 0; + } + if (!s->hit) { + if (s->session->ext.hostname != NULL) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return 0; + } + s->session->ext.hostname = OPENSSL_strdup(eff_sni); + if (s->session->ext.hostname == NULL) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return 0; + } + } +#else if (s->ext.hostname == NULL) { SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); return 0; @@ -1414,7 +1700,7 @@ int tls_parse_stoc_server_name(SSL_CONNECTION *s, PACKET *pkt, return 0; } } - +#endif return 1; } @@ -2170,6 +2456,9 @@ EXT_RETURN tls_construct_ctos_client_cert_type(SSL_CONNECTION *sc, WPACKET *pkt, sc->ext.client_cert_type_ctos = OSSL_CERT_TYPE_CTOS_NONE; if (sc->client_cert_type == NULL) return EXT_RETURN_NOT_SENT; +#ifndef OPENSSL_NO_ECH + ECH_SAME_EXT(sc, pkt) +#endif if (!WPACKET_put_bytes_u16(pkt, TLSEXT_TYPE_client_cert_type) || !WPACKET_start_sub_packet_u16(pkt) @@ -2222,6 +2511,9 @@ EXT_RETURN tls_construct_ctos_server_cert_type(SSL_CONNECTION *sc, WPACKET *pkt, sc->ext.server_cert_type_ctos = OSSL_CERT_TYPE_CTOS_NONE; if (sc->server_cert_type == NULL) return EXT_RETURN_NOT_SENT; +#ifndef OPENSSL_NO_ECH + ECH_SAME_EXT(sc, pkt) +#endif if (!WPACKET_put_bytes_u16(pkt, TLSEXT_TYPE_server_cert_type) || !WPACKET_start_sub_packet_u16(pkt) @@ -2266,3 +2558,96 @@ int tls_parse_stoc_server_cert_type(SSL_CONNECTION *sc, PACKET *pkt, sc->ext.server_cert_type = type; return 1; } + +#ifndef OPENSSL_NO_ECH +EXT_RETURN tls_construct_ctos_ech(SSL_CONNECTION *s, WPACKET *pkt, + unsigned int context, X509 *x, + size_t chainidx) +{ + if (s->ext.ech.attempted_type != TLSEXT_TYPE_ech + && s->ext.ech.grease != OSSL_ECH_IS_GREASE + && !(s->options & SSL_OP_ECH_GREASE)) + return EXT_RETURN_NOT_SENT; + /* send grease if not really attempting ECH */ + if (s->ext.ech.attempted == 0 + && (s->ext.ech.grease == OSSL_ECH_IS_GREASE + || (s->options & SSL_OP_ECH_GREASE))) { + if (s->hello_retry_request == SSL_HRR_PENDING + && s->ext.ech.sent != NULL) { + /* re-tx already sent GREASEy ECH */ + if (WPACKET_memcpy(pkt, s->ext.ech.sent, + s->ext.ech.sent_len) != 1) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return EXT_RETURN_FAIL; + } + return EXT_RETURN_SENT; + } + /* if nobody set a type, use the defaulf */ + if (s->ext.ech.attempted_type == OSSL_ECH_type_unknown) + s->ext.ech.attempted_type = TLSEXT_TYPE_ech; + if (ossl_ech_send_grease(s, pkt) != 1) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return EXT_RETURN_NOT_SENT; + } + return EXT_RETURN_SENT; + } + /* + * If not GREASEing we fake sending the outer value - after the + * entire thing has been constructed we only then finally encode + * and encrypt - need to do it that way as we need the rest of + * the outer CH as AAD input to the encryption. + */ + if (s->ext.ech.ch_depth == 0) + return EXT_RETURN_NOT_SENT; + /* For the inner CH - we simply include one of these saying "inner" */ + if (s->ext.ech.ch_depth == 1) { + if (!WPACKET_put_bytes_u16(pkt, TLSEXT_TYPE_ech) + || !WPACKET_start_sub_packet_u16(pkt) + || !WPACKET_put_bytes_u8(pkt, OSSL_ECH_INNER_CH_TYPE) + || !WPACKET_close(pkt)) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return EXT_RETURN_FAIL; + } + return EXT_RETURN_SENT; + } + return EXT_RETURN_FAIL; +} + +/* if the server thinks we GREASE'd then we may get an ECHConfigList */ +int tls_parse_stoc_ech(SSL_CONNECTION *s, PACKET *pkt, unsigned int context, + X509 *x, size_t chainidx) +{ + unsigned int rlen = 0; + const unsigned char *rval = NULL; + unsigned char *srval = NULL; + + /* + * An HRR will have an ECH extension with the + * 8-octet confirmation value, already handled + */ + if (context == SSL_EXT_TLS1_3_HELLO_RETRY_REQUEST) + return 1; + /* othewise we expect retry-configs */ + if (!PACKET_get_net_2(pkt, &rlen)) { + SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_LENGTH_MISMATCH); + return 0; + } + if (!PACKET_get_bytes(pkt, &rval, rlen)) { + SSLfatal(s, SSL_AD_ILLEGAL_PARAMETER, SSL_R_LENGTH_MISMATCH); + return 0; + } + OPENSSL_free(s->ext.ech.returned); + s->ext.ech.returned = NULL; + srval = OPENSSL_malloc(rlen + 2); + if (srval == NULL) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return 0; + } + srval[0] = (rlen >> 8) & 0xff; + srval[1] = rlen & 0xff; + memcpy(srval + 2, rval, rlen); + s->ext.ech.returned = srval; + s->ext.ech.returned_len = rlen + 2; + return 1; +} +#endif /* END_OPENSSL_NO_ECH */ diff --git a/ssl/statem/statem_clnt.c b/ssl/statem/statem_clnt.c index 3990a2b0c21..7239211298e 100644 --- a/ssl/statem/statem_clnt.c +++ b/ssl/statem/statem_clnt.c @@ -37,8 +37,6 @@ static MSG_PROCESS_RETURN tls_process_encrypted_extensions(SSL_CONNECTION *s, static ossl_inline int cert_req_allowed(SSL_CONNECTION *s); static int key_exchange_expected(SSL_CONNECTION *s); -static int ssl_cipher_list_to_bytes(SSL_CONNECTION *s, STACK_OF(SSL_CIPHER) *sk, - WPACKET *pkt); static ossl_inline int received_server_cert(SSL_CONNECTION *sc) { @@ -1163,7 +1161,252 @@ WORK_STATE ossl_statem_client_post_process_message(SSL_CONNECTION *s, } } -CON_FUNC_RETURN tls_construct_client_hello(SSL_CONNECTION *s, WPACKET *pkt) +#ifndef OPENSSL_NO_ECH +/* + * Wrap the existing ClientHello construction with ECH code. + * + * As needed, we'll call the existing CH constructor twice, + * first for inner, and then for outer. + * + * So the old tls_construct_client_hello is renamed to the _aux + * variant, and the new tls_construct_client_hello just calls + * that if there's no ECH involved, but otherwise does ECH + * things around calls to the _aux variant. + * + * Our basic model is that, when really attempting ECH we + * indicate via the ch_depth field whether we're dealing + * with inner or outer CH (1 for inner, 0 for outer). + * + * After creating the fields for the inner CH, we encode + * those (so we can re-use existing code) then decode again + * (using the existing tls_process_client_hello previously + * only used on servers), again to maximise code re-use. + * + * We next re-encode inner but this time including the + * optimisations for inner CH "compression" (outer exts etc.) + * to produce our plaintext for encrypting. + * + * We then process the outer CH in more or less the + * usual manner. + * + * We lastly form up the AAD etc and encrypt to give us + * the ciphertext for inclusion in the value of the outer + * CH ECH extension. + * + * It may seem odd to form up the outer CH before + * encrypting, but we need to do it that way so we get + * the octets for the AAD used in encryption. + * + * Phew! + */ +static int tls_construct_client_hello_aux(SSL_CONNECTION *s, WPACKET *pkt); + +__owur CON_FUNC_RETURN tls_construct_client_hello(SSL_CONNECTION *s, + WPACKET *pkt) +{ + unsigned char *innerch_full = NULL, *innerch_end = NULL; + WPACKET inner; /* "fake" pkt for inner */ + BUF_MEM *inner_mem = NULL; + PACKET rpkt; /* we'll decode back the inner ch to help make the outer */ + SSL_SESSION *sess = NULL; + size_t sess_id_len = 0, innerlen = 0; + int mt = SSL3_MT_CLIENT_HELLO, rv = 0; + OSSL_HPKE_SUITE suite; + OSSL_ECHSTORE_ENTRY *ee = NULL; + /* Work out what SSL/TLS/DTLS version to use */ + int protverr = ssl_set_client_hello_version(s); + + if (protverr != 0) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, protverr); + return 0; + } + /* If we're not really attempting ECH, just call existing code. */ + if (s->ext.ech.es == NULL) + return tls_construct_client_hello_aux(s, pkt); + /* note version we're attempting and that an attempt is being made */ + if (s->ext.ech.es->entries != NULL) { + if (ossl_ech_pick_matching_cfg(s, &ee, &suite) != 1 || ee == NULL) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, protverr); + return 0; + } + if (ee->version != OSSL_ECH_RFCXXXX_VERSION) { + /* we only support that version for now */ + SSLfatal(s, SSL_AD_INTERNAL_ERROR, protverr); + return 0; + } + s->ext.ech.attempted_type = TLSEXT_TYPE_ech; + s->ext.ech.attempted_cid = ee->config_id; + s->ext.ech.attempted = 1; + if (s->ext.ech.outer_hostname == NULL && ee->public_name != NULL) { + s->ext.ech.outer_hostname = OPENSSL_strdup((char *)ee->public_name); + if (s->ext.ech.outer_hostname == NULL) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return 0; + } + } + } + /* If doing real ECH and application requested GREASE too, over-ride that */ + if (s->ext.ech.grease == OSSL_ECH_IS_GREASE && s->ext.ech.attempted == 1) + s->ext.ech.grease = OSSL_ECH_NOT_GREASE; + /* + * Session ID is handled "oddly" by not being encoded into inner CH (an + * optimisation) so is the same for both inner and outer. + */ + sess = s->session; + if (sess == NULL + || !ssl_version_supported(s, sess->ssl_version, NULL) + || !SSL_SESSION_is_resumable(sess)) { + if (s->hello_retry_request == SSL_HRR_NONE + && !ssl_get_new_session(s, 0)) + return 0; /* SSLfatal() already called */ + } + if (s->new_session || s->session->ssl_version == TLS1_3_VERSION) { + if (s->version == TLS1_3_VERSION + && (s->options & SSL_OP_ENABLE_MIDDLEBOX_COMPAT) != 0) { + sess_id_len = sizeof(s->tmp_session_id); + s->tmp_session_id_len = sess_id_len; + if (s->hello_retry_request == SSL_HRR_NONE + && RAND_bytes_ex(s->ssl.ctx->libctx, s->tmp_session_id, + sess_id_len, RAND_DRBG_STRENGTH) <= 0) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return 0; + } + memcpy(s->session->session_id, s->tmp_session_id, sess_id_len); + s->session->session_id_length = sess_id_len; + } else { + sess_id_len = 0; + } + } else { + assert(s->session->session_id_length <= sizeof(s->session->session_id)); + sess_id_len = s->session->session_id_length; + if (s->version == TLS1_3_VERSION) { + s->tmp_session_id_len = sess_id_len; + memcpy(s->tmp_session_id, s->session->session_id, sess_id_len); + } + } + if (s->hello_retry_request != SSL_HRR_NONE) { + s->ext.ech.n_outer_only = 0; /* reset count of "compressed" exts */ + OPENSSL_free(s->ext.ech.encoded_innerch); + s->ext.ech.encoded_innerch = NULL; + s->ext.ech.encoded_innerch_len = 0; + if (s->ext.ech.innerch != NULL) { + OPENSSL_free(s->ext.ech.innerch1); + s->ext.ech.innerch1 = s->ext.ech.innerch; + s->ext.ech.innerch1_len = s->ext.ech.innerch_len; + s->ext.ech.innerch_len = 0; + s->ext.ech.innerch = NULL; + } + } + /* + * Set CH depth flag so that other code (e.g. extension handlers) + * know where we're at: 1 is "inner CH", 0 is "outer CH" + */ + s->ext.ech.ch_depth = 1; + if ((inner_mem = BUF_MEM_new()) == NULL + || !WPACKET_init(&inner, inner_mem) + || !ssl_set_handshake_header(s, &inner, mt) + || tls_construct_client_hello_aux(s, &inner) != 1 + || !WPACKET_close(&inner) + || !WPACKET_get_length(&inner, &innerlen)) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, protverr); + goto err; + } + innerch_full = OPENSSL_malloc(innerlen); + if (innerch_full == NULL) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + innerch_end = WPACKET_get_curr(&inner); + memcpy(innerch_full, innerch_end - innerlen, innerlen); + OPENSSL_free(s->ext.ech.innerch); + s->ext.ech.innerch = innerch_full; + s->ext.ech.innerch_len = innerlen; + WPACKET_cleanup(&inner); + BUF_MEM_free(inner_mem); + inner_mem = NULL; +# ifdef OSSL_ECH_SUPERVERBOSE + ossl_ech_pbuf("inner CH", s->ext.ech.innerch, s->ext.ech.innerch_len); + ossl_ech_pbuf("inner, client_random", s->ext.ech.client_random, + SSL3_RANDOM_SIZE); + ossl_ech_pbuf("inner, session_id", s->session->session_id, + s->session->session_id_length); +# endif + /* Decode inner so that we can make up encoded inner */ + if (!PACKET_buf_init(&rpkt, (unsigned char *)s->ext.ech.innerch + 4, + s->ext.ech.innerch_len - 4)) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + /* + * Parse the full inner CH (usually done on server). This gets us + * individually encoded extensions so we can choose to compress + * and/or to re-use the same value in outer. + */ + if (!tls_process_client_hello(s, &rpkt)) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + /* Make ClientHelloInner and EncodedClientHelloInner as per spec. */ + if (ossl_ech_encode_inner(s) != 1) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } +# ifdef OSSL_ECH_SUPERVERBOSE + ossl_ech_pbuf("encoded inner CH", s->ext.ech.encoded_innerch, + s->ext.ech.encoded_innerch_len); +# endif + s->ext.ech.ch_depth = 0; /* set depth for outer CH */ + /* + * If we want different key shares for inner and outer, then + * zap the one for the inner. The inner key_share is stashed + * in s.ext.ech.tmp_pkey already. + */ + if (ossl_ech_same_key_share() == 0) { + EVP_PKEY_free(s->s3.tmp.pkey); + s->s3.tmp.pkey = NULL; + } + /* Make second call into CH construction for outer CH. */ + rv = tls_construct_client_hello_aux(s, pkt); + if (rv != 1) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, protverr); + goto err; + } +# ifdef OSSL_ECH_SUPERVERBOSE + ossl_ech_pbuf("outer, client_random", s->s3.client_random, + SSL3_RANDOM_SIZE); + ossl_ech_pbuf("outer, session_id", s->session->session_id, + s->session->session_id_length); +# endif + /* Finally, calculate AAD and encrypt using HPKE */ + if (ossl_ech_aad_and_encrypt(s, pkt) != 1) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + /* Free up raw exts as needed (happens like this on real server) */ + if (s->clienthello != NULL + && s->clienthello->pre_proc_exts != NULL) { + OPENSSL_free(s->clienthello->pre_proc_exts); + OPENSSL_free(s->clienthello); + s->clienthello = NULL; + } + return 1; +err: + if (inner_mem != NULL) { + WPACKET_cleanup(&inner); + BUF_MEM_free(inner_mem); + } + if (s->clienthello != NULL) { + OPENSSL_free(s->clienthello->pre_proc_exts); + OPENSSL_free(s->clienthello); + s->clienthello = NULL; + } + return 0; +} + +static int tls_construct_client_hello_aux(SSL_CONNECTION *s, WPACKET *pkt) +#else +__owur CON_FUNC_RETURN tls_construct_client_hello(SSL_CONNECTION *s, WPACKET *pkt) +#endif { unsigned char *p; size_t sess_id_len; @@ -1182,18 +1425,30 @@ CON_FUNC_RETURN tls_construct_client_hello(SSL_CONNECTION *s, WPACKET *pkt) return CON_FUNC_ERROR; } - if (sess == NULL +#ifndef OPENSSL_NO_ECH + /* if we're doing ECH, re-use session ID setup earlier */ + if (s->ext.ech.es == NULL) +#endif + if (sess == NULL || !ssl_version_supported(s, sess->ssl_version, NULL) || !SSL_SESSION_is_resumable(sess)) { - if (s->hello_retry_request == SSL_HRR_NONE - && !ssl_get_new_session(s, 0)) { - /* SSLfatal() already called */ - return CON_FUNC_ERROR; + if (s->hello_retry_request == SSL_HRR_NONE + && !ssl_get_new_session(s, 0)) { + /* SSLfatal() already called */ + return CON_FUNC_ERROR; + } } - } - /* else use the pre-loaded session */ + /* else use the pre-loaded session */ +#ifndef OPENSSL_NO_ECH + /* use different client_random fields for inner and outer */ + if (s->ext.ech.es != NULL && s->ext.ech.ch_depth == 1) + p = s->ext.ech.client_random; + else + p = s->s3.client_random; +#else p = s->s3.client_random; +#endif /* * for DTLS if client_random is initialized, reuse it, we are @@ -1251,37 +1506,55 @@ CON_FUNC_RETURN tls_construct_client_hello(SSL_CONNECTION *s, WPACKET *pkt) * For TLS 1.3 we always set the ClientHello version to 1.2 and rely on the * supported_versions extension for the real supported versions. */ +#ifndef OPENSSL_NO_ECH + if (!WPACKET_put_bytes_u16(pkt, s->client_version) + || !WPACKET_memcpy(pkt, p, SSL3_RANDOM_SIZE)) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return CON_FUNC_ERROR; + } +#else if (!WPACKET_put_bytes_u16(pkt, s->client_version) || !WPACKET_memcpy(pkt, s->s3.client_random, SSL3_RANDOM_SIZE)) { SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); return CON_FUNC_ERROR; } +#endif /* Session ID */ session_id = s->session->session_id; - if (s->new_session || s->session->ssl_version == TLS1_3_VERSION) { - if (s->version == TLS1_3_VERSION - && (s->options & SSL_OP_ENABLE_MIDDLEBOX_COMPAT) != 0) { - sess_id_len = sizeof(s->tmp_session_id); - s->tmp_session_id_len = sess_id_len; - session_id = s->tmp_session_id; - if (s->hello_retry_request == SSL_HRR_NONE - && RAND_bytes_ex(sctx->libctx, s->tmp_session_id, - sess_id_len, 0) <= 0) { - SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); - return CON_FUNC_ERROR; +#ifndef OPENSSL_NO_ECH + /* same session ID is used for inner/outer when doing ECH */ + if (s->ext.ech.es != NULL) { + sess_id_len = sizeof(s->tmp_session_id); + } else { +#endif + if (s->new_session || s->session->ssl_version == TLS1_3_VERSION) { + if (s->version == TLS1_3_VERSION + && (s->options & SSL_OP_ENABLE_MIDDLEBOX_COMPAT) != 0) { + sess_id_len = sizeof(s->tmp_session_id); + s->tmp_session_id_len = sess_id_len; + session_id = s->tmp_session_id; + if (s->hello_retry_request == SSL_HRR_NONE + && RAND_bytes_ex(sctx->libctx, s->tmp_session_id, + sess_id_len, 0) <= 0) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return CON_FUNC_ERROR; + } + } else { + sess_id_len = 0; } } else { - sess_id_len = 0; - } - } else { - assert(s->session->session_id_length <= sizeof(s->session->session_id)); - sess_id_len = s->session->session_id_length; - if (s->version == TLS1_3_VERSION) { - s->tmp_session_id_len = sess_id_len; - memcpy(s->tmp_session_id, s->session->session_id, sess_id_len); + assert(s->session->session_id_length <= sizeof(s->session->session_id)); + sess_id_len = s->session->session_id_length; + if (s->version == TLS1_3_VERSION) { + s->tmp_session_id_len = sess_id_len; + memcpy(s->tmp_session_id, s->session->session_id, sess_id_len); + } } +#ifndef OPENSSL_NO_ECH } +#endif + if (!WPACKET_start_sub_packet_u8(pkt) || (sess_id_len != 0 && !WPACKET_memcpy(pkt, session_id, sess_id_len)) @@ -1470,6 +1743,24 @@ MSG_PROCESS_RETURN tls_process_server_hello(SSL_CONNECTION *s, PACKET *pkt) #ifndef OPENSSL_NO_COMP SSL_COMP *comp; #endif +#ifndef OPENSSL_NO_ECH + const unsigned char *shbuf = NULL; + size_t shlen, chend, fixedshbuf_len, alen; + /* + * client and server accept signal buffers, initialise in case of + * e.g. memory fail when calculating, only really applies when + * SUPERVERBOSE is defined and we trace these. + */ + unsigned char c_signal[OSSL_ECH_SIGNAL_LEN] = { 0 }; + unsigned char s_signal[OSSL_ECH_SIGNAL_LEN] = { 0xff }; + unsigned char *abuf = NULL; + + shlen = PACKET_remaining(pkt); + if (PACKET_peek_bytes(pkt, &shbuf, shlen) != 1) { + SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_LENGTH_MISMATCH); + goto err; + } +#endif if (!PACKET_get_net_2(pkt, &sversion)) { SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_LENGTH_MISMATCH); @@ -1525,6 +1816,71 @@ MSG_PROCESS_RETURN tls_process_server_hello(SSL_CONNECTION *s, PACKET *pkt) goto err; } +#ifndef OPENSSL_NO_ECH + /* + * If we sent an ECH then check if that worked based on the + * ServerHello.random confirmation trick. If that is good + * then we'll swap over the inner and outer contexts and + * proceed with inner. There are some HRR wrinkles too + * though. + */ + if (s->ext.ech.es != NULL + && s->ext.ech.done != 1 && s->ext.ech.ch_depth == 0 + && s->ext.ech.grease == OSSL_ECH_NOT_GREASE + && s->ext.ech.attempted_type == TLSEXT_TYPE_ech) { + /* try set this earlier see what happens */ + if (!set_client_ciphersuite(s, cipherchars)) { + /* SSLfatal() already called */ + goto err; + } + /* check the ECH accept signal */ + if (ossl_ech_calc_confirm(s, hrr, c_signal, shbuf, shlen) != 1 + || ossl_ech_find_confirm(s, hrr, s_signal, shbuf, shlen) != 1 + || memcmp(s_signal, c_signal, sizeof(c_signal)) != 0) { + OSSL_TRACE(TLS, "ECH accept check failed\n"); +# ifdef OSSL_ECH_SUPERVERBOSE + ossl_ech_pbuf("ECH client accept val:", c_signal, sizeof(c_signal)); + ossl_ech_pbuf("ECH server accept val:", s_signal, sizeof(s_signal)); +# endif + s->ext.ech.success = 0; + } else { /* match, ECH worked */ + OSSL_TRACE_BEGIN(TLS) { + BIO_printf(trc_out, "ECH accept check ok\n"); + BIO_printf(trc_out, "ECH set session hostname to %s\n", + s->ext.hostname ? s->ext.hostname : "NULL"); + } OSSL_TRACE_END(TLS); + s->ext.ech.success = 1; + } + if (!hrr && s->ext.ech.success == 1) { + if (ossl_ech_swaperoo(s) != 1 + || ossl_ech_make_transcript_buffer(s, hrr, shbuf, shlen, + &abuf, &alen, + &chend, &fixedshbuf_len) != 1 + || ossl_ech_reset_hs_buffer(s, abuf, alen) != 1) { + OPENSSL_free(abuf); + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + OPENSSL_free(abuf); + } else if (!hrr) { + /* + * If we got retry_configs then we should be validating + * the outer CH, so we better set the hostname for the + * connection accordingly. + */ + s->ext.ech.former_inner = s->ext.hostname; + s->ext.hostname = NULL; + if (s->ext.ech.outer_hostname != NULL) { + s->ext.hostname = OPENSSL_strdup(s->ext.ech.outer_hostname); + if (s->ext.hostname == NULL) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + goto err; + } + } + } + } +#endif + /* TLS extensions */ if (PACKET_remaining(pkt) == 0 && !hrr) { PACKET_null_init(&extpkt); @@ -2974,6 +3330,18 @@ int tls_process_initial_server_flight(SSL_CONNECTION *s) } #endif +#ifndef OPENSSL_NO_ECH + /* check result of ech and return error if needed */ + if (!s->server + && s->ext.ech.es != NULL + && s->ext.ech.attempted == 1 + && s->ext.ech.success != 1 + && s->ext.ech.grease != OSSL_ECH_IS_GREASE) { + SSLfatal(s, SSL_AD_ECH_REQUIRED, SSL_R_ECH_REQUIRED); + return 0; + } +#endif /* OPENSSL_NO_ECH */ + return 1; } @@ -4079,110 +4447,6 @@ int ssl_do_client_cert_cb(SSL_CONNECTION *s, X509 **px509, EVP_PKEY **ppkey) return i; } -int ssl_cipher_list_to_bytes(SSL_CONNECTION *s, STACK_OF(SSL_CIPHER) *sk, - WPACKET *pkt) -{ - int i; - size_t totlen = 0, len, maxlen, maxverok = 0; - int empty_reneg_info_scsv = !s->renegotiate - && !SSL_CONNECTION_IS_DTLS(s) - && ssl_security(s, SSL_SECOP_VERSION, 0, TLS1_VERSION, NULL) - && s->min_proto_version <= TLS1_VERSION; - SSL *ssl = SSL_CONNECTION_GET_SSL(s); - - /* Set disabled masks for this session */ - if (!ssl_set_client_disabled(s)) { - SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_R_NO_PROTOCOLS_AVAILABLE); - return 0; - } - - if (sk == NULL) { - SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); - return 0; - } - -#ifdef OPENSSL_MAX_TLS1_2_CIPHER_LENGTH -# if OPENSSL_MAX_TLS1_2_CIPHER_LENGTH < 6 -# error Max cipher length too short -# endif - /* - * Some servers hang if client hello > 256 bytes as hack workaround - * chop number of supported ciphers to keep it well below this if we - * use TLS v1.2 - */ - if (TLS1_get_version(ssl) >= TLS1_2_VERSION) - maxlen = OPENSSL_MAX_TLS1_2_CIPHER_LENGTH & ~1; - else -#endif - /* Maximum length that can be stored in 2 bytes. Length must be even */ - maxlen = 0xfffe; - - if (empty_reneg_info_scsv) - maxlen -= 2; - if (s->mode & SSL_MODE_SEND_FALLBACK_SCSV) - maxlen -= 2; - - for (i = 0; i < sk_SSL_CIPHER_num(sk) && totlen < maxlen; i++) { - const SSL_CIPHER *c; - - c = sk_SSL_CIPHER_value(sk, i); - /* Skip disabled ciphers */ - if (ssl_cipher_disabled(s, c, SSL_SECOP_CIPHER_SUPPORTED, 0)) - continue; - - if (!ssl->method->put_cipher_by_char(c, pkt, &len)) { - SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); - return 0; - } - - /* Sanity check that the maximum version we offer has ciphers enabled */ - if (!maxverok) { - int minproto = SSL_CONNECTION_IS_DTLS(s) ? c->min_dtls : c->min_tls; - int maxproto = SSL_CONNECTION_IS_DTLS(s) ? c->max_dtls : c->max_tls; - - if (ssl_version_cmp(s, maxproto, s->s3.tmp.max_ver) >= 0 - && ssl_version_cmp(s, minproto, s->s3.tmp.max_ver) <= 0) - maxverok = 1; - } - - totlen += len; - } - - if (totlen == 0 || !maxverok) { - const char *maxvertext = - !maxverok - ? "No ciphers enabled for max supported SSL/TLS version" - : NULL; - - SSLfatal_data(s, SSL_AD_INTERNAL_ERROR, SSL_R_NO_CIPHERS_AVAILABLE, - maxvertext); - return 0; - } - - if (totlen != 0) { - if (empty_reneg_info_scsv) { - static const SSL_CIPHER scsv = { - 0, NULL, NULL, SSL3_CK_SCSV, 0, 0, 0, 0, 0, 0, 0, 0, 0 - }; - if (!ssl->method->put_cipher_by_char(&scsv, pkt, &len)) { - SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); - return 0; - } - } - if (s->mode & SSL_MODE_SEND_FALLBACK_SCSV) { - static const SSL_CIPHER scsv = { - 0, NULL, NULL, SSL3_CK_FALLBACK_SCSV, 0, 0, 0, 0, 0, 0, 0, 0, 0 - }; - if (!ssl->method->put_cipher_by_char(&scsv, pkt, &len)) { - SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); - return 0; - } - } - } - - return 1; -} - CON_FUNC_RETURN tls_construct_end_of_early_data(SSL_CONNECTION *s, WPACKET *pkt) { if (s->early_data_state != SSL_EARLY_DATA_WRITE_RETRY diff --git a/ssl/statem/statem_local.h b/ssl/statem/statem_local.h index 352f9b8cec7..64eb45c96af 100644 --- a/ssl/statem/statem_local.h +++ b/ssl/statem/statem_local.h @@ -570,3 +570,13 @@ int tls_parse_ctos_server_cert_type(SSL_CONNECTION *sc, PACKET *pkt, int tls_parse_stoc_server_cert_type(SSL_CONNECTION *s, PACKET *pkt, unsigned int context, X509 *x, size_t chainidx); +#ifndef OPENSSL_NO_ECH +EXT_RETURN tls_construct_ctos_ech(SSL_CONNECTION *s, WPACKET *pkt, + unsigned int context, X509 *x, + size_t chainidx); +EXT_RETURN tls_construct_ctos_ech(SSL_CONNECTION *s, WPACKET *pkt, + unsigned int context, X509 *x, + size_t chainidx); +int tls_parse_stoc_ech(SSL_CONNECTION *s, PACKET *pkt, unsigned int context, + X509 *x, size_t chainidx); +#endif diff --git a/ssl/t1_enc.c b/ssl/t1_enc.c index 474ea7bf5b6..56f1fa6a2d3 100644 --- a/ssl/t1_enc.c +++ b/ssl/t1_enc.c @@ -578,6 +578,10 @@ int tls1_alert_code(int code) return SSL_AD_HANDSHAKE_FAILURE; case TLS13_AD_MISSING_EXTENSION: return SSL_AD_HANDSHAKE_FAILURE; +#ifndef OPENSSL_NO_ECH + case SSL_AD_ECH_REQUIRED: + return TLS1_AD_ECH_REQUIRED; +#endif default: return -1; } diff --git a/ssl/t1_trce.c b/ssl/t1_trce.c index f24669c4f55..34483b1f16c 100644 --- a/ssl/t1_trce.c +++ b/ssl/t1_trce.c @@ -500,6 +500,10 @@ static const ssl_trace_tbl ssl_exts_tbl[] = { # ifndef OPENSSL_NO_NEXTPROTONEG {TLSEXT_TYPE_next_proto_neg, "next_proto_neg"}, # endif +# ifndef OPENSSL_NO_ECH + {TLSEXT_TYPE_ech, "encrypted_client_hello"}, + {TLSEXT_TYPE_outer_extensions, "outer_extension"}, +# endif }; static const ssl_trace_tbl ssl_groups_tbl[] = { diff --git a/test/ech_test.c b/test/ech_test.c index ef71d158a57..743ab90208e 100644 --- a/test/ech_test.c +++ b/test/ech_test.c @@ -827,7 +827,7 @@ static int ech_ingest_test(int run) BIO *in = NULL, *out = NULL; int i, rv = 0, keysb4, keysaftr, actual_ents = 0, has_priv, for_retry; ingest_tv_t *tv = &ingest_tvs[run]; - time_t now = 0, secs = 0; + time_t secs = 0, add_time = 0, flush_time = 0; char *pn = NULL, *ec = NULL; if ((in = BIO_new(BIO_s_mem())) == NULL @@ -842,6 +842,7 @@ static int ech_ingest_test(int run) TEST_info("Bad test vector entry"); goto end; } + add_time = time(0); if (tv->pemenc == 1 && !TEST_int_eq(OSSL_ECHSTORE_read_pem(es, in, OSSL_ECH_NO_RETRY), tv->read)) @@ -885,10 +886,10 @@ static int ech_ingest_test(int run) || !TEST_true(OSSL_ECHSTORE_write_pem(es, OSSL_ECHSTORE_ALL, out)) || !TEST_false(OSSL_ECHSTORE_write_pem(es, 100, out))) goto end; - now = time(0); - if (!TEST_true(OSSL_ECHSTORE_flush_keys(es, now)) - || !TEST_true(OSSL_ECHSTORE_num_keys(es, &keysaftr)) - || !TEST_false(keysaftr)) + flush_time = time(0); + if (!TEST_true(OSSL_ECHSTORE_flush_keys(es, flush_time - add_time)) + || !TEST_int_eq(OSSL_ECHSTORE_num_keys(es, &keysaftr), 1) + || !TEST_int_eq(keysaftr, 0)) goto end; rv = 1; end: @@ -1128,6 +1129,7 @@ end: # define OSSL_ECH_TEST_HRR 1 # define OSSL_ECH_TEST_EARLY 2 # define OSSL_ECH_TEST_CUSTOM 3 +# define OSSL_ECH_TEST_ENOE 4 /* early + no-ech */ /* * @brief ECH roundtrip test helper @@ -1153,22 +1155,19 @@ end: static int test_ech_roundtrip_helper(int idx, int combo) { int res = 0, kemind, kdfind, aeadind, kemsz, kdfsz, aeadsz; - char suitestr[100]; + int clientstatus, serverstatus, server = 1, client = 0; + unsigned int context; OSSL_ECHSTORE *es = NULL; OSSL_HPKE_SUITE hpke_suite = OSSL_HPKE_SUITE_DEFAULT; uint16_t ech_version = OSSL_ECH_CURRENT_VERSION; uint8_t max_name_length = 0; - char *public_name = "example.com"; + char *public_name = "example.com", suitestr[100]; SSL_CTX *cctx = NULL, *sctx = NULL; SSL *clientssl = NULL, *serverssl = NULL; - int clientstatus, serverstatus; char *cinner = NULL, *couter = NULL, *sinner = NULL, *souter = NULL; SSL_SESSION *sess = NULL; - unsigned char ed[21]; size_t written = 0, readbytes = 0; - unsigned char buf[1024]; - unsigned int context; - int server = 1, client = 0; + unsigned char ed[21], buf[1024]; /* split idx into kemind, kdfind, aeadind */ kemsz = OSSL_NELEM(kem_str_list); @@ -1192,21 +1191,17 @@ static int test_ech_roundtrip_helper(int idx, int combo) TLS1_3_VERSION, TLS1_3_VERSION, &sctx, &cctx, cert, privkey))) goto end; - if (combo == OSSL_ECH_TEST_EARLY) { - /* just to keep the format checker happy :-) */ - int lrv = 0; - + if (combo == OSSL_ECH_TEST_EARLY || combo == OSSL_ECH_TEST_ENOE) { if (!TEST_true(SSL_CTX_set_options(sctx, SSL_OP_NO_ANTI_REPLAY)) || !TEST_true(SSL_CTX_set_max_early_data(sctx, SSL3_RT_MAX_PLAIN_LENGTH))) goto end; - lrv = SSL_CTX_set_recv_max_early_data(sctx, SSL3_RT_MAX_PLAIN_LENGTH); - if (!TEST_true(lrv)) + if (!TEST_true(SSL_CTX_set_recv_max_early_data(sctx, + SSL3_RT_MAX_PLAIN_LENGTH))) goto end; } if (combo == OSSL_ECH_TEST_CUSTOM) { - /* add custom CH ext to client and server */ - context = SSL_EXT_CLIENT_HELLO; + context = SSL_EXT_CLIENT_HELLO; /* add custom CH ext to client/server */ if (!TEST_true(SSL_CTX_add_custom_ext(cctx, TEST_EXT_TYPE1, context, new_add_cb, new_free_cb, &client, new_parse_cb, &client)) @@ -1221,18 +1216,40 @@ static int test_ech_roundtrip_helper(int idx, int combo) &server, NULL, &server))) goto end; } - if (!TEST_true(SSL_CTX_set1_echstore(cctx, es)) - || !TEST_true(SSL_CTX_set1_echstore(sctx, es)) + if (combo != OSSL_ECH_TEST_ENOE + && !TEST_true(SSL_CTX_set1_echstore(cctx, es))) + goto end; + if (!TEST_true(SSL_CTX_set1_echstore(sctx, es)) || !TEST_true(create_ssl_objects(sctx, cctx, &serverssl, &clientssl, NULL, NULL))) goto end; if (combo == OSSL_ECH_TEST_HRR && !TEST_true(SSL_set1_groups_list(serverssl, "P-384"))) goto end; - if (!TEST_true(SSL_set_tlsext_host_name(clientssl, "server.example")) - || !TEST_true(create_ssl_connection(serverssl, clientssl, + if (!TEST_true(SSL_set_tlsext_host_name(clientssl, "server.example"))) + goto end; +# undef DROPFORNOW +# ifdef DROPFORNOW + /* TODO(ECH): we'll re-instate this once server-side ECH code is in */ + if (!TEST_true(create_ssl_connection(serverssl, clientssl, + SSL_ERROR_NONE))) + goto end; +# else + /* + * For this PR, check connections fail when client does ECH + * and server doesn't, but work if client doesn't do ECH. + * Added in early data for the no-ECH case because an + * intermediate state of the code had an issue. + */ + if (combo != OSSL_ECH_TEST_ENOE + && !TEST_false(create_ssl_connection(serverssl, clientssl, + SSL_ERROR_NONE))) + goto end; + if (combo == OSSL_ECH_TEST_ENOE + && !TEST_true(create_ssl_connection(serverssl, clientssl, SSL_ERROR_NONE))) goto end; +# endif serverstatus = SSL_ech_get1_status(serverssl, &sinner, &souter); if (verbose) TEST_info("server status %d, %s, %s", serverstatus, sinner, souter); @@ -1253,8 +1270,16 @@ static int test_ech_roundtrip_helper(int idx, int combo) goto end; } /* continue for EARLY test */ - if (combo != OSSL_ECH_TEST_EARLY) +# ifdef DROPFORNOW + /* TODO(ECH): turn back on later */ + if (combo != OSSL_ECH_TEST_EARLY && combo != OSSL_ECH_TEST_ENOE) + goto end; +# else + if (combo != OSSL_ECH_TEST_ENOE) { + res = 1; goto end; + } +# endif /* shutdown for start over */ sess = SSL_get1_session(clientssl); OPENSSL_free(sinner); @@ -1348,6 +1373,14 @@ static int ech_custom_test(int idx) return test_ech_roundtrip_helper(idx, OSSL_ECH_TEST_CUSTOM); } +/* Test a roundtrip with No ECH, and early data */ +static int ech_enoe_test(int idx) +{ + if (verbose) + TEST_info("Doing: ech_no ech + early test "); + return test_ech_roundtrip_helper(idx, OSSL_ECH_TEST_ENOE); +} + #endif int setup_tests(void) @@ -1390,6 +1423,7 @@ int setup_tests(void) ADD_ALL_TESTS(test_ech_hrr, suite_combos); ADD_ALL_TESTS(test_ech_early, suite_combos); ADD_ALL_TESTS(ech_custom_test, suite_combos); + ADD_ALL_TESTS(ech_enoe_test, suite_combos); /* TODO(ECH): add more test code as other PRs done */ return 1; err: diff --git a/test/ext_internal_test.c b/test/ext_internal_test.c index 20cf708de27..c7d07d930ee 100644 --- a/test/ext_internal_test.c +++ b/test/ext_internal_test.c @@ -72,6 +72,13 @@ static EXT_LIST ext_list[] = { EXT_ENTRY(compress_certificate), EXT_ENTRY(early_data), EXT_ENTRY(certificate_authorities), +#ifndef OPENSSL_NO_ECH + EXT_ENTRY(ech), + EXT_ENTRY(outer_extensions), +#else + EXT_EXCEPTION(ech), + EXT_EXCEPTION(outer_extensions), +#endif EXT_ENTRY(padding), EXT_ENTRY(psk), EXT_END(num_builtins) diff --git a/test/recipes/75-test_quicapi_data/ssltraceref-zlib.txt b/test/recipes/75-test_quicapi_data/ssltraceref-zlib.txt index d36d58772b8..e7cf0b4e9e6 100644 --- a/test/recipes/75-test_quicapi_data/ssltraceref-zlib.txt +++ b/test/recipes/75-test_quicapi_data/ssltraceref-zlib.txt @@ -31,8 +31,6 @@ Header: ffdhe2048 (256) ffdhe3072 (257) extension_type=session_ticket(35), length=0 - extension_type=application_layer_protocol_negotiation(16), length=11 - ossltest extension_type=encrypt_then_mac(22), length=0 extension_type=extended_master_secret(23), length=0 extension_type=signature_algorithms(13), length=? @@ -67,6 +65,8 @@ Header: key_exchange: (len=32): ? extension_type=compress_certificate(27), length=3 zlib (1) + extension_type=application_layer_protocol_negotiation(16), length=11 + ossltest Sent Frame: Crypto Offset: 0 diff --git a/test/recipes/75-test_quicapi_data/ssltraceref.txt b/test/recipes/75-test_quicapi_data/ssltraceref.txt index 7b7fa28c088..b6daa53ae1e 100644 --- a/test/recipes/75-test_quicapi_data/ssltraceref.txt +++ b/test/recipes/75-test_quicapi_data/ssltraceref.txt @@ -31,8 +31,6 @@ Header: ffdhe2048 (256) ffdhe3072 (257) extension_type=session_ticket(35), length=0 - extension_type=application_layer_protocol_negotiation(16), length=11 - ossltest extension_type=encrypt_then_mac(22), length=0 extension_type=extended_master_secret(23), length=0 extension_type=signature_algorithms(13), length=? @@ -65,6 +63,8 @@ Header: key_exchange: (len=1216): ? NamedGroup: ecdh_x25519 (29) key_exchange: (len=32): ? + extension_type=application_layer_protocol_negotiation(16), length=11 + ossltest Sent Frame: Crypto Offset: 0