Commit 96c76dc72b for openssl.org
commit 96c76dc72bb20a6c2b93dd91ac3cb4fbf0e15670
Author: sftcd <stephen.farrell@cs.tcd.ie>
Date: Wed Apr 8 11:11:37 2026 +0100
curl ECH+QUIC fix
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
Reviewed-by: Matt Caswell <matt@openssl.foundation>
MergeDate: Sat Apr 11 18:29:37 2026
(Merged from https://github.com/openssl/openssl/pull/30727)
diff --git a/ssl/ech/ech_internal.c b/ssl/ech/ech_internal.c
index 72e927ad27..15069492f2 100644
--- a/ssl/ech/ech_internal.c
+++ b/ssl/ech/ech_internal.c
@@ -937,6 +937,7 @@ static int ech_hkdf_extract_wrap(SSL_CONNECTION *s, EVP_MD *md, int for_hrr,
EVP_PKEY_CTX *pctx = NULL;
const char *label = NULL;
unsigned char *p = NULL;
+ SSL_CTX *sctx = SSL_CONNECTION_GET_CTX(s);
if (for_hrr == 1) {
label = OSSL_ECH_HRR_CONFIRM_STRING;
@@ -950,7 +951,7 @@ static int ech_hkdf_extract_wrap(SSL_CONNECTION *s, EVP_MD *md, int for_hrr,
#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);
+ pctx = EVP_PKEY_CTX_new_from_name(sctx->libctx, "HKDF", sctx->propq);
if (pctx == NULL
|| EVP_PKEY_derive_init(pctx) != 1
|| EVP_PKEY_CTX_hkdf_mode(pctx,
diff --git a/ssl/ech/ech_store.c b/ssl/ech/ech_store.c
index 1f5d79e021..dd05bf3702 100644
--- a/ssl/ech/ech_store.c
+++ b/ssl/ech/ech_store.c
@@ -1114,7 +1114,7 @@ int OSSL_ECHSTORE_read_pem(OSSL_ECHSTORE *es, BIO *in, int for_retry)
* the BIO_f_buffer allows us to seek back to the start.
*/
BIO_push(fbio, in);
- if (!PEM_read_bio_PrivateKey(fbio, &priv, NULL, NULL)
+ if (!PEM_read_bio_PrivateKey_ex(fbio, &priv, NULL, NULL, es->libctx, es->propq)
&& BIO_seek(fbio, 0) < 0) {
ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
goto err;
diff --git a/ssl/statem/extensions.c b/ssl/statem/extensions.c
index cc6585493f..2f97850972 100644
--- a/ssl/statem/extensions.c
+++ b/ssl/statem/extensions.c
@@ -722,9 +722,16 @@ static int verify_extension(SSL_CONNECTION *s, unsigned int context,
ENDPOINT role = ENDPOINT_BOTH;
custom_ext_method *meth = NULL;
- if ((context & SSL_EXT_CLIENT_HELLO) != 0)
+ if ((context & SSL_EXT_CLIENT_HELLO) != 0) {
+#ifndef OPENSSL_NO_ECH
+ if (s->ext.ech.attempted == 1 && s->ext.ech.ch_depth == 1)
+ role = ENDPOINT_CLIENT;
+ else
+ role = ENDPOINT_SERVER;
+#else
role = ENDPOINT_SERVER;
- else if ((context & SSL_EXT_TLS1_2_SERVER_HELLO) != 0)
+#endif
+ } else if ((context & SSL_EXT_TLS1_2_SERVER_HELLO) != 0)
role = ENDPOINT_CLIENT;
meth = custom_ext_find(meths, role, type, &offset);
@@ -812,8 +819,13 @@ int tls_collect_extensions(SSL_CONNECTION *s, PACKET *packet,
* Initialise server side custom extensions. Client side is done during
* construction of extensions for the ClientHello.
*/
+#ifndef OPENSSL_NO_ECH
+ if ((context & SSL_EXT_CLIENT_HELLO) != 0 && s->ext.ech.attempted == 0)
+ custom_ext_init(&s->cert->custext);
+#else
if ((context & SSL_EXT_CLIENT_HELLO) != 0)
custom_ext_init(&s->cert->custext);
+#endif
num_exts = OSSL_NELEM(ext_defs) + (exts != NULL ? exts->meths_count : 0);
raw_extensions = OPENSSL_calloc(num_exts, sizeof(*raw_extensions));
@@ -1072,10 +1084,15 @@ int tls_construct_extensions(SSL_CONNECTION *s, WPACKET *pkt,
}
/* Add custom extensions first */
- if ((context & SSL_EXT_CLIENT_HELLO) != 0) {
+#ifndef OPENSSL_NO_ECH
+ if ((context & SSL_EXT_CLIENT_HELLO) != 0 && s->ext.ech.attempted == 0)
/* On the server side with initialise during ClientHello parsing */
custom_ext_init(&s->cert->custext);
- }
+#else
+ if ((context & SSL_EXT_CLIENT_HELLO) != 0)
+ /* On the server side with initialise during ClientHello parsing */
+ custom_ext_init(&s->cert->custext);
+#endif
if (!custom_ext_add(s, context, pkt, x, chainidx, max_version)) {
/* SSLfatal() already called */
return 0;
diff --git a/ssl/statem/statem_clnt.c b/ssl/statem/statem_clnt.c
index 3003466a02..36c698c163 100644
--- a/ssl/statem/statem_clnt.c
+++ b/ssl/statem/statem_clnt.c
@@ -1489,7 +1489,14 @@ __owur CON_FUNC_RETURN tls_construct_client_hello(SSL_CONNECTION *s, WPACKET *pk
#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);
+ if (s->version != TLS1_3_VERSION) {
+ SSLfatal(s, SSL_AD_PROTOCOL_VERSION, SSL_R_UNSUPPORTED_SSL_VERSION);
+ return CON_FUNC_ERROR;
+ }
+ if ((s->options & SSL_OP_ENABLE_MIDDLEBOX_COMPAT) != 0)
+ sess_id_len = sizeof(s->tmp_session_id);
+ else
+ sess_id_len = 0;
} else {
#endif
if (s->new_session || s->session->ssl_version == TLS1_3_VERSION) {
diff --git a/test/quicapitest.c b/test/quicapitest.c
index ad2c8ca2a9..84a5c9566f 100644
--- a/test/quicapitest.c
+++ b/test/quicapitest.c
@@ -22,6 +22,7 @@
#include "internal/quic_error.h"
static OSSL_LIB_CTX *libctx = NULL;
+static char *propq = NULL;
static OSSL_PROVIDER *defctxnull = NULL;
static char *certsdir = NULL;
static char *cert = NULL;
@@ -3424,6 +3425,98 @@ static int test_quic_peer_addr_v6(void)
"::2", 4434);
}
+/* Test ECH with quic */
+static int test_ech(void)
+{
+ /*
+ * Don't try this test if various ECC things are set of unavailable
+ * or we're in a no-ech build
+ */
+#if defined(OPENSSL_NO_EC) || defined(OPENSSL_NO_ECX) || defined(OPENSSL_NO_ECH)
+ propq = NULL; /* avoid unused var warning */
+ return 1;
+#else
+ SSL_CTX *cctx = NULL, *sctx = NULL;
+ SSL *clientquic = NULL;
+ char *rinner = NULL, *router = NULL;
+ const char *inner = "inner.example.com";
+ QUIC_TSERVER *qtserv = NULL;
+ int testresult = 0;
+ /* p256 ech key pair with public name server.example */
+ const char echpem[] = "-----BEGIN PRIVATE KEY-----\n"
+ "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg+Ygt9nhASeoYbzo2\n"
+ "Nz/jGFAdeTo25SVYWQvnf86qzbahRANCAARS9QqkjJU311J7kS8LsyISJ8xYFbJ5\n"
+ "5BX/pu4QiFXJ3dEGrjYh4PDH/ehFfaqZgtRRg2r/AP+vwkLiP2mqCfdv\n"
+ "-----END PRIVATE KEY-----\n"
+ "-----BEGIN ECHCONFIG-----\n"
+ "AGL+DQBezwAQAEEEUvUKpIyVN9dSe5EvC7MiEifMWBWyeeQV/6buEIhVyd3RBq42\n"
+ "IeDwx/3oRX2qmYLUUYNq/wD/r8JC4j9pqgn3bwAEAAEAAQAOc2VydmVyLmV4YW1w\n"
+ "bGUAAA==\n"
+ "-----END ECHCONFIG-----\n";
+ const char ec_pub[] = "AGL+DQBezwAQAEEEUvUKpIyVN9dSe5EvC7MiEifMWBWyeeQV/6buEIhVyd3RBq42"
+ "IeDwx/3oRX2qmYLUUYNq/wD/r8JC4j9pqgn3bwAEAAEAAQAOc2VydmVyLmV4YW1w"
+ "bGUAAA==";
+ size_t ec_publen = sizeof(ec_pub) - 1;
+ BIO *in = NULL;
+ OSSL_ECHSTORE *es = NULL;
+
+ /* HPKE and FIPS are not friends, so don't test in that case */
+ if (is_fips) {
+ TEST_info("No real ECH test as is_fips is set\n");
+ return 1;
+ } else {
+ TEST_info("Doing real ECH test as is_fips is not set\n");
+ }
+
+ /* make an OSSL_ECHSTORE for echpem */
+ if ((in = BIO_new(BIO_s_mem())) == NULL
+ || BIO_write(in, echpem, (int)strlen(echpem)) <= 0
+ || !TEST_ptr(es = OSSL_ECHSTORE_new(libctx, propq))
+ || !TEST_true(OSSL_ECHSTORE_read_pem(es, in, OSSL_ECH_FOR_RETRY)))
+ goto err;
+
+ cctx = SSL_CTX_new_ex(libctx, NULL, OSSL_QUIC_client_method());
+ sctx = SSL_CTX_new_ex(libctx, NULL, TLS_method());
+ /* set OSSL_ECHSTORE for server */
+ if (!TEST_ptr(sctx) || !TEST_true(SSL_CTX_set1_echstore(sctx, es)))
+ goto err;
+
+ if (!TEST_ptr(cctx)
+ || !TEST_true(qtest_create_quic_objects(libctx, cctx, sctx, cert,
+ privkey,
+ QTEST_FLAG_FAKE_TIME,
+ &qtserv,
+ &clientquic, NULL, NULL)))
+ goto err;
+
+ /* set echconfig for client */
+ if (!TEST_true(SSL_set1_ech_config_list(clientquic,
+ (unsigned char *)ec_pub, ec_publen))
+ || !TEST_true(SSL_set_tlsext_host_name(clientquic, inner)))
+ goto err;
+ /* we expect the connection to succeed */
+ if (!TEST_true(qtest_create_quic_connection(qtserv, clientquic)))
+ goto err;
+ SSL_set_verify_result(clientquic, X509_V_OK);
+ if (!TEST_int_eq(SSL_ech_get1_status(clientquic, &rinner, &router),
+ SSL_ECH_STATUS_SUCCESS))
+ goto err;
+
+ testresult = 1;
+err:
+ ossl_quic_tserver_free(qtserv);
+ SSL_free(clientquic);
+ OPENSSL_free(router);
+ OPENSSL_free(rinner);
+ SSL_CTX_free(cctx);
+ SSL_CTX_free(sctx);
+ OSSL_ECHSTORE_free(es);
+ BIO_free_all(in);
+
+ return testresult;
+#endif
+}
+
/***********************************************************************************/
OPT_TEST_DECLARE_USAGE("provider config certsdir datadir\n")
@@ -3531,6 +3624,7 @@ int setup_tests(void)
ADD_TEST(test_client_hello_retry);
ADD_TEST(test_quic_peer_addr_v6);
ADD_TEST(test_quic_peer_addr_v4);
+ ADD_TEST(test_ech);
return 1;
err: