@@ -31,6 +31,7 @@ SRC_URI += "\
file://CVE-2025-61724.patch \
file://CVE-2025-61727.patch \
file://CVE-2025-61729.patch \
+ file://CVE-2025-61730.patch \
"
SRC_URI[main.sha256sum] = "012a7e1f37f362c0918c1dfa3334458ac2da1628c4b9cf4d9ca02db986e17d71"
new file mode 100644
@@ -0,0 +1,460 @@
+From 2cfa797798cc982973d194eca3be19fb1f092556 Mon Sep 17 00:00:00 2001
+From: Roland Shoemaker <roland@golang.org>
+Date: Mon, 24 Nov 2025 14:03:10 -0800
+Subject: [PATCH] [release-branch.go1.24] crypto/tls: reject trailing messages
+ after client/server hello
+
+For TLS 1.3, after procesesing the server/client hello, if there isn't a
+CCS message, reject the trailing messages which were appended to the
+hello messages. This prevents an on-path attacker from injecting
+plaintext messages into the handshake.
+
+Additionally, check that we don't have any buffered messages before we
+switch the read traffic secret regardless, since any buffered messages
+would have been under an old key which is no longer appropriate.
+
+We also invert the ordering of setting the read/write secrets so that if
+we fail when changing the read secret we send the alert using the
+correct write secret.
+
+Updates #76443
+Fixes #76854
+Fixes CVE-2025-61730
+
+CVE: CVE-2025-61730
+Upstream-Status: Backport [https://github.com/golang/go/commit/ad2cd043db66]
+
+Backport Changes:
+- In version 1.24, the doHelloRetryRequest function defined in handshake_server_tls13.go
+ returns keyshare and error, but in version 1.22 it only returns error. The backport
+ was adjusted accordingly and These changes were introduced by commit
+ https://github.com/golang/go/commit/d0edd9acc80a in version 1.24.
+- In file src/crypto/tls/handshake_server_tls13.go, Replaced the function call
+ hs.handshakeSecret.ClientHandshakeTrafficSecret(hs.transcript) with
+ hs.suite.deriveSecret(hs.handshakeSecret, clientHandshakeTrafficLabel, hs.transcript).
+ This change is not present in version v1.22 and it was introduced by commit
+ https://github.com/golang/go/commit/743746a3a52d in version 1.24.
+
+Change-Id: If6ba8ad16f48d5cd5db5574824062ad4244a5b52
+Reviewed-on: https://go-review.googlesource.com/c/go/+/724120
+LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
+Reviewed-by: Michael Knyszek <mknyszek@google.com>
+Reviewed-by: Daniel McCarney <daniel@binaryparadox.net>
+Reviewed-by: Coia Prant <coiaprant@gmail.com>
+(cherry picked from commit 5046bdf8a612b35a2c1a9e168054c1d5c65e7dd7)
+Reviewed-on: https://go-review.googlesource.com/c/go/+/731961
+Reviewed-by: Damien Neil <dneil@google.com>
+(cherry picked from commit ad2cd043db66cd36e1f55359638729d2c8ff3d99)
+Signed-off-by: Deepak Rathore <deeratho@cisco.com>
+---
+ src/crypto/tls/conn.go | 39 ++++++-
+ src/crypto/tls/handshake_client_tls13.go | 22 ++--
+ src/crypto/tls/handshake_server_tls13.go | 39 ++++---
+ src/crypto/tls/handshake_test.go | 140 +++++++++++++++++++++++
+ src/crypto/tls/quic.go | 11 +-
+ 5 files changed, 219 insertions(+), 32 deletions(-)
+
+diff --git a/src/crypto/tls/conn.go b/src/crypto/tls/conn.go
+index 0e4669866e..08609ce17b 100644
+--- a/src/crypto/tls/conn.go
++++ b/src/crypto/tls/conn.go
+@@ -225,6 +225,9 @@ func (hc *halfConn) changeCipherSpec() error {
+ return nil
+ }
+
++// setTrafficSecret sets the traffic secret for the given encryption level. setTrafficSecret
++// should not be called directly, but rather through the Conn setWriteTrafficSecret and
++// setReadTrafficSecret wrapper methods.
+ func (hc *halfConn) setTrafficSecret(suite *cipherSuiteTLS13, level QUICEncryptionLevel, secret []byte) {
+ hc.trafficSecret = secret
+ hc.level = level
+@@ -1321,9 +1324,6 @@ func (c *Conn) handleKeyUpdate(keyUpdate *keyUpdateMsg) error {
+ return c.in.setErrorLocked(c.sendAlert(alertInternalError))
+ }
+
+- newSecret := cipherSuite.nextTrafficSecret(c.in.trafficSecret)
+- c.in.setTrafficSecret(cipherSuite, QUICEncryptionLevelInitial, newSecret)
+-
+ if keyUpdate.updateRequested {
+ c.out.Lock()
+ defer c.out.Unlock()
+@@ -1341,7 +1341,12 @@ func (c *Conn) handleKeyUpdate(keyUpdate *keyUpdateMsg) error {
+ }
+
+ newSecret := cipherSuite.nextTrafficSecret(c.out.trafficSecret)
+- c.out.setTrafficSecret(cipherSuite, QUICEncryptionLevelInitial, newSecret)
++ c.setWriteTrafficSecret(cipherSuite, QUICEncryptionLevelInitial, newSecret)
++ }
++
++ newSecret := cipherSuite.nextTrafficSecret(c.in.trafficSecret)
++ if err := c.setReadTrafficSecret(cipherSuite, QUICEncryptionLevelInitial, newSecret); err != nil {
++ return err
+ }
+
+ return nil
+@@ -1572,7 +1577,9 @@ func (c *Conn) handshakeContext(ctx context.Context) (ret error) {
+ // Provide the 1-RTT read secret now that the handshake is complete.
+ // The QUIC layer MUST NOT decrypt 1-RTT packets prior to completing
+ // the handshake (RFC 9001, Section 5.7).
+- c.quicSetReadSecret(QUICEncryptionLevelApplication, c.cipherSuite, c.in.trafficSecret)
++ if err := c.quicSetReadSecret(QUICEncryptionLevelApplication, c.cipherSuite, c.in.trafficSecret); err != nil {
++ return err
++ }
+ } else {
+ var a alert
+ c.out.Lock()
+@@ -1664,3 +1671,25 @@ func (c *Conn) VerifyHostname(host string) error {
+ }
+ return c.peerCertificates[0].VerifyHostname(host)
+ }
++
++// setReadTrafficSecret sets the read traffic secret for the given encryption level. If
++// being called at the same time as setWriteTrafficSecret, the caller must ensure the call
++// to setWriteTrafficSecret happens first so any alerts are sent at the write level.
++func (c *Conn) setReadTrafficSecret(suite *cipherSuiteTLS13, level QUICEncryptionLevel, secret []byte) error {
++ // Ensure that there are no buffered handshake messages before changing the
++ // read keys, since that can cause messages to be parsed that were encrypted
++ // using old keys which are no longer appropriate.
++ if c.hand.Len() != 0 {
++ c.sendAlert(alertUnexpectedMessage)
++ return errors.New("tls: handshake buffer not empty before setting read traffic secret")
++ }
++ c.in.setTrafficSecret(suite, level, secret)
++ return nil
++}
++
++// setWriteTrafficSecret sets the write traffic secret for the given encryption level. If
++// being called at the same time as setReadTrafficSecret, the caller must ensure the call
++// to setWriteTrafficSecret happens first so any alerts are sent at the write level.
++func (c *Conn) setWriteTrafficSecret(suite *cipherSuiteTLS13, level QUICEncryptionLevel, secret []byte) {
++ c.out.setTrafficSecret(suite, level, secret)
++}
+diff --git a/src/crypto/tls/handshake_client_tls13.go b/src/crypto/tls/handshake_client_tls13.go
+index 2f59f6888c..68ff92beda 100644
+--- a/src/crypto/tls/handshake_client_tls13.go
++++ b/src/crypto/tls/handshake_client_tls13.go
+@@ -393,17 +393,18 @@ func (hs *clientHandshakeStateTLS13) establishHandshakeKeys() error {
+
+ clientSecret := hs.suite.deriveSecret(handshakeSecret,
+ clientHandshakeTrafficLabel, hs.transcript)
+- c.out.setTrafficSecret(hs.suite, QUICEncryptionLevelHandshake, clientSecret)
++ c.setWriteTrafficSecret(hs.suite, QUICEncryptionLevelHandshake, clientSecret)
+ serverSecret := hs.suite.deriveSecret(handshakeSecret,
+ serverHandshakeTrafficLabel, hs.transcript)
+- c.in.setTrafficSecret(hs.suite, QUICEncryptionLevelHandshake, serverSecret)
++ if err := c.setReadTrafficSecret(hs.suite, QUICEncryptionLevelHandshake, serverSecret); err != nil {
++ return err
++ }
+
+ if c.quic != nil {
+- if c.hand.Len() != 0 {
+- c.sendAlert(alertUnexpectedMessage)
+- }
+ c.quicSetWriteSecret(QUICEncryptionLevelHandshake, hs.suite.id, clientSecret)
+- c.quicSetReadSecret(QUICEncryptionLevelHandshake, hs.suite.id, serverSecret)
++ if err := c.quicSetReadSecret(QUICEncryptionLevelHandshake, hs.suite.id, serverSecret); err != nil {
++ return err
++ }
+ }
+
+ err = c.config.writeKeyLog(keyLogLabelClientHandshake, hs.hello.random, clientSecret)
+@@ -606,7 +607,9 @@ func (hs *clientHandshakeStateTLS13) readServerFinished() error {
+ clientApplicationTrafficLabel, hs.transcript)
+ serverSecret := hs.suite.deriveSecret(hs.masterSecret,
+ serverApplicationTrafficLabel, hs.transcript)
+- c.in.setTrafficSecret(hs.suite, QUICEncryptionLevelApplication, serverSecret)
++ if err := c.setReadTrafficSecret(hs.suite, QUICEncryptionLevelApplication, serverSecret); err != nil {
++ return err
++ }
+
+ err = c.config.writeKeyLog(keyLogLabelClientTraffic, hs.hello.random, hs.trafficSecret)
+ if err != nil {
+@@ -702,7 +705,7 @@ func (hs *clientHandshakeStateTLS13) sendClientFinished() error {
+ return err
+ }
+
+- c.out.setTrafficSecret(hs.suite, QUICEncryptionLevelApplication, hs.trafficSecret)
++ c.setWriteTrafficSecret(hs.suite, QUICEncryptionLevelApplication, hs.trafficSecret)
+
+ if !c.config.SessionTicketsDisabled && c.config.ClientSessionCache != nil {
+ c.resumptionSecret = hs.suite.deriveSecret(hs.masterSecret,
+@@ -710,9 +713,6 @@ func (hs *clientHandshakeStateTLS13) sendClientFinished() error {
+ }
+
+ if c.quic != nil {
+- if c.hand.Len() != 0 {
+- c.sendAlert(alertUnexpectedMessage)
+- }
+ c.quicSetWriteSecret(QUICEncryptionLevelApplication, hs.suite.id, hs.trafficSecret)
+ }
+
+diff --git a/src/crypto/tls/handshake_server_tls13.go b/src/crypto/tls/handshake_server_tls13.go
+index 21d798de37..5aa69e9640 100644
+--- a/src/crypto/tls/handshake_server_tls13.go
++++ b/src/crypto/tls/handshake_server_tls13.go
+@@ -380,7 +380,9 @@ func (hs *serverHandshakeStateTLS13) checkForResumption() error {
+ return err
+ }
+ earlyTrafficSecret := hs.suite.deriveSecret(hs.earlySecret, clientEarlyTrafficLabel, transcript)
+- c.quicSetReadSecret(QUICEncryptionLevelEarly, hs.suite.id, earlyTrafficSecret)
++ if err := c.quicSetReadSecret(QUICEncryptionLevelEarly, hs.suite.id, earlyTrafficSecret); err != nil {
++ return err
++ }
+ }
+
+ c.didResume = true
+@@ -477,6 +479,14 @@ func (hs *serverHandshakeStateTLS13) sendDummyChangeCipherSpec() error {
+ func (hs *serverHandshakeStateTLS13) doHelloRetryRequest(selectedGroup CurveID) error {
+ c := hs.c
+
++ // Make sure the client didn't send extra handshake messages alongside
++ // their initial client_hello. If they sent two client_hello messages,
++ // we will consume the second before they respond to the server_hello.
++ if c.hand.Len() != 0 {
++ c.sendAlert(alertUnexpectedMessage)
++ return errors.New("tls: handshake buffer not empty before HelloRetryRequest")
++ }
++
+ // The first ClientHello gets double-hashed into the transcript upon a
+ // HelloRetryRequest. See RFC 8446, Section 4.4.1.
+ if err := transcriptMsg(hs.clientHello, hs.transcript); err != nil {
+@@ -615,19 +625,20 @@ func (hs *serverHandshakeStateTLS13) sendServerParameters() error {
+ hs.handshakeSecret = hs.suite.extract(hs.sharedKey,
+ hs.suite.deriveSecret(earlySecret, "derived", nil))
+
+- clientSecret := hs.suite.deriveSecret(hs.handshakeSecret,
+- clientHandshakeTrafficLabel, hs.transcript)
+- c.in.setTrafficSecret(hs.suite, QUICEncryptionLevelHandshake, clientSecret)
+ serverSecret := hs.suite.deriveSecret(hs.handshakeSecret,
+ serverHandshakeTrafficLabel, hs.transcript)
+- c.out.setTrafficSecret(hs.suite, QUICEncryptionLevelHandshake, serverSecret)
++ c.setWriteTrafficSecret(hs.suite, QUICEncryptionLevelHandshake, serverSecret)
++ clientSecret := hs.suite.deriveSecret(hs.handshakeSecret,
++ clientHandshakeTrafficLabel, hs.transcript)
++ if err := c.setReadTrafficSecret(hs.suite, QUICEncryptionLevelHandshake, clientSecret); err != nil {
++ return err
++ }
+
+ if c.quic != nil {
+- if c.hand.Len() != 0 {
+- c.sendAlert(alertUnexpectedMessage)
+- }
+ c.quicSetWriteSecret(QUICEncryptionLevelHandshake, hs.suite.id, serverSecret)
+- c.quicSetReadSecret(QUICEncryptionLevelHandshake, hs.suite.id, clientSecret)
++ if err := c.quicSetReadSecret(QUICEncryptionLevelHandshake, hs.suite.id, clientSecret); err != nil {
++ return err
++ }
+ }
+
+ err := c.config.writeKeyLog(keyLogLabelClientHandshake, hs.clientHello.random, clientSecret)
+@@ -751,13 +762,9 @@ func (hs *serverHandshakeStateTLS13) sendServerFinished() error {
+ clientApplicationTrafficLabel, hs.transcript)
+ serverSecret := hs.suite.deriveSecret(hs.masterSecret,
+ serverApplicationTrafficLabel, hs.transcript)
+- c.out.setTrafficSecret(hs.suite, QUICEncryptionLevelApplication, serverSecret)
++ c.setWriteTrafficSecret(hs.suite, QUICEncryptionLevelApplication, serverSecret)
+
+ if c.quic != nil {
+- if c.hand.Len() != 0 {
+- // TODO: Handle this in setTrafficSecret?
+- c.sendAlert(alertUnexpectedMessage)
+- }
+ c.quicSetWriteSecret(QUICEncryptionLevelApplication, hs.suite.id, serverSecret)
+ }
+
+@@ -992,7 +999,9 @@ func (hs *serverHandshakeStateTLS13) readClientFinished() error {
+ return errors.New("tls: invalid client finished hash")
+ }
+
+- c.in.setTrafficSecret(hs.suite, QUICEncryptionLevelApplication, hs.trafficSecret)
++ if err := c.setReadTrafficSecret(hs.suite, QUICEncryptionLevelApplication, hs.trafficSecret); err != nil {
++ return err
++ }
+
+ return nil
+ }
+diff --git a/src/crypto/tls/handshake_test.go b/src/crypto/tls/handshake_test.go
+index 27ab19ef31..4991a0e69b 100644
+--- a/src/crypto/tls/handshake_test.go
++++ b/src/crypto/tls/handshake_test.go
+@@ -6,6 +6,7 @@ package tls
+
+ import (
+ "bufio"
++ "context"
+ "crypto/ed25519"
+ "crypto/x509"
+ "encoding/hex"
+@@ -533,3 +534,142 @@ var clientEd25519KeyPEM = testingKey(`
+ -----BEGIN TESTING KEY-----
+ MC4CAQAwBQYDK2VwBCIEINifzf07d9qx3d44e0FSbV4mC/xQxT644RRbpgNpin7I
+ -----END TESTING KEY-----`)
++
++func TestServerHelloTrailingMessage(t *testing.T) {
++ // In TLS 1.3 the change cipher spec message is optional. If a CCS message
++ // is not sent, after reading the ServerHello, the read traffic secret is
++ // set, and all following messages must be encrypted. If the server sends
++ // additional unencrypted messages in a record with the ServerHello, the
++ // client must either fail or ignore the additional messages.
++
++ c, s := localPipe(t)
++ go func() {
++ ctx := context.Background()
++ srv := Server(s, testConfig)
++ clientHello, _, err := srv.readClientHello(ctx)
++ if err != nil {
++ testFatal(t, err)
++ }
++
++ hs := serverHandshakeStateTLS13{
++ c: srv,
++ ctx: ctx,
++ clientHello: clientHello,
++ }
++ if err := hs.processClientHello(); err != nil {
++ testFatal(t, err)
++ }
++ if err := transcriptMsg(hs.clientHello, hs.transcript); err != nil {
++ testFatal(t, err)
++ }
++
++ record, err := concatHandshakeMessages(hs.hello, &encryptedExtensionsMsg{alpnProtocol: "h2"})
++ if err != nil {
++ testFatal(t, err)
++ }
++
++ if _, err := s.Write(record); err != nil {
++ testFatal(t, err)
++ }
++ srv.Close()
++ }()
++
++ cli := Client(c, testConfig)
++ expectedErr := "tls: handshake buffer not empty before setting read traffic secret"
++ if err := cli.Handshake(); err == nil {
++ t.Fatal("expected error from incomplete handshake, got nil")
++ } else if err.Error() != expectedErr {
++ t.Fatalf("expected error %q, got %q", expectedErr, err.Error())
++ }
++}
++
++func TestClientHelloTrailingMessage(t *testing.T) {
++ // Same as TestServerHelloTrailingMessage but for the client side.
++
++ c, s := localPipe(t)
++ go func() {
++ cli := Client(c, testConfig)
++
++ hello, _, _, err := cli.makeClientHello()
++ if err != nil {
++ testFatal(t, err)
++ }
++
++ record, err := concatHandshakeMessages(hello, &certificateMsgTLS13{})
++ if err != nil {
++ testFatal(t, err)
++ }
++
++ if _, err := c.Write(record); err != nil {
++ testFatal(t, err)
++ }
++ cli.Close()
++ }()
++
++ srv := Server(s, testConfig)
++ expectedErr := "tls: handshake buffer not empty before setting read traffic secret"
++ if err := srv.Handshake(); err == nil {
++ t.Fatal("expected error from incomplete handshake, got nil")
++ } else if err.Error() != expectedErr {
++ t.Fatalf("expected error %q, got %q", expectedErr, err.Error())
++ }
++}
++
++func TestDoubleClientHelloHRR(t *testing.T) {
++ // If a client sends two ClientHello messages in a single record, and the
++ // server sends a HRR after reading the first ClientHello, the server must
++ // either fail or ignore the trailing ClientHello.
++
++ c, s := localPipe(t)
++
++ go func() {
++ cli := Client(c, testConfig)
++
++ hello, _, _, err := cli.makeClientHello()
++ if err != nil {
++ testFatal(t, err)
++ }
++ hello.keyShares = nil
++
++ record, err := concatHandshakeMessages(hello, hello)
++ if err != nil {
++ testFatal(t, err)
++ }
++
++ if _, err := c.Write(record); err != nil {
++ testFatal(t, err)
++ }
++ cli.Close()
++ }()
++
++ srv := Server(s, testConfig)
++ expectedErr := "tls: handshake buffer not empty before HelloRetryRequest"
++ if err := srv.Handshake(); err == nil {
++ t.Fatal("expected error from incomplete handshake, got nil")
++ } else if err.Error() != expectedErr {
++ t.Fatalf("expected error %q, got %q", expectedErr, err.Error())
++ }
++}
++
++// concatHandshakeMessages marshals and concatenates the given handshake
++// messages into a single record.
++func concatHandshakeMessages(msgs ...handshakeMessage) ([]byte, error) {
++ var marshalled []byte
++ for _, msg := range msgs {
++ data, err := msg.marshal()
++ if err != nil {
++ return nil, err
++ }
++ marshalled = append(marshalled, data...)
++ }
++ m := len(marshalled)
++ outBuf := make([]byte, recordHeaderLen)
++ outBuf[0] = byte(recordTypeHandshake)
++ vers := VersionTLS12
++ outBuf[1] = byte(vers >> 8)
++ outBuf[2] = byte(vers)
++ outBuf[3] = byte(m >> 8)
++ outBuf[4] = byte(m)
++ outBuf = append(outBuf, marshalled...)
++ return outBuf, nil
++}
+diff --git a/src/crypto/tls/quic.go b/src/crypto/tls/quic.go
+index 3518169bf7..aa14f1dadb 100644
+--- a/src/crypto/tls/quic.go
++++ b/src/crypto/tls/quic.go
+@@ -323,13 +323,22 @@ func (c *Conn) quicReadHandshakeBytes(n int) error {
+ return nil
+ }
+
+-func (c *Conn) quicSetReadSecret(level QUICEncryptionLevel, suite uint16, secret []byte) {
++func (c *Conn) quicSetReadSecret(level QUICEncryptionLevel, suite uint16, secret []byte) error {
++ // Ensure that there are no buffered handshake messages before changing the
++ // read keys, since that can cause messages to be parsed that were encrypted
++ // using old keys which are no longer appropriate.
++ // TODO(roland): we should merge this check with the similar one in setReadTrafficSecret.
++ if c.hand.Len() != 0 {
++ c.sendAlert(alertUnexpectedMessage)
++ return errors.New("tls: handshake buffer not empty before setting read traffic secret")
++ }
+ c.quic.events = append(c.quic.events, QUICEvent{
+ Kind: QUICSetReadSecret,
+ Level: level,
+ Suite: suite,
+ Data: secret,
+ })
++ return nil
+ }
+
+ func (c *Conn) quicSetWriteSecret(level QUICEncryptionLevel, suite uint16, secret []byte) {
+--
+2.35.6