From 0b8071916e2922d8c1a566d621086b5d5203c310 Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Thu, 10 Sep 2026 22:56:11 -0400 Subject: [PATCH] crypto/tls: clamp effective minimum version to TLS 1.3 when using QUIC Change-Id: Ieec72362bacf1956a2bd5e0b2eb8dad88e624bd1 Reviewed-on: https://go-review.googlesource.com/c/go/+/745980 Reviewed-by: Damien Neil LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Roland Shoemaker --- common.go | 18 ++++++++++++------ handshake_client.go | 6 +++--- handshake_server.go | 7 ++++--- handshake_server_tls13.go | 2 +- quic.go | 7 ------- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/common.go b/common.go index bfa9d8a..2655ead 100644 --- a/common.go +++ b/common.go @@ -495,6 +495,9 @@ type ClientHelloInfo struct { // for use with SupportsCertificate. config *Config + // isQUIC indicates whether the connection is a QUIC connection. + isQUIC bool + // ctx is the context of the handshake that is in progress. ctx context.Context } @@ -1257,7 +1260,7 @@ const roleServer = false // supportedVersions returns the list of supported TLS versions, sorted from // highest to lowest (and hence also in preference order). -func (c *Config) supportedVersions(isClient bool) []uint16 { +func (c *Config) supportedVersions(isClient, isQUIC bool) []uint16 { versions := make([]uint16, 0, len(supportedVersions)) for _, v := range supportedVersions { if fips140tls.Required() && !slices.Contains(allowedSupportedVersionsFIPS, v) { @@ -1275,13 +1278,16 @@ func (c *Config) supportedVersions(isClient bool) []uint16 { if c != nil && c.MaxVersion != 0 && v > c.MaxVersion { continue } + if isQUIC && v < VersionTLS13 { + continue + } versions = append(versions, v) } return versions } -func (c *Config) maxSupportedVersion(isClient bool) uint16 { - supportedVersions := c.supportedVersions(isClient) +func (c *Config) maxSupportedVersion(isClient, isQUIC bool) uint16 { + supportedVersions := c.supportedVersions(isClient, isQUIC) if len(supportedVersions) == 0 { return 0 } @@ -1333,8 +1339,8 @@ func (c *Config) supportsCurve(version uint16, x CurveID) bool { // mutualVersion returns the protocol version to use given the advertised // versions of the peer. The highest supported version is preferred. -func (c *Config) mutualVersion(isClient bool, peerVersions []uint16) (uint16, bool) { - supportedVersions := c.supportedVersions(isClient) +func (c *Config) mutualVersion(isClient, isQUIC bool, peerVersions []uint16) (uint16, bool) { + supportedVersions := c.supportedVersions(isClient, isQUIC) for _, v := range supportedVersions { if slices.Contains(peerVersions, v) { return v, true @@ -1420,7 +1426,7 @@ func (chi *ClientHelloInfo) SupportsCertificate(c *Certificate) error { if config == nil { config = &Config{} } - vers, ok := config.mutualVersion(roleServer, chi.SupportedVersions) + vers, ok := config.mutualVersion(roleServer, chi.isQUIC, chi.SupportedVersions) if !ok { return errors.New("no mutually supported protocol versions") } diff --git a/handshake_client.go b/handshake_client.go index 0f47b51..c4140e1 100644 --- a/handshake_client.go +++ b/handshake_client.go @@ -58,7 +58,7 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCli return nil, nil, nil, errors.New("tls: NextProtos values too large") } - supportedVersions := config.supportedVersions(roleClient) + supportedVersions := config.supportedVersions(roleClient, c.quic != nil) if len(supportedVersions) == 0 { return nil, nil, nil, errors.New("tls: no supported versions satisfy MinVersion and MaxVersion") } @@ -317,7 +317,7 @@ func (c *Conn) clientHandshake(ctx context.Context) (err error) { // If we are negotiating a protocol version that's lower than what we // support, check for the server downgrade canaries. // See RFC 8446, Section 4.1.3. - maxVers := c.config.maxSupportedVersion(roleClient) + maxVers := c.config.maxSupportedVersion(roleClient, c.quic != nil) tls12Downgrade := string(serverHello.random[24:]) == downgradeCanaryTLS12 tls11Downgrade := string(serverHello.random[24:]) == downgradeCanaryTLS11 if maxVers == VersionTLS13 && c.vers <= VersionTLS12 && (tls12Downgrade || tls11Downgrade) || @@ -510,7 +510,7 @@ func (c *Conn) pickTLSVersion(serverHello *serverHelloMsg) error { peerVersion = serverHello.supportedVersion } - vers, ok := c.config.mutualVersion(roleClient, []uint16{peerVersion}) + vers, ok := c.config.mutualVersion(roleClient, c.quic != nil, []uint16{peerVersion}) if !ok { c.sendAlert(alertProtocolVersion) return fmt.Errorf("tls: server selected unsupported protocol version %x", peerVersion) diff --git a/handshake_server.go b/handshake_server.go index 422cdb1..a83a78a 100644 --- a/handshake_server.go +++ b/handshake_server.go @@ -189,7 +189,7 @@ func (c *Conn) readClientHello(ctx context.Context) (*clientHelloMsg, *echServer } else if len(clientVersions) == 0 { clientVersions = supportedVersionsFromMax(clientHello.vers) } - c.vers, ok = c.config.mutualVersion(roleServer, clientVersions) + c.vers, ok = c.config.mutualVersion(roleServer, c.quic != nil, clientVersions) if !ok { c.sendAlert(alertProtocolVersion) return nil, nil, fmt.Errorf("tls: client offered only unsupported versions: %x", clientVersions) @@ -236,7 +236,7 @@ func (hs *serverHandshakeState) processClientHello() error { hs.hello.random = make([]byte, 32) serverRandom := hs.hello.random // Downgrade protection canaries. See RFC 8446, Section 4.1.3. - maxVers := c.config.maxSupportedVersion(roleServer) + maxVers := c.config.maxSupportedVersion(roleServer, c.quic != nil) if maxVers >= VersionTLS12 && c.vers < maxVers || testingOnlyForceDowngradeCanary { if c.vers == VersionTLS12 { copy(serverRandom[24:], downgradeCanaryTLS12) @@ -411,7 +411,7 @@ func (hs *serverHandshakeState) pickCipherSuite() error { for _, id := range hs.clientHello.cipherSuites { if id == TLS_FALLBACK_SCSV { // The client is doing a fallback connection. See RFC 7507. - if hs.clientHello.vers < c.config.maxSupportedVersion(roleServer) { + if hs.clientHello.vers < c.config.maxSupportedVersion(roleServer, c.quic != nil) { c.sendAlert(alertInappropriateFallback) return errors.New("tls: client using inappropriate protocol fallback") } @@ -1034,6 +1034,7 @@ func clientHelloInfo(ctx context.Context, c *Conn, clientHello *clientHelloMsg) Conn: conn, HelloRetryRequest: c.didHRR, config: c.config, + isQUIC: c.quic != nil, ctx: ctx, } } diff --git a/handshake_server_tls13.go b/handshake_server_tls13.go index 7ae0f6b..9a5839f 100644 --- a/handshake_server_tls13.go +++ b/handshake_server_tls13.go @@ -226,7 +226,7 @@ func (hs *serverHandshakeStateTLS13) processClientHello() error { if id == TLS_FALLBACK_SCSV { // Use c.vers instead of max(supported_versions) because an attacker // could defeat this by adding an arbitrary high version otherwise. - if c.vers < c.config.maxSupportedVersion(roleServer) { + if c.vers < c.config.maxSupportedVersion(roleServer, c.quic != nil) { c.sendAlert(alertInappropriateFallback) return errors.New("tls: client using inappropriate protocol fallback") } diff --git a/quic.go b/quic.go index 95a1995..6b16cc2 100644 --- a/quic.go +++ b/quic.go @@ -185,16 +185,12 @@ type quicState struct { // QUICClient returns a new TLS client side connection using QUICTransport as the // underlying transport. The config cannot be nil. -// -// The config's MinVersion must be at least TLS 1.3. func QUICClient(config *QUICConfig) *QUICConn { return newQUICConn(Client(nil, config.TLSConfig), config) } // QUICServer returns a new TLS server side connection using QUICTransport as the // underlying transport. The config cannot be nil. -// -// The config's MinVersion must be at least TLS 1.3. func QUICServer(config *QUICConfig) *QUICConn { c, _ := Server(context.Background(), nil, config.TLSConfig) return newQUICConn(c, config) @@ -222,9 +218,6 @@ func (q *QUICConn) Start(ctx context.Context) error { return quicError(errors.New("tls: Start called more than once")) } q.conn.quic.started = true - if q.conn.config.MinVersion < VersionTLS13 { - return quicError(errors.New("tls: Config MinVersion must be at least TLS 1.3")) - } go q.conn.HandshakeContext(ctx) if _, ok := <-q.conn.quic.blockedc; !ok { return q.conn.handshakeErr