Merge pull request #11306 from Frauschi/erlang-interop

Send server ChangeCipherSpec when client offers a session id
pull/11340/merge
JacobBarthelmeh 2026-09-14 15:10:07 -06:00 committed by GitHub
commit b6fd66987d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 724 additions and 76 deletions

View File

@ -16,6 +16,9 @@
"configure": ["--enable-all", "--enable-haproxy", "--enable-quic"]},
{"name": "all-asn-template-old-oid-sum", "minutes": 8.5,
"configure": ["--enable-all", "--enable-asn=template", "CPPFLAGS=-DWOLFSSL_OLD_OID_SUM"]},
{"name": "all-tls13-middlebox-compat", "minutes": 8.5,
"comment": "Only --enable-tls13-middlebox-compat and --enable-jni define WOLFSSL_TLS13_MIDDLEBOX_COMPAT, and configure.ac turns it back off when ECH is on, which --enable-all enables. So no other entry in this matrix compiles the client-side ChangeCipherSpec sends in wolfSSL_connect_TLSv13 or the tests/api/test_tls13.c middlebox tests guarded on that macro; without --disable-ech here they would all be skipped.",
"configure": ["--enable-all", "--disable-ech", "--enable-tls13-middlebox-compat"]},
{"name": "all-asn-original-old-oid-sum", "minutes": 8.4,
"configure": ["--enable-all", "--enable-asn=original", "CPPFLAGS=-DWOLFSSL_OLD_OID_SUM"]},
{"name": "all-asn-original", "minutes": 8.3,

View File

@ -734,7 +734,7 @@ add_option("WOLFSSL_TLS13_SHA512"
# TLS v1.3 middlebox compatibility (RFC 8446 Appendix D.4)
add_option("WOLFSSL_TLS13_MIDDLEBOX_COMPAT"
"Enable TLS v1.3 middlebox compatibility mode (default: disabled)"
"Enable client-side TLS v1.3 middlebox compatibility: send a fake session id and a ChangeCipherSpec. Server-side is always on (default: disabled)"
"no" "yes;no")
if(WOLFSSL_TLS13_MIDDLEBOX_COMPAT)
@ -3160,7 +3160,7 @@ if(WOLFSSL_ECH)
list(APPEND WOLFSSL_DEFINITIONS "-DHAVE_ECH")
if(WOLFSSL_TLS13_MIDDLEBOX_COMPAT)
message(WARNING
"ECH is incompatible with middlebox compatibility - disabling middlebox compatibility")
"ECH is incompatible with client-side middlebox compatibility - disabling middlebox compatibility")
override_cache(WOLFSSL_TLS13_MIDDLEBOX_COMPAT "no")
list(REMOVE_ITEM WOLFSSL_DEFINITIONS
"-DWOLFSSL_TLS13_MIDDLEBOX_COMPAT")

View File

@ -233,6 +233,19 @@
key of a reloaded key must keep the one exported at generation time, or
load it into a separate key with `ImportPubRaw`.
* **Behavioral change (TLS 1.3 server ChangeCipherSpec)**: a TLS 1.3 server
now answers a ClientHello carrying a non-empty `legacy_session_id` with a
ChangeCipherSpec record, as RFC 8446 Appendix D.4 describes for middlebox
compatibility mode. It only did so before in builds defining
`WOLFSSL_TLS13_MIDDLEBOX_COMPAT`, which only `--enable-tls13-middlebox-compat`
and `--enable-jni` set and neither is on by default, so a default-built
server stayed silent and peers that expect the record - Erlang's `ssl`
among them - aborted the handshake with an unexpected_message alert. A
client sending an empty session id still sees none, and DTLS and QUIC are
unaffected. One visible difference: `wolfSSL_get_state()` no longer passes
through `WOLFSSL_SS_SERVER_CHANGECIPHERSPEC` or
`WOLFSSL_SS_CLIENT_CHANGECIPHERSPEC` on a TLS 1.3 connection.
## New Features
* Added `WC_ALGO_TYPE_KEYSTORE`, a crypto callback algorithm type for lifetime operations on keys held in a hardware key store, with the public API in `wolfssl/wolfcrypt/wc_keystore.h` behind `--enable-cryptocbutils=keystore`. Seven operations - plaintext and wrapped import/export, derive, delete and get-info - address keys by an opaque device-defined reference that wolfCrypt copies through and never interprets, the same way it treats a key object's `id[]` blob. This lets a device create, wrap, derive and destroy keys that never appear in memory, which `WOLF_CRYPTO_CB_SETKEY` and `WOLF_CRYPTO_CB_EXPORT_KEY` cannot express because both are bound to a wolfCrypt key object holding material for its own use.

View File

@ -1429,7 +1429,8 @@ then
if test "$KERNEL_MODE_DEFAULTS" != "yes"
then
# Disable QUIC with JNI since incompatible with WOLFSSL_TLS13_MIDDLEBOX_COMPAT
# Disable QUIC with JNI since incompatible with client-side
# WOLFSSL_TLS13_MIDDLEBOX_COMPAT
test "$enable_quic" = "" && test "$enable_cryptonly" != "yes" && test "$enable_jni" != "yes" && enable_quic=yes
AM_CFLAGS="$AM_CFLAGS -DHAVE_CRL_IO -DHAVE_IO_TIMEOUT"
fi
@ -1447,7 +1448,8 @@ then
if test "$ENABLED_FIPS" = "no"
then
# Disable ECH with JNI since incompatible with WOLFSSL_TLS13_MIDDLEBOX_COMPAT
# Disable ECH with JNI since incompatible with client-side
# WOLFSSL_TLS13_MIDDLEBOX_COMPAT
test "$enable_ech" = "" && test "$enable_jni" != "yes" && enable_ech=yes
test "$enable_scep" = "" && enable_scep=yes
test "$enable_mcast" = "" && enable_mcast=yes
@ -2754,7 +2756,7 @@ AC_ARG_ENABLE([tls13-sha512],
# TLS v1.3 middlebox compatibility (RFC 8446 Appendix D.4)
AC_ARG_ENABLE([tls13-middlebox-compat],
[AS_HELP_STRING([--enable-tls13-middlebox-compat],[Enable TLS v1.3 middlebox compatibility mode (default: disabled)])],
[AS_HELP_STRING([--enable-tls13-middlebox-compat],[Enable client-side TLS v1.3 middlebox compatibility: send a fake session id and a ChangeCipherSpec. Server-side is always on (default: disabled)])],
[ ENABLED_TLS13_MIDDLEBOX_COMPAT=$enableval ],
[ ENABLED_TLS13_MIDDLEBOX_COMPAT=no ]
)
@ -2766,7 +2768,7 @@ then
ENABLED_TLS13_MIDDLEBOX_COMPAT="no"
elif test "x$ENABLED_ECH" = "xyes"
then
AC_MSG_NOTICE([ECH is incompatible with middlebox compatibility - disabling middlebox compatibility])
AC_MSG_NOTICE([ECH is incompatible with client-side middlebox compatibility - disabling middlebox compatibility])
ENABLED_TLS13_MIDDLEBOX_COMPAT="no"
else
AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_TLS13_MIDDLEBOX_COMPAT"
@ -9785,7 +9787,7 @@ then
AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_ALWAYS_KEEP_SNI"
if test "x$ENABLED_ECH" = "xyes"
then
AC_MSG_NOTICE([ECH is incompatible with middlebox compatibility - not enabling middlebox compatibility for JNI])
AC_MSG_NOTICE([ECH is incompatible with client-side middlebox compatibility - not enabling middlebox compatibility for JNI])
else
AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_TLS13_MIDDLEBOX_COMPAT"
fi

View File

@ -8493,9 +8493,8 @@ static void InitSSL_Tls13Options(WOLFSSL* ssl, WOLFSSL_CTX* ctx)
ssl->numGroups = ctx->numGroups;
}
#ifdef WOLFSSL_TLS13_MIDDLEBOX_COMPAT
ssl->options.tls13MiddleBoxCompat = 1;
#endif
/* Server clears this when the ClientHello legacy_session_id is empty. */
ssl->options.tls13MiddleBoxCompat = 1;
}
#endif /* WOLFSSL_TLS13 */
@ -26325,8 +26324,12 @@ int ProcessReplyEx(WOLFSSL* ssl, int allowSocketErr)
return ret;
}
/* The TLS 1.3 server accept path calls this for RFC 8446 Appendix D.4, so a
* server needs it whenever TLS 1.3 is built, not only with middlebox compat.
* A TLS 1.3 client only sends one in a middlebox compat build. */
#if !defined(WOLFSSL_NO_TLS12) || !defined(NO_OLD_TLS) || \
(defined(WOLFSSL_TLS13) && defined(WOLFSSL_TLS13_MIDDLEBOX_COMPAT))
(defined(WOLFSSL_TLS13) && (!defined(NO_WOLFSSL_SERVER) || \
defined(WOLFSSL_TLS13_MIDDLEBOX_COMPAT)))
int SendChangeCipher(WOLFSSL* ssl)
{
byte *output;
@ -26337,13 +26340,17 @@ int SendChangeCipher(WOLFSSL* ssl)
#ifdef OPENSSL_EXTRA
ssl->cbmode = WOLFSSL_CB_MODE_WRITE;
if (ssl->options.side == WOLFSSL_SERVER_END){
ssl->options.serverState = SERVER_CHANGECIPHERSPEC_COMPLETE;
/* A TLS 1.3 record here is a dummy and moves no state. */
if (!IsAtLeastTLSv1_3(ssl->version))
ssl->options.serverState = SERVER_CHANGECIPHERSPEC_COMPLETE;
if (ssl->CBIS != NULL)
ssl->CBIS(ssl, WOLFSSL_CB_ACCEPT_LOOP, WOLFSSL_SUCCESS);
}
else {
ssl->options.clientState =
CLIENT_CHANGECIPHERSPEC_COMPLETE;
/* As above; nothing on the TLS 1.3 connect path reads clientState. */
if (!IsAtLeastTLSv1_3(ssl->version)) {
ssl->options.clientState = CLIENT_CHANGECIPHERSPEC_COMPLETE;
}
if (ssl->CBIS != NULL)
ssl->CBIS(ssl, WOLFSSL_CB_CONNECT_LOOP, WOLFSSL_SUCCESS);
}
@ -26443,7 +26450,7 @@ int SendChangeCipher(WOLFSSL* ssl)
return SendBuffered(ssl);
}
#endif /* !WOLFSSL_NO_TLS12 || !NO_OLD_TLS ||
* (WOLFSSL_TLS13 && WOLFSSL_TLS13_MIDDLEBOX_COMPAT) */
* (WOLFSSL_TLS13 && (!NO_WOLFSSL_SERVER || WOLFSSL_TLS13_MIDDLEBOX_COMPAT)) */
#if !defined(NO_OLD_TLS) && !defined(WOLFSSL_AEAD_ONLY)

