@@ -3343,6 +3343,10 @@ void FreeCiphers(WOLFSSL* ssl)
33433343 ssl->dtlsRecordNumberDecrypt.aes = NULL;
33443344#endif /* BUILD_AES */
33453345#ifdef HAVE_CHACHA
3346+ if (ssl->dtlsRecordNumberEncrypt.chacha != NULL)
3347+ ForceZero(ssl->dtlsRecordNumberEncrypt.chacha, sizeof(ChaCha));
3348+ if (ssl->dtlsRecordNumberDecrypt.chacha != NULL)
3349+ ForceZero(ssl->dtlsRecordNumberDecrypt.chacha, sizeof(ChaCha));
33463350 XFREE(ssl->dtlsRecordNumberEncrypt.chacha, ssl->heap, DYNAMIC_TYPE_CIPHER);
33473351 XFREE(ssl->dtlsRecordNumberDecrypt.chacha, ssl->heap, DYNAMIC_TYPE_CIPHER);
33483352 ssl->dtlsRecordNumberEncrypt.chacha = NULL;
@@ -18049,6 +18053,17 @@ static int DoHelloRequest(WOLFSSL* ssl, word32 size)
1804918053 }
1805018054#ifdef HAVE_SECURE_RENEGOTIATION
1805118055 else if (ssl->secure_renegotiation && ssl->secure_renegotiation->enabled) {
18056+ /* WOLFSSL_OP_NO_RENEGOTIATION: caller opted into rejecting
18057+ * peer-initiated renegotiation. Respond with a no_renegotiation
18058+ * warning alert instead of starting a secure renegotiation. */
18059+ if (ssl->options.mask & WOLFSSL_OP_NO_RENEGOTIATION) {
18060+ int ret;
18061+ WOLFSSL_MSG("Rejecting HelloRequest: WOLFSSL_OP_NO_RENEGOTIATION");
18062+ ret = SendAlert(ssl, alert_warning, no_renegotiation);
18063+ WOLFSSL_LEAVE("DoHelloRequest", ret);
18064+ WOLFSSL_END(WC_FUNC_HELLO_REQUEST_DO);
18065+ return ret;
18066+ }
1805218067 ssl->secure_renegotiation->startScr = 1;
1805318068 WOLFSSL_LEAVE("DoHelloRequest", 0);
1805418069 WOLFSSL_END(WC_FUNC_HELLO_REQUEST_DO);
@@ -18781,6 +18796,17 @@ int DoHandShakeMsgType(WOLFSSL* ssl, byte* input, word32* inOutIdx,
1878118796 ssl->secure_renegotiation &&
1878218797 ssl->secure_renegotiation->enabled)
1878318798 {
18799+ /* WOLFSSL_OP_NO_RENEGOTIATION: caller opted into rejecting
18800+ * peer-initiated renegotiation. RFC 5246 7.2.2: no_renegotiation is a
18801+ * warning-level alert, so refuse the renegotiation but keep the
18802+ * established connection rather than aborting it. Skip the ClientHello
18803+ * body and leave handshake state untouched, mirroring the client-side
18804+ * HelloRequest refusal in DoHelloRequest(). */
18805+ if (ssl->options.mask & WOLFSSL_OP_NO_RENEGOTIATION) {
18806+ WOLFSSL_MSG("Refusing renegotiation: WOLFSSL_OP_NO_RENEGOTIATION");
18807+ *inOutIdx = expectedIdx;
18808+ return SendAlert(ssl, alert_warning, no_renegotiation);
18809+ }
1878418810 WOLFSSL_MSG("Reset handshake state");
1878518811 XMEMSET(&ssl->msgsReceived, 0, sizeof(MsgsReceived));
1878618812 ssl->options.serverState = NULL_STATE;
@@ -18789,6 +18815,8 @@ int DoHandShakeMsgType(WOLFSSL* ssl, byte* input, word32* inOutIdx,
1878918815 ssl->options.acceptState = ACCEPT_FIRST_REPLY_DONE;
1879018816 ssl->options.handShakeState = NULL_STATE;
1879118817 ssl->secure_renegotiation->cache_status = SCR_CACHE_NEEDED;
18818+ /* Reset for the renegotiation_info presence check below. */
18819+ ssl->secure_renegotiation->renegInfoSeen = 0;
1879218820
1879318821 ret = InitHandshakeHashes(ssl);
1879418822 if (ret != 0)
@@ -22487,6 +22515,32 @@ static void LogAlert(int type)
2248722515}
2248822516
2248922517/* process alert, return level */
22518+ #ifndef NO_SESSION_CACHE
22519+ /* RFC 5246 Section 7.2.2: a TLS 1.2 session whose connection is terminated by a
22520+ * fatal alert MUST be invalidated so it cannot be resumed. (TLS 1.3 RFC 8446
22521+ * Section 6.2 only requires closing the connection, but evicting here too is
22522+ * sound defense-in-depth.) Evict the cached session (which also drops any
22523+ * associated ticket). Acts on an established connection or an in-progress
22524+ * resumption - both reference a cached session; a brand-new full handshake has
22525+ * no cached session to remove. */
22526+ static void InvalidateSessionOnFatalAlert(WOLFSSL* ssl)
22527+ {
22528+ if (ssl == NULL || ssl->ctx == NULL || ssl->session == NULL)
22529+ return;
22530+ if (!ssl->options.handShakeDone && !ssl->options.resuming)
22531+ return;
22532+ /* Don't evict on an unauthenticated record: a TLS 1.3 plaintext alert
22533+ * received under encryption (current record not decrypted) is rejected (or
22534+ * ignored) by DoAlert, and the teardown alert routes back here. RFC 8446
22535+ * 6.2 doesn't require TLS 1.3 eviction; TLS 1.2 alerts are plaintext so are
22536+ * unaffected. */
22537+ if (IsAtLeastTLSv1_3(ssl->version) && IsEncryptionOn(ssl, 0) &&
22538+ !ssl->keys.decryptedCur)
22539+ return;
22540+ (void)wolfSSL_SSL_CTX_remove_session(ssl->ctx, ssl->session);
22541+ }
22542+ #endif /* !NO_SESSION_CACHE */
22543+
2249022544static int DoAlert(WOLFSSL* ssl, byte* input, word32* inOutIdx, int* type)
2249122545{
2249222546 byte level;
@@ -22593,6 +22647,15 @@ static int DoAlert(WOLFSSL* ssl, byte* input, word32* inOutIdx, int* type)
2259322647 */
2259422648 WOLFSSL_ERROR(*type);
2259522649 }
22650+ #ifndef NO_SESSION_CACHE
22651+ /* Validated fatal alert: invalidate the session so it can't be resumed
22652+ * (RFC 5246 7.2.2; in TLS 1.3 all error alerts are fatal, RFC 8446
22653+ * 6.2). */
22654+ if (*type != close_notify &&
22655+ (level == alert_fatal ||
22656+ (IsAtLeastTLSv1_3(ssl->version) && *type != user_canceled)))
22657+ InvalidateSessionOnFatalAlert(ssl);
22658+ #endif
2259622659 }
2259722660 return level;
2259822661}
@@ -23097,6 +23160,52 @@ static void DropAndRestartProcessReply(WOLFSSL* ssl)
2309723160#endif /* WOLFSSL_DTLS_DROP_STATS */
2309823161}
2309923162#endif /* WOLFSSL_DTLS */
23163+
23164+ #ifndef WOLFSSL_NO_TLS12
23165+ /* On a confirmed TLS 1.2 / DTLS 1.2 client resumption, check the abbreviated
23166+ * ServerHello's EMS state (RFC 7627 5.3) and cipher suite (RFC 5246 7.4.1.3)
23167+ * match the resumed session. Called once resumption is confirmed - at a renewed
23168+ * NewSessionTicket (before SetupSession refreshes the cached values) or the
23169+ * server ChangeCipherSpec. Deferred from ServerHello because a declined ticket
23170+ * (RFC 5077 3.4) falls back to a full handshake that must not be rejected.
23171+ * Returns 0 if consistent, else sends a fatal alert and returns an error. */
23172+ static int CheckResumptionConsistency(WOLFSSL* ssl)
23173+ {
23174+ if (ssl->session == NULL) /* nothing to compare against */
23175+ return 0;
23176+ /* EMS must match (RFC 7627 5.3); skip EAP-FAST (session-secret callback). */
23177+ if (
23178+ #ifdef HAVE_SECRET_CALLBACK
23179+ !(ssl->sessionSecretCb != NULL
23180+ #ifdef HAVE_SESSION_TICKET
23181+ && ssl->session->ticketLen > 0
23182+ #endif
23183+ ) &&
23184+ #endif
23185+ ssl->session->haveEMS != ssl->options.haveEMS) {
23186+ WOLFSSL_MSG("Resumed session EMS state does not match "
23187+ "ServerHello EMS state");
23188+ SendAlert(ssl, alert_fatal, handshake_failure);
23189+ WOLFSSL_ERROR_VERBOSE(EXT_MASTER_SECRET_NEEDED_E);
23190+ return EXT_MASTER_SECRET_NEEDED_E;
23191+ }
23192+ #ifndef NO_RESUME_SUITE_CHECK
23193+ /* Suite must match (RFC 5246 7.4.1.3), tickets included. Skip when no suite
23194+ * was retained (both zero = TLS_NULL_WITH_NULL_NULL, e.g. EAP-FAST PAC). */
23195+ if ((ssl->session->cipherSuite0 != 0 || ssl->session->cipherSuite != 0) &&
23196+ (ssl->options.cipherSuite0 != ssl->session->cipherSuite0 ||
23197+ ssl->options.cipherSuite != ssl->session->cipherSuite)) {
23198+ WOLFSSL_MSG("Resumed session cipher suite does not match "
23199+ "ServerHello cipher suite");
23200+ SendAlert(ssl, alert_fatal, illegal_parameter);
23201+ WOLFSSL_ERROR_VERBOSE(MATCH_SUITE_ERROR);
23202+ return MATCH_SUITE_ERROR;
23203+ }
23204+ #endif /* NO_RESUME_SUITE_CHECK */
23205+ return 0;
23206+ }
23207+ #endif /* !WOLFSSL_NO_TLS12 */
23208+
2310023209/* Process input requests. Return 0 is done, 1 is call again to complete, and
2310123210 negative number is error. If allowSocketErr is set, SOCKET_ERROR_E in
2310223211 ssl->error will be whitelisted. This is useful when the connection has been
@@ -23211,6 +23320,7 @@ static int DoProcessReplyEx(WOLFSSL* ssl, int allowSocketErr)
2321123320 /* see if sending SSLv2 client hello */
2321223321 if ( ssl->options.side == WOLFSSL_SERVER_END &&
2321323322 ssl->options.clientState == NULL_STATE &&
23323+ !ssl->options.handShakeDone &&
2321423324 ssl->buffers.inputBuffer.buffer[ssl->buffers.inputBuffer.idx]
2321523325 != handshake &&
2321623326 /* change_cipher_spec here is an error but we want to handle
@@ -23926,6 +24036,15 @@ static int DoProcessReplyEx(WOLFSSL* ssl, int allowSocketErr)
2392624036 }
2392724037 }
2392824038
24039+ /* Server CCS confirms the abbreviated handshake: validate
24040+ * the resumed session before installing keys. */
24041+ if (ssl->options.side == WOLFSSL_CLIENT_END &&
24042+ ssl->options.resuming) {
24043+ ret = CheckResumptionConsistency(ssl);
24044+ if (ret != 0)
24045+ return ret;
24046+ }
24047+
2392924048 ssl->keys.encryptionOn = 1;
2393024049
2393124050 /* setup decrypt keys for following messages */
@@ -24660,6 +24779,19 @@ int BuildMessage(WOLFSSL* ssl, byte* output, int outSz, const byte* input,
2466024779#endif
2466124780
2466224781#ifndef WOLFSSL_NO_TLS12
24782+ /* RFC 5246 6.1: sequence numbers MUST NOT wrap. GetSEQIncrement post-
24783+ * increments, so refuse at hi == lo == 0xFFFFFFFF (2^64-1): that last legal
24784+ * value is deliberately sacrificed to avoid wrapping to 0 and reusing
24785+ * sequence number 0. The caller must renegotiate or close. DTLS sequence
24786+ * numbers are epoch-scoped and handled elsewhere. */
24787+ if (!sizeOnly && !ssl->options.dtls &&
24788+ ssl->keys.sequence_number_hi == 0xFFFFFFFFU &&
24789+ ssl->keys.sequence_number_lo == 0xFFFFFFFFU) {
24790+ WOLFSSL_MSG("TLS write sequence number would wrap");
24791+ WOLFSSL_ERROR_VERBOSE(SEQUENCE_NUMBER_E);
24792+ return SEQUENCE_NUMBER_E;
24793+ }
24794+
2466324795#ifdef WOLFSSL_ASYNC_CRYPT
2466424796 ret = WC_NO_PENDING_E;
2466524797 if (asyncOkay) {
@@ -27552,6 +27684,17 @@ int SendAlert(WOLFSSL* ssl, int severity, int type)
2755227684 return BAD_FUNC_ARG;
2755327685 }
2755427686
27687+ /* InvalidateSessionOnFatalAlert() is defined in the !NO_TLS section, so the
27688+ * guard here must match (with NO_TLS there are no TLS sessions to evict). */
27689+ #if !defined(NO_SESSION_CACHE) && !defined(NO_TLS)
27690+ /* RFC 5246 Section 7.2.2: a fatal alert terminates the connection;
27691+ * invalidate the established session so it cannot be resumed. Do this as
27692+ * soon as the fatal alert is generated, before the pendingAlert/backpressure
27693+ * handling below which can return early without sending the alert now. */
27694+ if (severity == alert_fatal)
27695+ InvalidateSessionOnFatalAlert(ssl);
27696+ #endif
27697+
2755527698 if (ssl->pendingAlert.level != alert_none) {
2755627699 ret = RetrySendAlert(ssl);
2755727700 if (ret != 0) {
@@ -28234,6 +28377,9 @@ const char* wolfSSL_ERR_reason_error_string(unsigned long e)
2823428377
2823528378 case ECH_REQUIRED_E:
2823628379 return "ECH offered but rejected by server";
28380+
28381+ case SEQUENCE_NUMBER_E:
28382+ return "Record sequence number would wrap";
2823728383 }
2823828384
2823928385 return "unknown error number";
@@ -32403,6 +32549,9 @@ static void MakePSKPreMasterSecret(Arrays* arrays, byte use_psk_key)
3240332549 }
3240432550 else {
3240532551 if (DSH_CheckSessionId(ssl)) {
32552+ /* EMS/suite consistency is checked once resumption is confirmed
32553+ * (CheckResumptionConsistency), not here: a ticket the server
32554+ * declines (RFC 5077 3.4) must fall back to a full handshake. */
3240632555 if (SetCipherSpecs(ssl) == 0) {
3240732556 if (!HaveUniqueSessionObj(ssl)) {
3240832557 WOLFSSL_MSG("Unable to have unique session object");
@@ -35668,6 +35817,15 @@ static int DoSessionTicket(WOLFSSL* ssl, const byte* input, word32* inOutIdx,
3566835817 return SESSION_TICKET_EXPECT_E;
3566935818 }
3567035819
35820+ /* A renewed ticket while resuming confirms resumption; check before the
35821+ * SetupSession() below refreshes the cached suite/EMS and masks a downgrade.
35822+ * (The ChangeCipherSpec check covers the no-renewal case.) */
35823+ if (ssl->options.resuming) {
35824+ ret = CheckResumptionConsistency(ssl);
35825+ if (ret != 0)
35826+ return ret;
35827+ }
35828+
3567135829 if (OPAQUE32_LEN > size)
3567235830 return BUFFER_ERROR;
3567335831
@@ -38793,6 +38951,17 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl)
3879338951 0) {
3879438952 TLSX* extension;
3879538953
38954+ #ifdef HAVE_SECURE_RENEGOTIATION
38955+ /* SCSV not allowed on a renegotiation ClientHello (RFC 5746 3.5). */
38956+ if (ssl->secure_renegotiation &&
38957+ ssl->secure_renegotiation->enabled &&
38958+ ssl->secure_renegotiation->verifySet) {
38959+ WOLFSSL_MSG("SCSV received on renegotiation ClientHello");
38960+ SendAlert(ssl, alert_fatal, handshake_failure);
38961+ ret = SECURE_RENEGOTIATION_E;
38962+ goto out;
38963+ }
38964+ #endif
3879638965 /* check for TLS_EMPTY_RENEGOTIATION_INFO_SCSV suite */
3879738966 ret = TLSX_AddEmptyRenegotiationInfo(&ssl->extensions, ssl->heap);
3879838967 if (ret != WOLFSSL_SUCCESS) {
@@ -39045,6 +39214,19 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl)
3904539214 *inOutIdx = begin + helloSz; /* skip extensions */
3904639215 }
3904739216
39217+ #ifdef HAVE_SECURE_RENEGOTIATION
39218+ /* renegotiation_info MUST be present on a renegotiation (RFC 5746 3.7). */
39219+ if (ssl->secure_renegotiation &&
39220+ ssl->secure_renegotiation->enabled &&
39221+ ssl->secure_renegotiation->verifySet &&
39222+ !ssl->secure_renegotiation->renegInfoSeen) {
39223+ WOLFSSL_MSG("Renegotiation ClientHello missing renegotiation_info");
39224+ SendAlert(ssl, alert_fatal, handshake_failure);
39225+ ret = SECURE_RENEGOTIATION_E;
39226+ goto out;
39227+ }
39228+ #endif /* HAVE_SECURE_RENEGOTIATION */
39229+
3904839230#ifdef WOLFSSL_DTLS_CID
3904939231 if (ssl->options.useDtlsCID)
3905039232 DtlsCIDOnExtensionsParsed(ssl);
0 commit comments