]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
Add server-side handling of Encrypted Client Hello
authorsftcd <stephen.farrell@cs.tcd.ie>
Mon, 5 May 2025 13:23:55 +0000 (14:23 +0100)
committerTomas Mraz <tomas@openssl.org>
Mon, 11 Aug 2025 15:18:00 +0000 (17:18 +0200)
Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/27561)

15 files changed:
doc/man3/SSL_CTX_set_client_hello_cb.pod
include/internal/ech_helpers.h
ssl/ech/ech_helper.c
ssl/ech/ech_internal.c
ssl/ech/ech_local.h
ssl/ech/ech_ssl_apis.c
ssl/statem/extensions.c
ssl/statem/extensions_clnt.c
ssl/statem/extensions_cust.c
ssl/statem/extensions_srvr.c
ssl/statem/statem_clnt.c
ssl/statem/statem_local.h
ssl/statem/statem_srvr.c
test/ech_test.c
util/platform_symbols/windows-symbols.txt

index 74468ab8ac15655ba918c72d3fabcf868d636007..2cf5c4e5735b5637d90c3436b0a7b45b34a09816 100644 (file)
@@ -104,6 +104,8 @@ resumption and the historical servername callback.
 The SSL_client_hello_* family of functions may only be called from code executing
 within a ClientHello callback.
 
+TODO(ECH): How ECH is handled here needs to be documented.
+
 =head1 RETURN VALUES
 
 The application's supplied ClientHello callback returns
index 3e13936e2e9cf2351a04fff0bb2b12ab3e7b3029..946ad2f1dcff4d1a8e01a06a832c9167eb826621 100644 (file)
 
 # ifndef OPENSSL_NO_ECH
 
+/*
+ * the max HPKE 'info' we'll process is the max ECHConfig size
+ * (OSSL_ECH_MAX_ECHCONFIG_LEN) plus OSSL_ECH_CONTEXT_STRING(len=7) + 1
+ */
+#  define OSSL_ECH_MAX_INFO_LEN (OSSL_ECH_MAX_ECHCONFIG_LEN + 8)
+
 int ossl_ech_make_enc_info(const unsigned char *encoding,
                            size_t encoding_length,
                            unsigned char *info, size_t *info_len);
 
+/*
+ * Given a CH find the offsets of the session id, extensions and ECH
+ * ch is the encoded client hello
+ * ch_len is the length of ch
+ * sessid_off returns offset of session_id length
+ * exts_off points to offset of extensions
+ * exts_len returns length of extensions
+ * ech_off returns offset of ECH
+ * echtype returns the ext type of the ECH
+ * ech_len returns the length of the ECH
+ * sni_off returns offset of (outer) SNI
+ * sni_len returns the length of the SNI
+ * inner 1 if the ECH is marked as an inner, 0 for outer
+ * return 1 for success, other otherwise
+ *
+ * Offsets are set to zero if relevant thing not found.
+ * Offsets are returned to the type or length field in question.
+ *
+ * Note: input here is untrusted!
+ */
+int ossl_ech_helper_get_ch_offsets(const unsigned char *ch, size_t ch_len,
+                                   size_t *sessid_off, size_t *exts_off,
+                                   size_t *exts_len,
+                                   size_t *ech_off, uint16_t *echtype,
+                                   size_t *ech_len, size_t *sni_off,
+                                   size_t *sni_len, int *inner);
+
 # endif
 #endif
index 4303a1b6eb08244c6f4f690850d3de2c8ba99489..b2c2a87aa4f8c967f1e73c1c0de8da4a03f24b2b 100644 (file)
@@ -13,8 +13,6 @@
 #include "ech_local.h"
 #include "internal/ech_helpers.h"
 
-/* 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";
@@ -52,3 +50,101 @@ int ossl_ech_make_enc_info(const unsigned char *encoding,
     WPACKET_cleanup(&ipkt);
     return 1;
 }
+
+/*
+ * Given a CH find the offsets of the session id, extensions and ECH
+ * ch is the encoded client hello
+ * ch_len is the length of ch
+ * sessid_off returns offset of session_id length
+ * exts_off points to offset of extensions
+ * exts_len returns length of extensions
+ * ech_off returns offset of ECH
+ * echtype returns the ext type of the ECH
+ * ech_len returns the length of the ECH
+ * sni_off returns offset of (outer) SNI
+ * sni_len returns the length of the SNI
+ * inner 1 if the ECH is marked as an inner, 0 for outer
+ * return 1 for success, other otherwise
+ *
+ * Offsets are set to zero if relevant thing not found.
+ * Offsets are returned to the type or length field in question.
+ *
+ * Note: input here is untrusted!
+ */
+int ossl_ech_helper_get_ch_offsets(const unsigned char *ch, size_t ch_len,
+                                   size_t *sessid_off, size_t *exts_off,
+                                   size_t *exts_len,
+                                   size_t *ech_off, uint16_t *echtype,
+                                   size_t *ech_len, size_t *sni_off,
+                                   size_t *sni_len, int *inner)
+{
+    unsigned int elen = 0, etype = 0, pi_tmp = 0;
+    const unsigned char *pp_tmp = NULL, *chstart = NULL, *estart = NULL;
+    PACKET pkt;
+    int done = 0;
+
+    if (ch == NULL || ch_len == 0 || sessid_off == NULL || exts_off == NULL
+        || ech_off == NULL || echtype == NULL || ech_len == NULL
+        || sni_off == NULL || inner == NULL)
+        return 0;
+    *sessid_off = *exts_off = *ech_off = *sni_off = *sni_len = *ech_len = 0;
+    *echtype = 0xffff;
+    if (!PACKET_buf_init(&pkt, ch, ch_len))
+        return 0;
+    chstart = PACKET_data(&pkt);
+    if (!PACKET_get_net_2(&pkt, &pi_tmp))
+        return 0;
+    /* 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;
+    /* chew up the packet to extensions */
+    if (!PACKET_get_bytes(&pkt, &pp_tmp, SSL3_RANDOM_SIZE)
+        || (*sessid_off = PACKET_data(&pkt) - chstart) == 0
+        || !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 len */
+        || !PACKET_get_bytes(&pkt, &pp_tmp, pi_tmp) /* suites */
+        || !PACKET_get_1(&pkt, &pi_tmp) /* compression meths */
+        || !PACKET_get_bytes(&pkt, &pp_tmp, pi_tmp) /* comp meths */
+        || (*exts_off = PACKET_data(&pkt) - chstart) == 0
+        || !PACKET_get_net_2(&pkt, &pi_tmp) /* len(extensions) */
+        || (*exts_len = (size_t) pi_tmp) == 0)
+        /*
+         * unexpectedly, we return 1 here, as doing otherwise will
+         * break some non-ECH test code that truncates CH messages
+         * The same is true below when looking through extensions.
+         * That's ok though, we'll only set those offsets we've
+         * found.
+         */
+        return 1;
+    /* no extensions is theoretically ok, if uninteresting */
+    if (*exts_len == 0)
+        return 1;
+    /* find what we want from extensions */
+    estart = PACKET_data(&pkt);
+    while (PACKET_remaining(&pkt) > 0
+           && (size_t)(PACKET_data(&pkt) - estart) < *exts_len
+           && done < 2) {
+        if (!PACKET_get_net_2(&pkt, &etype)
+            || !PACKET_get_net_2(&pkt, &elen))
+            return 1; /* see note above */
+        if (etype == TLSEXT_TYPE_ech) {
+            if (elen == 0)
+                return 0;
+            *ech_off = PACKET_data(&pkt) - chstart - 4;
+            *echtype = etype;
+            *ech_len = elen;
+            done++;
+        }
+        if (etype == TLSEXT_TYPE_server_name) {
+            *sni_off = PACKET_data(&pkt) - chstart - 4;
+            *sni_len = elen;
+            done++;
+        }
+        if (!PACKET_get_bytes(&pkt, &pp_tmp, elen))
+            return 1; /* see note above */
+        if (etype == TLSEXT_TYPE_ech)
+            *inner = pp_tmp[0];
+    }
+    return 1;
+}
index 017872d60ccf41f3cbc1314903fbea8fbc881dd3..7fdcf3c5c75b52d9743ebe5fe3aac10affcbe516 100644 (file)
@@ -60,15 +60,11 @@ static void ossl_ech_ptranscript(SSL_CONNECTION *s, const char *msg)
     ossl_ech_pbuf(msg, hdata, hdatalen);
     if (s->s3.handshake_dgst != NULL) {
         if (ssl_handshake_hash(s, ddata, sizeof(ddata), &ddatalen) == 0) {
-            OSSL_TRACE_BEGIN(TLS) {
-                BIO_printf(trc_out, "ssl_handshake_hash failed\n");
-            } OSSL_TRACE_END(TLS);
+            OSSL_TRACE(TLS, "ssl_handshake_hash failed\n");
             ossl_ech_pbuf(msg, ddata, ddatalen);
         }
     }
-    OSSL_TRACE_BEGIN(TLS) {
-        BIO_printf(trc_out, "new transbuf:\n");
-    } OSSL_TRACE_END(TLS);
+    OSSL_TRACE(TLS, "new transbuf:\n");
     ossl_ech_pbuf(msg, s->ext.ech.transbuf, s->ext.ech.transbuf_len);
     return;
 }
@@ -1018,7 +1014,7 @@ int ossl_ech_calc_confirm(SSL_CONNECTION *s, int for_hrr,
     size_t fixedshbuf_len = 0, tlen = 0, chend = 0;
     /* shoffset is: 4 + 2 + 32 - 8 */
     size_t shoffset = SSL3_HM_HEADER_LENGTH + sizeof(uint16_t)
-                      + SSL3_RANDOM_SIZE - OSSL_ECH_SIGNAL_LEN;
+        + SSL3_RANDOM_SIZE - OSSL_ECH_SIGNAL_LEN;
     unsigned int hashlen = 0;
     unsigned char hashval[EVP_MAX_MD_SIZE];
 
@@ -1100,6 +1096,1019 @@ end:
     return rv;
 }
 
