Repository: quic-go/quic-go (Go)

Issue:
missing tls.Config.MinVersion on configs returned by GetConfigForClient
crypto/tls currently checks that the `tls.Config` sets TLS 1.3 as a minimum version (note that this might change with https://github.com/golang/go/issues/63722). However, there's no such check for the config returns by the `GetConfigForClient` callback. And while we set the `MinVersion` field for the outer `tls.Config`, we don't do that for the config returned by `GetConfigForClient`.

This will allow a client to negotiate a TLS version lower than TLS 1.3, since crypto/tls doesn't currently implement a check for the negotiation result (because it incorrectly assumes that any other result than TLS 1.3 is not possible), and also doesn't implement any check for the client's offered versions as defined in section 4.2 of RFC 9001.

I don't see any security implications, since the TLS handshake fails at a later stage, but we should still fix this.


Discussion:

Patch:
diff --git a/internal/handshake/crypto_setup.go b/internal/handshake/crypto_setup.go
index e15aad54acb..c5787e86a6d 100644
--- a/internal/handshake/crypto_setup.go
+++ b/internal/handshake/crypto_setup.go
@@ -148,6 +148,8 @@ func addConnToClientHelloInfo(conf *tls.Config, localAddr, remoteAddr net.Addr)
 			c, err := gcfc(info)
 			if c != nil {
 				c = c.Clone()
+				// This won't be necessary anymore once https://github.com/golang/go/issues/63722 is accepted.
+				c.MinVersion = tls.VersionTLS13
 				// We're returning a tls.Config here, so we need to apply this recursively.
 				addConnToClientHelloInfo(c, localAddr, remoteAddr)
 			}


Test patch:
diff --git a/internal/handshake/crypto_setup_test.go b/internal/handshake/crypto_setup_test.go
index 21278339303..4e92118a13b 100644
--- a/internal/handshake/crypto_setup_test.go
+++ b/internal/handshake/crypto_setup_test.go
@@ -140,10 +140,12 @@ var _ = Describe("Crypto Setup TLS", func() {
 				},
 			}
 			addConnToClientHelloInfo(tlsConf, local, remote)
-			_, err := tlsConf.GetConfigForClient(&tls.ClientHelloInfo{})
+			conf, err := tlsConf.GetConfigForClient(&tls.ClientHelloInfo{})
 			Expect(err).ToNot(HaveOccurred())
 			Expect(localAddr).To(Equal(local))
 			Expect(remoteAddr).To(Equal(remote))
+			Expect(conf).ToNot(BeNil())
+			Expect(conf.MinVersion).To(BeEquivalentTo(tls.VersionTLS13))
 		})
 
 		It("wraps GetConfigForClient, recursively", func() {
@@ -158,18 +160,23 @@ var _ = Describe("Crypto Setup TLS", func() {
 			}
 			tlsConf.GetConfigForClient = func(info *tls.ClientHelloInfo) (*tls.Config, error) {
 				innerConf = tlsConf.Clone()
+				// set the MaxVersion, so we can check that quic-go doesn't overwrite the user's config
+				innerConf.MaxVersion = tls.VersionTLS12
 				innerConf.GetCertificate = getCert
 				return innerConf, nil
 			}
 			addConnToClientHelloInfo(tlsConf, local, remote)
 			conf, err := tlsConf.GetConfigForClient(&tls.ClientHelloInfo{})
 			Expect(err).ToNot(HaveOccurred())
+			Expect(conf).ToNot(BeNil())
+			Expect(conf.MinVersion).To(BeEquivalentTo(tls.VersionTLS13))
 			_, err = conf.GetCertificate(&tls.ClientHelloInfo{})
 			Expect(err).ToNot(HaveOccurred())
 			Expect(localAddr).To(Equal(local))
 			Expect(remoteAddr).To(Equal(remote))
 			// make sure that the tls.Config returned by GetConfigForClient isn't modified
 			Expect(reflect.ValueOf(innerConf.GetCertificate).Pointer() == reflect.ValueOf(getCert).Pointer()).To(BeTrue())
+			Expect(innerConf.MaxVersion).To(BeEquivalentTo(tls.VersionTLS12))
 		})
 	})