diff --git a/src/internal.c b/src/internal.c index c3d6379eac..b27403ad3b 100644 --- a/src/internal.c +++ b/src/internal.c @@ -18051,6 +18051,15 @@ static int DoHelloRequest(WOLFSSL* ssl, word32 size) } #ifdef HAVE_SECURE_RENEGOTIATION else if (ssl->secure_renegotiation && ssl->secure_renegotiation->enabled) { + /* WOLFSSL_OP_NO_RENEGOTIATION: caller opted into rejecting + * peer-initiated renegotiation. Respond with a no_renegotiation + * warning alert instead of starting a secure renegotiation. */ + if (ssl->options.mask & WOLFSSL_OP_NO_RENEGOTIATION) { + WOLFSSL_MSG("Rejecting HelloRequest: WOLFSSL_OP_NO_RENEGOTIATION"); + WOLFSSL_LEAVE("DoHelloRequest", 0); + WOLFSSL_END(WC_FUNC_HELLO_REQUEST_DO); + return SendAlert(ssl, alert_warning, no_renegotiation); + } ssl->secure_renegotiation->startScr = 1; WOLFSSL_LEAVE("DoHelloRequest", 0); WOLFSSL_END(WC_FUNC_HELLO_REQUEST_DO); @@ -18783,6 +18792,17 @@ int DoHandShakeMsgType(WOLFSSL* ssl, byte* input, word32* inOutIdx, ssl->secure_renegotiation && ssl->secure_renegotiation->enabled) { + /* WOLFSSL_OP_NO_RENEGOTIATION: caller opted into rejecting + * peer-initiated renegotiation. RFC 5246 7.2.2: no_renegotiation is a + * warning-level alert, so refuse the renegotiation but keep the + * established connection rather than aborting it. Skip the ClientHello + * body and leave handshake state untouched, mirroring the client-side + * HelloRequest refusal in DoHelloRequest(). */ + if (ssl->options.mask & WOLFSSL_OP_NO_RENEGOTIATION) { + WOLFSSL_MSG("Refusing renegotiation: WOLFSSL_OP_NO_RENEGOTIATION"); + *inOutIdx = expectedIdx; + return SendAlert(ssl, alert_warning, no_renegotiation); + } WOLFSSL_MSG("Reset handshake state"); XMEMSET(&ssl->msgsReceived, 0, sizeof(MsgsReceived)); ssl->options.serverState = NULL_STATE; @@ -23240,6 +23260,7 @@ static int DoProcessReplyEx(WOLFSSL* ssl, int allowSocketErr) /* see if sending SSLv2 client hello */ if ( ssl->options.side == WOLFSSL_SERVER_END && ssl->options.clientState == NULL_STATE && + !ssl->options.handShakeDone && ssl->buffers.inputBuffer.buffer[ssl->buffers.inputBuffer.idx] != handshake && /* change_cipher_spec here is an error but we want to handle diff --git a/tests/api.c b/tests/api.c index 6208cc2b2d..96718dc334 100644 --- a/tests/api.c +++ b/tests/api.c @@ -35075,6 +35075,8 @@ TEST_CASE testCases[] = { TEST_DECL(test_tls12_chacha20_poly1305_bad_tag), TEST_DECL(test_tls13_null_cipher_bad_hmac), TEST_DECL(test_scr_verify_data_mismatch), + TEST_DECL(test_scr_no_renegotiation_option), + TEST_DECL(test_helloRequest_no_renegotiation_option), TEST_DECL(test_tls13_hrr_cipher_suite_mismatch), TEST_DECL(test_tls13_ticket_age_out_of_window), TEST_DECL(test_wolfSSL_DisableExtendedMasterSecret), diff --git a/tests/api/test_tls_ext.c b/tests/api/test_tls_ext.c index 294fa9c903..7da0976eb4 100644 --- a/tests/api/test_tls_ext.c +++ b/tests/api/test_tls_ext.c @@ -350,6 +350,145 @@ int test_scr_verify_data_mismatch(void) return EXPECT_RESULT(); } +/* F-4144: WOLFSSL_OP_NO_RENEGOTIATION on the server must refuse a + * client-initiated renegotiation with a no_renegotiation *warning* while + * keeping the established connection alive, rather than aborting it. */ +int test_scr_no_renegotiation_option(void) +{ + EXPECT_DECLS; +#if defined(HAVE_SECURE_RENEGOTIATION) && !defined(WOLFSSL_NO_TLS12) && \ + defined(BUILD_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) + struct test_memio_ctx test_ctx; + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + WOLFSSL_ALERT_HISTORY history; + byte readBuf[16]; + int ret = WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR); + int i; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(&history, 0, sizeof(history)); + test_ctx.c_ciphers = test_ctx.s_ciphers = "ECDHE-RSA-AES128-GCM-SHA256"; + + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, + &ssl_s, wolfTLSv1_2_client_method, + wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_CTX_UseSecureRenegotiation(ctx_c), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_UseSecureRenegotiation(ctx_s), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseSecureRenegotiation(ssl_c), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseSecureRenegotiation(ssl_s), WOLFSSL_SUCCESS); + + /* Server opts into rejecting peer-initiated renegotiation. */ + wolfSSL_set_options(ssl_s, WOLFSSL_OP_NO_RENEGOTIATION); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* Client initiates renegotiation: it sends a ClientHello and waits for a + * ServerHello that never comes. */ + ExpectIntLT(wolfSSL_Rehandshake(ssl_c), 0); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + + /* Server processes the renegotiation ClientHello. It must refuse without + * aborting: the read returns WANT_READ (connection still alive), not a + * SECURE_RENEGOTIATION_E fatal error. */ + ExpectIntLT(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), 0); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + + /* The refusal was a warning-level no_renegotiation alert. */ + ExpectIntEQ(wolfSSL_get_alert_history(ssl_s, &history), WOLFSSL_SUCCESS); + ExpectIntEQ(history.last_tx.level, alert_warning); + ExpectIntEQ(history.last_tx.code, no_renegotiation); + + /* The connection is still active and passes data: the server sends + * application data which the client receives and decrypts correctly, even + * though the client's renegotiation attempt was refused. The client + * surfaces the data once it has processed the no_renegotiation warning. */ + ExpectIntEQ(wolfSSL_write(ssl_s, "hello", 5), 5); + for (i = 0; i < 10 && ret != 5; i++) + ret = wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)); + ExpectIntEQ(ret, 5); + ExpectIntEQ(XMEMCMP(readBuf, "hello", 5), 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* F-4144: WOLFSSL_OP_NO_RENEGOTIATION on the client must refuse a + * server-initiated renegotiation (HelloRequest) with a no_renegotiation + * *warning* while keeping the established connection alive, rather than + * starting a secure renegotiation. */ +int test_helloRequest_no_renegotiation_option(void) +{ + EXPECT_DECLS; +#if defined(HAVE_SECURE_RENEGOTIATION) && !defined(WOLFSSL_NO_TLS12) && \ + defined(BUILD_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) + struct test_memio_ctx test_ctx; + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + WOLFSSL_ALERT_HISTORY history; + byte readBuf[16]; + int ret = WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR); + int i; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(&history, 0, sizeof(history)); + test_ctx.c_ciphers = test_ctx.s_ciphers = "ECDHE-RSA-AES128-GCM-SHA256"; + + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, + &ssl_s, wolfTLSv1_2_client_method, + wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_CTX_UseSecureRenegotiation(ctx_c), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_UseSecureRenegotiation(ctx_s), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseSecureRenegotiation(ssl_c), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseSecureRenegotiation(ssl_s), WOLFSSL_SUCCESS); + + /* Client opts into rejecting peer-initiated renegotiation. */ + wolfSSL_set_options(ssl_c, WOLFSSL_OP_NO_RENEGOTIATION); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* Server asks the client to renegotiate by sending a HelloRequest, then + * waits for the ClientHello that never comes. */ + ExpectIntLT(wolfSSL_Rehandshake(ssl_s), 0); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + + /* Client processes the HelloRequest. It must refuse without starting a + * renegotiation: the read returns WANT_READ (connection still alive). */ + ExpectIntLT(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), 0); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + + /* The refusal was a warning-level no_renegotiation alert. */ + ExpectIntEQ(wolfSSL_get_alert_history(ssl_c, &history), WOLFSSL_SUCCESS); + ExpectIntEQ(history.last_tx.level, alert_warning); + ExpectIntEQ(history.last_tx.code, no_renegotiation); + + /* The connection is still active and passes data: the client sends + * application data which the server receives and decrypts correctly, even + * though its renegotiation request was refused. */ + ExpectIntEQ(wolfSSL_write(ssl_c, "hello", 5), 5); + for (i = 0; i < 10 && ret != 5; i++) + ret = wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)); + ExpectIntEQ(ret, 5); + ExpectIntEQ(XMEMCMP(readBuf, "hello", 5), 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + /* F-2126: DoTls13ClientHello must reject a second ClientHello whose * cipher suite does not match the server's HelloRetryRequest. The * client offers two suites in CH1 and only a different one in CH2. */ diff --git a/tests/api/test_tls_ext.h b/tests/api/test_tls_ext.h index e9d07d81ec..eda61aeb14 100644 --- a/tests/api/test_tls_ext.h +++ b/tests/api/test_tls_ext.h @@ -27,6 +27,8 @@ int test_tls_ems_resumption_downgrade(void); int test_tls12_chacha20_poly1305_bad_tag(void); int test_tls13_null_cipher_bad_hmac(void); int test_scr_verify_data_mismatch(void); +int test_scr_no_renegotiation_option(void); +int test_helloRequest_no_renegotiation_option(void); int test_tls13_hrr_cipher_suite_mismatch(void); int test_tls13_ticket_age_out_of_window(void); int test_wolfSSL_DisableExtendedMasterSecret(void);