diff --git a/src/dtls13.c b/src/dtls13.c index 0de419def8..ad69f2612a 100644 --- a/src/dtls13.c +++ b/src/dtls13.c @@ -2225,6 +2225,7 @@ static int Dtls13InitChaChaCipher(RecordNumberCiphers* c, byte* key, ret = wc_Chacha_SetKey(c->chacha, key, keySize); if (ret != 0) { + ForceZero(c->chacha, sizeof(ChaCha)); XFREE(c->chacha, heap, DYNAMIC_TYPE_CIPHER); c->chacha = NULL; } diff --git a/src/internal.c b/src/internal.c index e1c3a4a14a..8b84e7f2b9 100644 --- a/src/internal.c +++ b/src/internal.c @@ -3343,6 +3343,10 @@ void FreeCiphers(WOLFSSL* ssl) ssl->dtlsRecordNumberDecrypt.aes = NULL; #endif /* BUILD_AES */ #ifdef HAVE_CHACHA + if (ssl->dtlsRecordNumberEncrypt.chacha != NULL) + ForceZero(ssl->dtlsRecordNumberEncrypt.chacha, sizeof(ChaCha)); + if (ssl->dtlsRecordNumberDecrypt.chacha != NULL) + ForceZero(ssl->dtlsRecordNumberDecrypt.chacha, sizeof(ChaCha)); XFREE(ssl->dtlsRecordNumberEncrypt.chacha, ssl->heap, DYNAMIC_TYPE_CIPHER); XFREE(ssl->dtlsRecordNumberDecrypt.chacha, ssl->heap, DYNAMIC_TYPE_CIPHER); ssl->dtlsRecordNumberEncrypt.chacha = NULL; @@ -18049,6 +18053,17 @@ 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) { + int ret; + WOLFSSL_MSG("Rejecting HelloRequest: WOLFSSL_OP_NO_RENEGOTIATION"); + ret = SendAlert(ssl, alert_warning, no_renegotiation); + WOLFSSL_LEAVE("DoHelloRequest", ret); + WOLFSSL_END(WC_FUNC_HELLO_REQUEST_DO); + return ret; + } ssl->secure_renegotiation->startScr = 1; WOLFSSL_LEAVE("DoHelloRequest", 0); WOLFSSL_END(WC_FUNC_HELLO_REQUEST_DO); @@ -18781,6 +18796,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; @@ -18789,6 +18815,8 @@ int DoHandShakeMsgType(WOLFSSL* ssl, byte* input, word32* inOutIdx, ssl->options.acceptState = ACCEPT_FIRST_REPLY_DONE; ssl->options.handShakeState = NULL_STATE; ssl->secure_renegotiation->cache_status = SCR_CACHE_NEEDED; + /* Reset for the renegotiation_info presence check below. */ + ssl->secure_renegotiation->renegInfoSeen = 0; ret = InitHandshakeHashes(ssl); if (ret != 0) @@ -22487,6 +22515,32 @@ static void LogAlert(int type) } /* process alert, return level */ +#ifndef NO_SESSION_CACHE +/* RFC 5246 Section 7.2.2: a TLS 1.2 session whose connection is terminated by a + * fatal alert MUST be invalidated so it cannot be resumed. (TLS 1.3 RFC 8446 + * Section 6.2 only requires closing the connection, but evicting here too is + * sound defense-in-depth.) Evict the cached session (which also drops any + * associated ticket). Acts on an established connection or an in-progress + * resumption - both reference a cached session; a brand-new full handshake has + * no cached session to remove. */ +static void InvalidateSessionOnFatalAlert(WOLFSSL* ssl) +{ + if (ssl == NULL || ssl->ctx == NULL || ssl->session == NULL) + return; + if (!ssl->options.handShakeDone && !ssl->options.resuming) + return; + /* Don't evict on an unauthenticated record: a TLS 1.3 plaintext alert + * received under encryption (current record not decrypted) is rejected (or + * ignored) by DoAlert, and the teardown alert routes back here. RFC 8446 + * 6.2 doesn't require TLS 1.3 eviction; TLS 1.2 alerts are plaintext so are + * unaffected. */ + if (IsAtLeastTLSv1_3(ssl->version) && IsEncryptionOn(ssl, 0) && + !ssl->keys.decryptedCur) + return; + (void)wolfSSL_SSL_CTX_remove_session(ssl->ctx, ssl->session); +} +#endif /* !NO_SESSION_CACHE */ + static int DoAlert(WOLFSSL* ssl, byte* input, word32* inOutIdx, int* type) { byte level; @@ -22593,6 +22647,15 @@ static int DoAlert(WOLFSSL* ssl, byte* input, word32* inOutIdx, int* type) */ WOLFSSL_ERROR(*type); } +#ifndef NO_SESSION_CACHE + /* Validated fatal alert: invalidate the session so it can't be resumed + * (RFC 5246 7.2.2; in TLS 1.3 all error alerts are fatal, RFC 8446 + * 6.2). */ + if (*type != close_notify && + (level == alert_fatal || + (IsAtLeastTLSv1_3(ssl->version) && *type != user_canceled))) + InvalidateSessionOnFatalAlert(ssl); +#endif } return level; } @@ -23097,6 +23160,52 @@ static void DropAndRestartProcessReply(WOLFSSL* ssl) #endif /* WOLFSSL_DTLS_DROP_STATS */ } #endif /* WOLFSSL_DTLS */ + +#ifndef WOLFSSL_NO_TLS12 +/* On a confirmed TLS 1.2 / DTLS 1.2 client resumption, check the abbreviated + * ServerHello's EMS state (RFC 7627 5.3) and cipher suite (RFC 5246 7.4.1.3) + * match the resumed session. Called once resumption is confirmed - at a renewed + * NewSessionTicket (before SetupSession refreshes the cached values) or the + * server ChangeCipherSpec. Deferred from ServerHello because a declined ticket + * (RFC 5077 3.4) falls back to a full handshake that must not be rejected. + * Returns 0 if consistent, else sends a fatal alert and returns an error. */ +static int CheckResumptionConsistency(WOLFSSL* ssl) +{ + if (ssl->session == NULL) /* nothing to compare against */ + return 0; + /* EMS must match (RFC 7627 5.3); skip EAP-FAST (session-secret callback). */ + if ( +#ifdef HAVE_SECRET_CALLBACK + !(ssl->sessionSecretCb != NULL +#ifdef HAVE_SESSION_TICKET + && ssl->session->ticketLen > 0 +#endif + ) && +#endif + ssl->session->haveEMS != ssl->options.haveEMS) { + WOLFSSL_MSG("Resumed session EMS state does not match " + "ServerHello EMS state"); + SendAlert(ssl, alert_fatal, handshake_failure); + WOLFSSL_ERROR_VERBOSE(EXT_MASTER_SECRET_NEEDED_E); + return EXT_MASTER_SECRET_NEEDED_E; + } +#ifndef NO_RESUME_SUITE_CHECK + /* Suite must match (RFC 5246 7.4.1.3), tickets included. Skip when no suite + * was retained (both zero = TLS_NULL_WITH_NULL_NULL, e.g. EAP-FAST PAC). */ + if ((ssl->session->cipherSuite0 != 0 || ssl->session->cipherSuite != 0) && + (ssl->options.cipherSuite0 != ssl->session->cipherSuite0 || + ssl->options.cipherSuite != ssl->session->cipherSuite)) { + WOLFSSL_MSG("Resumed session cipher suite does not match " + "ServerHello cipher suite"); + SendAlert(ssl, alert_fatal, illegal_parameter); + WOLFSSL_ERROR_VERBOSE(MATCH_SUITE_ERROR); + return MATCH_SUITE_ERROR; + } +#endif /* NO_RESUME_SUITE_CHECK */ + return 0; +} +#endif /* !WOLFSSL_NO_TLS12 */ + /* Process input requests. Return 0 is done, 1 is call again to complete, and negative number is error. If allowSocketErr is set, SOCKET_ERROR_E in ssl->error will be whitelisted. This is useful when the connection has been @@ -23211,6 +23320,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 @@ -23926,6 +24036,15 @@ default: } } + /* Server CCS confirms the abbreviated handshake: validate + * the resumed session before installing keys. */ + if (ssl->options.side == WOLFSSL_CLIENT_END && + ssl->options.resuming) { + ret = CheckResumptionConsistency(ssl); + if (ret != 0) + return ret; + } + ssl->keys.encryptionOn = 1; /* setup decrypt keys for following messages */ @@ -24660,6 +24779,19 @@ int BuildMessage(WOLFSSL* ssl, byte* output, int outSz, const byte* input, #endif #ifndef WOLFSSL_NO_TLS12 + /* RFC 5246 6.1: sequence numbers MUST NOT wrap. GetSEQIncrement post- + * increments, so refuse at hi == lo == 0xFFFFFFFF (2^64-1): that last legal + * value is deliberately sacrificed to avoid wrapping to 0 and reusing + * sequence number 0. The caller must renegotiate or close. DTLS sequence + * numbers are epoch-scoped and handled elsewhere. */ + if (!sizeOnly && !ssl->options.dtls && + ssl->keys.sequence_number_hi == 0xFFFFFFFFU && + ssl->keys.sequence_number_lo == 0xFFFFFFFFU) { + WOLFSSL_MSG("TLS write sequence number would wrap"); + WOLFSSL_ERROR_VERBOSE(SEQUENCE_NUMBER_E); + return SEQUENCE_NUMBER_E; + } + #ifdef WOLFSSL_ASYNC_CRYPT ret = WC_NO_PENDING_E; if (asyncOkay) { @@ -27552,6 +27684,17 @@ int SendAlert(WOLFSSL* ssl, int severity, int type) return BAD_FUNC_ARG; } + /* InvalidateSessionOnFatalAlert() is defined in the !NO_TLS section, so the + * guard here must match (with NO_TLS there are no TLS sessions to evict). */ +#if !defined(NO_SESSION_CACHE) && !defined(NO_TLS) + /* RFC 5246 Section 7.2.2: a fatal alert terminates the connection; + * invalidate the established session so it cannot be resumed. Do this as + * soon as the fatal alert is generated, before the pendingAlert/backpressure + * handling below which can return early without sending the alert now. */ + if (severity == alert_fatal) + InvalidateSessionOnFatalAlert(ssl); +#endif + if (ssl->pendingAlert.level != alert_none) { ret = RetrySendAlert(ssl); if (ret != 0) { @@ -28234,6 +28377,9 @@ const char* wolfSSL_ERR_reason_error_string(unsigned long e) case ECH_REQUIRED_E: return "ECH offered but rejected by server"; + + case SEQUENCE_NUMBER_E: + return "Record sequence number would wrap"; } return "unknown error number"; @@ -32403,6 +32549,9 @@ static void MakePSKPreMasterSecret(Arrays* arrays, byte use_psk_key) } else { if (DSH_CheckSessionId(ssl)) { + /* EMS/suite consistency is checked once resumption is confirmed + * (CheckResumptionConsistency), not here: a ticket the server + * declines (RFC 5077 3.4) must fall back to a full handshake. */ if (SetCipherSpecs(ssl) == 0) { if (!HaveUniqueSessionObj(ssl)) { WOLFSSL_MSG("Unable to have unique session object"); @@ -35668,6 +35817,15 @@ static int DoSessionTicket(WOLFSSL* ssl, const byte* input, word32* inOutIdx, return SESSION_TICKET_EXPECT_E; } + /* A renewed ticket while resuming confirms resumption; check before the + * SetupSession() below refreshes the cached suite/EMS and masks a downgrade. + * (The ChangeCipherSpec check covers the no-renewal case.) */ + if (ssl->options.resuming) { + ret = CheckResumptionConsistency(ssl); + if (ret != 0) + return ret; + } + if (OPAQUE32_LEN > size) return BUFFER_ERROR; @@ -38793,6 +38951,17 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) 0) { TLSX* extension; +#ifdef HAVE_SECURE_RENEGOTIATION + /* SCSV not allowed on a renegotiation ClientHello (RFC 5746 3.5). */ + if (ssl->secure_renegotiation && + ssl->secure_renegotiation->enabled && + ssl->secure_renegotiation->verifySet) { + WOLFSSL_MSG("SCSV received on renegotiation ClientHello"); + SendAlert(ssl, alert_fatal, handshake_failure); + ret = SECURE_RENEGOTIATION_E; + goto out; + } +#endif /* check for TLS_EMPTY_RENEGOTIATION_INFO_SCSV suite */ ret = TLSX_AddEmptyRenegotiationInfo(&ssl->extensions, ssl->heap); if (ret != WOLFSSL_SUCCESS) { @@ -39045,6 +39214,19 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) *inOutIdx = begin + helloSz; /* skip extensions */ } +#ifdef HAVE_SECURE_RENEGOTIATION + /* renegotiation_info MUST be present on a renegotiation (RFC 5746 3.7). */ + if (ssl->secure_renegotiation && + ssl->secure_renegotiation->enabled && + ssl->secure_renegotiation->verifySet && + !ssl->secure_renegotiation->renegInfoSeen) { + WOLFSSL_MSG("Renegotiation ClientHello missing renegotiation_info"); + SendAlert(ssl, alert_fatal, handshake_failure); + ret = SECURE_RENEGOTIATION_E; + goto out; + } +#endif /* HAVE_SECURE_RENEGOTIATION */ + #ifdef WOLFSSL_DTLS_CID if (ssl->options.useDtlsCID) DtlsCIDOnExtensionsParsed(ssl); diff --git a/src/tls.c b/src/tls.c index 2fa6abd611..501e57ad28 100644 --- a/src/tls.c +++ b/src/tls.c @@ -6277,6 +6277,9 @@ static int TLSX_SecureRenegotiation_Parse(WOLFSSL* ssl, const byte* input, if (ret == WOLFSSL_SUCCESS) ret = 0; } + /* renegotiation_info seen (checked by DoClientHello, RFC 5746 3.7) */ + if (ssl->secure_renegotiation != NULL) + ssl->secure_renegotiation->renegInfoSeen = 1; if (ret != 0 && ret != WC_NO_ERR_TRACE(SECURE_RENEGOTIATION_E)) { } else if (ssl->secure_renegotiation == NULL) { diff --git a/src/tls13.c b/src/tls13.c index d2e9888a13..18ca7c5dcd 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -6030,7 +6030,7 @@ static int DoTls13EncryptedExtensions(WOLFSSL* ssl, const byte* input, i += OPAQUE16_LEN; /* Extension data. */ - if (i - begin + totalExtSz > totalSz) + if (i - begin + totalExtSz != totalSz) return BUFFER_ERROR; if ((ret = TLSX_Parse(ssl, input + i, totalExtSz, encrypted_extensions, NULL))) { @@ -6168,6 +6168,10 @@ static int DoTls13CertificateRequest(WOLFSSL* ssl, const byte* input, } *inOutIdx += len; + /* No trailing bytes allowed (RFC 8446 4.3.2). */ + if ((*inOutIdx - begin) != size) + return BUFFER_ERROR; + /* RFC 8446 Section 4.3.2: the signature_algorithms extension MUST be * present in a CertificateRequest. */ if (peerSuites.hashSigAlgoSz == 0) { @@ -6175,7 +6179,6 @@ static int DoTls13CertificateRequest(WOLFSSL* ssl, const byte* input, WOLFSSL_ERROR_VERBOSE(INVALID_PARAMETER); return INVALID_PARAMETER; } - #ifdef WOLFSSL_CERT_SETUP_CB if ((ret = CertSetupCbWrapper(ssl)) != 0) return ret; diff --git a/tests/api.c b/tests/api.c index f67b99901e..e81fee2b5d 100644 --- a/tests/api.c +++ b/tests/api.c @@ -35193,9 +35193,12 @@ TEST_CASE testCases[] = { #endif TEST_DECL(test_tls_ems_downgrade), TEST_DECL(test_tls_ems_resumption_downgrade), + TEST_DECL(test_tls_ems_resumption_server_downgrade), 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_ossl_rsa.c b/tests/api/test_ossl_rsa.c index 26cf360105..dc0cee665b 100644 --- a/tests/api/test_ossl_rsa.c +++ b/tests/api/test_ossl_rsa.c @@ -520,6 +520,24 @@ int test_wolfSSL_RSA_padding_add_PKCS1_PSS(void) ExpectIntEQ(RSA_verify_PKCS1_PSS(rsa, mHash, EVP_sha256(), em, RSA_PSS_SALTLEN_DIGEST), 1); + /* Negative test: a tampered PSS encoding must be rejected. Flip a byte in + * the encoded message, confirm failure, then restore and re-verify. */ + em[0] ^= 0xFFU; + ExpectIntEQ(RSA_verify_PKCS1_PSS(rsa, mHash, EVP_sha256(), em, + RSA_PSS_SALTLEN_DIGEST), 0); + em[0] ^= 0xFFU; + ExpectIntEQ(RSA_verify_PKCS1_PSS(rsa, mHash, EVP_sha256(), em, + RSA_PSS_SALTLEN_DIGEST), 1); + + /* Negative test: a tampered hash must be rejected by PSS verification. */ + { + unsigned char badHash[WC_SHA256_DIGEST_SIZE]; + XMEMCPY(badHash, mHash, sizeof(badHash)); + badHash[0] ^= 0xFFU; + ExpectIntEQ(RSA_verify_PKCS1_PSS(rsa, badHash, EVP_sha256(), em, + RSA_PSS_SALTLEN_DIGEST), 0); + } + ExpectIntEQ(RSA_padding_add_PKCS1_PSS(rsa, em, mHash, EVP_sha256(), RSA_PSS_SALTLEN_MAX_SIGN), 1); ExpectIntEQ(RSA_verify_PKCS1_PSS(rsa, mHash, EVP_sha256(), em, @@ -696,8 +714,8 @@ int test_wolfSSL_RSA_verify(void) RSA *pubKey = NULL; X509 *cert = NULL; const char *text = "Hello wolfSSL !"; - unsigned char hash[SHA256_DIGEST_LENGTH]; - unsigned char signature[2048/8]; + unsigned char hash[SHA256_DIGEST_LENGTH] = {0}; + unsigned char signature[2048/8] = {0}; unsigned int signatureLength; byte *buf = NULL; BIO *bio = NULL; @@ -747,6 +765,24 @@ int test_wolfSSL_RSA_verify(void) ExpectIntEQ(RSA_verify(NID_sha256, hash, SHA256_DIGEST_LENGTH, signature, signatureLength, pubKey), SSL_SUCCESS); + /* Negative test: a tampered signature must be rejected. Flip a byte in the + * signature, confirm verification fails, then restore it. */ + signature[0] ^= 0xFFU; + ExpectIntEQ(RSA_verify(NID_sha256, hash, SHA256_DIGEST_LENGTH, signature, + signatureLength, pubKey), WC_NO_ERR_TRACE(WOLFSSL_FAILURE)); + signature[0] ^= 0xFFU; + /* Sanity: the restored signature verifies again. */ + ExpectIntEQ(RSA_verify(NID_sha256, hash, SHA256_DIGEST_LENGTH, signature, + signatureLength, pubKey), SSL_SUCCESS); + + /* Negative test: a tampered hash must be rejected (the encoded comparison + * string differs). Flip a byte in the hash, confirm failure, then + * restore it. */ + hash[0] ^= 0xFFU; + ExpectIntEQ(RSA_verify(NID_sha256, hash, SHA256_DIGEST_LENGTH, signature, + signatureLength, pubKey), WC_NO_ERR_TRACE(WOLFSSL_FAILURE)); + hash[0] ^= 0xFFU; + ExpectIntEQ(RSA_verify(NID_sha256, NULL, SHA256_DIGEST_LENGTH, NULL, signatureLength, NULL), WC_NO_ERR_TRACE(WOLFSSL_FAILURE)); ExpectIntEQ(RSA_verify(NID_sha256, NULL, SHA256_DIGEST_LENGTH, signature, diff --git a/tests/api/test_tls.c b/tests/api/test_tls.c index a0635675c0..d7380f7fd7 100644 --- a/tests/api/test_tls.c +++ b/tests/api/test_tls.c @@ -1083,6 +1083,168 @@ int test_tls12_etm_failed_resumption(void) return EXPECT_RESULT(); } +/* RFC 5246 7.4.1.3: a server resuming a TLS 1.2 session ticket MUST reuse the + * session's cipher suite. The ticket is opaque to the client, so the client + * cannot rely on the suite being bound inside it and must compare the + * ServerHello suite against the suite retained in the cached session (F-5811 + * does this for session-ID resumption; it must hold for tickets too). This + * test establishes a ticket-based session, rewrites the cached session's suite + * to emulate a server that resumes the ticket under a different suite, and + * asserts the client aborts the resumption with MATCH_SUITE_ERROR. The same + * server CTX is reused for the second handshake so its ticket key persists. */ +int test_tls12_resume_ticket_wrong_suite(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(WOLFSSL_NO_TLS12) && defined(HAVE_SESSION_TICKET) && \ + !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ + !defined(NO_RESUME_SUITE_CHECK) && !defined(NO_RSA) && defined(HAVE_ECC) && \ + !defined(NO_AES) && defined(HAVE_AESGCM) && !defined(NO_SHA256) && \ + defined(BUILD_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) + const char* suite = "ECDHE-RSA-AES128-GCM-SHA256"; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL *ssl_c2 = NULL, *ssl_s2 = NULL; + WOLFSSL *ssl_c3 = NULL, *ssl_s3 = NULL; + WOLFSSL_SESSION *sess = NULL; + struct test_memio_ctx test_ctx; + struct test_memio_ctx test_ctx2; + struct test_memio_ctx test_ctx3; + int ret; + + /* First handshake: establish a ticket-based TLS 1.2 session. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + 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_set_cipher_list(ssl_c, suite), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_cipher_list(ssl_s, suite), WOLFSSL_SUCCESS); + /* Opt the client into TLS 1.2 session tickets so the server issues one. */ + ExpectIntEQ(wolfSSL_UseSessionTicket(ssl_c), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectNotNull(sess = wolfSSL_get1_session(ssl_c)); + /* Must be a ticket session to exercise the ticket path. */ + ExpectIntGT(sess->ticketLen, 0); + + /* Case 1 - downgrading server: change the cached suite so it no longer + * matches the suite the server reuses from the ticket, but keep it + * non-zero so it still counts as a retained suite. The value only feeds + * the comparison (the real keys come from the ServerHello suite), so + * flipping it is sufficient and safe. The client must reject the + * resumption against the same server CTX (ticket key persists). */ + if (sess != NULL) + sess->cipherSuite = (byte)(sess->cipherSuite ^ 0xFF); + + XMEMSET(&test_ctx2, 0, sizeof(test_ctx2)); + ExpectIntEQ(test_memio_setup(&test_ctx2, &ctx_c, &ctx_s, &ssl_c2, &ssl_s2, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_set_cipher_list(ssl_c2, suite), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_cipher_list(ssl_s2, suite), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseSessionTicket(ssl_c2), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_session(ssl_c2, sess), WOLFSSL_SUCCESS); + ret = test_memio_do_handshake(ssl_c2, ssl_s2, 10, NULL); + ExpectIntNE(ret, 0); + ExpectIntEQ(ssl_c2->error, WC_NO_ERR_TRACE(MATCH_SUITE_ERROR)); + + /* Case 2 - session that retained no suite (cipherSuite0/cipherSuite both + * zero), as for an EAP-FAST PAC whose keys come from the session-secret + * callback. There is nothing to compare against, so the check must be + * skipped and the resumption must still succeed. */ + if (sess != NULL) { + sess->cipherSuite0 = 0; + sess->cipherSuite = 0; + } + + XMEMSET(&test_ctx3, 0, sizeof(test_ctx3)); + ExpectIntEQ(test_memio_setup(&test_ctx3, &ctx_c, &ctx_s, &ssl_c3, &ssl_s3, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_set_cipher_list(ssl_c3, suite), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_cipher_list(ssl_s3, suite), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseSessionTicket(ssl_c3), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_session(ssl_c3, sess), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c3, ssl_s3, 10, NULL), 0); + ExpectIntEQ(wolfSSL_session_reused(ssl_c3), 1); + + wolfSSL_SESSION_free(sess); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_free(ssl_c2); + wolfSSL_free(ssl_s2); + wolfSSL_free(ssl_c3); + wolfSSL_free(ssl_s3); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* A ticket the server can't honor must fall back to a full handshake (RFC 5077 + * 3.4), even under a different suite than the cached ticket session - the + * F-5811 suite check must not abort it. The second handshake uses a fresh + * server CTX (new ticket key -> decline) offering only suite B while the client + * offers B and the session's suite A. */ +int test_tls12_resume_ticket_decline_fallback(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(WOLFSSL_NO_TLS12) && defined(HAVE_SESSION_TICKET) && \ + !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && !defined(NO_SESSION_CACHE) && \ + !defined(NO_RESUME_SUITE_CHECK) && !defined(NO_RSA) && defined(HAVE_ECC) && \ + !defined(NO_AES) && defined(HAVE_AESGCM) && !defined(NO_SHA256) && \ + defined(BUILD_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) && \ + defined(BUILD_TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) + const char* suiteA = "ECDHE-RSA-AES128-GCM-SHA256"; + const char* suiteB = "ECDHE-RSA-AES256-GCM-SHA384"; + const char* suiteBA = + "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256"; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL, *ctx_s2 = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL *ssl_c2 = NULL, *ssl_s2 = NULL; + WOLFSSL_SESSION *sess = NULL; + struct test_memio_ctx test_ctx; + struct test_memio_ctx test_ctx2; + + /* First handshake: establish a ticket-based TLS 1.2 session on suite A. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + 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_set_cipher_list(ssl_c, suiteA), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_cipher_list(ssl_s, suiteA), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseSessionTicket(ssl_c), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectNotNull(sess = wolfSSL_get1_session(ssl_c)); + ExpectIntGT(sess->ticketLen, 0); + + /* Second handshake: fresh server CTX (NULL ctx_s2 -> new ticket key) so the + * ticket is declined and the server does a full handshake on suite B. */ + XMEMSET(&test_ctx2, 0, sizeof(test_ctx2)); + ExpectIntEQ(test_memio_setup(&test_ctx2, &ctx_c, &ctx_s2, &ssl_c2, &ssl_s2, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_set_cipher_list(ssl_c2, suiteBA), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_cipher_list(ssl_s2, suiteB), WOLFSSL_SUCCESS); + /* Session cache off so the declining server emits an empty session ID and + * the client takes the graceful full-handshake fallback (set on the SSL as + * the flag is copied from the CTX at wolfSSL_new() time). */ + if (ssl_s2 != NULL) + ssl_s2->options.sessionCacheOff = 1; + ExpectIntEQ(wolfSSL_UseSessionTicket(ssl_c2), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_session(ssl_c2, sess), WOLFSSL_SUCCESS); + /* Fallback must succeed (no MATCH_SUITE_ERROR), not resume, and use B. */ + ExpectIntEQ(test_memio_do_handshake(ssl_c2, ssl_s2, 10, NULL), 0); + ExpectIntEQ(wolfSSL_session_reused(ssl_c2), 0); + ExpectStrEQ(wolfSSL_get_cipher_name(ssl_c2), suiteB); + + wolfSSL_SESSION_free(sess); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_free(ssl_c2); + wolfSSL_free(ssl_s2); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + wolfSSL_CTX_free(ctx_s2); +#endif + return EXPECT_RESULT(); +} + /* wolfSSL_set_session() must reject a TLS 1.2 session when minDowngrade is * set to TLS 1.3. */ int test_tls_set_session_min_downgrade(void) diff --git a/tests/api/test_tls.h b/tests/api/test_tls.h index 79d6f18dc8..c5a0315345 100644 --- a/tests/api/test_tls.h +++ b/tests/api/test_tls.h @@ -35,6 +35,8 @@ int test_tls12_no_null_compression(void); int test_tls12_ec_point_formats_no_uncompressed(void); int test_tls12_ec_point_formats_no_uncompressed_non_ecc(void); int test_tls12_etm_failed_resumption(void); +int test_tls12_resume_ticket_wrong_suite(void); +int test_tls12_resume_ticket_decline_fallback(void); int test_tls_set_session_min_downgrade(void); int test_tls12_session_id_resumption_sni_mismatch(void); int test_tls13_session_resumption_sni_mismatch(void); @@ -67,6 +69,8 @@ int test_wolfSSL_get_shared_ciphers(void); TEST_DECL_GROUP("tls", \ test_tls12_ec_point_formats_no_uncompressed_non_ecc), \ TEST_DECL_GROUP("tls", test_tls12_etm_failed_resumption), \ + TEST_DECL_GROUP("tls", test_tls12_resume_ticket_wrong_suite), \ + TEST_DECL_GROUP("tls", test_tls12_resume_ticket_decline_fallback), \ TEST_DECL_GROUP("tls", test_tls_set_session_min_downgrade), \ TEST_DECL_GROUP("tls", test_tls12_session_id_resumption_sni_mismatch), \ TEST_DECL_GROUP("tls", test_tls13_session_resumption_sni_mismatch), \ diff --git a/tests/api/test_tls_ext.c b/tests/api/test_tls_ext.c index 781042a4ce..40ce46eca3 100644 --- a/tests/api/test_tls_ext.c +++ b/tests/api/test_tls_ext.c @@ -153,6 +153,166 @@ int test_tls_ems_resumption_downgrade(void) } +#if !defined(WOLFSSL_NO_TLS12) && defined(HAVE_EXTENDED_MASTER) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_SESSION_CACHE) +/* Remove the extended_master_secret extension from the ServerHello record at + * the head of the server-to-client memio buffer, patching up the record, + * handshake and extension-block lengths so the message still parses. + * Returns 0 on success. */ +static int StripEmsFromServerHello(struct test_memio_ctx* test_ctx) +{ + byte* buf = test_ctx->c_buff; + int len = test_ctx->c_len; + int recLen; + int hsLen; + int extsLenIdx; + int extsLen; + int idx; + int extsEnd; + + /* Record header: type(1) version(2) length(2) */ + if (len < 5 || buf[0] != handshake) + return -1; + recLen = (buf[3] << 8) | buf[4]; + if (5 + recLen > len) + return -1; + /* Handshake header: type(1) length(3) */ + if (recLen < HANDSHAKE_HEADER_SZ || buf[5] != server_hello) + return -1; + hsLen = (buf[6] << 16) | (buf[7] << 8) | buf[8]; + /* Skip version(2), random(32) to the session ID length, then skip the + * session ID, cipher suite(2) and compression(1) to the extensions + * length. */ + extsLenIdx = 5 + HANDSHAKE_HEADER_SZ + OPAQUE16_LEN + RAN_LEN + + OPAQUE8_LEN + buf[5 + HANDSHAKE_HEADER_SZ + OPAQUE16_LEN + + RAN_LEN] + + OPAQUE16_LEN + OPAQUE8_LEN; + if (extsLenIdx + OPAQUE16_LEN > 5 + recLen) + return -1; + extsLen = (buf[extsLenIdx] << 8) | buf[extsLenIdx + 1]; + idx = extsLenIdx + OPAQUE16_LEN; + extsEnd = idx + extsLen; + if (extsEnd > 5 + recLen) + return -1; + while (idx + 4 <= extsEnd) { + int extType = (buf[idx] << 8) | buf[idx + 1]; + int extLen = (buf[idx + 2] << 8) | buf[idx + 3]; + int rmLen = 4 + extLen; + + if (idx + rmLen > extsEnd) + return -1; + if (extType == HELLO_EXT_EXTMS) { + XMEMMOVE(buf + idx, buf + idx + rmLen, + (size_t)(len - idx - rmLen)); + recLen -= rmLen; + hsLen -= rmLen; + extsLen -= rmLen; + buf[3] = (byte)(recLen >> 8); + buf[4] = (byte)recLen; + buf[6] = (byte)(hsLen >> 16); + buf[7] = (byte)(hsLen >> 8); + buf[8] = (byte)hsLen; + buf[extsLenIdx] = (byte)(extsLen >> 8); + buf[extsLenIdx + 1] = (byte)extsLen; + test_ctx->c_len -= rmLen; + /* The ServerHello record sits wholly inside the first buffered + * message. */ + test_ctx->c_msg_sizes[0] -= rmLen; + return 0; + } + idx += rmLen; + } + return -1; +} + +/* Full handshake with EMS, then resume and strip the EMS extension from the + * ServerHello in transit. The client must catch the downgrade and abort + * (RFC 7627 Section 5.3). useTicket selects session-ticket resumption + * instead of session-ID resumption. */ +static int test_tls_ems_resumption_server_downgrade_ex(int useTicket) +{ + EXPECT_DECLS; + 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_SESSION *session = NULL; + +#ifndef HAVE_SESSION_TICKET + (void)useTicket; +#endif + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); +#ifdef HAVE_SESSION_TICKET + if (useTicket) + ExpectIntEQ(wolfSSL_UseSessionTicket(ssl_c), WOLFSSL_SUCCESS); +#endif + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + ExpectNotNull(session = wolfSSL_get1_session(ssl_c)); + ExpectTrue(session->haveEMS); +#ifdef HAVE_SESSION_TICKET + if (useTicket) + ExpectIntGT(session->ticketLen, 0); +#endif + + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_free(ssl_s); + ssl_s = NULL; + test_memio_clear_buffer(&test_ctx, 0); + test_memio_clear_buffer(&test_ctx, 1); + + 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_set_session(ssl_c, session), WOLFSSL_SUCCESS); + + /* ClientHello */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + /* Server flight accepting the resumption */ + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + /* Drop EMS from the ServerHello to simulate a downgrading server. */ + ExpectIntEQ(StripEmsFromServerHello(&test_ctx), 0); + /* The client must refuse to resume without EMS. */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), + WC_NO_ERR_TRACE(EXT_MASTER_SECRET_NEEDED_E)); + + wolfSSL_SESSION_free(session); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif + +/* F-5807: a server that resumes an EMS session but omits the + * extended_master_secret extension from its ServerHello must be rejected by + * the client with EXT_MASTER_SECRET_NEEDED_E (RFC 7627 Section 5.3), on both + * session-ID and session-ticket resumption. */ +int test_tls_ems_resumption_server_downgrade(void) +{ + EXPECT_DECLS; +#if !defined(WOLFSSL_NO_TLS12) && defined(HAVE_EXTENDED_MASTER) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_SESSION_CACHE) + ExpectIntEQ(test_tls_ems_resumption_server_downgrade_ex(0), TEST_SUCCESS); +#if defined(HAVE_SESSION_TICKET) && !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) + ExpectIntEQ(test_tls_ems_resumption_server_downgrade_ex(1), TEST_SUCCESS); +#endif +#endif + return EXPECT_RESULT(); +} + + #if !defined(WOLFSSL_NO_TLS12) && \ defined(BUILD_TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256) && \ defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) @@ -350,6 +510,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 00499de3b0..90ccdf17c9 100644 --- a/tests/api/test_tls_ext.h +++ b/tests/api/test_tls_ext.h @@ -24,9 +24,12 @@ int test_tls_ems_downgrade(void); int test_tls_ems_resumption_downgrade(void); +int test_tls_ems_resumption_server_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); diff --git a/wolfssl/error-ssl.h b/wolfssl/error-ssl.h index b98a52d40c..379008afb0 100644 --- a/wolfssl/error-ssl.h +++ b/wolfssl/error-ssl.h @@ -244,7 +244,9 @@ enum wolfSSL_ErrorCodes { ECH_REQUIRED_E = -519, /* ECH offered but rejected by server */ - WOLFSSL_LAST_E = -519 + SEQUENCE_NUMBER_E = -520, /* Record sequence number would wrap */ + + WOLFSSL_LAST_E = -520 /* codes -1000 to -1999 are reserved for wolfCrypt. */ }; diff --git a/wolfssl/internal.h b/wolfssl/internal.h index b7969241f0..90856252b3 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -3503,13 +3503,16 @@ enum key_cache_state { /* Additional Connection State according to rfc5746 section 3.1 */ typedef struct SecureRenegotiation { - byte enabled; /* secure_renegotiation flag in rfc */ - byte verifySet; - byte startScr; /* server requested client to start scr */ + /* Single-bit flags grouped together so they pack into one storage unit. */ + WC_BITFIELD enabled:1; /* secure_renegotiation flag in rfc */ + WC_BITFIELD verifySet:1; + WC_BITFIELD startScr:1; /* server requested client to start scr */ + WC_BITFIELD renegInfoSeen:1; /* renegotiation_info ext seen this + * handshake (RFC 5746 3.7) */ + WC_BITFIELD subject_hash_set:1; /* if peer cert hash is set */ enum key_cache_state cache_status; /* track key cache state */ byte client_verify_data[TLS_FINISHED_SZ]; /* cached */ byte server_verify_data[TLS_FINISHED_SZ]; /* cached */ - byte subject_hash_set; /* if peer cert hash is set */ byte subject_hash[KEYID_SIZE]; /* peer cert hash */ Keys tmp_keys; /* can't overwrite real keys yet */ } SecureRenegotiation;