+/*!
+ * Given a CH find the offsets of the session id, extensions and ECH
+ * pkt is the CH
+ * sessid_off points to offset of session_id length
+ * exts_off points to offset of extensions
+ * ech_off points to offset of ECH
+ * echtype points to the ext type of the ECH
+ * inner 1 if the ECH is marked as an inner, 0 for outer
+ * sni_off points to offset of (outer) SNI
+ * return 1 for success, other otherwise
+ *
+ * Offsets are set to zero if relevant thing not found.
+ * Offsets are returned to the type or length field in question.
+ *
+ * Note: input here is untrusted!
+ */
+int ossl_ech_get_ch_offsets(SSL_CONNECTION *s, PACKET *pkt, size_t *sessid_off,
+                            size_t *exts_off, size_t *ech_off, uint16_t *echtype,
+                            int *inner, size_t *sni_off)
+{
+    const unsigned char *ch = NULL;
+    size_t ch_len = 0, exts_len = 0, sni_len = 0, ech_len = 0;
+
+    if (s == NULL || pkt == NULL || sessid_off == NULL || exts_off == NULL
+        || ech_off == NULL || echtype == NULL || inner == NULL
+        || sni_off == NULL) {
+        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+        return 0;
+    }
+    /* check if we've already done the work */
+    if (s->ext.ech.ch_offsets_done == 1) {
+        *sessid_off = s->ext.ech.sessid_off;
+        *exts_off = s->ext.ech.exts_off;
+        *ech_off = s->ext.ech.ech_off;
+        *echtype = s->ext.ech.echtype;
+        *inner = s->ext.ech.inner;
+        *sni_off = s->ext.ech.sni_off;
+        return 1;
+    }
+    *sessid_off = 0;
+    *exts_off = 0;
+    *ech_off = 0;
+    *echtype = OSSL_ECH_type_unknown;
+    *sni_off = 0;
+    /* do the work */
+    ch_len = PACKET_remaining(pkt);
+    if (PACKET_peek_bytes(pkt, &ch, ch_len) != 1) {
+        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+        return 0;
+    }
+    if (ossl_ech_helper_get_ch_offsets(ch, ch_len, sessid_off, exts_off,
+                                       &exts_len, ech_off, echtype, &ech_len,
+                                       sni_off, &sni_len, inner) != 1) {
+        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+        return 0;
+    }
+# ifdef OSSL_ECH_SUPERVERBOSE
+    OSSL_TRACE_BEGIN(TLS) {
+        BIO_printf(trc_out, "orig CH/ECH type: %4x\n", *echtype);
+    } OSSL_TRACE_END(TLS);
+    ossl_ech_pbuf("orig CH", (unsigned char *)ch, ch_len);
+    ossl_ech_pbuf("orig CH exts", (unsigned char *)ch + *exts_off, exts_len);
+    ossl_ech_pbuf("orig CH/ECH", (unsigned char *)ch + *ech_off, ech_len);
+    ossl_ech_pbuf("orig CH SNI", (unsigned char *)ch + *sni_off, sni_len);
+# endif
+    s->ext.ech.sessid_off = *sessid_off;
+    s->ext.ech.exts_off = *exts_off;
+    s->ext.ech.ech_off = *ech_off;
+    s->ext.ech.echtype = *echtype;
+    s->ext.ech.inner = *inner;
+    s->ext.ech.sni_off = *sni_off;
+    s->ext.ech.ch_offsets_done = 1;
+    return 1;
+}
+
+static void ossl_ech_encch_free(OSSL_ECH_ENCCH *tbf)
+{
+    if (tbf == NULL)
+        return;
+    OPENSSL_free(tbf->enc);
+    OPENSSL_free(tbf->payload);
+    return;
+}
+
+/*
+ * decode outer sni value so we can trace it
+ * osni_str is the string-form of the SNI
+ * opd is the outer CH buffer
+ * opl is the length of the above
+ * snioffset is where we find the outer SNI
+ *
+ * The caller doesn't have to free the osni_str.
+ */
+static int ech_get_outer_sni(SSL_CONNECTION *s, char **osni_str,
+                             const unsigned char *opd, size_t opl,
+                             size_t snioffset)
+{
+    PACKET wrap, osni;
+    unsigned int type, osnilen;
+
+    if (snioffset >= opl
+        || !PACKET_buf_init(&wrap, opd + snioffset, opl - snioffset)
+        || !PACKET_get_net_2(&wrap, &type)
+        || type != 0
+        || !PACKET_get_net_2(&wrap, &osnilen)
+        || !PACKET_get_sub_packet(&wrap, &osni, osnilen)
+        || tls_parse_ctos_server_name(s, &osni, 0, NULL, 0) != 1)
+        return 0;
+    OPENSSL_free(s->ext.ech.outer_hostname);
+    *osni_str = s->ext.ech.outer_hostname = s->ext.hostname;
+    /* clean up what the ECH-unaware parse func above left behind */
+    s->ext.hostname = NULL;
+    s->servername_done = 0;
+    return 1;
+}
+
+/*
+ * decode EncryptedClientHello extension value
+ * pkt contains the ECH value as a PACKET
+ * retext is the returned decoded structure
+ * payload_offset is the offset to the ciphertext
+ * return 1 for good, 0 for bad
+ *
+ * SSLfatal called from inside, as needed
+ */
+static int ech_decode_inbound_ech(SSL_CONNECTION *s, PACKET *pkt,
+                                  OSSL_ECH_ENCCH **retext,
+                                  size_t *payload_offset)
+{
+    unsigned int innerorouter = 0xff;
+    unsigned int pval_tmp; /* tmp placeholder of value from packet */
+    OSSL_ECH_ENCCH *extval = NULL;
+    const unsigned char *startofech = NULL;
+
+    /*
+     * Decode the inbound ECH value.
+     *  enum { outer(0), inner(1) } ECHClientHelloType;
+     *  struct {
+     *     ECHClientHelloType type;
+     *     select (ECHClientHello.type) {
+     *         case outer:
+     *             HpkeSymmetricCipherSuite cipher_suite;
+     *             uint8 config_id;
+     *             opaque enc<0..2^16-1>;
+     *             opaque payload<1..2^16-1>;
+     *         case inner:
+     *             Empty;
+     *     };
+     *  } ECHClientHello;
+     */
+    startofech = PACKET_data(pkt);
+    extval = OPENSSL_zalloc(sizeof(OSSL_ECH_ENCCH));
+    if (extval == NULL)
+        goto err;
+    if (!PACKET_get_1(pkt, &innerorouter)) {
+        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+        goto err;
+    }
+    if (innerorouter != OSSL_ECH_OUTER_CH_TYPE) {
+        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+        goto err;
+    }
+    if (!PACKET_get_net_2(pkt, &pval_tmp)) {
+        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+        goto err;
+    }
+    extval->kdf_id = pval_tmp & 0xffff;
+    if (!PACKET_get_net_2(pkt, &pval_tmp)) {
+        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+        goto err;
+    }
+    extval->aead_id = pval_tmp & 0xffff;
+    /* config id */
+    if (!PACKET_copy_bytes(pkt, &extval->config_id, 1)) {
+        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+        goto err;
+    }
+# ifdef OSSL_ECH_SUPERVERBOSE
+    ossl_ech_pbuf("EARLY config id", &extval->config_id, 1);
+# endif
+    s->ext.ech.attempted_cid = extval->config_id;
+    /* enc - the client's public share */
+    if (!PACKET_get_net_2(pkt, &pval_tmp)) {
+        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+        goto err;
+    }
+    if (pval_tmp > OSSL_ECH_MAX_GREASE_PUB) {
+        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+        goto err;
+    }
+    if (pval_tmp > PACKET_remaining(pkt)) {
+        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+        goto err;
+    }
+    if (pval_tmp == 0 && s->hello_retry_request != SSL_HRR_PENDING) {
+        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+        goto err;
+    } else if (pval_tmp > 0 && s->hello_retry_request == SSL_HRR_PENDING) {
+        unsigned char *tmpenc = NULL;
+
+        /*
+         * if doing HRR, client should only send this when GREASEing
+         * and it should be the same value as 1st time, so we'll check
+         * that
+         */
+        if (s->ext.ech.pub == NULL || s->ext.ech.pub_len == 0) {
+            SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+            goto err;
+        }
+        if (pval_tmp != s->ext.ech.pub_len) {
+            SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+            goto err;
+        }
+        tmpenc = OPENSSL_malloc(pval_tmp);
+        if (tmpenc == NULL)
+            goto err;
+        if (!PACKET_copy_bytes(pkt, tmpenc, pval_tmp)) {
+            SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+            goto err;
+        }
+        if (memcmp(tmpenc, s->ext.ech.pub, pval_tmp) != 0) {
+            OPENSSL_free(tmpenc);
+            SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+            goto err;
+        }
+        OPENSSL_free(tmpenc);
+    } else if (pval_tmp == 0 && s->hello_retry_request == SSL_HRR_PENDING) {
+        if (s->ext.ech.pub == NULL || s->ext.ech.pub_len == 0) {
+            SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+            goto err;
+        }
+        extval->enc_len = s->ext.ech.pub_len;
+        extval->enc = OPENSSL_malloc(extval->enc_len);
+        if (extval->enc == NULL)
+            goto err;
+        memcpy(extval->enc, s->ext.ech.pub, extval->enc_len);
+    } else {
+        extval->enc_len = pval_tmp;
+        extval->enc = OPENSSL_malloc(pval_tmp);
+        if (extval->enc == NULL)
+            goto err;
+        if (!PACKET_copy_bytes(pkt, extval->enc, pval_tmp)) {
+            SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+            goto err;
+        }
+        /* squirrel away that value in case of future HRR */
+        OPENSSL_free(s->ext.ech.pub);
+        s->ext.ech.pub_len = extval->enc_len;
+        s->ext.ech.pub = OPENSSL_malloc(extval->enc_len);
+        if (s->ext.ech.pub == NULL)
+            goto err;
+        memcpy(s->ext.ech.pub, extval->enc, extval->enc_len);
+    }
+    /* payload - the encrypted CH */
+    *payload_offset = PACKET_data(pkt) - startofech;
+    if (!PACKET_get_net_2(pkt, &pval_tmp)) {
+        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+        goto err;
+    }
+    if (pval_tmp > OSSL_ECH_MAX_PAYLOAD_LEN) {
+        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+        goto err;
+    }
+    if (pval_tmp > PACKET_remaining(pkt)) {
+        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+        goto err;
+    }
+    extval->payload_len = pval_tmp;
+    extval->payload = OPENSSL_malloc(pval_tmp);
+    if (extval->payload == NULL)
+        goto err;
+    if (!PACKET_copy_bytes(pkt, extval->payload, pval_tmp)) {
+        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+        goto err;
+    }
+    *retext = extval;
+    return 1;
+err:
+    if (extval != NULL) {
+        ossl_ech_encch_free(extval);
+        OPENSSL_free(extval);
+        extval = NULL;
+    }
+    return 0;
+}
+
+/*
+ * find outers if any, and do initial checks
+ * pkt is the encoded inner
+ * outers is the array of outer ext types
+ * n_outers is the number of outers found
+ * return 1 for good, 0 for error
+ *
+ * recall we're dealing with recovered ECH plaintext here so
+ * the content must be a TLSv1.3 ECH encoded inner
+ */
+static int ech_find_outers(SSL_CONNECTION *s, PACKET *pkt,
+                           uint16_t *outers, size_t *n_outers)
+{
+    const unsigned char *pp_tmp;
+    unsigned int pi_tmp, extlens, etype, elen, olen;
+    int outers_found = 0;
+    size_t i;
+    PACKET op;
+
+    PACKET_null_init(&op);
+    /* chew up the packet to extensions */
+    if (!PACKET_get_net_2(pkt, &pi_tmp)
+        || pi_tmp != TLS1_2_VERSION
+        || !PACKET_get_bytes(pkt, &pp_tmp, SSL3_RANDOM_SIZE)
+        || !PACKET_get_1(pkt, &pi_tmp)
+        || pi_tmp != 0x00 /* zero'd session id */
+        || !PACKET_get_net_2(pkt, &pi_tmp) /* ciphersuite len */
+        || !PACKET_get_bytes(pkt, &pp_tmp, pi_tmp) /* suites */
+        || !PACKET_get_1(pkt, &pi_tmp) /* compression meths */
+        || pi_tmp != 0x01 /* 1 octet of comressions */
+        || !PACKET_get_1(pkt, &pi_tmp) /* compression meths */
+        || pi_tmp != 0x00 /* 1 octet of no comressions */
+        || !PACKET_get_net_2(pkt, &extlens) /* len(extensions) */
+        || extlens == 0) { /* no extensions! */
+        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+        goto err;
+    }
+    while (PACKET_remaining(pkt) > 0 && outers_found == 0) {
+        if (!PACKET_get_net_2(pkt, &etype)) {
+            SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+            goto err;
+        }
+        if (etype == TLSEXT_TYPE_outer_extensions) {
+            outers_found = 1;
+            if (!PACKET_get_length_prefixed_2(pkt, &op)) {
+                SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+                goto err;
+            }
+        } else { /* skip over */
+            if (!PACKET_get_net_2(pkt, &elen)
+                || !PACKET_get_bytes(pkt, &pp_tmp, elen)) {
+                SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+                goto err;
+            }
+        }
+    }
+
+    if (outers_found == 0) { /* which is fine! */
+        *n_outers = 0;
+        return 1;
+    }
+    /*
+     * outers has a silly internal length as well and that better
+     * be one less than the extension length and an even number
+     * and we only support a certain max of outers
+     */
+    if (!PACKET_get_1(&op, &olen)
+        || olen % 2 == 1
+        || olen / 2 > OSSL_ECH_OUTERS_MAX) {
+        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+        goto err;
+    }
+    *n_outers = olen / 2;
+    for (i = 0; i != *n_outers; i++) {
+        if (!PACKET_get_net_2(&op, &pi_tmp)
+            || pi_tmp == TLSEXT_TYPE_outer_extensions) {
+            SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+            goto err;
+        }
+        outers[i] = (uint16_t) pi_tmp;
+    }
+    return 1;
+err:
+    return 0;
+}
+
+/*
+ * copy one extension from outer to inner
+ * di is the reconstituted inner CH
+ * type2copy is the outer type to copy
+ * extsbuf is the outer extensions buffer
+ * extslen is the outer extensions buffer length
+ * return 1 for good 0 for error
+ */
+static int ech_copy_ext(SSL_CONNECTION *s, WPACKET *di, uint16_t type2copy,
+                        const unsigned char *extsbuf, size_t extslen)
+{
+    PACKET exts;
+    unsigned int etype, elen;
+    const unsigned char *eval;
+
+    if (PACKET_buf_init(&exts, extsbuf, extslen) != 1) {
+        SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
+        goto err;
+    }
+    while (PACKET_remaining(&exts) > 0) {
+        if (!PACKET_get_net_2(&exts, &etype)
+            || !PACKET_get_net_2(&exts, &elen)
+            || !PACKET_get_bytes(&exts, &eval, elen)) {
+            SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+            goto err;
+        }
+        if (etype == type2copy) {
+            if (!WPACKET_put_bytes_u16(di, etype)
+                || !WPACKET_put_bytes_u16(di, elen)
+                || !WPACKET_memcpy(di, eval, elen)) {
+                SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+                goto err;
+            }
+            return 1;
+        }
+    }
+    /* we didn't find such an extension - that's an error */
+    SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+err:
+    return 0;
+}
+
+/*
+ * reconstitute the inner CH from encoded inner and outers
+ * di is the reconstituted inner CH
+ * ei is the encoded inner
+ * ob is the outer CH as a buffer
+ * ob_len is the size of the above
+ * outers is the array of outer ext types
+ * n_outers is the number of outers found
+ * return 1 for good, 0 for error
+ */
+static int ech_reconstitute_inner(SSL_CONNECTION *s, WPACKET *di, PACKET *ei,
+                                  const unsigned char *ob, size_t ob_len,
+                                  uint16_t *outers, size_t n_outers)
+{
+    const unsigned char *pp_tmp, *eval, *outer_exts;
+    unsigned int pi_tmp, etype, elen, outer_extslen;
+    PACKET outer, session_id;
+    size_t i;
+
+    if (PACKET_buf_init(&outer, ob, ob_len) != 1) {
+        SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
+        goto err;
+    }
+    /* read/write from encoded inner to decoded inner with help from outer */
+    if (/* version */
+        !PACKET_get_net_2(&outer, &pi_tmp)
+        || !PACKET_get_net_2(ei, &pi_tmp)
+        || !WPACKET_put_bytes_u16(di, pi_tmp)
+
+        /* client random */
+        || !PACKET_get_bytes(&outer, &pp_tmp, SSL3_RANDOM_SIZE)
+        || !PACKET_get_bytes(ei, &pp_tmp, SSL3_RANDOM_SIZE)
+        || !WPACKET_memcpy(di, pp_tmp, SSL3_RANDOM_SIZE)
+
+        /* session ID */
+        || !PACKET_get_1(ei, &pi_tmp)
+        || !PACKET_get_length_prefixed_1(&outer, &session_id)
+        || !WPACKET_start_sub_packet_u8(di)
+        || (PACKET_remaining(&session_id) != 0
+            && !WPACKET_memcpy(di, PACKET_data(&session_id),
+                               PACKET_remaining(&session_id)))
+        || !WPACKET_close(di)
+
+        /* ciphersuites */
+        || !PACKET_get_net_2(&outer, &pi_tmp) /* ciphersuite len */
+        || !PACKET_get_bytes(&outer, &pp_tmp, pi_tmp) /* suites */
+        || !PACKET_get_net_2(ei, &pi_tmp) /* ciphersuite len */
+        || !PACKET_get_bytes(ei, &pp_tmp, pi_tmp) /* suites */
+        || !WPACKET_put_bytes_u16(di, pi_tmp)
+        || !WPACKET_memcpy(di, pp_tmp, pi_tmp)
+
+        /* compression len & meth */
+        || !PACKET_get_net_2(ei, &pi_tmp)
+        || !PACKET_get_net_2(&outer, &pi_tmp)
+        || !WPACKET_put_bytes_u16(di, pi_tmp)) {
+        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+        goto err;
+    }
+    /* handle simple, but unlikely, case first */
+    if (n_outers == 0) {
+        if (PACKET_remaining(ei) == 0)
+            return 1; /* no exts is theoretically possible */
+        if (!PACKET_get_net_2(ei, &pi_tmp) /* len(extensions) */
+            || !PACKET_get_bytes(ei, &pp_tmp, pi_tmp)
+            || !WPACKET_put_bytes_u16(di, pi_tmp)
+            || !WPACKET_memcpy(di, pp_tmp, pi_tmp)) {
+            SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+            goto err;
+        }
+        WPACKET_close(di);
+        return 1;
+    }
+    /*
+     * general case, copy one by one from inner, 'till we hit
+     * the outers extension, then copy one by one from outer
+     */
+    if (!PACKET_get_net_2(ei, &pi_tmp) /* len(extensions) */
+        || !PACKET_get_net_2(&outer, &outer_extslen)
+        || !PACKET_get_bytes(&outer, &outer_exts, outer_extslen)
+        || !WPACKET_start_sub_packet_u16(di)) {
+        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+        goto err;
+    }
+    while (PACKET_remaining(ei) > 0) {
+        if (!PACKET_get_net_2(ei, &etype)
+            || !PACKET_get_net_2(ei, &elen)
+            || !PACKET_get_bytes(ei, &eval, elen)) {
+            SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+            goto err;
+        }
+        if (etype == TLSEXT_TYPE_outer_extensions) {
+            for (i = 0; i != n_outers; i++) {
+                if (ech_copy_ext(s, di, outers[i],
+                                 outer_exts, outer_extslen) != 1)
+                    /* SSLfatal called already */
+                    goto err;
+            }
+        } else {
+            if (!WPACKET_put_bytes_u16(di, etype)
+                || !WPACKET_put_bytes_u16(di, elen)
+                || !WPACKET_memcpy(di, eval, elen)) {
+                SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+                goto err;
+            }
+        }
+    }
+    WPACKET_close(di);
+    return 1;
+err:
+    WPACKET_cleanup(di);
+    return 0;
+}
+
+/*
+ * After successful ECH decrypt, we decode, decompress etc.
+ * ob is the outer CH as a buffer
+ * ob_len is the size of the above
+ * return 1 for success, error otherwise
+ *
+ * We need the outer CH as a buffer (ob, below) so we can
+ * ECH-decompress.
+ * The plaintext we start from is in encoded_innerch
+ * and our final decoded, decompressed buffer will end up
+ * in innerch (which'll then be further processed).
+ * That further processing includes all existing decoding
+ * checks so we should be fine wrt fuzzing without having
+ * to make all checks here (e.g. we can assume that the
+ * protocol version, NULL compression etc are correct here -
+ * if not, those'll be caught later).
+ * Note: there are a lot of literal values here, but it's
+ * not clear that changing those to #define'd symbols will
+ * help much - a change to the length of a type or from a
+ * 2 octet length to longer would seem unlikely.
+ */
+static int ech_decode_inner(SSL_CONNECTION *s, const unsigned char *ob,
+                            size_t ob_len, unsigned char *encoded_inner,
+                            size_t encoded_inner_len)
+{
+    int rv = 0;
+    PACKET ei; /* encoded inner */
+    BUF_MEM *di_mem = NULL;
+    uint16_t outers[OSSL_ECH_OUTERS_MAX]; /* compressed extension types */
+    size_t n_outers = 0;
+    WPACKET di;
+
+    if (encoded_inner == NULL || ob == NULL || ob_len == 0) {
+        SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
+        return 0;
+    }
+    if ((di_mem = BUF_MEM_new()) == NULL
+        || !BUF_MEM_grow(di_mem, SSL3_RT_MAX_PLAIN_LENGTH)
+        || !WPACKET_init(&di, di_mem)
+        || !WPACKET_put_bytes_u8(&di, SSL3_MT_CLIENT_HELLO)
+        || !WPACKET_start_sub_packet_u24(&di)
+        || !PACKET_buf_init(&ei, encoded_inner, encoded_inner_len)) {
+        SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
+        goto err;
+    }
+# ifdef OSSL_ECH_SUPERVERBOSE
+    memset(outers, -1, sizeof(outers)); /* fill with known values for debug */
+# endif
+
+    /* 1. check for outers and make inital checks of those */
+    if (ech_find_outers(s, &ei, outers, &n_outers) != 1)
+        goto err; /* SSLfatal called already */
+
+    /* 2. reconstitute inner CH */
+    /* reset ei */
+    if (PACKET_buf_init(&ei, encoded_inner, encoded_inner_len) != 1) {
+        SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
+        goto err;
+    }
+    if (ech_reconstitute_inner(s, &di, &ei, ob, ob_len, outers, n_outers) != 1)
+        goto err; /* SSLfatal called already */
+    /* 3. store final inner CH in connection */
+    WPACKET_close(&di);
+    if (!WPACKET_get_length(&di, &s->ext.ech.innerch_len)) {
+        SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
+        goto err;
+    }
+    OPENSSL_free(s->ext.ech.innerch);
+    s->ext.ech.innerch = (unsigned char *)di_mem->data;
+    di_mem->data = NULL;
+    rv = 1;
+err:
+    WPACKET_cleanup(&di);
+    BUF_MEM_free(di_mem);
+    return rv;
+}
+
+/*
+ * wrapper for hpke_dec just to save code repetition
+ * ee is the selected ECH_STORE entry
+ * the_ech is the value sent by the client
+ * aad_len is the length of the AAD to use
+ * aad is the AAD to use
+ * forhrr is 0 if not hrr, 1 if this is for 2nd CH
+ * innerlen points to the size of the recovered plaintext
+ * return pointer to plaintext or NULL (if error)
+ *
+ * The plaintext returned is allocated here and must
+ * be freed by the caller later.
+ */
+static unsigned char *hpke_decrypt_encch(SSL_CONNECTION *s,
+                                         OSSL_ECHSTORE_ENTRY *ee,
+                                         OSSL_ECH_ENCCH *the_ech,
+                                         size_t aad_len, unsigned char *aad,
+                                         int forhrr, size_t *innerlen)
+{
+    size_t cipherlen = 0;
+    unsigned char *cipher = NULL;
+    size_t senderpublen = 0;
+    unsigned char *senderpub = NULL;
+    size_t clearlen = 0;
+    unsigned char *clear = NULL;
+    int hpke_mode = OSSL_HPKE_MODE_BASE;
+    OSSL_HPKE_SUITE hpke_suite = OSSL_HPKE_SUITE_DEFAULT;
+    unsigned char info[OSSL_ECH_MAX_INFO_LEN];
+    size_t info_len = OSSL_ECH_MAX_INFO_LEN;
+    int rv = 0;
+    OSSL_HPKE_CTX *hctx = NULL;
+# ifdef OSSL_ECH_SUPERVERBOSE
+    size_t publen = 0;
+    unsigned char *pub = NULL;
+# endif
+
+    if (ee == NULL || ee->nsuites == 0)
+        return NULL;
+    cipherlen = the_ech->payload_len;
+    cipher = the_ech->payload;
+    senderpublen = the_ech->enc_len;
+    senderpub = the_ech->enc;
+    hpke_suite.aead_id = the_ech->aead_id;
+    hpke_suite.kdf_id = the_ech->kdf_id;
+    clearlen = cipherlen; /* small overestimate */
+    clear = OPENSSL_malloc(clearlen);
+    if (clear == NULL)
+        return NULL;
+    /* The kem_id will be the same for all suites in the entry */
+    hpke_suite.kem_id = ee->suites[0].kem_id;
+# ifdef OSSL_ECH_SUPERVERBOSE
+    publen = ee->pub_len;
+    pub = ee->pub;
+    ossl_ech_pbuf("aad", aad, aad_len);
+    ossl_ech_pbuf("my local pub", pub, publen);
+    ossl_ech_pbuf("senderpub", senderpub, senderpublen);
+    ossl_ech_pbuf("cipher", cipher, cipherlen);
+# endif
+    if (ossl_ech_make_enc_info(ee->encoded, ee->encoded_len,
+                               info, &info_len) != 1) {
+        OPENSSL_free(clear);
+        return NULL;
+    }
+# ifdef OSSL_ECH_SUPERVERBOSE
+    ossl_ech_pbuf("info", info, info_len);
+# endif
+    OSSL_TRACE_BEGIN(TLS) {
+        BIO_printf(trc_out,
+                   "hpke_dec suite: kem: %04x, kdf: %04x, aead: %04x\n",
+                   hpke_suite.kem_id, hpke_suite.kdf_id, hpke_suite.aead_id);
+    } OSSL_TRACE_END(TLS);
+    /*
+     * We may generate externally visible OpenSSL errors
+     * if decryption fails (which is normal) but we'll
+     * ignore those as we might be dealing with a GREASEd
+     * ECH. To do that we need to now ignore some errors
+     * so we use ERR_set_mark() then later ERR_pop_to_mark().
+     */
+    ERR_set_mark();
+    /* Use OSSL_HPKE_* APIs */
+    hctx = OSSL_HPKE_CTX_new(hpke_mode, hpke_suite, OSSL_HPKE_ROLE_RECEIVER,
+                             NULL, NULL);
+    if (hctx == NULL)
+        goto clearerrs;
+    rv = OSSL_HPKE_decap(hctx, senderpub, senderpublen, ee->keyshare,
+                         info, info_len);
+    if (rv != 1)
+        goto clearerrs;
+    if (forhrr == 1) {
+        rv = OSSL_HPKE_CTX_set_seq(hctx, 1);
+        if (rv != 1) {
+            /* don't clear this error - GREASE can't cause it */
+            ERR_clear_last_mark();
+            SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
+            goto end;
+        }
+    }
+    rv = OSSL_HPKE_open(hctx, clear, &clearlen, aad, aad_len,
+                        cipher, cipherlen);
+clearerrs:
+    /* close off our error handling */
+    ERR_pop_to_mark();
+end:
+    OSSL_HPKE_CTX_free(hctx);
+    if (rv != 1) {
+        OSSL_TRACE(TLS, "HPKE decryption failed somehow\n");
+        OPENSSL_free(clear);
+        return NULL;
+    }
+# ifdef OSSL_ECH_SUPERVERBOSE
+    ossl_ech_pbuf("padded clear", clear, clearlen);
+# endif
+    /* we need to remove possible (actually, v. likely) padding */
+    *innerlen = clearlen;
+    if (ee->version == OSSL_ECH_RFCXXXX_VERSION) {
+        /* draft-13 pads after the encoded CH with zeros */
+        size_t extsoffset = 0;
+        size_t extslen = 0;
+        size_t ch_len = 0;
+        size_t startofsessid = 0;
+        size_t echoffset = 0; /* offset of start of ECH within CH */
+        uint16_t echtype = OSSL_ECH_type_unknown; /* type of ECH seen */
+        size_t outersnioffset = 0; /* offset to SNI in outer */
+        int innerflag = -1;
+        PACKET innerchpkt;
+
+        if (PACKET_buf_init(&innerchpkt, clear, clearlen) != 1) {
+            SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+            goto paderr;
+        }
+        /* reset the offsets, as we move from outer to inner CH */
+        s->ext.ech.ch_offsets_done = 0;
+        rv = ossl_ech_get_ch_offsets(s, &innerchpkt, &startofsessid,
+                                     &extsoffset, &echoffset, &echtype,
+                                     &innerflag, &outersnioffset);
+        if (rv != 1) {
+            SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+            goto paderr;
+        }
+        /* odd form of check below just for emphasis */
+        if ((extsoffset + 1) > clearlen) {
+            SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+            goto paderr;
+        }
+        extslen = (unsigned char)(clear[extsoffset]) * 256
+            + (unsigned char)(clear[extsoffset + 1]);
+        ch_len = extsoffset + 2 + extslen;
+        /* the check below protects us from bogus data */
+        if (ch_len > clearlen) {
+            SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+            goto paderr;
+        }
+        /*
+         * The RFC calls for that padding to be all zeros. I'm not so
+         * keen on that being a good idea to enforce, so we'll make it
+         * easy to not do so (but check by default)
+         */
+# define CHECKZEROS
+# ifdef CHECKZEROS
+        {
+            size_t zind = 0;
+
+            if (*innerlen < ch_len)
+                goto paderr;
+            for (zind = ch_len; zind != *innerlen; zind++) {
+                if (clear[zind] != 0x00)
+                    goto paderr;
+            }
+        }
+# endif
+        *innerlen = ch_len;
+# ifdef OSSL_ECH_SUPERVERBOSE
+        ossl_ech_pbuf("unpadded clear", clear, *innerlen);
+# endif
+        return clear;
+    }
+paderr:
+    OPENSSL_free(clear);
+    return NULL;
+}
+
+/*
+ * If an ECH is present, attempt decryption
+ * outerpkt is the packet with the outer CH
+ * newpkt is the packet with the decrypted inner CH
+ * return 1 for success, other otherwise
+ *
+ * If decryption succeeds, the caller can swap the inner and outer
+ * CHs so that all further processing will only take into account
+ * the inner CH.
+ *
+ * The fact that decryption worked is signalled to the caller
+ * via s->ext.ech.success
+ *
+ * This function is called early, (hence the name:-), before
+ * the outer CH decoding has really started, so we need to be
+ * careful peeking into the packet
+ *
+ * The plan:
+ * 1. check if there's an ECH
+ * 2. trial-decrypt or check if config matches one loaded
+ * 3. if decrypt fails tee-up GREASE
+ * 4. if decrypt worked, decode and de-compress cleartext to
+ *    make up real inner CH for later processing
+ */
+int ossl_ech_early_decrypt(SSL_CONNECTION *s, PACKET *outerpkt, PACKET *newpkt)
+{
+    int num = 0, cfgind = -1, foundcfg = 0, forhrr = 0, innerflag = -1;
+    OSSL_ECH_ENCCH *extval = NULL;
+    PACKET echpkt;
+    const unsigned char *startofech = NULL, *opd = NULL;
+    size_t echlen = 0, clearlen = 0, aad_len = 0;
+    unsigned char *clear = NULL, *aad = NULL;
+    /* offsets of things within CH */
+    size_t startofsessid = 0, startofexts = 0, echoffset = 0, opl = 0;
+    size_t outersnioffset = 0, startofciphertext = 0, lenofciphertext = 0;
+    uint16_t echtype = OSSL_ECH_type_unknown; /* type of ECH seen */
+    char *osni_str = NULL;
+    OSSL_ECHSTORE *es = NULL;
+    OSSL_ECHSTORE_ENTRY *ee = NULL;
+
+    if (s == NULL)
+        return 0;
+    if (outerpkt == NULL || newpkt == NULL) {
+        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+        return 0;
+    }
+    /* find offsets - on success, outputs are safe to use */
+    if (ossl_ech_get_ch_offsets(s, outerpkt, &startofsessid, &startofexts,
+                                &echoffset, &echtype, &innerflag,
+                                &outersnioffset) != 1) {
+        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+        return 0;
+    }
+    if (echoffset == 0 || echtype != TLSEXT_TYPE_ech)
+        return 1; /* ECH not present or wrong version */
+    if (innerflag == 1) {
+        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+        return 0;
+    }
+    s->ext.ech.attempted = 1; /* Remember that we got an ECH */
+    s->ext.ech.attempted_type = echtype;
+    if (s->hello_retry_request == SSL_HRR_PENDING)
+        forhrr = 1; /* set forhrr if that's correct */
+    opl = PACKET_remaining(outerpkt);
+    opd = PACKET_data(outerpkt);
+    s->tmp_session_id_len = opd[startofsessid]; /* grab the session id */
+    if (s->tmp_session_id_len > SSL_MAX_SSL_SESSION_ID_LENGTH
+        || startofsessid + 1 + s->tmp_session_id_len > opl) {
+        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+        goto err;
+    }
+    memcpy(s->tmp_session_id, &opd[startofsessid + 1], s->tmp_session_id_len);
+    if (outersnioffset > 0) { /* Grab the outer SNI for tracing */
+        if (ech_get_outer_sni(s, &osni_str, opd, opl, outersnioffset) != 1
+            || osni_str == NULL) {
+            SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+            goto err;
+        }
+        OSSL_TRACE1(TLS, "EARLY: outer SNI of %s\n", osni_str);
+    } else {
+        OSSL_TRACE(TLS, "EARLY: no sign of an outer SNI\n");
+    }
+    if (echoffset > opl - 4) {
+        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+        goto err;
+    }
+    startofech = &opd[echoffset + 4];
+    echlen = opd[echoffset + 2] * 256 + opd[echoffset + 3];
+    if (echlen > opl - echoffset - 4) {
+        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+        goto err;
+    }
+    if (PACKET_buf_init(&echpkt, startofech, echlen) != 1) {
+        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+        goto err;
+    }
+    if (ech_decode_inbound_ech(s, &echpkt, &extval, &startofciphertext) != 1)
+        goto err; /* SSLfatal already called if needed */
+    /*
+     * startofciphertext is within the ECH value and after the length of the
+     * ciphertext, so we need to bump it by the offset of ECH within the CH
+     * plus the ECH type (2 octets) and length (also 2 octets) and that
+     * ciphertext length (another 2 octets) for a total of 6 octets
+     */
+    startofciphertext += echoffset + 6;
+    lenofciphertext = extval->payload_len;
+    aad_len = opl;
+    if (aad_len < startofciphertext + lenofciphertext) {
+        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+        goto err;
+    }
+    aad = OPENSSL_memdup(opd, aad_len);
+    if (aad == NULL) {
+        SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
+        goto err;
+    }
+    memset(aad + startofciphertext, 0, lenofciphertext);
+# ifdef OSSL_ECH_SUPERVERBOSE
+    ossl_ech_pbuf("EARLY aad", aad, aad_len);
+# endif
+    s->ext.ech.grease = OSSL_ECH_GREASE_UNKNOWN;
+    if (s->ext.ech.es == NULL) {
+        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+        goto err;
+    }
+    es = s->ext.ech.es;
+    num = (es == NULL || es->entries == NULL ? 0
+           : sk_OSSL_ECHSTORE_ENTRY_num(es->entries));
+    for (cfgind = 0; cfgind != num; cfgind++) {
+        ee = sk_OSSL_ECHSTORE_ENTRY_value(es->entries, cfgind);
+        OSSL_TRACE_BEGIN(TLS) {
+            BIO_printf(trc_out,
+                       "EARLY: rx'd config id (%x) ==? %d-th configured (%x)\n",
+                       extval->config_id, cfgind, ee->config_id);
+        } OSSL_TRACE_END(TLS);
+        if (extval->config_id == ee->config_id) {
+            foundcfg = 1;
+            break;
+        }
+    }
+    if (foundcfg == 1) {
+        clear = hpke_decrypt_encch(s, ee, extval, aad_len, aad,
+                                   forhrr, &clearlen);
+        if (clear == NULL)
+            s->ext.ech.grease = OSSL_ECH_IS_GREASE;
+    }
+    /* if still needed, trial decryptions */
+    if (clear == NULL && (s->options & SSL_OP_ECH_TRIALDECRYPT)) {
+        foundcfg = 0; /* reset as we're trying again */
+        for (cfgind = 0; cfgind != num; cfgind++) {
+            ee = sk_OSSL_ECHSTORE_ENTRY_value(es->entries, cfgind);
+            clear = hpke_decrypt_encch(s, ee, extval,
+                                       aad_len, aad, forhrr, &clearlen);
+            if (clear != NULL) {
+                foundcfg = 1;
+                s->ext.ech.grease = OSSL_ECH_NOT_GREASE;
+                break;
+            }
+        }
+    }
+    OPENSSL_free(aad);
+    aad = NULL;
+    s->ext.ech.done = 1; /* decrypting worked or not, but we're done now */
+    /* 3. if decrypt fails tee-up GREASE */
+    s->ext.ech.grease = OSSL_ECH_IS_GREASE;
+    s->ext.ech.success = 0;
+    if (clear != NULL) {
+        s->ext.ech.grease = OSSL_ECH_NOT_GREASE;
+        s->ext.ech.success = 1;
+    }
+    OSSL_TRACE_BEGIN(TLS) {
+        BIO_printf(trc_out, "EARLY: success: %d, assume_grease: %d, "
+                   "foundcfg: %d, cfgind: %d, clearlen: %zd, clear %p\n",
+                   s->ext.ech.success, s->ext.ech.grease, foundcfg,
+                   cfgind, clearlen, (void *)clear);
+    } OSSL_TRACE_END(TLS);
+# ifdef OSSL_ECH_SUPERVERBOSE
+    if (foundcfg == 1 && clear != NULL) { /* Bit more logging */
+        ossl_ech_pbuf("local config_id", &ee->config_id, 1);
+        ossl_ech_pbuf("remote config_id", &extval->config_id, 1);
+        ossl_ech_pbuf("clear", clear, clearlen);
+    }
+# endif
+    if (extval != NULL) {
+        ossl_ech_encch_free(extval);
+        OPENSSL_free(extval);
+        extval = NULL;
+    }
+    if (s->ext.ech.grease == OSSL_ECH_IS_GREASE) {
+        OPENSSL_free(clear);
+        return 1;
+    }
+    /* 4. if decrypt worked, de-compress cleartext to make up real inner CH */
+    if (ech_decode_inner(s, opd, opl, clear, clearlen) != 1) {
+        SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
+        goto err;
+    }
+    OPENSSL_free(clear);
+# ifdef OSSL_ECH_SUPERVERBOSE
+    ossl_ech_pbuf("Inner CH (decoded)", s->ext.ech.innerch,
+                  s->ext.ech.innerch_len);
+# endif
+    if (PACKET_buf_init(newpkt, s->ext.ech.innerch,
+                        s->ext.ech.innerch_len) != 1) {
+        SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
+        goto err;
+    }
+    /* tls_process_client_hello doesn't want the message header, so skip it */
+    if (!PACKET_forward(newpkt, SSL3_HM_HEADER_LENGTH)) {
+        SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
+        goto err;
+    }
+    if (ossl_ech_intbuf_add(s, s->ext.ech.innerch,
+                            s->ext.ech.innerch_len, 0) != 1) {
+        SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
+        goto err;
+    }
+    return 1;
+err:
+    OPENSSL_free(aad);
+    if (extval != NULL) {
+        ossl_ech_encch_free(extval);
+        OPENSSL_free(extval);
+    }
+    OPENSSL_free(clear);
+    return 0;
+}
+
 int ossl_ech_intbuf_add(SSL_CONNECTION *s, const unsigned char *buf,
                         size_t blen, int hash_existing)
 {
@@ -1137,7 +2146,7 @@ int ossl_ech_intbuf_add(SSL_CONNECTION *s, const unsigned char *buf,
     } else {
         /* just add new octets */
         if ((t1 = OPENSSL_realloc(s->ext.ech.transbuf,
-                                   s->ext.ech.transbuf_len + blen)) == NULL)
+                                  s->ext.ech.transbuf_len + blen)) == NULL)
             goto err;
         s->ext.ech.transbuf = t1;
         memcpy(s->ext.ech.transbuf + s->ext.ech.transbuf_len, buf, blen);
