Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 32 additions & 22 deletions src/tls13.c
Original file line number Diff line number Diff line change
Expand Up @@ -12725,6 +12725,15 @@ static int DoTls13CertificateVerify(WOLFSSL* ssl, byte* input,
* HAVE_FALCON || WOLFSSL_HAVE_MLDSA || WOLFSSL_HAVE_SLHDSA */
#endif /* !NO_CERTS */

#ifdef WOLFSSL_POST_HANDSHAKE_AUTH
/* Message is being processed after the enclosing handshake completed. Whatever
* resumption, PSK or deferred (post-handshake) verification excused during that
* handshake, a later post-handshake exchange has to stand on its own. */
#define TLS13_AFTER_HANDSHAKE(ssl) ((ssl)->options.handShakeDone)
#else
#define TLS13_AFTER_HANDSHAKE(ssl) 0
#endif
Comment thread
kareem-wolfssl marked this conversation as resolved.

/* Parse and handle a TLS v1.3 Finished message.
*
* ssl The SSL/TLS object.
Expand All @@ -12749,21 +12758,22 @@ int DoTls13Finished(WOLFSSL* ssl, const byte* input, word32* inOutIdx,

#if !defined(NO_CERTS) && !defined(WOLFSSL_NO_CLIENT_AUTH)
/* verify the client sent certificate if required */
if (ssl->options.side == WOLFSSL_SERVER_END && !ssl->options.resuming &&
if (ssl->options.side == WOLFSSL_SERVER_END &&
(!ssl->options.resuming || TLS13_AFTER_HANDSHAKE(ssl)) &&
(ssl->options.mutualAuth || ssl->options.failNoCert)) {
#ifdef OPENSSL_COMPATIBLE_DEFAULTS
if (ssl->options.isPSK) {
if (ssl->options.isPSK && !TLS13_AFTER_HANDSHAKE(ssl)) {
WOLFSSL_MSG("TLS v1.3 client used PSK but cert required. Allowing "
"for OpenSSL compatibility");
}
else
#endif
if (
#ifdef WOLFSSL_POST_HANDSHAKE_AUTH
/* Exempt only the initial handshake; a pending post-handshake
* CertificateRequest (certReqCtx != NULL) still requires a peer
* certificate and a valid CertificateVerify. */
(!ssl->options.verifyPostHandshake || ssl->certReqCtx != NULL) &&
/* Exempt only the enclosing handshake; a post-handshake exchange
* still requires a peer certificate and a valid
* CertificateVerify. */
(!ssl->options.verifyPostHandshake || TLS13_AFTER_HANDSHAKE(ssl)) &&
#endif
(!ssl->options.havePeerCert || !ssl->options.havePeerVerify)) {
ret = NO_PEER_CERT; /* NO_PEER_VERIFY */
Expand Down Expand Up @@ -14302,8 +14312,9 @@ static int SanityCheckTls13MsgReceived(WOLFSSL* ssl, byte type)
}
#endif
#if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)
/* Server's authenticating with PSK must not send this. */
if (ssl->options.pskNegotiated
/* RFC 8446 4.3.2: a server authenticating with a PSK must not send
* this in the main handshake, but may send it post-handshake. */
if (ssl->options.pskNegotiated && !TLS13_AFTER_HANDSHAKE(ssl)
#ifdef WOLFSSL_CERT_WITH_EXTERN_PSK
&& !ssl->options.certWithExternPsk
#endif
Expand Down Expand Up @@ -14463,7 +14474,9 @@ static int SanityCheckTls13MsgReceived(WOLFSSL* ssl, byte type)
}
#endif
#if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)
if (!ssl->options.pskNegotiated
if (!ssl->options.pskNegotiated ||
(ssl->options.side == WOLFSSL_SERVER_END &&
TLS13_AFTER_HANDSHAKE(ssl))
#ifdef WOLFSSL_CERT_WITH_EXTERN_PSK
|| ssl->options.certWithExternPsk
#endif
Expand All @@ -14477,18 +14490,13 @@ static int SanityCheckTls13MsgReceived(WOLFSSL* ssl, byte type)
if (ssl->options.verifyPeer &&
#ifdef WOLFSSL_POST_HANDSHAKE_AUTH
/* The post-handshake-auth exemption is only valid during
* the initial handshake. On the server, once a
* post-handshake CertificateRequest is outstanding
* (certReqCtx != NULL), a Certificate is required again.
* Scoped to the server: certReqCtx means something
* different on the client (a received request) and the
* client does not process an inbound Finished in that
* state. Whether an empty Certificate is then accepted
* follows the verify mode (FAIL_IF_NO_PEER_CERT), exactly
* as for first-handshake client authentication. */
* the enclosing handshake. Once the server has requested a
* certificate post-handshake, one is required again.
* Whether an empty Certificate is then accepted follows the
* verify mode (FAIL_IF_NO_PEER_CERT), exactly as for
* first-handshake client authentication. */
(!ssl->options.verifyPostHandshake ||
(ssl->options.side == WOLFSSL_SERVER_END &&
ssl->certReqCtx != NULL)) &&
TLS13_AFTER_HANDSHAKE(ssl)) &&
#endif
!ssl->msgsReceived.got_certificate) {
WOLFSSL_MSG("Finished received out of order - "
Expand Down Expand Up @@ -15565,7 +15573,8 @@ int wolfSSL_connect_TLSv13(WOLFSSL* ssl)
return WOLFSSL_FATAL_ERROR;
}
#ifndef NO_CERTS
if (!ssl->options.resuming && ssl->options.sendVerify) {
if ((!ssl->options.resuming || TLS13_AFTER_HANDSHAKE(ssl)) &&
ssl->options.sendVerify) {
ssl->error = SendTls13Certificate(ssl);
if (ssl->error != 0) {
wolfssl_local_MaybeCheckAlertOnErr(ssl, ssl->error);
Expand All @@ -15586,7 +15595,8 @@ int wolfSSL_connect_TLSv13(WOLFSSL* ssl)
defined(HAVE_FALCON) || defined(WOLFSSL_HAVE_MLDSA) || \
defined(WOLFSSL_HAVE_SLHDSA))) && \
(!defined(NO_WOLFSSL_SERVER) || !defined(WOLFSSL_NO_CLIENT_AUTH))
if (!ssl->options.resuming && ssl->options.sendVerify) {
if ((!ssl->options.resuming || TLS13_AFTER_HANDSHAKE(ssl)) &&
ssl->options.sendVerify) {
ssl->error = SendTls13CertificateVerify(ssl);
if (ssl->error != 0) {
wolfssl_local_MaybeCheckAlertOnErr(ssl, ssl->error);
Expand Down
193 changes: 193 additions & 0 deletions tests/api/test_tls13.c
Original file line number Diff line number Diff line change
Expand Up @@ -3986,6 +3986,199 @@ int test_tls13_pha(void)
return EXPECT_RESULT();
}

/* Post-handshake auth over a resumed (ticket-PSK) connection. Client
* credentials go on the CTX: FreeHandshakeResources() unloads SSL-owned
* certificates, leaving nothing to answer a later request with. */
int test_tls13_pha_resumption(void)
{
EXPECT_DECLS;
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_TLS13) && \
defined(WOLFSSL_POST_HANDSHAKE_AUTH) && defined(HAVE_SESSION_TICKET) && \
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \
!defined(NO_RSA) && !defined(NO_CERTS)
WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL;
WOLFSSL *ssl_c = NULL, *ssl_s = NULL;
WOLFSSL_SESSION* sess = NULL;
struct test_memio_ctx test_ctx;
char msg[] = "hello wolfssl!";
char buf[sizeof(msg)];
int i;

/* Setup only creates a CTX when the pointer is NULL, so the call in the
* loop reuses these and creates just the SSL objects. */
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, NULL, NULL,
wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0);
ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx_c, cliCertFile,
WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx_c, cliKeyFile,
WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS);
/* PHA has to be offered in the ClientHello. */
ExpectIntEQ(wolfSSL_CTX_allow_post_handshake_auth(ctx_c), 0);
ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_s, cliCertFile, NULL),
WOLFSSL_SUCCESS);
/* No client certificate wanted for the handshake itself. */
wolfSSL_CTX_set_verify(ctx_s, WOLFSSL_VERIFY_NONE, NULL);

for (i = 0; i < 2 && EXPECT_SUCCESS(); i++) {
test_ctx.c_len = test_ctx.s_len = 0;
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0);
Comment thread
kareem-wolfssl marked this conversation as resolved.

if (i == 1)
ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS);

ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
ExpectIntEQ(wolfSSL_session_reused(ssl_c), i);

if (i == 0) {
/* Drain the NewSessionTicket so the ticket gets cached. */
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));
}

if (EXPECT_SUCCESS()) {
wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_PEER |
WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
ExpectIntEQ(wolfSSL_request_certificate(ssl_s), WOLFSSL_SUCCESS);
}

/* The server's write carries the CertificateRequest. */
ExpectIntEQ(wolfSSL_write(ssl_s, msg, (int)sizeof(msg) - 1),
(int)sizeof(msg) - 1);
ExpectIntEQ(wolfSSL_read(ssl_c, buf, sizeof(buf) - 1),
(int)sizeof(msg) - 1);
ExpectIntEQ(wolfSSL_write(ssl_c, msg, (int)sizeof(msg) - 1),
(int)sizeof(msg) - 1);
ExpectIntEQ(wolfSSL_read(ssl_s, buf, sizeof(buf) - 1),
(int)sizeof(msg) - 1);

ExpectIntEQ(ssl_s->options.havePeerCert, 1);
ExpectIntEQ(ssl_s->options.havePeerVerify, 1);

wolfSSL_free(ssl_c);
ssl_c = NULL;
wolfSSL_free(ssl_s);
ssl_s = NULL;
}

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();
}

/* Server must reject a post-handshake Finished that omits the Certificate
* flight on a resumed connection. A client answers the request from inside
* wolfSSL_read(), so handShakeState is parked for that read and the response is
* driven by hand with sendVerify cleared. handShakeDone stays set, so
* SendTls13Finished() still produces a valid MAC. */
int test_tls13_pha_resumption_bare_finished(void)
{
EXPECT_DECLS;
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_TLS13) && \
defined(WOLFSSL_POST_HANDSHAKE_AUTH) && defined(HAVE_SESSION_TICKET) && \
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \
!defined(NO_RSA) && !defined(NO_CERTS)
WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL;
WOLFSSL *ssl_c = NULL, *ssl_s = NULL;
WOLFSSL_SESSION* sess = NULL;
struct test_memio_ctx test_ctx;
char msg[] = "hello wolfssl!";
char buf[sizeof(msg)];
int i;

/* Setup only creates a CTX when the pointer is NULL, so the call in the
* loop reuses these and creates just the SSL objects. */
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, NULL, NULL,
wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0);
ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx_c, cliCertFile,
WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx_c, cliKeyFile,
WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_CTX_allow_post_handshake_auth(ctx_c), 0);
ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_s, cliCertFile, NULL),
WOLFSSL_SUCCESS);
wolfSSL_CTX_set_verify(ctx_s, WOLFSSL_VERIFY_NONE, NULL);

/* First connection only exists to obtain a ticket. */
for (i = 0; i < 2 && EXPECT_SUCCESS(); i++) {
test_ctx.c_len = test_ctx.s_len = 0;
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0);

if (i == 1)
ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS);

ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
ExpectIntEQ(wolfSSL_session_reused(ssl_c), i);

if (i == 0) {
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));
}
else {
if (EXPECT_SUCCESS()) {
wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_PEER |
WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
ExpectIntEQ(wolfSSL_request_certificate(ssl_s),
WOLFSSL_SUCCESS);
}
ExpectIntEQ(wolfSSL_write(ssl_s, msg, (int)sizeof(msg) - 1),
(int)sizeof(msg) - 1);

/* Consume the CertificateRequest without answering it. */
if (EXPECT_SUCCESS()) {
ssl_c->options.handShakeState = CLIENT_FINISHED_COMPLETE;
}
ExpectIntEQ(wolfSSL_read(ssl_c, buf, sizeof(buf) - 1),
(int)sizeof(msg) - 1);
ExpectIntEQ(ssl_c->options.sendVerify, SEND_CERT);

/* Answer with a bare Finished over that same transcript. */
if (EXPECT_SUCCESS()) {
ssl_c->options.sendVerify = 0;
ssl_c->options.clientState = CLIENT_HELLO_COMPLETE;
ssl_c->options.connectState = FIRST_REPLY_DONE;
ssl_c->options.handShakeState = CLIENT_HELLO_COMPLETE;
ssl_c->options.processReply = 0;
ExpectIntEQ(wolfSSL_connect_TLSv13(ssl_c), WOLFSSL_SUCCESS);
}

/* Data behind the Finished: a server that wrongly accepted it
* returns this instead of an error. */
ExpectIntEQ(wolfSSL_write(ssl_c, msg, (int)sizeof(msg) - 1),
(int)sizeof(msg) - 1);

ExpectIntEQ(wolfSSL_read(ssl_s, buf, sizeof(buf) - 1), -1);
ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), OUT_OF_ORDER_E);
ExpectIntEQ(ssl_s->options.havePeerCert, 0);
ExpectIntEQ(ssl_s->options.havePeerVerify, 0);
}

wolfSSL_free(ssl_c);
ssl_c = NULL;
wolfSSL_free(ssl_s);
ssl_s = NULL;
}

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();
}

#if defined(HAVE_RPK) && defined(WOLFSSL_TLS13) && \
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \
!defined(NO_SHA256)
Expand Down
4 changes: 4 additions & 0 deletions tests/api/test_tls13.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ int test_tls13_bad_psk_binder(void);
int test_tls13_rpk_handshake(void);
int test_tls13_rpk_handshake_no_negotiation(void);
int test_tls13_pha(void);
int test_tls13_pha_resumption(void);
int test_tls13_pha_resumption_bare_finished(void);
int test_tls13_rpk_untrusted(void);
int test_tls13_rpk_trust(void);
int test_tls13_rpk_unoffered_cert_type(void);
Expand Down Expand Up @@ -118,6 +120,8 @@ int test_tls13_pha_status_request(void);
TEST_DECL_GROUP("tls13", test_tls13_rpk_handshake), \
TEST_DECL_GROUP("tls13", test_tls13_rpk_handshake_no_negotiation), \
TEST_DECL_GROUP("tls13", test_tls13_pha), \
TEST_DECL_GROUP("tls13", test_tls13_pha_resumption), \
TEST_DECL_GROUP("tls13", test_tls13_pha_resumption_bare_finished), \
TEST_DECL_GROUP("tls13", test_tls13_rpk_untrusted), \
TEST_DECL_GROUP("tls13", test_tls13_rpk_trust), \
TEST_DECL_GROUP("tls13", test_tls13_rpk_unoffered_cert_type), \
Expand Down
Loading