mirror of https://github.com/wolfSSL/wolfssl.git
Merge pull request #11306 from Frauschi/erlang-interop
Send server ChangeCipherSpec when client offers a session idpull/11340/merge
commit
b6fd66987d
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
13
ChangeLog.md
13
ChangeLog.md
|
|
@ -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.
|
||||
|
|
|
|||
12
configure.ac
12
configure.ac
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
109
src/tls13.c
109
src/tls13.c
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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), \
|
||||
|
|
|
|||
|
|
@ -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) && \
|
||||
|
|
|
|||
Loading…
Reference in New Issue