index 6ae0b42648900c3bd396ff936983d6d3650cdeeb..7dcd553de983c29fa4e6d2369ade8d2b576cc51e 100644 (file)
@@ -118,6 +118,33 @@ typedef struct ossl_echstore_entry_st {
     unsigned char *encoded; /* overall encoded content */
 } OSSL_ECHSTORE_ENTRY;
 
+/*
+ * What we send in the ech CH extension:
+ *     enum { outer(0), inner(1) } ECHClientHelloType;
+ *     struct {
+ *        ECHClientHelloType type;
+ *        select (ECHClientHello.type) {
+ *            case outer:
+ *                HpkeSymmetricCipherSuite cipher_suite;
+ *                uint8 config_id;
+ *                opaque enc<0..2^16-1>;
+ *                opaque payload<1..2^16-1>;
+ *            case inner:
+ *                Empty;
+ *        };
+ *     } ECHClientHello;
+ *
+ */
+typedef struct ech_encch_st {
+    uint16_t kdf_id; /* ciphersuite  */
+    uint16_t aead_id; /* ciphersuite  */
+    uint8_t config_id; /* (maybe) identifies DNS RR value used */
+    size_t enc_len; /* public share */
+    unsigned char *enc; /* public share for sender */
+    size_t payload_len; /* ciphertext  */
+    unsigned char *payload; /* ciphertext  */
+} OSSL_ECH_ENCCH;
+
 DEFINE_STACK_OF(OSSL_ECHSTORE_ENTRY)
 
 struct ossl_echstore_st {
@@ -205,6 +232,22 @@ typedef struct ossl_ech_conn_st {
     unsigned char *pub; /* client ephemeral public kept by server in case HRR */
     size_t pub_len;
     OSSL_HPKE_CTX *hpke_ctx; /* HPKE context, needed for HRR */
+    /*
+     * Offsets of various things we need to know about in an inbound
+     * ClientHello (CH) plus the type of ECH and whether that CH is an inner or
+     * outer CH. We find these once for the outer CH, by roughly parsing the CH
+     * so store them for later re-use. We need to re-do this parsing when we
+     * get the 2nd CH in the case of HRR, and when we move to processing the
+     * inner CH after successful ECH decyption, so we have a flag to say if
+     * we've done the work or not.
+     */
+    int ch_offsets_done;
+    size_t sessid_off; /* offset of session_id length */
+    size_t exts_off; /* to offset of extensions */
+    size_t ech_off; /* offset of ECH */
+    size_t sni_off; /* offset of (outer) SNI */
+    int echtype; /* ext type of the ECH */
+    int inner; /* 1 if the ECH is marked as an inner, 0 for outer */
     /*
      * A pointer to, and copy of, the hrrsignal from an HRR message.
      * We need both, as we zero-out the octets when re-calculating and
index 601a8ad16213e76964e74d9eaebe89d457b36bbe..436e0fa71ccf9a8ad1b2dc73ab73e33cd759a0f5 100644 (file)
@@ -185,7 +185,7 @@ int SSL_ech_get1_status(SSL *ssl, char **inner_sni, char **outer_sni)
             return SSL_ECH_STATUS_GREASE_ECH;
         return SSL_ECH_STATUS_GREASE;
     }
-    if ((s->options & SSL_OP_ECH_GREASE) !=0 && s->ext.ech.attempted != 1)
+    if ((s->options & SSL_OP_ECH_GREASE) != 0 && s->ext.ech.attempted != 1)
         return SSL_ECH_STATUS_GREASE;
     if (s->ext.ech.backend == 1) {
         if (s->ext.hostname != NULL
index 78e3c31272246b17216c4814e9314ec2e6eabee5..bb225decd3774eca3ca56801cdb3897cb9c7150a 100644 (file)
@@ -498,13 +498,8 @@ static const EXTENSION_DEFINITION ext_defs[] = {
         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,
+        tls_parse_ctos_ech, tls_parse_stoc_ech,
+        tls_construct_stoc_ech, tls_construct_ctos_ech,
         NULL
     },
     {
index ccb29d71386e10029a51e35e77c76f925a9ca33e..461463efa2a5ea71ee85aaa6e3ef021513109bac 100644 (file)
 #include "statem_local.h"
 #ifndef OPENSSL_NO_ECH
 # include <openssl/rand.h>
-#include "internal/ech_helpers.h"
-/*
- * the max HPKE 'info' we'll process is the max ECHConfig size
- * (OSSL_ECH_MAX_ECHCONFIG_LEN) plus OSSL_ECH_CONTEXT_STRING(len=7) + 1
- */
-#define OSSL_ECH_MAX_INFO_LEN (OSSL_ECH_MAX_ECHCONFIG_LEN + 8)
+# include "internal/ech_helpers.h"
 #endif
 
 EXT_RETURN tls_construct_ctos_renegotiate(SSL_CONNECTION *s, WPACKET *pkt,
@@ -2524,7 +2519,6 @@ EXT_RETURN tls_construct_ctos_ech(SSL_CONNECTION *s, WPACKET *pkt,
     unsigned char *encoded = NULL, *mypub = NULL;
     size_t cipherlen = 0, aad_len = 0, lenclen = 0, mypub_len = 0;
     size_t info_len = OSSL_ECH_MAX_INFO_LEN, clear_len = 0, encoded_len = 0;
-
     /* whether or not we've been asked to GREASE, one way or another */
     int grease_opt_set = (s->ext.ech.grease == OSSL_ECH_IS_GREASE
                           || ((s->options & SSL_OP_ECH_GREASE) != 0));
index bda7e460b9d053ef6fc7f2981522e2b4a6154f5e..5ea06eb0a7271a10577683edb63e1af285036a0c 100644 (file)
@@ -209,8 +209,8 @@ int custom_ext_add(SSL_CONNECTION *s, int context, WPACKET *pkt, X509 *x,
                 if (s->ext.ech.n_outer_only >= OSSL_ECH_OUTERS_MAX) {
                     OSSL_TRACE_BEGIN(TLS) {
                         BIO_printf(trc_out,
-                                "Too many outers to compress (max=%d)\n",
-                                OSSL_ECH_OUTERS_MAX);
+                                   "Too many outers to compress (max=%d)\n",
+                                   OSSL_ECH_OUTERS_MAX);
                     } OSSL_TRACE_END(TLS);
                     SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_R_BAD_EXTENSION);
                     return 0;
@@ -219,9 +219,9 @@ int custom_ext_add(SSL_CONNECTION *s, int context, WPACKET *pkt, X509 *x,
                 s->ext.ech.n_outer_only++;
                 OSSL_TRACE_BEGIN(TLS) {
                     BIO_printf(trc_out, "ECH compressing type "
-                            "0x%04x (tot: %d)\n",
-                            (int) meth->ext_type,
-                            (int) s->ext.ech.n_outer_only);
+                               "0x%04x (tot: %d)\n",
+                               (int) meth->ext_type,
+                               (int) s->ext.ech.n_outer_only);
                 } OSSL_TRACE_END(TLS);
             }
             if (s->ext.ech.ch_depth == 0) {
@@ -247,8 +247,8 @@ int custom_ext_add(SSL_CONNECTION *s, int context, WPACKET *pkt, X509 *x,
                     SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_R_BAD_EXTENSION);
                     return 0;
                 }
-                if (ossl_ech_copy_inner2outer(s, meth->ext_type, tind, pkt)
-                        != OSSL_ECH_SAME_EXT_DONE) {
+                if (ossl_ech_copy_inner2outer(s, meth->ext_type, tind,
+                                              pkt) != OSSL_ECH_SAME_EXT_DONE) {
                     /* for custom exts, we really should have found it */
                     SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_R_BAD_EXTENSION);
                     return 0;
@@ -505,19 +505,7 @@ int ossl_tls_add_custom_ext_intern(SSL_CTX *ctx, custom_ext_methods *exts,
      * for extension types that previously were not supported, but now are.
      */
     if (SSL_extension_supported(ext_type)
-#if !defined(OPENSSL_NO_ECH) && defined(OPENSSL_ECH_ALLOW_CUST_INJECT)
-            /*
-             * Do this conditionally so we can test an ECH in TLSv1.2 
-             * via the custom extensions API.
-             * OPENSSL_ECH_ALLOW_CUST_INJECT is defined (or not) in
-             * include/openssl/ech.h and if defined enables a test in
-             * test/ech_test.c
-             */
-            && ext_type != TLSEXT_TYPE_ech
             && ext_type != TLSEXT_TYPE_signed_certificate_timestamp)
-#else
-            && ext_type != TLSEXT_TYPE_signed_certificate_timestamp)
-#endif
         return 0;
 
     /* Extension type must fit in 16 bits */
index 884b9ca4891e38494212fdfa895cb5d244cf6238..91a076a3840c1134593fef97a198baafb911658b 100644 (file)
 #include "internal/cryptlib.h"
 #include "internal/ssl_unwrap.h"
 
-#define COOKIE_STATE_FORMAT_VERSION     1
+#ifndef OPENSSL_NO_ECH
+# include <openssl/rand.h>
+# include <openssl/trace.h>
+#endif
+
+#define COOKIE_STATE_FORMAT_VERSION 1
 
 /*
  * 2 bytes for packet length, 2 bytes for format version, 2 bytes for
@@ -2420,3 +2425,152 @@ int tls_parse_ctos_server_cert_type(SSL_CONNECTION *sc, PACKET *pkt,
     SSLfatal(sc, SSL_AD_UNSUPPORTED_CERTIFICATE, SSL_R_BAD_EXTENSION);
     return 0;
 }
+
+#ifndef OPENSSL_NO_ECH
+/*
+ * ECH handling for edge cases (GREASE/inner) and errors.
+ * return 1 for good, 0 otherwise
+ *
+ * Real ECH handling (i.e. decryption) happens before, via
+ * ech_early_decrypt(), but if that failed (e.g. decryption
+ * failed, which may be down to GREASE) then we end up here,
+ * processing the ECH from the outer CH.
+ * Otherwise, we only expect to see an inner ECH with a fixed
+ * value here.
+ */
+int tls_parse_ctos_ech(SSL_CONNECTION *s, PACKET *pkt, unsigned int context,
+                       X509 *x, size_t chainidx)
+{
+    unsigned int echtype = 0;
+
+    if (s->ext.ech.grease == OSSL_ECH_IS_GREASE) {
+        /* GREASE is fine */
+        return 1;
+    }
+    if (s->ext.ech.es == NULL) {
+        /* If not configured for ECH then we ignore it */
+        return 1;
+    }
+    if (s->ext.ech.attempted_type != TLSEXT_TYPE_ech) {
+        /* if/when new versions of ECH are added we'll update here */
+        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+        return 0;
+    }
+    /*
+     * we only allow "inner" which is one octet, valued 0x01
+     * and only if we decrypted ok or are a backend
+     */
+    if (PACKET_get_1(pkt, &echtype) != 1
+        || echtype != OSSL_ECH_INNER_CH_TYPE
+        || PACKET_remaining(pkt) != 0) {
+        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+        return 0;
+    }
+    if (s->ext.ech.success != 1 && s->ext.ech.backend != 1) {
+        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+        return 0;
+    }
+    /* yay - we're ok with this */
+    OSSL_TRACE_BEGIN(TLS) {
+        BIO_printf(trc_out, "ECH seen in inner as exptected.\n");
+    } OSSL_TRACE_END(TLS);
+    return 1;
+}
+
+/*
+ * Answer an ECH, as needed
+ * return 1 for good, 0 otherwise
+ *
+ * Return most-recent ECH config for retry, as needed.
+ * If doing HRR we include the confirmation value, but
+ * for now, we'll just add the zeros - the real octets
+ * will be added later via ech_calc_ech_confirm() which
+ * is called when constructing the server hello.
+ */
+EXT_RETURN tls_construct_stoc_ech(SSL_CONNECTION *s, WPACKET *pkt,
+                                  unsigned int context, X509 *x,
+                                  size_t chainidx)
+{
+    unsigned char *rcfgs = NULL;
+    size_t rcfgslen = 0;
+
+    if (context == SSL_EXT_TLS1_3_HELLO_RETRY_REQUEST
+        && (s->ext.ech.success == 1 || s->ext.ech.backend == 1)
+        && s->ext.ech.attempted_type == TLSEXT_TYPE_ech) {
+        unsigned char eightzeros[8] = {0, 0, 0, 0, 0, 0, 0, 0};
+
+        if (!WPACKET_put_bytes_u16(pkt, s->ext.ech.attempted_type)
+            || !WPACKET_sub_memcpy_u16(pkt, eightzeros, 8)) {
+            SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
+            return 0;
+        }
+        OSSL_TRACE_BEGIN(TLS) {
+            BIO_printf(trc_out, "set 8 zeros for ECH accept confirm in HRR\n");
+        } OSSL_TRACE_END(TLS);
+        return EXT_RETURN_SENT;
+    }
+    /* GREASE or error => random confirmation in HRR case */
+    if (context == SSL_EXT_TLS1_3_HELLO_RETRY_REQUEST
+        && s->ext.ech.attempted_type == TLSEXT_TYPE_ech
+        && s->ext.ech.attempted == 1) {
+        unsigned char randomconf[8];
+
+        if (RAND_bytes_ex(s->ssl.ctx->libctx, randomconf, 8,
+                          RAND_DRBG_STRENGTH) <= 0) {
+            SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
+            return 0;
+        }
+        if (!WPACKET_put_bytes_u16(pkt, s->ext.ech.attempted_type)
+            || !WPACKET_sub_memcpy_u16(pkt, randomconf, 8)) {
+            SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
+            return 0;
+        }
+        OSSL_TRACE_BEGIN(TLS) {
+            BIO_printf(trc_out, "set random for ECH acccpt confirm in HRR\n");
+        } OSSL_TRACE_END(TLS);
+        return EXT_RETURN_SENT;
+    }
+    /* in other HRR circumstances: don't set */
+    if (context == SSL_EXT_TLS1_3_HELLO_RETRY_REQUEST)
+        return EXT_RETURN_NOT_SENT;
+    /* If in some weird state we ignore and send nothing */
+    if (s->ext.ech.grease != OSSL_ECH_IS_GREASE
+        || s->ext.ech.attempted_type != TLSEXT_TYPE_ech)
+        return EXT_RETURN_NOT_SENT;
+    /*
+     * If the client GREASEd, or we think it did, return the
+     * most-recently loaded ECHConfigList, as the value of the
+     * extension. Most-recently loaded can be anywhere in the
+     * list, depending on changing or non-changing file names.
+     */
+    if (s->ext.ech.es == NULL) {
+        OSSL_TRACE_BEGIN(TLS) {
+            BIO_printf(trc_out, "ECH - not sending ECHConfigList to client "
+                       "even though they GREASE'd as I've no loaded configs\n");
+        } OSSL_TRACE_END(TLS);
+        return EXT_RETURN_NOT_SENT;
+    }
+    if (ossl_ech_get_retry_configs(s, &rcfgs, &rcfgslen) != 1) {
+        SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
+        return 0;
+    }
+    if (rcfgslen == 0) {
+        OSSL_TRACE_BEGIN(TLS) {
+            BIO_printf(trc_out, "ECH - not sending ECHConfigList to client "
+                       "even though they GREASE'd and I have configs but "
+                       "I've no configs set to be returned\n");
+        } OSSL_TRACE_END(TLS);
+        return EXT_RETURN_NOT_SENT;
+    }
+    if (!WPACKET_put_bytes_u16(pkt, TLSEXT_TYPE_ech)
+        || !WPACKET_start_sub_packet_u16(pkt)
+        || !WPACKET_sub_memcpy_u16(pkt, rcfgs, rcfgslen)
+        || !WPACKET_close(pkt)) {
+        SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
+        OPENSSL_free(rcfgs);
+        return 0;
+    }
+    OPENSSL_free(rcfgs);
+    return EXT_RETURN_SENT;
+}
+#endif /* END OPENSSL_NO_ECH */
index 4514b63066867303247de5a2b8ba7506ae2372f5..ff11d98080e31b06ec8c27c50708de3cf75e74de 100644 (file)
@@ -1312,7 +1312,7 @@ __owur CON_FUNC_RETURN tls_construct_client_hello(SSL_CONNECTION *s,
         goto err;
     }
     OPENSSL_free(s->ext.ech.innerch);
-    s->ext.ech.innerch = (unsigned char*)inner_mem->data;
+    s->ext.ech.innerch = (unsigned char *)inner_mem->data;
     inner_mem->data = NULL;
     s->ext.ech.innerch_len = innerlen;
     /* add inner to transcript */
index 6328e1ee828bd444164f9c3719979af9dcd9de8d..8ba5ef242a8b2949730faf1fef4f7656ffdc64b6 100644 (file)
@@ -574,7 +574,9 @@ int tls_parse_stoc_server_cert_type(SSL_CONNECTION *s, PACKET *pkt,
 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,
+int tls_parse_ctos_ech(SSL_CONNECTION *s, PACKET *pkt, unsigned int context,
+                       X509 *x, size_t chainidx);
+EXT_RETURN tls_construct_stoc_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,
index 80d09d76e1c15c99aa38cf1d4904cd39f3b43568..ac0d7d761e1e496503a38f59cf4d60b547c128e1 100644 (file)
 
 #define TICKET_NONCE_SIZE       8
 
+#ifndef OPENSSL_NO_ECH
+# include "../ech/ech_local.h"
+#endif
+
 typedef struct {
   ASN1_TYPE *kxBlob;
   ASN1_TYPE *opaqueBlob;
@@ -1495,6 +1499,86 @@ MSG_PROCESS_RETURN tls_process_client_hello(SSL_CONNECTION *s, PACKET *pkt)
     static const unsigned char null_compression = 0;
     CLIENTHELLO_MSG *clienthello = NULL;
 
+#ifndef OPENSSL_NO_ECH
+    /*
+     * For a split-mode backend we want to have a way to point at the CH octets
+     * for the accept-confirmation calculation. The split-mode backend does not
+     * need any ECH secrets, but it does need to see the inner CH and be the TLS
+     * endpoint with which the ECH encrypting client sets up the TLS session.
+     * The split-mode backend however does need to do an ECH confirm calculation
+     * so we need to tee that up. The result of that calculation will be put in
+     * the ServerHello.random (or ECH extension if HRR) to signal to the client
+     * that ECH "worked."
+     */
+    if (s->server && PACKET_remaining(pkt) != 0) {
+        int rv = 0, innerflag = -1;
+        size_t startofsessid = 0, startofexts = 0, echoffset = 0;
+        size_t outersnioffset = 0; /* offset to SNI in outer */
+        uint16_t echtype = OSSL_ECH_type_unknown; /* type of ECH seen */
+        const unsigned char *pbuf = NULL;
+
+        /* reset needed in case of HRR */
+        s->ext.ech.ch_offsets_done = 0;
+        rv = ossl_ech_get_ch_offsets(s, pkt, &startofsessid, &startofexts,
+                                     &echoffset, &echtype, &innerflag,
+                                     &outersnioffset);
+        if (rv != 1) {
+            SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
+            goto err;
+        }
+        if (innerflag == OSSL_ECH_INNER_CH_TYPE) {
+            WPACKET inner;
+
+            OSSL_TRACE(TLS, "Got inner ECH so setting backend\n");
+            /* For backend, include msg type & 3 octet length */
+            s->ext.ech.backend = 1;
+            s->ext.ech.attempted_type = TLSEXT_TYPE_ech;
+            OPENSSL_free(s->ext.ech.innerch);
+            s->ext.ech.innerch_len = PACKET_remaining(pkt);
+            if (PACKET_peek_bytes(pkt, &pbuf, s->ext.ech.innerch_len) != 1) {
+                SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
+                goto err;
+            }
+            s->ext.ech.innerch_len += SSL3_HM_HEADER_LENGTH; /* 4 */
+            s->ext.ech.innerch = OPENSSL_malloc(s->ext.ech.innerch_len);
+            if (s->ext.ech.innerch == NULL) {
+                SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
+                goto err;
+            }
+            if (!WPACKET_init_static_len(&inner, s->ext.ech.innerch,
+                                         s->ext.ech.innerch_len, 0)
+                || !WPACKET_put_bytes_u8(&inner, SSL3_MT_CLIENT_HELLO)
+                || !WPACKET_put_bytes_u24(&inner, s->ext.ech.innerch_len
+                                          - SSL3_HM_HEADER_LENGTH)
+                || !WPACKET_memcpy(&inner, pbuf, s->ext.ech.innerch_len
+                                   - SSL3_HM_HEADER_LENGTH)
+                || !WPACKET_finish(&inner)) {
+                SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
+                goto err;
+            }
+        } else if (s->ext.ech.es != NULL) {
+            PACKET newpkt;
+
+            if (ossl_ech_early_decrypt(s, pkt, &newpkt) != 1) {
+                SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
+                goto err;
+            }
+            if (s->ext.ech.success == 1) {
+                /*
+                 * Replace the outer CH with the inner, as long as there's
+                 * space, which there better be! (a bug triggered a bigger
+                 * inner CH once;-)
+                 */
+                if (PACKET_remaining(&newpkt) > PACKET_remaining(pkt)) {
+                    SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
+                    goto err;
+                }
+                *pkt = newpkt;
+            }
+        }
+    }
+#endif
+
     /* Check if this is actually an unexpected renegotiation ClientHello */
     if (s->renegotiate == 0 && !SSL_IS_FIRST_HANDSHAKE(s)) {
         if (!ossl_assert(!SSL_CONNECTION_IS_TLS13(s))) {
@@ -1696,6 +1780,12 @@ MSG_PROCESS_RETURN tls_process_client_hello(SSL_CONNECTION *s, PACKET *pkt)
     if (clienthello != NULL)
         OPENSSL_free(clienthello->pre_proc_exts);
     OPENSSL_free(clienthello);
+#ifndef OPENSSL_NO_ECH
+    s->clienthello = NULL;
+    OPENSSL_free(s->ext.ech.innerch);
+    s->ext.ech.innerch = NULL;
+    s->ext.ech.innerch_len = 0;
+#endif
 
     return MSG_PROCESS_ERROR;
 }
@@ -1989,12 +2079,24 @@ static int tls_early_post_process_client_hello(SSL_CONNECTION *s)
         goto err;
     }
 
-    if (!s->hit
-            && s->version >= TLS1_VERSION
-            && !SSL_CONNECTION_IS_TLS13(s)
-            && !SSL_CONNECTION_IS_DTLS(s)
-            && s->ext.session_secret_cb != NULL) {
+    /*
+     * Unless ECH has worked or not been configured we won't call
+     * the session_secret_cb now because we'll need to calculate the
+     * server random later to include the ECH accept value.
+     * We can't do it now as we don't yet have the SH encoding.
+     */
+    if (
+#ifndef OPENSSL_NO_ECH
+        ((s->ext.ech.es != NULL && s->ext.ech.success == 1)
+         || s->ext.ech.es == NULL) &&
+#endif
+        !s->hit
+        && s->version >= TLS1_VERSION
+        && !SSL_CONNECTION_IS_TLS13(s)
+        && !SSL_CONNECTION_IS_DTLS(s)
+        && s->ext.session_secret_cb != NULL) {
         const SSL_CIPHER *pref_cipher = NULL;
+
         /*
          * s->session->master_key_length is a size_t, but this is an int for
          * backwards compat reasons
@@ -2151,7 +2253,8 @@ static int tls_early_post_process_client_hello(SSL_CONNECTION *s)
  err:
     sk_SSL_CIPHER_free(ciphers);
     sk_SSL_CIPHER_free(scsvs);
-    OPENSSL_free(clienthello->pre_proc_exts);
+    if (clienthello != NULL)
+        OPENSSL_free(clienthello->pre_proc_exts);
     OPENSSL_free(s->clienthello);
     s->clienthello = NULL;
 
@@ -2513,16 +2616,147 @@ CON_FUNC_RETURN tls_construct_server_hello(SSL_CONNECTION *s, WPACKET *pkt)
          * Re-initialise the Transcript Hash. We're going to prepopulate it with
          * a synthetic message_hash in place of ClientHello1.
          */
-        if (!create_synthetic_message_hash(s, NULL, 0, NULL, 0)) {
+#ifndef OPENSSL_NO_ECH
+        /*
+         * if we're sending 2nd SH after HRR and we did ECH
+         * then we want to inject the hash of the inner CH1
+         * and not the outer (which is the default)
+         */
+        OSSL_TRACE_BEGIN(TLS) {
+            BIO_printf(trc_out, "Checking success (%d)/innerCH (%p)\n",
+                       s->ext.ech.success, (void *)s->ext.ech.innerch);
+        } OSSL_TRACE_END(TLS);
+        if ((s->ext.ech.backend == 1 || s->ext.ech.success == 1)
+            && s->ext.ech.innerch != NULL) {
+            /* do pre-existing HRR stuff */
+            unsigned char hashval[EVP_MAX_MD_SIZE];
+            unsigned int hashlen;
+            EVP_MD_CTX *ctx = EVP_MD_CTX_new();
+            const EVP_MD *md = NULL;
+
+            OSSL_TRACE(TLS, "Adding in digest of ClientHello\n");
+# ifdef OSSL_ECH_SUPERVERBOSE
+            ossl_ech_pbuf("innerch", s->ext.ech.innerch,
+                          s->ext.ech.innerch_len);
+# endif
+            if (ctx == NULL) {
+                SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
+                return CON_FUNC_ERROR;
+            }
+            md = ssl_handshake_md(s);
+            if (md == NULL) {
+                EVP_MD_CTX_free(ctx);
+                SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
+                return CON_FUNC_ERROR;
+            }
+            if (EVP_DigestInit_ex(ctx, md, NULL) <= 0
+                || EVP_DigestUpdate(ctx, s->ext.ech.innerch,
+                                    s->ext.ech.innerch_len) <= 0
+                || EVP_DigestFinal_ex(ctx, hashval, &hashlen) <= 0) {
+                EVP_MD_CTX_free(ctx);
+                SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
+                return CON_FUNC_ERROR;
+            }
+# ifdef OSSL_ECH_SUPERVERBOSE
+            ossl_ech_pbuf("digested CH", hashval, hashlen);
+# endif
+            EVP_MD_CTX_free(ctx);
+            if (ossl_ech_reset_hs_buffer(s, NULL, 0) != 1) {
+                SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
+                return CON_FUNC_ERROR;
+            }
+            if (!create_synthetic_message_hash(s, hashval, hashlen, NULL, 0)) {
+                /* SSLfatal() already called */
+                return CON_FUNC_ERROR;
+            }
+        } else {
+            if (!create_synthetic_message_hash(s, NULL, 0, NULL, 0))
+                return CON_FUNC_ERROR; /* SSLfatal() already called */
+        }
+#else
+        if (!create_synthetic_message_hash(s, NULL, 0, NULL, 0))
             /* SSLfatal() already called */
             return CON_FUNC_ERROR;
-        }
+#endif /* OPENSSL_NO_ECH */
     } else if (!(s->verify_mode & SSL_VERIFY_PEER)
-                && !ssl3_digest_cached_records(s, 0)) {
+               && !ssl3_digest_cached_records(s, 0)) {
         /* SSLfatal() already called */;
         return CON_FUNC_ERROR;
     }
 
+#ifndef OPENSSL_NO_ECH
+    /*
+     * Calculate the ECH-accept server random to indicate that
+     * we're accepting ECH, if that's the case
+     */
+    if (s->ext.ech.attempted_type == TLSEXT_TYPE_ech
+        && (s->ext.ech.backend == 1
+            || (s->ext.ech.es != NULL && s->ext.ech.success == 1))) {
+        unsigned char acbuf[8];
+        unsigned char *shbuf = NULL;
+        size_t shlen = 0;
+        size_t shoffset = 0;
+        int hrr = 0;
+
+        if (s->hello_retry_request == SSL_HRR_PENDING)
+            hrr = 1;
+        memset(acbuf, 0, 8);
+        if (WPACKET_get_total_written(pkt, &shlen) != 1) {
+            SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
+            return CON_FUNC_ERROR;
+        }
+        shbuf = WPACKET_get_curr(pkt) - shlen;
+        /* we need to fixup SH length here */
+        shbuf[1] = ((shlen - 4)) >> 16 & 0xff;
+        shbuf[2] = ((shlen - 4)) >> 8 & 0xff;
+        shbuf[3] = (shlen - 4) & 0xff;
+        if (ossl_ech_intbuf_add(s, shbuf, shlen, hrr) != 1) {
+            SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
+            return CON_FUNC_ERROR;
+        }
+        if (ossl_ech_calc_confirm(s, hrr, acbuf, shlen) != 1) {
+            SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
+            return CON_FUNC_ERROR;
+        }
+        memcpy(s->s3.server_random + SSL3_RANDOM_SIZE - 8, acbuf, 8);
+        if (hrr == 0) {
+            /* confirm value hacked into SH.random rightmost octets */
+            shoffset = SSL3_HM_HEADER_LENGTH /* 4 */
+                + CLIENT_VERSION_LEN /* 2 */
+                + SSL3_RANDOM_SIZE /* 32 */
+                - 8;
+            memcpy(shbuf + shoffset, acbuf, 8);
+        } else {
+            /*
+             * confirm value is in extension in HRR case as the SH.random
+             * is already hacked to be a specific value in a HRR
+             */
+            memcpy(WPACKET_get_curr(pkt) - 8, acbuf, 8);
+        }
+    }
+    /* call ECH callback, if appropriate */
+    if (s->ext.ech.attempted == 1 && s->ext.ech.cb != NULL
+        && s->hello_retry_request != SSL_HRR_PENDING) {
+        char pstr[OSSL_ECH_PBUF_SIZE + 1];
+        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 CON_FUNC_ERROR;
+        }
+        memset(pstr, 0, OSSL_ECH_PBUF_SIZE + 1);
+        ossl_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) {
+            OSSL_TRACE(TLS, "Error from tls_construct_server_hello/ech_cb\n");
+            SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
+            return CON_FUNC_ERROR;
+        }
+    }
+#endif /* OPENSSL_NO_ECH */
     return CON_FUNC_SUCCESS;
 }
 
index 968fca896d1c2dd9c66fcde6a6dec1ee7e291e6b..2f3cddc27b1a7fc7f220d816bb51a8ed00e9c1a8 100644 (file)
@@ -893,11 +893,15 @@ static int ech_ingest_test(int run)
      * Occasionally, flush_time will be 1 more than add_time. We'll
      * check for that as that should catch a few more code paths
      * in the flush_keys API.
+     * When flush_time is 1 more, we may or may not have flushed
+     * the one and only key (depending on which "side" of the second
+     * it was generated, so we may be left with 0 or 1 keys.
      */
     if (!TEST_true(OSSL_ECHSTORE_flush_keys(es, flush_time - add_time))
         || !TEST_int_eq(OSSL_ECHSTORE_num_keys(es, &keysaftr), 1)
         || ((flush_time <= add_time) && !TEST_int_eq(keysaftr, 0))
-        || ((flush_time > add_time) && !TEST_int_eq(keysaftr, 1))) {
+        || ((flush_time > add_time) && !TEST_int_eq(keysaftr, 1)
+            && !TEST_int_eq(keysaftr, 0))) {
         TEST_info("Flush time: %lld, add_time: %lld", (long long)flush_time,
                   (long long)add_time);
         goto end;
@@ -1141,6 +1145,7 @@ end:
 # define OSSL_ECH_TEST_EARLY    2
 # define OSSL_ECH_TEST_CUSTOM   3
 # define OSSL_ECH_TEST_ENOE     4 /* early + no-ech */
+/* note: early-data is prohibited after HRR so no tests for that */
 
 /*
  * @brief ECH roundtrip test helper
@@ -1155,13 +1160,6 @@ end:
  *
  * The combo input is one of the #define'd OSSL_ECH_TEST_*
  * values above.
- *
- * TODO(ECH): we're not yet really attempting ECH, but we currently
- * set the inputs as if we were doing ECH, yet don't expect to see
- * real ECH status outcomes, so while we do make calls to get that
- * status outcome, we don't compare vs. real expected results.
- * That's done via the "if (0 &&" clauses below which will be
- * removed once ECH is really being attempted.
  */
 static int test_ech_roundtrip_helper(int idx, int combo)
 {
@@ -1205,10 +1203,9 @@ static int test_ech_roundtrip_helper(int idx, int combo)
     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;
-        if (!TEST_true(SSL_CTX_set_recv_max_early_data(sctx,
-                                                       SSL3_RT_MAX_PLAIN_LENGTH)))
+                                                     SSL3_RT_MAX_PLAIN_LENGTH))
+            || !TEST_true(SSL_CTX_set_recv_max_early_data(sctx,
+                                                          SSL3_RT_MAX_PLAIN_LENGTH)))
             goto end;
     }
     if (combo == OSSL_ECH_TEST_CUSTOM) {
@@ -1239,58 +1236,38 @@ static int test_ech_roundtrip_helper(int idx, int combo)
         goto end;
     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);
-    if (0 && !TEST_int_eq(serverstatus, SSL_ECH_STATUS_SUCCESS))
-        goto end;
     /* override cert verification */
     SSL_set_verify_result(clientssl, X509_V_OK);
     clientstatus = SSL_ech_get1_status(clientssl, &cinner, &couter);
     if (verbose)
         TEST_info("client status %d, %s, %s", clientstatus, cinner, couter);
-    if (0 && !TEST_int_eq(clientstatus, SSL_ECH_STATUS_SUCCESS))
+    serverstatus = SSL_ech_get1_status(serverssl, &sinner, &souter);
+    if (verbose)
+        TEST_info("server status %d, %s, %s", serverstatus, sinner, souter);
+    if (combo != OSSL_ECH_TEST_ENOE
+        && !TEST_int_eq(serverstatus, SSL_ECH_STATUS_SUCCESS))
+        goto end;
+    if (combo == OSSL_ECH_TEST_ENOE
+        && !TEST_int_eq(serverstatus, SSL_ECH_STATUS_NOT_TRIED))
+        goto end;
+    if (combo != OSSL_ECH_TEST_ENOE
+        && !TEST_int_eq(clientstatus, SSL_ECH_STATUS_SUCCESS))
+        goto end;
+    if (combo == OSSL_ECH_TEST_ENOE
+        && !TEST_int_eq(clientstatus, SSL_ECH_STATUS_NOT_CONFIGURED))
         goto end;
     /* all good */
-    if (combo == OSSL_ECH_TEST_BASIC
-        || combo == OSSL_ECH_TEST_HRR
+    if (combo == OSSL_ECH_TEST_BASIC || combo == OSSL_ECH_TEST_HRR
         || combo == OSSL_ECH_TEST_CUSTOM) {
         res = 1;
         goto end;
     }
     /* continue for EARLY test */
-# 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);
@@ -1310,8 +1287,8 @@ static int test_ech_roundtrip_helper(int idx, int combo)
         || !TEST_true(SSL_set_session(clientssl, sess))
         || !TEST_true(SSL_write_early_data(clientssl, ed, sizeof(ed), &written))
         || !TEST_size_t_eq(written, sizeof(ed))
-        || !TEST_int_eq(SSL_read_early_data(serverssl, buf,
-                                            sizeof(buf), &readbytes),
+        || !TEST_int_eq(SSL_read_early_data(serverssl, buf, sizeof(buf),
+                                            &readbytes),
                         SSL_READ_EARLY_DATA_SUCCESS)
         || !TEST_size_t_eq(written, readbytes))
         goto end;
@@ -1324,17 +1301,25 @@ static int test_ech_roundtrip_helper(int idx, int combo)
             || !TEST_true(SSL_read_ex(clientssl, buf, sizeof(buf), &readbytes))
             || !TEST_mem_eq(buf, readbytes, ed, sizeof(ed)))
         goto end;
-    serverstatus = SSL_ech_get1_status(serverssl, &sinner, &souter);
-    if (verbose)
-        TEST_info("server status %d, %s, %s", serverstatus, sinner, souter);
-    if (0 && !TEST_int_eq(serverstatus, SSL_ECH_STATUS_SUCCESS))
-        goto end;
     /* override cert verification */
     SSL_set_verify_result(clientssl, X509_V_OK);
     clientstatus = SSL_ech_get1_status(clientssl, &cinner, &couter);
     if (verbose)
         TEST_info("client status %d, %s, %s", clientstatus, cinner, couter);
-    if (0 && !TEST_int_eq(clientstatus, SSL_ECH_STATUS_SUCCESS))
+    serverstatus = SSL_ech_get1_status(serverssl, &sinner, &souter);
+    if (verbose)
+        TEST_info("server status %d, %s, %s", serverstatus, sinner, souter);
+    if (combo != OSSL_ECH_TEST_ENOE
+        && !TEST_int_eq(serverstatus, SSL_ECH_STATUS_SUCCESS))
+        goto end;
+    if (combo == OSSL_ECH_TEST_ENOE
+        && !TEST_int_eq(serverstatus, SSL_ECH_STATUS_NOT_TRIED))
+        goto end;
+    if (combo != OSSL_ECH_TEST_ENOE
+        && !TEST_int_eq(clientstatus, SSL_ECH_STATUS_SUCCESS))
+        goto end;
+    if (combo == OSSL_ECH_TEST_ENOE
+        && !TEST_int_eq(clientstatus, SSL_ECH_STATUS_NOT_CONFIGURED))
         goto end;
     /* all good */
     res = 1;
index 69fb23bfc1e7d590c69fd1775eb6011193a2b946..fa14f51834dec45bcfa766638e41276027d83734 100644 (file)
@@ -89,6 +89,7 @@ __current_exception_context
 strlen
 strstr
 strchr
+strlen
 memmove
 strrchr
 memcmp