View File

@ -5701,6 +5701,10 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out,
ssl->options.hrrSentCookie = 0;
#endif
ssl->options.hrrSentKeyShare = 0;
ssl->options.sentChangeCipher = 0;
/* Matches InitSSL_Tls13Options(); the server clears it again when the
* next ClientHello carries an empty session id. */
ssl->options.tls13MiddleBoxCompat = 1;
#endif
#ifdef WOLFSSL_DTLS
ssl->options.dtlsStateful = 0;

View File

@ -33,8 +33,13 @@
* WOLFSSL_DTLS_CH_FRAG: Enable DTLS 1.3 ClientHello frag default: off
*
* Handshake:
* WOLFSSL_TLS13_MIDDLEBOX_COMPAT: Enable middlebox compatibility default: on
* Sends ChangeCipherSpec and includes session id
* WOLFSSL_TLS13_MIDDLEBOX_COMPAT: Client-side middlebox compatibility
* default: off
* Makes the client send a fake session id and its
* own ChangeCipherSpec. The server always answers
* a non-empty client session id with a
* ChangeCipherSpec, as RFC 8446 Appendix D.4
* requires, whether or not this is defined.
* WOLFSSL_SEND_HRR_COOKIE: Send cookie in HelloRetryRequest default: off
* for stateless ClientHello tracking. A client
* always echoes back a cookie it is sent.
@ -8071,12 +8076,17 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx,
}
args->idx += sessIdSz;
#ifdef WOLFSSL_TLS13_MIDDLEBOX_COMPAT
/* RFC 8446 Appendix D.4: server MUST only send CCS if the client's
* ClientHello contains a non-empty legacy_session_id. */
* ClientHello contains a non-empty legacy_session_id. An ECH inner hello
* is rebuilt with the outer session id, so it decides either way. */
if (sessIdSz == 0) {
ssl->options.tls13MiddleBoxCompat = 0;
}
#ifdef WOLFSSL_QUIC
/* RFC 9001 Section 8.4: QUIC has no compatibility mode to be had. */
if (WOLFSSL_IS_QUIC(ssl)) {
ssl->options.tls13MiddleBoxCompat = 0;
}
#endif
#ifdef WOLFSSL_DTLS13
@ -16409,13 +16419,20 @@ int wolfSSL_connect_TLSv13(WOLFSSL* ssl)
if (ssl->earlyData != no_early_data &&
ssl->options.handShakeState != CLIENT_HELLO_COMPLETE) {
#if defined(WOLFSSL_TLS13_MIDDLEBOX_COMPAT)
if (!ssl->options.dtls &&
ssl->options.tls13MiddleBoxCompat) {
if ((ssl->error = SendChangeCipher(ssl)) != 0) {
if (!ssl->options.dtls && !ssl->options.sentChangeCipher
&& ssl->options.tls13MiddleBoxCompat) {
ssl->error = SendChangeCipher(ssl);
/* A short send leaves the record queued in the output
* buffer, so a resumed connect must not build a second
* one. Same on every other site. */
if (ssl->error == 0 ||
ssl->error == WC_NO_ERR_TRACE(WANT_WRITE)) {
ssl->options.sentChangeCipher = 1;
}
if (ssl->error != 0) {
WOLFSSL_ERROR(ssl->error);
return WOLFSSL_FATAL_ERROR;
}
ssl->options.sentChangeCipher = 1;
}
#endif
ssl->options.handShakeState = CLIENT_HELLO_COMPLETE;
@ -16461,11 +16478,19 @@ int wolfSSL_connect_TLSv13(WOLFSSL* ssl)
#if defined(WOLFSSL_TLS13_MIDDLEBOX_COMPAT)
if (!ssl->options.dtls && !ssl->options.sentChangeCipher
&& ssl->options.tls13MiddleBoxCompat) {
if ((ssl->error = SendChangeCipher(ssl)) != 0) {
ssl->error = SendChangeCipher(ssl);
if (ssl->error == 0 ||
ssl->error == WC_NO_ERR_TRACE(WANT_WRITE)) {
ssl->options.sentChangeCipher = 1;
}
if (ssl->error != 0) {
/* The second ClientHello still has to follow, so hold
* the state machine on this case while the record
* drains. */
ssl->options.buildingMsg = 1;
WOLFSSL_ERROR(ssl->error);
return WOLFSSL_FATAL_ERROR;
}
ssl->options.sentChangeCipher = 1;
}
#endif
/* Try again with different security parameters. */
@ -16542,11 +16567,15 @@ int wolfSSL_connect_TLSv13(WOLFSSL* ssl)
#if defined(WOLFSSL_TLS13_MIDDLEBOX_COMPAT)
if (!ssl->options.sentChangeCipher && !ssl->options.dtls
&& ssl->options.tls13MiddleBoxCompat) {
if ((ssl->error = SendChangeCipher(ssl)) != 0) {
ssl->error = SendChangeCipher(ssl);
if (ssl->error == 0 ||
ssl->error == WC_NO_ERR_TRACE(WANT_WRITE)) {
ssl->options.sentChangeCipher = 1;
}
if (ssl->error != 0) {
WOLFSSL_ERROR(ssl->error);
return WOLFSSL_FATAL_ERROR;
}
ssl->options.sentChangeCipher = 1;
}
#endif
@ -17551,6 +17580,39 @@ const char* wolfSSL_get_cipher_name_by_hash(WOLFSSL* ssl, const char* hash)
#ifndef NO_WOLFSSL_SERVER
/* Send the RFC 8446 Appendix D.4 ChangeCipherSpec a server owes a client that
* offered a non-empty legacy_session_id.
*
* ssl The SSL/TLS object.
* flush Force the record out on its own. A HelloRetryRequest needs this
* because SendTls13ServerHello has already sent it, unlike a
* ServerHello, which waits for the rest of its flight.
* returns 0 on success.
*/
static int SendTls13ServerChangeCipher(WOLFSSL* ssl, int flush)
{
int ret;
/* DoTls13ClientHello clears tls13MiddleBoxCompat for a QUIC peer, so the
* check here is a local backstop, 0 when QUIC is not built. */
if (ssl->options.dtls || WOLFSSL_IS_QUIC(ssl)
|| !ssl->options.tls13MiddleBoxCompat
|| ssl->options.sentChangeCipher) {
return 0;
}
ret = SendChangeCipher(ssl);
/* A short send leaves the record queued in the output buffer. Mark it sent
* anyway, or the resumed accept, which comes back in at the ServerHello
* case, puts a second record on the wire. */
if (ret == 0 || ret == WC_NO_ERR_TRACE(WANT_WRITE))
ssl->options.sentChangeCipher = 1;
if (ret == 0 && flush && ssl->options.groupMessages)
ret = SendBuffered(ssl);
return ret;
}
/* The server accepting a connection from a client.
* The protocol version is expecting to be TLS v1.3.
* If the client downgrades, and older versions of the protocol are compiled
@ -17792,18 +17854,14 @@ int wolfSSL_accept_TLSv13(WOLFSSL* ssl)
FALL_THROUGH;
case TLS13_ACCEPT_HELLO_RETRY_REQUEST_DONE :
#ifdef WOLFSSL_TLS13_MIDDLEBOX_COMPAT
if (!ssl->options.dtls && ssl->options.tls13MiddleBoxCompat
&& ssl->options.serverState ==
if (ssl->options.serverState ==
SERVER_HELLO_RETRY_REQUEST_COMPLETE) {
if ((ssl->error = SendChangeCipher(ssl)) != 0) {
ssl->error = SendTls13ServerChangeCipher(ssl, 1);
if (ssl->error != 0) {
WOLFSSL_ERROR(ssl->error);
return WOLFSSL_FATAL_ERROR;
}
ssl->options.sentChangeCipher = 1;
ssl->options.serverState = SERVER_HELLO_RETRY_REQUEST_COMPLETE;
}
#endif
ssl->options.acceptState = TLS13_ACCEPT_FIRST_REPLY_DONE;
WOLFSSL_MSG("accept state ACCEPT_FIRST_REPLY_DONE");
FALL_THROUGH;
@ -17851,16 +17909,11 @@ int wolfSSL_accept_TLSv13(WOLFSSL* ssl)
FALL_THROUGH;
case TLS13_SERVER_HELLO_SENT :
#if defined(WOLFSSL_TLS13_MIDDLEBOX_COMPAT)
if (!ssl->options.dtls && ssl->options.tls13MiddleBoxCompat
&& !ssl->options.sentChangeCipher && !ssl->options.dtls) {
if ((ssl->error = SendChangeCipher(ssl)) != 0) {
WOLFSSL_ERROR(ssl->error);
return WOLFSSL_FATAL_ERROR;
}
ssl->options.sentChangeCipher = 1;
ssl->error = SendTls13ServerChangeCipher(ssl, 0);
if (ssl->error != 0) {
WOLFSSL_ERROR(ssl->error);
return WOLFSSL_FATAL_ERROR;
}
#endif
ssl->options.acceptState = TLS13_ACCEPT_THIRD_REPLY_DONE;
WOLFSSL_MSG("accept state ACCEPT_THIRD_REPLY_DONE");

View File

@ -7114,20 +7114,41 @@ static int Tls13PTARecv(WOLFSSL* ssl, char* buf, int sz, void* ctx)
}
#endif
#if defined(WOLFSSL_TLS13) && \
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER)
/* How many ChangeCipherSpec records does this buffer of TLS records carry?
* A count rather than a yes or no, so that a second record going out, which
* is what marking the record sent before the send guards against, does not
* read the same as the one record that is owed. */
static int test_tls13_count_ccs(const byte* buff, int len)
{
int i = 0;
int count = 0;
while (i + RECORD_HEADER_SZ <= len) {
if (buff[i] == change_cipher_spec)
count++;
i += RECORD_HEADER_SZ + ((buff[i + 3] << 8) | buff[i + 4]);
}
return count;
}
#endif
/* Test that when a TLS 1.3 client sends a ClientHello with an empty
* legacy_session_id (indicating no middlebox compatibility), the server
* should NOT send a ChangeCipherSpec message. Per RFC 8446 Appendix D.4,
* the server only sends CCS if the client's ClientHello contains a
* non-empty session_id.
*
* This test reproduces the bug reported in GitHub issue #9156 where
* wolfSSL server always sends CCS when compiled with
* WOLFSSL_TLS13_MIDDLEBOX_COMPAT, regardless of the client's session_id.
* This test reproduces the bug reported in GitHub issue #9156, where the
* server sent a ChangeCipherSpec whatever the client's session_id was.
*/
int test_tls13_middlebox_compat_empty_session_id(void)
{
EXPECT_DECLS;
#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_TLS13_MIDDLEBOX_COMPAT) && \
#if defined(WOLFSSL_TLS13) && \
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER)
WOLFSSL_CTX *ctx_c = NULL;
@ -7135,16 +7156,15 @@ int test_tls13_middlebox_compat_empty_session_id(void)
WOLFSSL *ssl_c = NULL;
WOLFSSL *ssl_s = NULL;
struct test_memio_ctx test_ctx;
int i;
int found_ccs = 0;
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0);
/* Disable middlebox compatibility on the client so it sends an empty
* legacy_session_id in ClientHello. The server should respect this and
* NOT send a ChangeCipherSpec. */
/* Without WOLFSSL_TLS13_MIDDLEBOX_COMPAT the client already sends an
* empty legacy_session_id; clearing the flag covers the build where it
* would otherwise send a fake one. Either way the server must respect
* the empty id and NOT send a ChangeCipherSpec. */
if (EXPECT_SUCCESS()) {
ssl_c->options.tls13MiddleBoxCompat = 0;
}
@ -7161,30 +7181,10 @@ int test_tls13_middlebox_compat_empty_session_id(void)
ExpectIntEQ(wolfSSL_get_error(ssl_s,
WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), WOLFSSL_ERROR_WANT_READ);
/* Now examine the server's output (stored in c_buff, since the server
* writes to the client's read buffer). Scan through TLS records looking
* for a ChangeCipherSpec record (content type 0x14 = 20). */
if (EXPECT_SUCCESS()) {
i = 0;
while (i + 5 <= test_ctx.c_len) {
byte content_type = test_ctx.c_buff[i];
int record_len = (test_ctx.c_buff[i + 3] << 8) |
test_ctx.c_buff[i + 4];
if (content_type == 20) { /* change_cipher_spec */
found_ccs = 1;
break;
}
/* Move to next TLS record: 5 byte header + payload */
i += 5 + record_len;
}
}
/* The server should NOT have sent CCS since the client's ClientHello
* had an empty legacy_session_id. If found_ccs is 1, this demonstrates
/* The server writes into c_buff. It must NOT have sent a CCS, since the
* client's ClientHello had an empty legacy_session_id. Sending one is
* the bug from issue #9156. */
ExpectIntEQ(found_ccs, 0);
ExpectIntEQ(test_tls13_count_ccs(test_ctx.c_buff, test_ctx.c_len), 0);
wolfSSL_free(ssl_c);
wolfSSL_free(ssl_s);
@ -7237,6 +7237,557 @@ int test_tls13_middlebox_compat_session_id(void)
return EXPECT_RESULT();
}
int test_tls13_middlebox_compat_server_ccs(void)
{
EXPECT_DECLS;
#if defined(WOLFSSL_TLS13) && \
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER)
WOLFSSL_CTX *ctx_c = NULL;
WOLFSSL_CTX *ctx_s = NULL;
WOLFSSL *ssl_c = NULL;
WOLFSSL *ssl_s = NULL;
struct test_memio_ctx test_ctx;
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0);
/* Give the client a non-empty legacy_session_id. GetTls13SessionId puts
* it on the wire whatever the build options are, which is what drives
* the server behaviour RFC 8446 Appendix D.4 requires. */
if (EXPECT_SUCCESS()) {
XMEMSET(ssl_c->session->sessionID, 0xA5, ID_LEN);
ssl_c->session->sessionIDSz = ID_LEN;
}
/* Client sends ClientHello with a non-empty session id. */
ExpectIntNE(wolfSSL_connect(ssl_c), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_get_error(ssl_c,
WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), WOLFSSL_ERROR_WANT_READ);
/* Server processes it and writes its first flight. */
ExpectIntNE(wolfSSL_accept(ssl_s), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_get_error(ssl_s,
WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), WOLFSSL_ERROR_WANT_READ);
/* The client offered a non-empty session id, so the server owes it a
* ChangeCipherSpec. Peers such as Erlang's ssl abort the handshake with
* an unexpected_message alert when it is missing. */
ExpectIntEQ(test_tls13_count_ccs(test_ctx.c_buff, test_ctx.c_len), 1);
wolfSSL_free(ssl_c);
wolfSSL_free(ssl_s);
wolfSSL_CTX_free(ctx_c);
wolfSSL_CTX_free(ctx_s);
#endif
return EXPECT_RESULT();
}
/* wolfSSL_clear() must not leave the previous handshake's ChangeCipherSpec
* state behind, or a reused server object stops answering a non-empty
* legacy_session_id after the first handshake. */
int test_tls13_middlebox_compat_server_reuse(void)
{
EXPECT_DECLS;
#if defined(WOLFSSL_TLS13) && \
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER)
WOLFSSL_CTX *ctx_c = NULL;
WOLFSSL_CTX *ctx_s = NULL;
WOLFSSL *ssl_c = NULL;
WOLFSSL *ssl_c2 = NULL;
WOLFSSL *ssl_s = NULL;
struct test_memio_ctx test_ctx;
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0);
if (EXPECT_SUCCESS()) {
XMEMSET(ssl_c->session->sessionID, 0xA5, ID_LEN);
ssl_c->session->sessionIDSz = ID_LEN;
}
ExpectIntNE(wolfSSL_connect(ssl_c), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_get_error(ssl_c,
WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), WOLFSSL_ERROR_WANT_READ);
ExpectIntNE(wolfSSL_accept(ssl_s), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_get_error(ssl_s,
WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), WOLFSSL_ERROR_WANT_READ);
ExpectIntEQ(test_tls13_count_ccs(test_ctx.c_buff, test_ctx.c_len), 1);
/* Reuse the server object for a second handshake with a fresh client. */
ExpectIntEQ(wolfSSL_clear(ssl_s), WOLFSSL_SUCCESS);
test_memio_clear_buffer(&test_ctx, 0);
test_memio_clear_buffer(&test_ctx, 1);
ExpectNotNull(ssl_c2 = wolfSSL_new(ctx_c));
if (EXPECT_SUCCESS()) {
wolfSSL_SetIOWriteCtx(ssl_c2, &test_ctx);
wolfSSL_SetIOReadCtx(ssl_c2, &test_ctx);
XMEMSET(ssl_c2->session->sessionID, 0x5A, ID_LEN);
ssl_c2->session->sessionIDSz = ID_LEN;
}
ExpectIntNE(wolfSSL_connect(ssl_c2), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_get_error(ssl_c2,
WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), WOLFSSL_ERROR_WANT_READ);
ExpectIntNE(wolfSSL_accept(ssl_s), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_get_error(ssl_s,
WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), WOLFSSL_ERROR_WANT_READ);
/* The second peer also offered a session id, so it is owed a CCS too. */
ExpectIntEQ(test_tls13_count_ccs(test_ctx.c_buff, test_ctx.c_len), 1);
wolfSSL_free(ssl_c);
wolfSSL_free(ssl_c2);
wolfSSL_free(ssl_s);
wolfSSL_CTX_free(ctx_c);
wolfSSL_CTX_free(ctx_s);
#endif
return EXPECT_RESULT();
}
#if defined(WOLFSSL_TLS13) && \
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER)
/* One HelloRetryRequest handshake, with the server's messages grouped or
* not. Its own verdict, so a failure in one setting still leaves the other
* exercised. */
static int test_tls13_hrr_ccs_grouped(int grouped)
{
EXPECT_DECLS;
WOLFSSL_CTX *ctx_c = NULL;
WOLFSSL_CTX *ctx_s = NULL;
WOLFSSL *ssl_c = NULL;
WOLFSSL *ssl_s = NULL;
struct test_memio_ctx test_ctx;
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0);
if (EXPECT_SUCCESS()) {
XMEMSET(ssl_c->session->sessionID, 0xA5, ID_LEN);
ssl_c->session->sessionIDSz = ID_LEN;
ssl_s->options.groupMessages = (byte)grouped;
}
/* No key share entries, so the server has to ask for one. */
ExpectIntEQ(wolfSSL_NoKeyShares(ssl_c), WOLFSSL_SUCCESS);
ExpectIntNE(wolfSSL_connect(ssl_c), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_get_error(ssl_c,
WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), WOLFSSL_ERROR_WANT_READ);
ExpectIntNE(wolfSSL_accept(ssl_s), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_get_error(ssl_s,
WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), WOLFSSL_ERROR_WANT_READ);
/* The HelloRetryRequest flight carries exactly one, however the
* server groups its messages. */
ExpectIntEQ(ssl_s->options.serverState,
SERVER_HELLO_RETRY_REQUEST_COMPLETE);
ExpectIntEQ(test_tls13_count_ccs(test_ctx.c_buff, test_ctx.c_len), 1);
/* Step to the second ClientHello and the flight answering it. The
* client drains c_buff as it reads, so what is left is that flight
* alone: the server owes no second record and must not send one. */
ExpectIntNE(wolfSSL_connect(ssl_c), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_get_error(ssl_c,
WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), WOLFSSL_ERROR_WANT_READ);
ExpectIntNE(wolfSSL_accept(ssl_s), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_get_error(ssl_s,
WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), WOLFSSL_ERROR_WANT_READ);
ExpectIntEQ(test_tls13_count_ccs(test_ctx.c_buff, test_ctx.c_len), 0);
/* And the handshake the retry started still completes. */
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
wolfSSL_free(ssl_c);
wolfSSL_free(ssl_s);
wolfSSL_CTX_free(ctx_c);
wolfSSL_CTX_free(ctx_s);
return EXPECT_RESULT();
}
#endif
/* A HelloRetryRequest is flushed as soon as it is built, so the
* ChangeCipherSpec that follows it has to be flushed too. With grouped
* messages, which every OPENSSL_COMPATIBLE_DEFAULTS build turns on, it would
* otherwise sit in the output buffer and reach the peer a flight late. */
int test_tls13_middlebox_compat_hrr_ccs(void)
{
EXPECT_DECLS;
#if defined(WOLFSSL_TLS13) && \
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER)
int ungrouped_res;
int grouped_res;
/* Both run whatever the other does; both owe the same record. */
ungrouped_res = test_tls13_hrr_ccs_grouped(0);
grouped_res = test_tls13_hrr_ccs_grouped(1);
ExpectIntEQ(ungrouped_res, TEST_SUCCESS);
ExpectIntEQ(grouped_res, TEST_SUCCESS);
#endif
return EXPECT_RESULT();
}
#if defined(WOLFSSL_TLS13) && \
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER)
/* Refuses one ChangeCipherSpec write, then counts the ones that land. */
struct test_tls13_ccs_ctx {
struct test_memio_ctx* memio;
int refuse;
int sent;
};
/* Refuse the write carrying the ChangeCipherSpec, once, and let every other
* write through. Refusing by record type rather than by write count keeps
* the test tied to the record it is about. */
static int test_tls13_ccs_write_cb(WOLFSSL* ssl, char* data, int sz, void* ctx)
{
struct test_tls13_ccs_ctx* cctx = (struct test_tls13_ccs_ctx*)ctx;
int ret;
if (sz > 0 && (byte)data[0] == change_cipher_spec && cctx->refuse) {
cctx->refuse = 0;
return WOLFSSL_CBIO_ERR_WANT_WRITE;
}
ret = test_memio_write_cb(ssl, data, sz, cctx->memio);
if (ret > 0 && sz > 0 && (byte)data[0] == change_cipher_spec)
cctx->sent++;
return ret;
}
#endif
/* A server send that stops short must not put a second ChangeCipherSpec on
* the wire when it is resumed. Drive that path with a transport that refuses
* that one write. The HelloRetryRequest flavour below is the one that pins
* where sentChangeCipher is set; this one covers the straight-through
* flight, which the accept-state prologue recovers on its own. */
int test_tls13_middlebox_compat_server_ccs_retry(void)
{
EXPECT_DECLS;
#if defined(WOLFSSL_TLS13) && \
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER)
WOLFSSL_CTX *ctx_c = NULL;
WOLFSSL_CTX *ctx_s = NULL;
WOLFSSL *ssl_c = NULL;
WOLFSSL *ssl_s = NULL;
struct test_memio_ctx test_ctx;
struct test_tls13_ccs_ctx ccs_ctx;
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
XMEMSET(&ccs_ctx, 0, sizeof(ccs_ctx));
ccs_ctx.memio = &test_ctx;
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0);
if (EXPECT_SUCCESS()) {
XMEMSET(ssl_c->session->sessionID, 0xA5, ID_LEN);
ssl_c->session->sessionIDSz = ID_LEN;
/* Ungrouped, so the record is flushed on its own and the refusal
* below lands on it rather than on a flight carrying it along, and
* without the automatic retry an OPENSSL_COMPATIBLE_DEFAULTS build
* turns on, which would resume inside the send instead of through
* the accept state machine this test is about. */
ssl_s->options.groupMessages = 0;
#if defined(OPENSSL_EXTRA) || defined(HAVE_WEBSERVER) || \
defined(HAVE_MEMCACHED)
/* Only a build with the mode API can have turned auto retry on, and
* an OPENSSL_COMPATIBLE_DEFAULTS one has. Resuming the send has to
* come back through the accept state machine, not inside it. */
wolfSSL_CTX_clear_mode(ctx_s, WOLFSSL_MODE_AUTO_RETRY);
#endif
wolfSSL_SSLSetIOSend(ssl_s, test_tls13_ccs_write_cb);
wolfSSL_SetIOWriteCtx(ssl_s, &ccs_ctx);
ccs_ctx.refuse = 1;
}
ExpectIntNE(wolfSSL_connect(ssl_c), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_get_error(ssl_c,
WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), WOLFSSL_ERROR_WANT_READ);
/* The ChangeCipherSpec write is refused, so the flight stops on it. */
ExpectIntNE(wolfSSL_accept(ssl_s), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_get_error(ssl_s,
WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), WOLFSSL_ERROR_WANT_WRITE);
ExpectIntEQ(ccs_ctx.refuse, 0);
/* Resume the same handshake, now that the write goes through. */
ExpectIntNE(wolfSSL_accept(ssl_s), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_get_error(ssl_s,
WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), WOLFSSL_ERROR_WANT_READ);
/* One record, not two: the resumed accept must not repeat the send. */
ExpectIntEQ(test_tls13_count_ccs(test_ctx.c_buff, test_ctx.c_len), 1);
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
wolfSSL_free(ssl_c);
wolfSSL_free(ssl_s);
wolfSSL_CTX_free(ctx_c);
wolfSSL_CTX_free(ctx_s);
#endif
return EXPECT_RESULT();
}
/* The HelloRetryRequest path marks the record sent before sending it,
* because a resumed accept falls through into TLS13_SERVER_HELLO_SENT and
* only that flag stops it sending a second one. Refuse the write, resume,
* and check the flight answering the second ClientHello stays empty. */
int test_tls13_middlebox_compat_hrr_ccs_retry(void)
{
EXPECT_DECLS;
#if defined(WOLFSSL_TLS13) && \
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER)
WOLFSSL_CTX *ctx_c = NULL;
WOLFSSL_CTX *ctx_s = NULL;
WOLFSSL *ssl_c = NULL;
WOLFSSL *ssl_s = NULL;
struct test_memio_ctx test_ctx;
struct test_tls13_ccs_ctx ccs_ctx;
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
XMEMSET(&ccs_ctx, 0, sizeof(ccs_ctx));
ccs_ctx.memio = &test_ctx;
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0);
if (EXPECT_SUCCESS()) {
XMEMSET(ssl_c->session->sessionID, 0xA5, ID_LEN);
ssl_c->session->sessionIDSz = ID_LEN;
ssl_s->options.groupMessages = 0;
#if defined(OPENSSL_EXTRA) || defined(HAVE_WEBSERVER) || \
defined(HAVE_MEMCACHED)
/* Only a build with the mode API can have turned auto retry on, and
* an OPENSSL_COMPATIBLE_DEFAULTS one has. Resuming the send has to
* come back through the accept state machine, not inside it. */
wolfSSL_CTX_clear_mode(ctx_s, WOLFSSL_MODE_AUTO_RETRY);
#endif
wolfSSL_SSLSetIOSend(ssl_s, test_tls13_ccs_write_cb);
wolfSSL_SetIOWriteCtx(ssl_s, &ccs_ctx);
ccs_ctx.refuse = 1;
}
/* No key share entries, so the server has to ask for one. */
ExpectIntEQ(wolfSSL_NoKeyShares(ssl_c), WOLFSSL_SUCCESS);
ExpectIntNE(wolfSSL_connect(ssl_c), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_get_error(ssl_c,
WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), WOLFSSL_ERROR_WANT_READ);
/* The record behind the HelloRetryRequest is refused. */
ExpectIntNE(wolfSSL_accept(ssl_s), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_get_error(ssl_s,
WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), WOLFSSL_ERROR_WANT_WRITE);
ExpectIntEQ(ccs_ctx.refuse, 0);
/* Resume: the retry owes exactly the one record it stopped on. */
ExpectIntNE(wolfSSL_accept(ssl_s), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_get_error(ssl_s,
WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), WOLFSSL_ERROR_WANT_READ);
ExpectIntEQ(test_tls13_count_ccs(test_ctx.c_buff, test_ctx.c_len), 1);
/* The flight answering the second ClientHello owes none. A resumed
* accept falls into TLS13_SERVER_HELLO_SENT, so this is what catches
* the record being marked sent after the send rather than before. */
ExpectIntNE(wolfSSL_connect(ssl_c), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_get_error(ssl_c,
WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), WOLFSSL_ERROR_WANT_READ);
ExpectIntNE(wolfSSL_accept(ssl_s), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_get_error(ssl_s,
WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), WOLFSSL_ERROR_WANT_READ);
ExpectIntEQ(test_tls13_count_ccs(test_ctx.c_buff, test_ctx.c_len), 0);
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
wolfSSL_free(ssl_c);
wolfSSL_free(ssl_s);
wolfSSL_CTX_free(ctx_c);
wolfSSL_CTX_free(ctx_s);
#endif
return EXPECT_RESULT();
}
#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_TLS13_MIDDLEBOX_COMPAT) && \
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER)
/* One client handshake with its ChangeCipherSpec write refused once, either
* straight through or after a HelloRetryRequest. */
static int test_tls13_client_ccs_retry_once(int noKeyShares)
{
EXPECT_DECLS;
WOLFSSL_CTX *ctx_c = NULL;
WOLFSSL_CTX *ctx_s = NULL;
WOLFSSL *ssl_c = NULL;
WOLFSSL *ssl_s = NULL;
struct test_memio_ctx test_ctx;
struct test_tls13_ccs_ctx ccs_ctx;
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
XMEMSET(&ccs_ctx, 0, sizeof(ccs_ctx));
ccs_ctx.memio = &test_ctx;
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0);
if (EXPECT_SUCCESS()) {
wolfSSL_SSLSetIOSend(ssl_c, test_tls13_ccs_write_cb);
wolfSSL_SetIOWriteCtx(ssl_c, &ccs_ctx);
/* Refuse the client's first ChangeCipherSpec write, wherever in the
* flight it falls. */
ccs_ctx.refuse = 1;
}
if (noKeyShares) {
ExpectIntEQ(wolfSSL_NoKeyShares(ssl_c), WOLFSSL_SUCCESS);
}
/* A refused record answering a HelloRetryRequest must not advance
* connectState past the second ClientHello. */
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
ExpectIntEQ(ccs_ctx.sent, 1);
wolfSSL_free(ssl_c);
wolfSSL_free(ssl_s);
wolfSSL_CTX_free(ctx_c);
wolfSSL_CTX_free(ctx_s);
return EXPECT_RESULT();
}
#endif
/* The client marks its own middlebox ChangeCipherSpec sent before sending it,
* for the same reason the server does. Both flights get their own verdict. */
int test_tls13_middlebox_compat_client_ccs_retry(void)
{
EXPECT_DECLS;
#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_TLS13_MIDDLEBOX_COMPAT) && \
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER)
int hrr_res;
int direct_res;
hrr_res = test_tls13_client_ccs_retry_once(1);
direct_res = test_tls13_client_ccs_retry_once(0);
ExpectIntEQ(hrr_res, TEST_SUCCESS);
ExpectIntEQ(direct_res, TEST_SUCCESS);
#endif
return EXPECT_RESULT();
}
/* The third client send site is the early data one, which puts the
* ChangeCipherSpec out before the 0-RTT records rather than as part of a
* handshake flight. Refuse that one write and resume: the record is owed
* once, and a resumed wolfSSL_write_early_data() must not repeat it. */
int test_tls13_middlebox_compat_client_ccs_retry_early_data(void)
{
EXPECT_DECLS;
#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_TLS13_MIDDLEBOX_COMPAT) && \
defined(WOLFSSL_EARLY_DATA) && defined(HAVE_SESSION_TICKET) && \
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER)
WOLFSSL_CTX *ctx_c = NULL;
WOLFSSL_CTX *ctx_s = NULL;
WOLFSSL *ssl_c = NULL;
WOLFSSL *ssl_s = NULL;
WOLFSSL_SESSION *sess = NULL;
struct test_memio_ctx test_ctx;
struct test_tls13_ccs_ctx ccs_ctx;
const char earlyMsg[] = "This is early data";
char buf[64];
int written = 0;
int earlyRead = 0;
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
XMEMSET(&ccs_ctx, 0, sizeof(ccs_ctx));
ccs_ctx.memio = &test_ctx;
/* A full handshake first, only to get a ticket to resume with; early
* data has nowhere else to come from. */
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0);
/* Opt the server into 0-RTT (off by default per RFC 8446 E.5). */
ExpectIntGE(wolfSSL_CTX_set_max_early_data(ctx_s, MAX_EARLY_DATA_SZ), 0);
ExpectIntGE(wolfSSL_set_max_early_data(ssl_s, MAX_EARLY_DATA_SZ), 0);
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
/* Read so that the NewSessionTicket is taken in. */
ExpectIntEQ(wolfSSL_read(ssl_c, buf, sizeof(buf)), -1);
ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ);
ExpectNotNull(sess = wolfSSL_get1_session(ssl_c));
wolfSSL_free(ssl_c);
ssl_c = NULL;
wolfSSL_free(ssl_s);
ssl_s = NULL;
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0);
ExpectIntGE(wolfSSL_set_max_early_data(ssl_s, MAX_EARLY_DATA_SZ), 0);
ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS);
if (EXPECT_SUCCESS()) {
/* Ungrouped, so the record is flushed on its own and the refusal
* below lands on it rather than on a flight carrying it along. */
ssl_c->options.groupMessages = 0;
#if defined(OPENSSL_EXTRA) || defined(HAVE_WEBSERVER) || \
defined(HAVE_MEMCACHED)
/* Only a build with the mode API can have turned auto retry on, and
* an OPENSSL_COMPATIBLE_DEFAULTS one has. Resuming the send has to
* come back through the connect state machine, not inside it. */
wolfSSL_CTX_clear_mode(ctx_c, WOLFSSL_MODE_AUTO_RETRY);
#endif
wolfSSL_SSLSetIOSend(ssl_c, test_tls13_ccs_write_cb);
wolfSSL_SetIOWriteCtx(ssl_c, &ccs_ctx);
ccs_ctx.refuse = 1;
}
/* The ChangeCipherSpec ahead of the early data is refused, so the
* client stops on it with nothing written. */
ExpectIntEQ(wolfSSL_write_early_data(ssl_c, earlyMsg, (int)sizeof(earlyMsg),
&written), WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR));
ExpectIntEQ(wolfSSL_get_error(ssl_c,
WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), WOLFSSL_ERROR_WANT_WRITE);
ExpectIntEQ(ccs_ctx.refuse, 0);
ExpectIntEQ(written, 0);
/* Resume: the record it stopped on goes out, and only that one. */
ExpectIntEQ(wolfSSL_write_early_data(ssl_c, earlyMsg, (int)sizeof(earlyMsg),
&written), (int)sizeof(earlyMsg));
ExpectIntEQ(written, (int)sizeof(earlyMsg));
ExpectIntEQ(ccs_ctx.sent, 1);
/* The 0-RTT data the retry carried still arrives, and the handshake it
* started still completes. */
XMEMSET(buf, 0, sizeof(buf));
(void)test_tls13_early_data_read_until_write_ok(ssl_s, buf, sizeof(buf),
&earlyRead);
ExpectIntEQ(earlyRead, (int)sizeof(earlyMsg));
ExpectStrEQ(earlyMsg, buf);
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
ExpectTrue(wolfSSL_session_reused(ssl_c));
/* Still the one record, over the whole handshake. */
ExpectIntEQ(ccs_ctx.sent, 1);
wolfSSL_SESSION_free(sess);
wolfSSL_free(ssl_c);
wolfSSL_free(ssl_s);
wolfSSL_CTX_free(ctx_c);
wolfSSL_CTX_free(ctx_s);
#endif
return EXPECT_RESULT();
}
int test_tls13_plaintext_alert(void)
{
EXPECT_DECLS;

View File

@ -55,6 +55,13 @@ int test_key_share_mismatch(void);
int test_key_share_mismatch_psk_dhe(void);
int test_tls13_middlebox_compat_empty_session_id(void);
int test_tls13_middlebox_compat_session_id(void);
int test_tls13_middlebox_compat_server_ccs(void);
int test_tls13_middlebox_compat_server_reuse(void);
int test_tls13_middlebox_compat_hrr_ccs(void);
int test_tls13_middlebox_compat_server_ccs_retry(void);
int test_tls13_middlebox_compat_hrr_ccs_retry(void);
int test_tls13_middlebox_compat_client_ccs_retry(void);
int test_tls13_middlebox_compat_client_ccs_retry_early_data(void);
int test_tls13_plaintext_alert(void);
int test_tls13_warning_alert_is_fatal(void);
int test_tls13_unknown_ext_rejected(void);
@ -172,6 +179,14 @@ int test_tls13_cryptocb_async(void);
TEST_DECL_GROUP("tls13", test_key_share_mismatch_psk_dhe), \
TEST_DECL_GROUP("tls13", test_tls13_middlebox_compat_empty_session_id), \
TEST_DECL_GROUP("tls13", test_tls13_middlebox_compat_session_id), \
TEST_DECL_GROUP("tls13", test_tls13_middlebox_compat_server_ccs), \
TEST_DECL_GROUP("tls13", test_tls13_middlebox_compat_server_reuse), \
TEST_DECL_GROUP("tls13", test_tls13_middlebox_compat_hrr_ccs), \
TEST_DECL_GROUP("tls13", test_tls13_middlebox_compat_server_ccs_retry), \
TEST_DECL_GROUP("tls13", test_tls13_middlebox_compat_hrr_ccs_retry), \
TEST_DECL_GROUP("tls13", test_tls13_middlebox_compat_client_ccs_retry), \
TEST_DECL_GROUP("tls13", \
test_tls13_middlebox_compat_client_ccs_retry_early_data), \
TEST_DECL_GROUP("tls13", test_tls13_plaintext_alert), \
TEST_DECL_GROUP("tls13", test_tls13_warning_alert_is_fatal), \
TEST_DECL_GROUP("tls13", test_tls13_cert_req_sigalgs), \

View File

@ -5576,7 +5576,7 @@ struct Options {
#ifdef WOLFSSL_ALT_CERT_CHAINS
word16 usingAltCertChain:1;/* Alternate cert chain was used */
#endif
#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_TLS13_MIDDLEBOX_COMPAT)
#ifdef WOLFSSL_TLS13
word16 sentChangeCipher:1; /* Change Cipher Spec sent */
#endif
#if !defined(WOLFSSL_NO_CLIENT_AUTH) && \