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);
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;
{"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"},
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;
}
}
+# 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 "
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;
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);
}
[B<-enable_server_rpk>]
[B<-enable_client_rpk>]
[I<host>:I<port>]
+[B<-ech_config_list>]
=head1 DESCRIPTION
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<value>
+
+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<https://datatracker.ietf.org/doc/html/draft-ietf-tls-esni/>)
+=for comment TODO(ECH): replace XXXX when RFC published.
+
=item B<-proxy_user> I<userid>
When used with the B<-proxy> flag, the program will attempt to authenticate
--- /dev/null
+/*
+ * 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
#include <openssl/ech.h>
#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;
+}
#include <openssl/ech.h>
#include "../ssl_local.h"
#include "ech_local.h"
+#include <openssl/rand.h>
+#include <internal/ech_helpers.h>
+#include <openssl/kdf.h>
#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;
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:
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
/* 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
*
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;
*/
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
*/
* 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 */
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);
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
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);
}
"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;
+}
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 */
__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);
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";
}
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";
}
#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);
* 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
* 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
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,
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
{
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
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
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
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
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
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
},
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
},
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
},
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
},
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
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. */
{
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,
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,
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,
{
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
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,
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
},
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
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
},
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,
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
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
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)
{
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)
/*
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;
}
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;
&& 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);
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)) {
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
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;
}
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)
#include "internal/cryptlib.h"
#include "internal/ssl_unwrap.h"
#include "statem_local.h"
+#ifndef OPENSSL_NO_ECH
+# include <openssl/rand.h>
+#endif
EXT_RETURN tls_construct_ctos_renegotiate(SSL_CONNECTION *s, WPACKET *pkt,
unsigned int context, X509 *x,
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)
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)
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;
}
return EXT_RETURN_SENT;
+#endif
}
/* Push a Max Fragment Len extension into ClientHello */
{
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. */
/*-
/* 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 */
}
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);
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
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
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 */
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 */
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
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;
SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
return EXT_RETURN_FAIL;
}
+#endif
s->s3.alpn_sent = 1;
return EXT_RETURN_SENT;
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 */
{
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)) {
/* 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)) {
{
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)) {
*/
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)
#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)
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;
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 */
/* 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 */
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);
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)
#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)
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;
return 0;
}
}
-
+#endif
return 1;
}
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)
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)
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 */
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)
{
}
}
-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;
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
* 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))
#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);
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);
}
#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;
}
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
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
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;
}
# 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[] = {
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
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))
|| !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:
# 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
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);
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))
&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);
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);
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)
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:
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)
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=?
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
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=?
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