From: sftcd Date: Wed, 8 Apr 2026 10:11:37 +0000 (+0100) Subject: curl ECH+QUIC fix X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=96c76dc72bb20a6c2b93dd91ac3cb4fbf0e15670;p=thirdparty%2Fopenssl.git curl ECH+QUIC fix Reviewed-by: Tomas Mraz Reviewed-by: Matt Caswell 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 72e927ad279..15069492f2d 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 1f5d79e0212..dd05bf3702c 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 cc6585493f3..2f978509720 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 3003466a020..36c698c163c 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 ad2c8ca2a9d..84a5c9566f5 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: