TLS 1.3 plaintext alert: ignore before seeing encrypted

Change to ignore plaintext alerts when
WOLFSSL_TLS13_IGNORE_PT_ALERT_ON_ENC is defined only until first
encrypted message from peer is seen.

Negative testing added.
This commit is contained in:
Sean Parkinson
2026-05-27 16:10:05 +10:00
parent 7cf84dd833
commit b0757c1cb7
2 changed files with 153 additions and 12 deletions
+30 -12
View File
@@ -22420,6 +22420,9 @@ static int DoAlert(WOLFSSL* ssl, byte* input, word32* inOutIdx, int* type)
byte level;
byte code;
word32 dataSz = (word32)ssl->curSize;
#ifdef WOLFSSL_TLS13_IGNORE_PT_ALERT_ON_ENC
int ignorePtAlert;
#endif
#if defined(WOLFSSL_CALLBACKS) || defined(OPENSSL_EXTRA)
if (ssl->hsInfoOn)
@@ -22448,9 +22451,19 @@ static int DoAlert(WOLFSSL* ssl, byte* input, word32* inOutIdx, int* type)
code = input[(*inOutIdx)++];
*type = code;
#ifdef WOLFSSL_TLS13_IGNORE_PT_ALERT_ON_ENC
/* Don't process alert when TLS 1.3 and encrypting but plaintext alert. */
if (!IsAtLeastTLSv1_3(ssl->version) || !IsEncryptionOn(ssl, 0) ||
ssl->keys.decryptedCur)
/* A plaintext alert received in TLS 1.3 once we are decrypting is only
* tolerated while still in the handshake and before the peer has sent an
* encrypted message. The peer sequence number is reset to zero each time
* decryption keys are installed and incremented for each record decrypted,
* so a non-zero value means the peer has sent an encrypted message and a
* plaintext alert is treated as an error. */
ignorePtAlert = IsAtLeastTLSv1_3(ssl->version) && IsEncryptionOn(ssl, 0) &&
!ssl->keys.decryptedCur && !ssl->options.handShakeDone &&
ssl->keys.peer_sequence_number_hi == 0 &&
ssl->keys.peer_sequence_number_lo == 0;
/* Don't record an ignored plaintext alert in the alert history. */
if (!ignorePtAlert)
#endif
{
ssl->alert_history.last_rx.code = code;
@@ -22481,16 +22494,21 @@ static int DoAlert(WOLFSSL* ssl, byte* input, word32* inOutIdx, int* type)
!ssl->keys.decryptedCur)
{
#ifdef WOLFSSL_TLS13_IGNORE_PT_ALERT_ON_ENC
/* Ignore alert if TLS 1.3 and encrypting but was plaintext alert. */
*type = invalid_alert;
level = alert_none;
#else
/* Unexpected message when encryption is on and alert not encrypted. */
SendAlert(ssl, alert_fatal, unexpected_message);
WOLFSSL_ERROR_VERBOSE(PARSE_ERROR);
return PARSE_ERROR;
if (ignorePtAlert) {
/* Ignore plaintext alert: TLS 1.3, decrypting, and the peer has
* not yet sent an encrypted handshake message. */
*type = invalid_alert;
level = alert_none;
}
else
#endif
{
/* Unexpected message when encryption is on and alert not
* encrypted. */
SendAlert(ssl, alert_fatal, unexpected_message);
WOLFSSL_ERROR_VERBOSE(PARSE_ERROR);
return PARSE_ERROR;
}
}
else {
if (*type == close_notify) {
+123
View File
@@ -4542,6 +4542,129 @@ int test_tls13_plaintext_alert(void)
wolfSSL_free(ssl);
wolfSSL_CTX_free(ctx);
ssl = NULL;
ctx = NULL;
/* Negative test: a plaintext alert must NOT be ignored once the peer has
* responded with an encrypted handshake message. Complete a handshake so
* the peer is encrypting, then feed the client a plaintext alert. */
#if !defined(NO_WOLFSSL_CLIENT) && !defined(NO_FILESYSTEM)
{
WOLFSSL_CTX* ctx_c = NULL;
WOLFSSL_CTX* ctx_s = NULL;
WOLFSSL* ssl_c = NULL;
WOLFSSL* ssl_s = NULL;
struct test_memio_ctx test_ctx;
/* Plaintext alert record: fatal (2), handshake_failure (40). */
byte ptAlert[] = { 0x15, 0x03, 0x03, 0x00, 0x02, 0x02, 0x28 };
char data[16];
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);
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
/* Drop any post-handshake data (e.g. session tickets) queued for the
* client and feed it only the plaintext alert. */
test_memio_clear_buffer(&test_ctx, 1);
ExpectIntEQ(test_memio_inject_message(&test_ctx, 1, (const char*)ptAlert,
(int)sizeof(ptAlert)), 0);
/* Plaintext alert is rejected as the peer is encrypting. */
ExpectIntLT(wolfSSL_read(ssl_c, data, (int)sizeof(data)), 0);
ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR),
WC_NO_ERR_TRACE(PARSE_ERROR));
wolfSSL_free(ssl_c);
wolfSSL_free(ssl_s);
wolfSSL_CTX_free(ctx_c);
wolfSSL_CTX_free(ctx_s);
}
/* Negative test (server): a plaintext alert must NOT be ignored once the
* client has sent an encrypted handshake message, even before the
* handshake is complete. Use client authentication so that the client
* sends an encrypted Certificate message before Finished. */
{
WOLFSSL_CTX* ctx_c = NULL;
WOLFSSL_CTX* ctx_s = NULL;
WOLFSSL* ssl_c = NULL;
WOLFSSL* ssl_s = NULL;
struct test_memio_ctx test_ctx;
/* Plaintext alert record: fatal (2), handshake_failure (40). */
byte ptAlert[] = { 0x15, 0x03, 0x03, 0x00, 0x02, 0x02, 0x28 };
int end = 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);
/* Server requires a client certificate. */
ExpectTrue(wolfSSL_CTX_load_verify_locations(ctx_s, cliCertFile,
NULL) == WOLFSSL_SUCCESS);
if (EXPECT_SUCCESS()) {
wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_PEER |
WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
}
ExpectTrue(wolfSSL_use_certificate_file(ssl_c, cliCertFile,
CERT_FILETYPE) == WOLFSSL_SUCCESS);
ExpectTrue(wolfSSL_use_PrivateKey_file(ssl_c, cliKeyFile,
CERT_FILETYPE) == WOLFSSL_SUCCESS);
/* Client Hello. */
ExpectIntEQ(wolfSSL_connect(ssl_c), -1);
ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ);
/* Server flight including CertificateRequest. */
ExpectIntEQ(wolfSSL_accept(ssl_s), -1);
ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ);
/* Client flight: [CCS,] Certificate, CertificateVerify, Finished. */
ExpectIntEQ(wolfSSL_connect(ssl_c), WOLFSSL_SUCCESS);
/* Find the end of the first encrypted record (outer content type
* application_data) the client sent - the Certificate message. */
while (end + 5 <= test_ctx.s_len) {
byte recType = test_ctx.s_buff[end];
end += 5 + ((test_ctx.s_buff[end + 3] << 8) |
test_ctx.s_buff[end + 4]);
if (recType == 0x17)
break;
}
ExpectIntLE(end, test_ctx.s_len);
ExpectIntGT(end, 0);
/* Remove the records after it (CertificateVerify and Finished),
* working backwards a message at a time. */
while (EXPECT_SUCCESS() && test_ctx.s_len > end) {
int i;
int msgOff = 0;
for (i = 0; i < test_ctx.s_msg_count - 1; i++)
msgOff += test_ctx.s_msg_sizes[i];
if (msgOff >= end) {
/* Last message is wholly after the Certificate record. */
ExpectIntEQ(test_memio_drop_message(&test_ctx, 0,
test_ctx.s_msg_count - 1), 0);
}
else {
/* Last message also holds the records to keep. */
ExpectIntEQ(test_memio_remove_from_buffer(&test_ctx, 0, end,
test_ctx.s_len - end), 0);
}
}
/* Follow the encrypted Certificate message with a plaintext alert. */
ExpectIntEQ(test_memio_inject_message(&test_ctx, 0,
(const char*)ptAlert, (int)sizeof(ptAlert)), 0);
/* Plaintext alert is rejected as the client has sent an encrypted
* handshake message. */
ExpectIntEQ(wolfSSL_accept(ssl_s), -1);
ExpectIntEQ(wolfSSL_get_error(ssl_s, WOLFSSL_FATAL_ERROR),
WC_NO_ERR_TRACE(PARSE_ERROR));
wolfSSL_free(ssl_c);
wolfSSL_free(ssl_s);
wolfSSL_CTX_free(ctx_c);
wolfSSL_CTX_free(ctx_s);
}
#endif
#else
/* Fail on plaintext alert when encryption keys on. */