]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
ECH client side
authorsftcd <stephen.farrell@cs.tcd.ie>
Wed, 20 Nov 2024 14:10:30 +0000 (14:10 +0000)
committerMatt Caswell <matt@openssl.org>
Thu, 1 May 2025 13:29:52 +0000 (14:29 +0100)
Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/26011)

20 files changed:
apps/s_client.c
doc/man1/openssl-s_client.pod.in
include/internal/ech_helpers.h [new file with mode: 0644]
ssl/ech/ech_helper.c
ssl/ech/ech_internal.c
ssl/ech/ech_local.h
ssl/ech/ech_store.c
ssl/ssl_ciph.c
ssl/ssl_local.h
ssl/ssl_stat.c
ssl/statem/extensions.c
ssl/statem/extensions_clnt.c
ssl/statem/statem_clnt.c
ssl/statem/statem_local.h
ssl/t1_enc.c
ssl/t1_trce.c
test/ech_test.c
test/ext_internal_test.c
test/recipes/75-test_quicapi_data/ssltraceref-zlib.txt
test/recipes/75-test_quicapi_data/ssltraceref.txt

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