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 <tomas@openssl.foundation> Reviewed-by: Daniel Kubec <kubec@openssl.foundation> MergeDate: Tue Jun 30 08:54:48 2026 (Merged from https://github.com/openssl/openssl/pull/31618)
This commit is contained in:
parent
7d4475bdf2
commit
c36a9b463e
3 changed files with 758 additions and 0 deletions
|
|
@ -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
|
||||
|
|
|
|||
19
test/recipes/25-test_statem_clnt.t
Normal file
19
test/recipes/25-test_statem_clnt.t
Normal file
|
|
@ -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");
|
||||
734
test/statem_clnt_construct_test.c
Normal file
734
test/statem_clnt_construct_test.c
Normal file
|
|
@ -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 <openssl/ssl.h>
|
||||
#ifndef OPENSSL_NO_ECH
|
||||
#include <openssl/ech.h>
|
||||
#include <openssl/hpke.h>
|
||||
#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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue