From: Jakub Zelenka Date: Fri, 19 Jun 2026 16:52:37 +0000 (+0200) Subject: statem: add direct tests for tls_construct_client_hello X-Git-Url: http://git.ipfire.org/gitweb/?a=commitdiff_plain;h=c36a9b4;p=thirdparty%2Fopenssl.git statem: add direct tests for tls_construct_client_hello Add a new test which calls tls_construct_client_hello() directly, creating an SSL_CONNECTION enough to invoke the construct function without making a full handshake, then verifies the produced ClientHello structurally and by round-tripping it through the server-side tls_process_client_hello(). The covered branches include TLS 1.3 (with and without middlebox compat), TLS 1.2, DTLS, session resumption, HelloRetryRequest, the DTLS cookie and client_random reuse paths, the deterministic error branches (WPACKET overflow and no usable ciphers), and the ECH wrapper path (happy path with server-side decryption, the TLS 1.2 version error, and OOM). Out-of-memory branches are exercised with the mfail tests. Assisted-by: Claude:claude-opus-4-8 Reviewed-by: Tomas Mraz Reviewed-by: Daniel Kubec MergeDate: Tue Jun 30 08:54:48 2026 (Merged from https://github.com/openssl/openssl/pull/31618) --- diff --git a/test/build.info b/test/build.info index 20aec5eb3ed..ac51116e56c 100644 --- a/test/build.info +++ b/test/build.info @@ -1167,6 +1167,11 @@ IF[{- !$disabled{tests} -}] INCLUDE[ext_internal_test]=.. ../include ../apps/include DEPEND[ext_internal_test]=../libcrypto.a ../libssl.a libtestutil.a + PROGRAMS{noinst}=statem_clnt_construct_test + SOURCE[statem_clnt_construct_test]=statem_clnt_construct_test.c + INCLUDE[statem_clnt_construct_test]=.. ../include ../apps/include + DEPEND[statem_clnt_construct_test]=../libcrypto.a ../libssl.a libtestutil.a + PROGRAMS{noinst}=algorithmid_test SOURCE[algorithmid_test]=algorithmid_test.c INCLUDE[algorithmid_test]=../include ../apps/include diff --git a/test/recipes/25-test_statem_clnt.t b/test/recipes/25-test_statem_clnt.t new file mode 100644 index 00000000000..4352029eedd --- /dev/null +++ b/test/recipes/25-test_statem_clnt.t @@ -0,0 +1,19 @@ +#! /usr/bin/env perl +# Copyright 2026 The OpenSSL Project Authors. All Rights Reserved. +# +# Licensed under the Apache License 2.0 (the "License"). You may not use +# this file except in compliance with the License. You can obtain a copy +# in the file LICENSE in the source distribution or at +# https://www.openssl.org/source/license.html + +use OpenSSL::Test; +use OpenSSL::Test::Utils; + +setup("test_statem_clnt"); + +plan skip_all => "No TLS protocol enabled in this build" + if disabled("tls1_2") && disabled("tls1_3"); + +plan tests => 1; + +ok(run(test(["statem_clnt_construct_test"])), "statem_clnt construct functions"); diff --git a/test/statem_clnt_construct_test.c b/test/statem_clnt_construct_test.c new file mode 100644 index 00000000000..43dc330decc --- /dev/null +++ b/test/statem_clnt_construct_test.c @@ -0,0 +1,734 @@ +/* + * Copyright 2026 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +/* + * Direct tests for tls_construct_client_hello(): prime a client SSL_CONNECTION + * enough to call the construct function without a full handshake, then check + * the result structurally and by round-tripping it through the server parser. + * OOM branches are covered with mfail tests. + */ + +#include +#ifndef OPENSSL_NO_ECH +#include +#include +#endif + +#include "internal/ssl_unwrap.h" +#include "../ssl/ssl_local.h" +#include "../ssl/statem/statem_local.h" +#include "testutil.h" + +/* + * TLS 1.3 needs a key-share group, so it is unusable when both EC and DH are + * disabled even though the protocol itself is compiled in (e.g. no-bulk). + */ +#if defined(OPENSSL_NO_TLS1_3) \ + || (defined(OPENSSL_NO_EC) && defined(OPENSSL_NO_DH)) +#define OSSL_NO_USABLE_TLS1_3 +#endif + +/* ECH needs a curve (EC/ECX) for its default suite and usable TLS 1.3. */ +#if defined(OPENSSL_NO_ECH) || defined(OPENSSL_NO_EC) \ + || defined(OPENSSL_NO_ECX) || defined(OSSL_NO_USABLE_TLS1_3) +#define OSSL_NO_USABLE_ECH +#endif + +/* + * Helpers down to prime_ssl() are generic and reusable by tests for any + * statem_clnt construct function; the ClientHello-specific code follows. + */ + +/* Connection configuration shared by the construct tests. */ +typedef struct { + int is_dtls; + int min_ver; /* 0 for library default */ + int max_ver; /* 0 for library default */ + int clear_midbox; /* clear SSL_OP_ENABLE_MIDDLEBOX_COMPAT */ +} CH_CONFIG; + +static const SSL_METHOD *client_method(const CH_CONFIG *cfg) +{ + return cfg->is_dtls ? DTLS_client_method() : TLS_client_method(); +} + +/* Handshake message header length (msg precedes the ClientHello body). */ +static size_t hdr_len(const CH_CONFIG *cfg) +{ + return cfg->is_dtls ? DTLS1_HM_HEADER_LENGTH : SSL3_HM_HEADER_LENGTH; +} + +static const SSL_METHOD *server_method(const CH_CONFIG *cfg) +{ + return cfg->is_dtls ? DTLS_server_method() : TLS_server_method(); +} + +static SSL_CTX *new_ctx(const CH_CONFIG *cfg, const SSL_METHOD *meth) +{ + SSL_CTX *ctx = SSL_CTX_new(meth); + + if (ctx == NULL) + return NULL; + if ((cfg->min_ver != 0 + && !SSL_CTX_set_min_proto_version(ctx, cfg->min_ver)) + || (cfg->max_ver != 0 + && !SSL_CTX_set_max_proto_version(ctx, cfg->max_ver))) { + SSL_CTX_free(ctx); + return NULL; + } + return ctx; +} + +/* + * Set up the init_buf and handshake state the write state machine would have + * established before calling a construct function. For the client a WPACKET + * with the handshake header is emitted into init_buf. initbuf_len of 0 means + * full size; a small value exercises WPACKET failures. + */ +static int prime_ssl(SSL *ssl, int is_client, size_t initbuf_len, WPACKET *pkt) +{ + SSL_CONNECTION *s = SSL_CONNECTION_FROM_SSL(ssl); + + if (!TEST_ptr(s)) + return 0; + + if (is_client) + SSL_set_connect_state(ssl); + else + SSL_set_accept_state(ssl); + + if (initbuf_len == 0) + initbuf_len = SSL3_RT_MAX_PLAIN_LENGTH; + + if (!TEST_ptr(s->init_buf = BUF_MEM_new()) + || !TEST_true(BUF_MEM_grow(s->init_buf, initbuf_len))) + return 0; + + if (!TEST_true(tls_setup_handshake(s))) + return 0; + + if (pkt != NULL + && (!TEST_true(WPACKET_init(pkt, s->init_buf)) + || !TEST_true(ssl_set_handshake_header(s, pkt, + SSL3_MT_CLIENT_HELLO)))) + return 0; + + return 1; +} + +/* + * =========================================================================== + * tls_construct_client_hello + * =========================================================================== + */ + +/* Finalize the constructed message and return its bytes (header + body). */ +static int finish_ch(SSL *ssl, WPACKET *pkt, unsigned char **msg, + size_t *msglen) +{ + SSL_CONNECTION *s = SSL_CONNECTION_FROM_SSL(ssl); + + if (!TEST_true(ssl_close_construct_packet(s, pkt, SSL3_MT_CLIENT_HELLO)) + || !TEST_true(WPACKET_get_total_written(pkt, msglen)) + || !TEST_true(WPACKET_finish(pkt))) + return 0; + + *msg = (unsigned char *)s->init_buf->data; + return 1; +} + +/* Recover the session_id length, the main branching difference in construct. */ +static int get_ch_sessid_len(const CH_CONFIG *cfg, const unsigned char *msg, + size_t msglen, size_t *sidlen) +{ + PACKET pkt = { 0 }, sessid = { 0 }, cookie = { 0 }; + PACKET ciphers = { 0 }, comp = { 0 }; + unsigned int legacy_version; + size_t hl = hdr_len(cfg); + + if (!TEST_size_t_gt(msglen, hl) + || !TEST_true(PACKET_buf_init(&pkt, msg + hl, msglen - hl))) + return 0; + + if (!TEST_true(PACKET_get_net_2(&pkt, &legacy_version)) + || !TEST_true(PACKET_forward(&pkt, SSL3_RANDOM_SIZE)) + || !TEST_true(PACKET_get_length_prefixed_1(&pkt, &sessid))) + return 0; + + if (cfg->is_dtls + && !TEST_true(PACKET_get_length_prefixed_1(&pkt, &cookie))) + return 0; + + /* Sanity: cipher list non-empty, compression present and contains NULL. */ + if (!TEST_true(PACKET_get_length_prefixed_2(&pkt, &ciphers)) + || !TEST_size_t_gt(PACKET_remaining(&ciphers), 0) + || !TEST_true(PACKET_get_length_prefixed_1(&pkt, &comp)) + || !TEST_size_t_gt(PACKET_remaining(&comp), 0)) + return 0; + + *sidlen = PACKET_remaining(&sessid); + return 1; +} + +/* Run the produced ClientHello body through the server-side parser. */ +static int roundtrip_process_ch(const CH_CONFIG *cfg, const unsigned char *msg, + size_t msglen) +{ + SSL_CTX *sctx = NULL; + SSL *ssl = NULL; + SSL_CONNECTION *s; + PACKET pkt; + int ret = 0; + + if (!TEST_ptr(sctx = new_ctx(cfg, server_method(cfg))) + || !TEST_ptr(ssl = SSL_new(sctx))) + goto err; + + if (!prime_ssl(ssl, 0, 0, NULL)) + goto err; + s = SSL_CONNECTION_FROM_SSL(ssl); + + if (!TEST_true(PACKET_buf_init(&pkt, msg + hdr_len(cfg), + msglen - hdr_len(cfg)))) + goto err; + + if (!TEST_int_eq(tls_process_client_hello(s, &pkt), + MSG_PROCESS_CONTINUE_PROCESSING)) + goto err; + + ret = 1; +err: + SSL_free(ssl); + SSL_CTX_free(sctx); + return ret; +} + +/* + * Construct a ClientHello, assert its session_id length and optionally + * round-trip it. prep injects extra state just before construct; + * expect_random asserts the produced client random (to check reuse). + * Both may be NULL. + */ +static int do_construct_ch(const CH_CONFIG *cfg, + int (*prep)(SSL_CONNECTION *s), size_t expect_sidlen, int roundtrip, + const unsigned char *expect_random) +{ + SSL_CTX *cctx = NULL; + SSL *ssl = NULL; + SSL_CONNECTION *s; + WPACKET pkt; + unsigned char *msg = NULL; + size_t msglen = 0, sidlen = 0; + int ret = 0; + + if (!TEST_ptr(cctx = new_ctx(cfg, client_method(cfg))) + || !TEST_ptr(ssl = SSL_new(cctx))) + goto err; + if (cfg->clear_midbox) + SSL_clear_options(ssl, SSL_OP_ENABLE_MIDDLEBOX_COMPAT); + + if (!prime_ssl(ssl, 1, 0, &pkt)) + goto err; + s = SSL_CONNECTION_FROM_SSL(ssl); + + if (prep != NULL && !prep(s)) { + WPACKET_cleanup(&pkt); + goto err; + } + + if (!TEST_int_eq(tls_construct_client_hello(s, &pkt), CON_FUNC_SUCCESS)) { + WPACKET_cleanup(&pkt); + goto err; + } + if (!finish_ch(ssl, &pkt, &msg, &msglen)) + goto err; + + if (!get_ch_sessid_len(cfg, msg, msglen, &sidlen) + || !TEST_size_t_eq(sidlen, expect_sidlen)) + goto err; + + /* The client random follows the 2-byte legacy_version in the body. */ + if (expect_random != NULL + && !TEST_mem_eq(msg + hdr_len(cfg) + 2, SSL3_RANDOM_SIZE, + expect_random, SSL3_RANDOM_SIZE)) + goto err; + + if (roundtrip && !roundtrip_process_ch(cfg, msg, msglen)) + goto err; + + ret = 1; +err: + SSL_free(ssl); + SSL_CTX_free(cctx); + return ret; +} + +/* Expect tls_construct_client_hello() to fail (CON_FUNC_ERROR). */ +static int do_construct_ch_expect_fail(const CH_CONFIG *cfg, + int (*prep)(SSL_CONNECTION *s), + size_t initbuf_len, int empty_ciphers) +{ + SSL_CTX *cctx = NULL; + SSL *ssl = NULL; + SSL_CONNECTION *s; + WPACKET pkt; + int have_pkt = 0; + int ret = 0; + + if (!TEST_ptr(cctx = new_ctx(cfg, client_method(cfg))) + || !TEST_ptr(ssl = SSL_new(cctx))) + goto err; + + if (empty_ciphers) { + /* Leave no usable cipher: aNULL is disabled at default sec level. */ + int r1 = SSL_set_ciphersuites(ssl, ""); + int r2 = SSL_set_cipher_list(ssl, "aNULL"); + + (void)r1; + (void)r2; + } + + if (!prime_ssl(ssl, 1, initbuf_len, &pkt)) + goto err; + have_pkt = 1; + s = SSL_CONNECTION_FROM_SSL(ssl); + + if (prep != NULL && !prep(s)) + goto err; + + if (!TEST_int_eq(tls_construct_client_hello(s, &pkt), CON_FUNC_ERROR)) + goto err; + + ret = 1; +err: + if (have_pkt) + WPACKET_cleanup(&pkt); + SSL_free(ssl); + SSL_CTX_free(cctx); + return ret; +} + +/* TLS 1.2 happy-path / session tests */ + +#ifndef OPENSSL_NO_TLS1_2 +static int test_construct_ch_tls12(void) +{ + CH_CONFIG cfg = { 0, TLS1_2_VERSION, TLS1_2_VERSION, 0 }; + + /* Fresh (non-resumable) TLS 1.2 session: empty session id. */ + return do_construct_ch(&cfg, NULL, 0, 1, NULL); +} + +/* Resumable non-TLS1.3 session: construct reuses its session id. */ +static int prep_resume(SSL_CONNECTION *s) +{ + SSL *ssl = SSL_CONNECTION_GET_SSL(s); + SSL_SESSION *sess = SSL_SESSION_new(); + int ret = 0; + + if (!TEST_ptr(sess)) + goto err; + sess->ssl_version = TLS1_2_VERSION; + sess->session_id_length = sizeof(sess->session_id); + memset(sess->session_id, 0x5A, sess->session_id_length); + sess->cipher = sk_SSL_CIPHER_value(SSL_get_ciphers(ssl), 0); + if (!TEST_ptr(sess->cipher) || !TEST_true(SSL_set_session(ssl, sess))) + goto err; + ret = 1; +err: + SSL_SESSION_free(sess); + return ret; +} + +static int test_construct_ch_resume(void) +{ + CH_CONFIG cfg = { 0, TLS1_2_VERSION, TLS1_2_VERSION, 0 }; + + /* Resumed session: the pre-loaded 32-byte session id is sent. */ + return do_construct_ch(&cfg, prep_resume, SSL_MAX_SSL_SESSION_ID_LENGTH, 1, + NULL); +} +#endif /* OPENSSL_NO_TLS1_2 */ + +/* TLS 1.3 happy-path / HRR tests */ + +#ifndef OSSL_NO_USABLE_TLS1_3 +static int test_construct_ch_tls13(void) +{ + CH_CONFIG cfg = { 0, TLS1_3_VERSION, TLS1_3_VERSION, 0 }; + + /* Middlebox compat is on by default: a random 32-byte session id. */ + return do_construct_ch(&cfg, NULL, SSL_MAX_SSL_SESSION_ID_LENGTH, 1, NULL); +} + +static int test_construct_ch_tls13_no_middlebox(void) +{ + CH_CONFIG cfg = { 0, TLS1_3_VERSION, TLS1_3_VERSION, 1 }; + + /* No middlebox compat: empty session id. */ + return do_construct_ch(&cfg, NULL, 0, 1, NULL); +} + +static int prep_hrr(SSL_CONNECTION *s) +{ + SSL *ssl = SSL_CONNECTION_GET_SSL(s); + SSL_SESSION *sess = SSL_SESSION_new(); + int ret = 0; + + /* Under HRR construct skips ssl_get_new_session(), so a session must + * already exist (created for the first ClientHello). */ + if (!TEST_ptr(sess)) + goto err; + sess->ssl_version = TLS1_3_VERSION; + sess->cipher = sk_SSL_CIPHER_value(SSL_get_ciphers(ssl), 0); + if (!TEST_ptr(sess->cipher) || !TEST_true(SSL_set_session(ssl, sess))) + goto err; + s->hello_retry_request = SSL_HRR_COMPLETE; + ret = 1; +err: + SSL_SESSION_free(sess); + return ret; +} + +static int test_construct_ch_hrr(void) +{ + CH_CONFIG cfg = { 0, TLS1_3_VERSION, TLS1_3_VERSION, 0 }; + + /* TLS 1.3 + middlebox compat still emits a 32-byte session id under HRR. */ + return do_construct_ch(&cfg, prep_hrr, SSL_MAX_SSL_SESSION_ID_LENGTH, 1, + NULL); +} +#endif /* OSSL_NO_USABLE_TLS1_3 */ + +/* DTLS happy-path / cookie / random-reuse tests */ + +#ifndef OPENSSL_NO_DTLS +static int test_construct_ch_dtls(void) +{ + CH_CONFIG cfg = { 1, 0, 0, 0 }; + + /* DTLS uses a different server parser path; skip the roundtrip. */ + return do_construct_ch(&cfg, NULL, 0, 0, NULL); +} + +static int prep_dtls_cookie(SSL_CONNECTION *s) +{ + /* A HelloVerifyRequest cookie is echoed in the ClientHello. */ + static const unsigned char cookie[16] = { + 0xc0, 0x01, 0xc0, 0x02, 0xc0, 0x03, 0xc0, 0x04, + 0xc0, 0x05, 0xc0, 0x06, 0xc0, 0x07, 0xc0, 0x08 + }; + + memcpy(s->d1->cookie, cookie, sizeof(cookie)); + s->d1->cookie_len = sizeof(cookie); + return 1; +} + +static int test_construct_ch_dtls_cookie(void) +{ + CH_CONFIG cfg = { 1, 0, 0, 0 }; + + return do_construct_ch(&cfg, prep_dtls_cookie, 0, 0, NULL); +} + +/* A recognizable, all-non-zero client random to detect reuse. */ +static const unsigned char reused_random[SSL3_RANDOM_SIZE] = { + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf +}; + +static int prep_dtls_client_random(SSL_CONNECTION *s) +{ + /* DTLS reuses an already-set client random (HelloVerifyRequest reply). */ + memcpy(s->s3.client_random, reused_random, sizeof(reused_random)); + return 1; +} + +static int test_construct_ch_dtls_client_random(void) +{ + CH_CONFIG cfg = { 1, 0, 0, 0 }; + + return do_construct_ch(&cfg, prep_dtls_client_random, 0, 0, reused_random); +} +#endif /* OPENSSL_NO_DTLS */ + +/* Deterministic error-branch tests */ + +static int test_construct_ch_small_buf(void) +{ + /* Default version: the overflow is version-independent. */ + CH_CONFIG cfg = { 0, 0, 0, 0 }; + SSL_CTX *cctx = NULL; + SSL *ssl = NULL; + SSL_CONNECTION *s; + WPACKET pkt; + /* + * Fixed, non-growable buffer that overflows part-way through the body (a + * BUF_MEM-backed WPACKET would just grow), hitting a WPACKET write failure. + */ + unsigned char buf[40]; + int have_pkt = 0; + int ret = 0; + + if (!TEST_ptr(cctx = new_ctx(&cfg, client_method(&cfg))) + || !TEST_ptr(ssl = SSL_new(cctx))) + goto err; + SSL_set_connect_state(ssl); + s = SSL_CONNECTION_FROM_SSL(ssl); + if (!TEST_ptr(s) + || !TEST_ptr(s->init_buf = BUF_MEM_new()) + || !TEST_true(BUF_MEM_grow(s->init_buf, SSL3_RT_MAX_PLAIN_LENGTH)) + || !TEST_true(tls_setup_handshake(s))) + goto err; + + if (!TEST_true(WPACKET_init_static_len(&pkt, buf, sizeof(buf), 0))) + goto err; + have_pkt = 1; + if (!TEST_true(ssl_set_handshake_header(s, &pkt, SSL3_MT_CLIENT_HELLO))) + goto err; + + if (!TEST_int_eq(tls_construct_client_hello(s, &pkt), CON_FUNC_ERROR)) + goto err; + + ret = 1; +err: + if (have_pkt) + WPACKET_cleanup(&pkt); + SSL_free(ssl); + SSL_CTX_free(cctx); + return ret; +} + +static int test_construct_ch_no_ciphers(void) +{ + CH_CONFIG cfg = { 0, 0, 0, 0 }; + + return do_construct_ch_expect_fail(&cfg, NULL, 0, 1); +} + +/* Allocation-failure (mfail) tests */ + +/* Compiled when any mfail caller below (TLS 1.2, TLS 1.3 or ECH) is. */ +#if !defined(OSSL_NO_USABLE_TLS1_3) || !defined(OPENSSL_NO_TLS1_2) +static int mfail_construct_ch_common(const CH_CONFIG *cfg, + int (*prep)(SSL_CONNECTION *s)) +{ + SSL_CTX *cctx = NULL; + SSL *ssl = NULL; + SSL_CONNECTION *s; + WPACKET pkt; + int ok = 0; + int ret = 0; + + if (!TEST_ptr(cctx = new_ctx(cfg, client_method(cfg))) + || !TEST_ptr(ssl = SSL_new(cctx))) + goto err; + if (cfg->clear_midbox) + SSL_clear_options(ssl, SSL_OP_ENABLE_MIDDLEBOX_COMPAT); + + if (!prime_ssl(ssl, 1, 0, &pkt)) + goto err; + s = SSL_CONNECTION_FROM_SSL(ssl); + + if (prep != NULL && !prep(s)) { + WPACKET_cleanup(&pkt); + goto err; + } + + MFAIL_start(); + ok = (tls_construct_client_hello(s, &pkt) == CON_FUNC_SUCCESS); + MFAIL_end(); + + WPACKET_cleanup(&pkt); + + /* 1 on clean success, 0 on an injected allocation failure. */ + ret = ok ? 1 : 0; +err: + SSL_free(ssl); + SSL_CTX_free(cctx); + return ret; +} +#endif + +#if !defined(OSSL_NO_USABLE_TLS1_3) && !defined(OPENSSL_NO_CACHED_FETCH) +static int mfail_construct_ch_tls13(void) +{ + CH_CONFIG cfg = { 0, TLS1_3_VERSION, TLS1_3_VERSION, 0 }; + + return mfail_construct_ch_common(&cfg, NULL); +} +#endif + +#ifndef OPENSSL_NO_TLS1_2 +static int mfail_construct_ch_tls12(void) +{ + CH_CONFIG cfg = { 0, TLS1_2_VERSION, TLS1_2_VERSION, 0 }; + + return mfail_construct_ch_common(&cfg, NULL); +} +#endif + +#ifndef OSSL_NO_USABLE_ECH +/* ECH path tests */ + +/* Attach an ECH config so construct takes the ECH wrapper path. */ +static int prep_ech(SSL_CONNECTION *s) +{ + SSL *ssl = SSL_CONNECTION_GET_SSL(s); + OSSL_ECHSTORE *es = NULL; + OSSL_HPKE_SUITE suite = OSSL_HPKE_SUITE_DEFAULT; + int ret = 0; + + if (!TEST_ptr(es = OSSL_ECHSTORE_new(NULL, NULL)) + || !TEST_true(OSSL_ECHSTORE_new_config(es, OSSL_ECH_CURRENT_VERSION, 0, + "example.com", suite)) + || !TEST_true(SSL_set1_echstore(ssl, es))) + goto err; + ret = 1; +err: + OSSL_ECHSTORE_free(es); + return ret; +} + +/* + * ECH happy path: the server reuses the client's store, which holds the private + * key needed to decrypt the inner ClientHello, so the round-trip can succeed. + */ +static int test_construct_ch_ech(void) +{ + CH_CONFIG cfg = { 0, TLS1_3_VERSION, TLS1_3_VERSION, 0 }; + SSL_CTX *cctx = NULL, *sctx = NULL; + SSL *cssl = NULL, *sssl = NULL; + SSL_CONNECTION *cs, *ss; + OSSL_ECHSTORE *es = NULL; + OSSL_HPKE_SUITE suite = OSSL_HPKE_SUITE_DEFAULT; + WPACKET pkt; + PACKET rpkt; + unsigned char *msg = NULL; + size_t msglen = 0, sidlen = 0; + int ret = 0; + + if (!TEST_ptr(es = OSSL_ECHSTORE_new(NULL, NULL)) + || !TEST_true(OSSL_ECHSTORE_new_config(es, OSSL_ECH_CURRENT_VERSION, 0, + "example.com", suite))) + goto err; + + /* Client: construct the outer ClientHello with ECH. */ + if (!TEST_ptr(cctx = new_ctx(&cfg, client_method(&cfg))) + || !TEST_ptr(cssl = SSL_new(cctx)) + || !TEST_true(SSL_set1_echstore(cssl, es)) + || !prime_ssl(cssl, 1, 0, &pkt)) + goto err; + cs = SSL_CONNECTION_FROM_SSL(cssl); + if (!TEST_int_eq(tls_construct_client_hello(cs, &pkt), CON_FUNC_SUCCESS)) { + WPACKET_cleanup(&pkt); + goto err; + } + if (!finish_ch(cssl, &pkt, &msg, &msglen) + || !get_ch_sessid_len(&cfg, msg, msglen, &sidlen) + || !TEST_size_t_eq(sidlen, SSL_MAX_SSL_SESSION_ID_LENGTH)) + goto err; + + /* Server: decrypt and process the outer using the same store. */ + if (!TEST_ptr(sctx = new_ctx(&cfg, server_method(&cfg))) + || !TEST_ptr(sssl = SSL_new(sctx)) + || !TEST_true(SSL_set1_echstore(sssl, es)) + || !prime_ssl(sssl, 0, 0, NULL)) + goto err; + ss = SSL_CONNECTION_FROM_SSL(sssl); + if (!TEST_true(PACKET_buf_init(&rpkt, msg + hdr_len(&cfg), + msglen - hdr_len(&cfg))) + || !TEST_int_eq(tls_process_client_hello(ss, &rpkt), + MSG_PROCESS_CONTINUE_PROCESSING)) + goto err; + + ret = 1; +err: + OSSL_ECHSTORE_free(es); + SSL_free(cssl); + SSL_free(sssl); + SSL_CTX_free(cctx); + SSL_CTX_free(sctx); + return ret; +} + +#ifndef OPENSSL_NO_TLS1_2 +static int test_construct_ch_ech_tls12(void) +{ + CH_CONFIG cfg = { 0, TLS1_2_VERSION, TLS1_2_VERSION, 0 }; + + /* ECH requires TLS 1.3: the inner construct fails the version check. */ + return do_construct_ch_expect_fail(&cfg, prep_ech, 0, 0); +} +#endif /* OPENSSL_NO_TLS1_2 */ + +#ifndef OPENSSL_NO_CACHED_FETCH +static int mfail_construct_ch_ech(void) +{ + CH_CONFIG cfg = { 0, TLS1_3_VERSION, TLS1_3_VERSION, 0 }; + + return mfail_construct_ch_common(&cfg, prep_ech); +} +#endif /* OPENSSL_NO_CACHED_FETCH */ +#endif /* OSSL_NO_USABLE_ECH */ + +int setup_tests(void) +{ + ADD_TEST(test_construct_ch_small_buf); + ADD_TEST(test_construct_ch_no_ciphers); + +#ifndef OPENSSL_NO_TLS1_2 + ADD_TEST(test_construct_ch_tls12); + ADD_TEST(test_construct_ch_resume); + ADD_MFAIL_TEST(mfail_construct_ch_tls12); +#endif + +#ifndef OSSL_NO_USABLE_TLS1_3 + ADD_TEST(test_construct_ch_tls13); + ADD_TEST(test_construct_ch_tls13_no_middlebox); + ADD_TEST(test_construct_ch_hrr); +#ifndef OPENSSL_NO_CACHED_FETCH + /* + * The non-cached mfail run takes too long and does not test too much extra + * so better to skip it. + */ +#if defined(OPENSSL_NO_ECX) + /* + * Without ECX the key_share falls back to EC keygen, which makes a + * best-effort param-cache allocation whose failure does not propagate; + * only crash/leak checking is meaningful then. + * + * No caching also needs no check. + */ + ADD_MFAIL_NO_CHECK_TEST(mfail_construct_ch_tls13); +#else + ADD_MFAIL_TEST(mfail_construct_ch_tls13); +#endif /* OPENSSL_NO_ECX */ +#endif /* OPENSSL_NO_CACHED_FETCH */ +#endif /* OSSL_NO_USABLE_TLS1_3 */ + +#ifndef OPENSSL_NO_DTLS + ADD_TEST(test_construct_ch_dtls); + ADD_TEST(test_construct_ch_dtls_cookie); + ADD_TEST(test_construct_ch_dtls_client_random); +#endif + +#ifndef OSSL_NO_USABLE_ECH + ADD_TEST(test_construct_ch_ech); +#ifndef OPENSSL_NO_TLS1_2 + ADD_TEST(test_construct_ch_ech_tls12); +#endif +#ifndef OPENSSL_NO_CACHED_FETCH + ADD_MFAIL_TEST(mfail_construct_ch_ech); +#endif /* OPENSSL_NO_CACHED_FETCH */ +#endif /* OSSL_NO_USABLE_ECH */ + return 1; +}