diff --git a/src/internal.c b/src/internal.c index 8b34e20c37b..8cdbf608b83 100644 --- a/src/internal.c +++ b/src/internal.c @@ -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) { diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index 09a5dfacd17..cd6314b080f 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -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. */