diff --git a/cipher_suites.go b/cipher_suites.go index 6a7e7fc5..4204ff83 100644 --- a/cipher_suites.go +++ b/cipher_suites.go @@ -48,18 +48,13 @@ var ( // CipherSuites returns a list of cipher suites currently implemented by this // package, excluding those with security issues, which are returned by -// InsecureCipherSuites. +// [InsecureCipherSuites]. // // The list is sorted by ID. Note that the default cipher suites selected by // this package might depend on logic that can't be captured by a static list, // and might not match those returned by this function. func CipherSuites() []*CipherSuite { return []*CipherSuite{ - {TLS_RSA_WITH_AES_128_CBC_SHA, "TLS_RSA_WITH_AES_128_CBC_SHA", supportedUpToTLS12, false}, - {TLS_RSA_WITH_AES_256_CBC_SHA, "TLS_RSA_WITH_AES_256_CBC_SHA", supportedUpToTLS12, false}, - {TLS_RSA_WITH_AES_128_GCM_SHA256, "TLS_RSA_WITH_AES_128_GCM_SHA256", supportedOnlyTLS12, false}, - {TLS_RSA_WITH_AES_256_GCM_SHA384, "TLS_RSA_WITH_AES_256_GCM_SHA384", supportedOnlyTLS12, false}, - {TLS_AES_128_GCM_SHA256, "TLS_AES_128_GCM_SHA256", supportedOnlyTLS13, false}, {TLS_AES_256_GCM_SHA384, "TLS_AES_256_GCM_SHA384", supportedOnlyTLS13, false}, {TLS_CHACHA20_POLY1305_SHA256, "TLS_CHACHA20_POLY1305_SHA256", supportedOnlyTLS13, false}, @@ -81,14 +76,18 @@ func CipherSuites() []*CipherSuite { // this package and which have security issues. // // Most applications should not use the cipher suites in this list, and should -// only use those returned by CipherSuites. +// only use those returned by [CipherSuites]. func InsecureCipherSuites() []*CipherSuite { // This list includes RC4, CBC_SHA256, and 3DES cipher suites. See // cipherSuitesPreferenceOrder for details. return []*CipherSuite{ {TLS_RSA_WITH_RC4_128_SHA, "TLS_RSA_WITH_RC4_128_SHA", supportedUpToTLS12, true}, {TLS_RSA_WITH_3DES_EDE_CBC_SHA, "TLS_RSA_WITH_3DES_EDE_CBC_SHA", supportedUpToTLS12, true}, + {TLS_RSA_WITH_AES_128_CBC_SHA, "TLS_RSA_WITH_AES_128_CBC_SHA", supportedUpToTLS12, true}, + {TLS_RSA_WITH_AES_256_CBC_SHA, "TLS_RSA_WITH_AES_256_CBC_SHA", supportedUpToTLS12, true}, {TLS_RSA_WITH_AES_128_CBC_SHA256, "TLS_RSA_WITH_AES_128_CBC_SHA256", supportedOnlyTLS12, true}, + {TLS_RSA_WITH_AES_128_GCM_SHA256, "TLS_RSA_WITH_AES_128_GCM_SHA256", supportedOnlyTLS12, true}, + {TLS_RSA_WITH_AES_256_GCM_SHA384, "TLS_RSA_WITH_AES_256_GCM_SHA384", supportedOnlyTLS12, true}, {TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", supportedUpToTLS12, true}, {TLS_ECDHE_RSA_WITH_RC4_128_SHA, "TLS_ECDHE_RSA_WITH_RC4_128_SHA", supportedUpToTLS12, true}, {TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", supportedUpToTLS12, true}, @@ -325,23 +324,48 @@ var cipherSuitesPreferenceOrderNoAES = []uint16{ TLS_RSA_WITH_RC4_128_SHA, } -// disabledCipherSuites are not used unless explicitly listed in -// Config.CipherSuites. They MUST be at the end of cipherSuitesPreferenceOrder. -var disabledCipherSuites = []uint16{ +// disabledCipherSuites are not used unless explicitly listed in Config.CipherSuites. +var disabledCipherSuites = map[uint16]bool{ // CBC_SHA256 - TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, - TLS_RSA_WITH_AES_128_CBC_SHA256, + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: true, + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: true, + TLS_RSA_WITH_AES_128_CBC_SHA256: true, // RC4 - TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, TLS_ECDHE_RSA_WITH_RC4_128_SHA, - TLS_RSA_WITH_RC4_128_SHA, + TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: true, + TLS_ECDHE_RSA_WITH_RC4_128_SHA: true, + TLS_RSA_WITH_RC4_128_SHA: true, +} + +// rsaKexCiphers contains the ciphers which use RSA based key exchange, +// which we also disable by default unless a GODEBUG is set. +var rsaKexCiphers = map[uint16]bool{ + TLS_RSA_WITH_RC4_128_SHA: true, + TLS_RSA_WITH_3DES_EDE_CBC_SHA: true, + TLS_RSA_WITH_AES_128_CBC_SHA: true, + TLS_RSA_WITH_AES_256_CBC_SHA: true, + TLS_RSA_WITH_AES_128_CBC_SHA256: true, + TLS_RSA_WITH_AES_128_GCM_SHA256: true, + TLS_RSA_WITH_AES_256_GCM_SHA384: true, +} + +var defaultCipherSuites []uint16 +var defaultCipherSuitesWithRSAKex []uint16 + +func init() { + defaultCipherSuites = make([]uint16, 0, len(cipherSuitesPreferenceOrder)) + defaultCipherSuitesWithRSAKex = make([]uint16, 0, len(cipherSuitesPreferenceOrder)) + for _, c := range cipherSuitesPreferenceOrder { + if disabledCipherSuites[c] { + continue + } + if !rsaKexCiphers[c] { + defaultCipherSuites = append(defaultCipherSuites, c) + } + defaultCipherSuitesWithRSAKex = append(defaultCipherSuitesWithRSAKex, c) + } } -var ( - defaultCipherSuitesLen = len(cipherSuitesPreferenceOrder) - len(disabledCipherSuites) - defaultCipherSuites = cipherSuitesPreferenceOrder[:defaultCipherSuitesLen] -) - // defaultCipherSuitesTLS13 is also the preference order, since there are no // disabled by default TLS 1.3 cipher suites. The same AES vs ChaCha20 logic as // cipherSuitesPreferenceOrder applies. @@ -535,7 +559,13 @@ func aeadAESGCMTLS13(key, nonceMask []byte) aead { if err != nil { panic(err) } - aead, err := cipher.NewGCM(aes) + var aead cipher.AEAD + if boring.Enabled { + aead, err = boring.NewGCMTLS13(aes) + } else { + boring.Unreachable() + aead, err = cipher.NewGCM(aes) + } if err != nil { panic(err) } diff --git a/common.go b/common.go index 7f0cc87d..c6da2c7a 100644 --- a/common.go +++ b/common.go @@ -316,11 +316,13 @@ type ConnectionState struct { // ExportKeyingMaterial returns length bytes of exported key material in a new // slice as defined in RFC 5705. If context is nil, it is not used as part of // the seed. If the connection was set to allow renegotiation via -// Config.Renegotiation, this function will return an error. +// Config.Renegotiation, or if the connections supports neither TLS 1.3 nor +// Extended Master Secret, this function will return an error. // -// There are conditions in which the returned values might not be unique to a -// connection. See the Security Considerations sections of RFC 5705 and RFC 7627, -// and https://mitls.org/pages/attacks/3SHAKE#channelbindings. +// Exporting key material without Extended Master Secret or TLS 1.3 was disabled +// in Go 1.22 due to security issues (see the Security Considerations sections +// of RFC 5705 and RFC 7627), but can be re-enabled with the GODEBUG setting +// tlsunsafeekm=1. func (cs *ConnectionState) ExportKeyingMaterial(label string, context []byte, length int) ([]byte, error) { return cs.ekm(label, context, length) } @@ -722,7 +724,9 @@ type Config struct { // the list is ignored. Note that TLS 1.3 ciphersuites are not configurable. // // If CipherSuites is nil, a safe default list is used. The default cipher - // suites might change over time. + // suites might change over time. In Go 1.22 RSA key exchange based cipher + // suites were removed from the default list, but can be re-added with the + // GODEBUG setting tlsrsakex=1. CipherSuites []uint16 // PreferServerCipherSuites is a legacy field and has no effect. @@ -785,14 +789,11 @@ type Config struct { // MinVersion contains the minimum TLS version that is acceptable. // - // By default, TLS 1.2 is currently used as the minimum when acting as a - // client, and TLS 1.0 when acting as a server. TLS 1.0 is the minimum - // supported by this package, both as a client and as a server. + // By default, TLS 1.2 is currently used as the minimum. TLS 1.0 is the + // minimum supported by this package. // - // The client-side default can temporarily be reverted to TLS 1.0 by - // including the value "x509sha1=1" in the GODEBUG environment variable. - // Note that this option will be removed in Go 1.19 (but it will still be - // possible to set this field to VersionTLS10 explicitly). + // The server-side default can be reverted to TLS 1.0 by including the value + // "tls10server=1" in the GODEBUG environment variable. MinVersion uint16 // MaxVersion contains the maximum TLS version that is acceptable. @@ -891,7 +892,7 @@ func (c *Config) ticketKeyFromBytes(b [32]byte) (key ticketKey) { // ticket, and the lifetime we set for all tickets we send. const maxSessionTicketLifetime = 7 * 24 * time.Hour -// Clone returns a shallow clone of c or nil if c is nil. It is safe to clone a Config that is +// Clone returns a shallow clone of c or nil if c is nil. It is safe to clone a [Config] that is // being used concurrently by a TLS client or server. func (c *Config) Clone() *Config { if c == nil { @@ -1083,6 +1084,8 @@ func (c *Config) time() time.Time { return t() } +// var tlsrsakex = godebug.New("tlsrsakex") // [UTLS] unsupported + func (c *Config) cipherSuites() []uint16 { if needFIPS() { return fipsCipherSuites(c) @@ -1090,6 +1093,13 @@ func (c *Config) cipherSuites() []uint16 { if c.CipherSuites != nil { return c.CipherSuites } + + // [uTLS SECTION BEGIN] + // Disable unsupported godebug package + // if tlsrsakex.Value() == "1" { + // return defaultCipherSuitesWithRSAKex + // } + // [uTLS SECTION END] return defaultCipherSuites } @@ -1105,15 +1115,24 @@ var supportedVersions = []uint16{ const roleClient = true const roleServer = false +// var tls10server = godebug.New("tls10server") // [UTLS] unsupported + func (c *Config) supportedVersions(isClient bool) []uint16 { versions := make([]uint16, 0, len(supportedVersions)) for _, v := range supportedVersions { if needFIPS() && (v < fipsMinVersion(c) || v > fipsMaxVersion(c)) { continue } - if (c == nil || c.MinVersion == 0) && - isClient && v < VersionTLS12 { - continue + if (c == nil || c.MinVersion == 0) && v < VersionTLS12 { + // [uTLS SECTION BEGIN] + // Disable unsupported godebug package + // if isClient || tls10server.Value() != "1" { + // continue + // } + if isClient { + continue + } + // [uTLS SECTION END] } if c != nil && c.MinVersion != 0 && v < c.MinVersion { continue @@ -1234,9 +1253,9 @@ func (c *Config) getCertificate(clientHello *ClientHelloInfo) (*Certificate, err // the client that sent the ClientHello. Otherwise, it returns an error // describing the reason for the incompatibility. // -// If this ClientHelloInfo was passed to a GetConfigForClient or GetCertificate -// callback, this method will take into account the associated Config. Note that -// if GetConfigForClient returns a different Config, the change can't be +// If this [ClientHelloInfo] was passed to a GetConfigForClient or GetCertificate +// callback, this method will take into account the associated [Config]. Note that +// if GetConfigForClient returns a different [Config], the change can't be // accounted for by this method. // // This function will call x509.ParseCertificate unless c.Leaf is set, which can @@ -1527,7 +1546,7 @@ type lruSessionCacheEntry struct { state *ClientSessionState } -// NewLRUClientSessionCache returns a ClientSessionCache with the given +// NewLRUClientSessionCache returns a [ClientSessionCache] with the given // capacity that uses an LRU strategy. If capacity is < 1, a default capacity // is used instead. func NewLRUClientSessionCache(capacity int) ClientSessionCache { @@ -1576,7 +1595,7 @@ func (c *lruSessionCache) Put(sessionKey string, cs *ClientSessionState) { c.m[sessionKey] = elem } -// Get returns the ClientSessionState value associated with a given key. It +// Get returns the [ClientSessionState] value associated with a given key. It // returns (nil, false) if no value is found. func (c *lruSessionCache) Get(sessionKey string) (*ClientSessionState, bool) { c.Lock() diff --git a/conn.go b/conn.go index a61bd04f..36222bdd 100644 --- a/conn.go +++ b/conn.go @@ -138,21 +138,21 @@ func (c *Conn) RemoteAddr() net.Addr { } // SetDeadline sets the read and write deadlines associated with the connection. -// A zero value for t means Read and Write will not time out. +// A zero value for t means [Conn.Read] and [Conn.Write] will not time out. // After a Write has timed out, the TLS state is corrupt and all future writes will return the same error. func (c *Conn) SetDeadline(t time.Time) error { return c.conn.SetDeadline(t) } // SetReadDeadline sets the read deadline on the underlying connection. -// A zero value for t means Read will not time out. +// A zero value for t means [Conn.Read] will not time out. func (c *Conn) SetReadDeadline(t time.Time) error { return c.conn.SetReadDeadline(t) } // SetWriteDeadline sets the write deadline on the underlying connection. -// A zero value for t means Write will not time out. -// After a Write has timed out, the TLS state is corrupt and all future writes will return the same error. +// A zero value for t means [Conn.Write] will not time out. +// After a [Conn.Write] has timed out, the TLS state is corrupt and all future writes will return the same error. func (c *Conn) SetWriteDeadline(t time.Time) error { return c.conn.SetWriteDeadline(t) } @@ -1184,10 +1184,10 @@ var ( // Write writes data to the connection. // -// As Write calls Handshake, in order to prevent indefinite blocking a deadline -// must be set for both Read and Write before Write is called when the handshake -// has not yet completed. See SetDeadline, SetReadDeadline, and -// SetWriteDeadline. +// As Write calls [Conn.Handshake], in order to prevent indefinite blocking a deadline +// must be set for both [Conn.Read] and Write before Write is called when the handshake +// has not yet completed. See [Conn.SetDeadline], [Conn.SetReadDeadline], and +// [Conn.SetWriteDeadline]. func (c *Conn) Write(b []byte) (int, error) { // interlock with Close below for { @@ -1359,10 +1359,10 @@ func (c *Conn) handleKeyUpdate(keyUpdate *keyUpdateMsg) error { // Read reads data from the connection. // -// As Read calls Handshake, in order to prevent indefinite blocking a deadline -// must be set for both Read and Write before Read is called when the handshake -// has not yet completed. See SetDeadline, SetReadDeadline, and -// SetWriteDeadline. +// As Read calls [Conn.Handshake], in order to prevent indefinite blocking a deadline +// must be set for both Read and [Conn.Write] before Read is called when the handshake +// has not yet completed. See [Conn.SetDeadline], [Conn.SetReadDeadline], and +// [Conn.SetWriteDeadline]. func (c *Conn) Read(b []byte) (int, error) { if err := c.Handshake(); err != nil { return 0, err @@ -1446,7 +1446,7 @@ var errEarlyCloseWrite = errors.New("tls: CloseWrite called before handshake com // CloseWrite shuts down the writing side of the connection. It should only be // called once the handshake has completed and does not call CloseWrite on the -// underlying connection. Most callers should just use Close. +// underlying connection. Most callers should just use [Conn.Close]. func (c *Conn) CloseWrite() error { if !c.isHandshakeComplete.Load() { return errEarlyCloseWrite @@ -1474,10 +1474,15 @@ func (c *Conn) closeNotify() error { // protocol if it has not yet been run. // // Most uses of this package need not call Handshake explicitly: the -// first Read or Write will call it automatically. +// first [Conn.Read] or [Conn.Write] will call it automatically. // // For control over canceling or setting a timeout on a handshake, use -// HandshakeContext or the Dialer's DialContext method instead. +// [Conn.HandshakeContext] or the [Dialer]'s DialContext method instead. +// +// In order to avoid denial of service attacks, the maximum RSA key size allowed +// in certificates sent by either the TLS server or client is limited to 8192 +// bits. This limit can be overridden by setting tlsmaxrsasize in the GODEBUG +// environment variable (e.g. GODEBUG=tlsmaxrsasize=4096). func (c *Conn) Handshake() error { return c.HandshakeContext(context.Background()) } @@ -1491,7 +1496,7 @@ func (c *Conn) Handshake() error { // connection. // // Most uses of this package need not call HandshakeContext explicitly: the -// first Read or Write will call it automatically. +// first [Conn.Read] or [Conn.Write] will call it automatically. func (c *Conn) HandshakeContext(ctx context.Context) error { // Delegate to unexported method for named return // without confusing documented signature. @@ -1605,6 +1610,8 @@ func (c *Conn) ConnectionState() ConnectionState { return c.connectionStateLocked() } +// var tlsunsafeekm = godebug.New("tlsunsafeekm") // [uTLS] unsupportted + func (c *Conn) connectionStateLocked() ConnectionState { var state ConnectionState state.HandshakeComplete = c.isHandshakeComplete.Load() @@ -1626,7 +1633,18 @@ func (c *Conn) connectionStateLocked() ConnectionState { } } if c.config.Renegotiation != RenegotiateNever { - state.ekm = noExportedKeyingMaterial + state.ekm = noEKMBecauseRenegotiation + } else if c.vers != VersionTLS13 && !c.extMasterSecret { + state.ekm = func(label string, context []byte, length int) ([]byte, error) { + // [uTLS SECTION START] + // Disabling unsupported godebug package + // if tlsunsafeekm.Value() == "1" { + // tlsunsafeekm.IncNonDefault() + // return c.ekm(label, context, length) + // } + // [uTLS SECTION END] + return noEKMBecauseNoEMS(label, context, length) + } } else { state.ekm = c.ekm } diff --git a/fipsonly/fipsonly.go b/fipsonly/fipsonly.go new file mode 100644 index 00000000..e5e47835 --- /dev/null +++ b/fipsonly/fipsonly.go @@ -0,0 +1,29 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build boringcrypto + +// Package fipsonly restricts all TLS configuration to FIPS-approved settings. +// +// The effect is triggered by importing the package anywhere in a program, as in: +// +// import _ "crypto/tls/fipsonly" +// +// This package only exists when using Go compiled with GOEXPERIMENT=boringcrypto. +package fipsonly + +// This functionality is provided as a side effect of an import to make +// it trivial to add to an existing program. It requires only a single line +// added to an existing source file, or it can be done by adding a whole +// new source file and not modifying any existing source files. + +import ( + "crypto/internal/boring/fipstls" + "crypto/internal/boring/sig" +) + +func init() { + fipstls.Force() + sig.FIPSOnly() +} diff --git a/fipsonly/fipsonly_test.go b/fipsonly/fipsonly_test.go new file mode 100644 index 00000000..f8485dc3 --- /dev/null +++ b/fipsonly/fipsonly_test.go @@ -0,0 +1,18 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build boringcrypto + +package fipsonly + +import ( + "crypto/internal/boring/fipstls" + "testing" +) + +func Test(t *testing.T) { + if !fipstls.Required() { + t.Fatal("fipstls.Required() = false, must be true") + } +} diff --git a/handshake_client.go b/handshake_client.go index 0b1460c7..b142b4d6 100644 --- a/handshake_client.go +++ b/handshake_client.go @@ -41,7 +41,7 @@ type clientHandshakeState struct { var testingOnlyForceClientHelloSignatureAlgorithms []SignatureScheme -func (c *Conn) makeClientHello() (*clientHelloMsg, clientKeySharePrivate, error) { +func (c *Conn) makeClientHello() (*clientHelloMsg, clientKeySharePrivate, error) { // [uTLS] using clientKeySharePrivate instead of ecdheKey config := c.config // [UTLS SECTION START] @@ -132,19 +132,23 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, clientKeySharePrivate, error) } if hello.vers >= VersionTLS12 { + // hello.supportedSignatureAlgorithms = supportedSignatureAlgorithms() hello.supportedSignatureAlgorithms = config.supportedSignatureAlgorithms() // [UTLS] ported from cloudflare/go } if testingOnlyForceClientHelloSignatureAlgorithms != nil { hello.supportedSignatureAlgorithms = testingOnlyForceClientHelloSignatureAlgorithms } + // var key *ecdh.PrivateKey var secret clientKeySharePrivate // [UTLS] if hello.supportedVersions[0] == VersionTLS13 { // Reset the list of ciphers when the client only supports TLS 1.3. if len(hello.supportedVersions) == 1 { hello.cipherSuites = nil } - if hasAESGCMHardwareSupport { + if needFIPS() { + hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13FIPS...) + } else if hasAESGCMHardwareSupport { hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13...) } else { hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13NoAES...) @@ -201,7 +205,8 @@ func (c *Conn) clientHandshake(ctx context.Context) (err error) { // need to be reset. c.didResume = false - hello, keySharePrivate, err := c.makeClientHello() + // hello, ecdheKey, err := c.makeClientHello() + hello, keySharePrivate, err := c.makeClientHello() // [uTLS] using keySharePrivate instead of ecdheKey if err != nil { return err } @@ -275,7 +280,7 @@ func (c *Conn) clientHandshake(ctx context.Context) (err error) { ctx: ctx, serverHello: serverHello, hello: hello, - // ecdheKey: ecdheKey, + // ecdheKey: ecdheKey, // [uTLS] session: session, earlySecret: earlySecret, binderKey: binderKey, @@ -471,6 +476,7 @@ func (c *Conn) loadSession(hello *clientHelloMsg) ( if err := hello.updateBinders(pskBinders); err != nil { return nil, nil, nil, err } + return } @@ -586,6 +592,13 @@ func (hs *clientHandshakeState) pickCipherSuite() error { return errors.New("tls: server chose an unconfigured cipher suite") } + // [UTLS SECTION START] + // Disable unsupported godebug packages + // if hs.c.config.CipherSuites == nil && rsaKexCiphers[hs.suite.id] { + // tlsrsakex.IncNonDefault() + // } + // [UTLS SECTION END] + hs.c.cipherSuite = hs.suite.id return nil } @@ -1002,9 +1015,26 @@ func (hs *clientHandshakeState) sendFinished(out []byte) error { return nil } -// maxRSAKeySize is the maximum RSA key size in bits that we are willing +// defaultMaxRSAKeySize is the maximum RSA key size in bits that we are willing // to verify the signatures of during a TLS handshake. -const maxRSAKeySize = 8192 +const defaultMaxRSAKeySize = 8192 + +// var tlsmaxrsasize = godebug.New("tlsmaxrsasize") // [uTLS] unused + +func checkKeySize(n int) (max int, ok bool) { + // [uTLS SECTION START] + // Disable the unsupported godebug package + // if v := tlsmaxrsasize.Value(); v != "" { + // if max, err := strconv.Atoi(v); err == nil { + // if (n <= max) != (n <= defaultMaxRSAKeySize) { + // tlsmaxrsasize.IncNonDefault() + // } + // return max, n <= max + // } + // } + // [uTLS SECTION END] + return defaultMaxRSAKeySize, n <= defaultMaxRSAKeySize +} // verifyServerCertificate parses and verifies the provided chain, setting // c.verifiedChains and c.peerCertificates or sending the appropriate alert. @@ -1017,9 +1047,12 @@ func (c *Conn) verifyServerCertificate(certificates [][]byte) error { c.sendAlert(alertBadCertificate) return errors.New("tls: failed to parse certificate from server: " + err.Error()) } - if cert.cert.PublicKeyAlgorithm == x509.RSA && cert.cert.PublicKey.(*rsa.PublicKey).N.BitLen() > maxRSAKeySize { - c.sendAlert(alertBadCertificate) - return fmt.Errorf("tls: server sent certificate containing RSA key larger than %d bits", maxRSAKeySize) + if cert.cert.PublicKeyAlgorithm == x509.RSA { + n := cert.cert.PublicKey.(*rsa.PublicKey).N.BitLen() + if max, ok := checkKeySize(n); !ok { + c.sendAlert(alertBadCertificate) + return fmt.Errorf("tls: server sent certificate containing RSA key larger than %d bits", max) + } } activeHandles[i] = cert certs[i] = cert.cert diff --git a/handshake_client_tls13.go b/handshake_client_tls13.go index 52c71390..e4f955e0 100644 --- a/handshake_client_tls13.go +++ b/handshake_client_tls13.go @@ -87,7 +87,7 @@ type clientHandshakeStateTLS13 struct { session *SessionState earlySecret []byte binderKey []byte - selectedGroup CurveID + selectedGroup CurveID // [uTLS] ported from cloudflare/go certReq *certificateRequestMsgTLS13 usingPSK bool diff --git a/handshake_server.go b/handshake_server.go index c29e9a32..ca10a3a2 100644 --- a/handshake_server.go +++ b/handshake_server.go @@ -171,6 +171,13 @@ func (c *Conn) readClientHello(ctx context.Context) (*clientHelloMsg, error) { c.in.version = c.vers c.out.version = c.vers + // [UTLS SECTION BEGIN] + // Disable unsupported godebug package + // if c.config.MinVersion == 0 && c.vers < VersionTLS12 { + // tls10server.IncNonDefault() + // } + // [UTLS SECTION END] + return clientHello, nil } @@ -369,6 +376,13 @@ func (hs *serverHandshakeState) pickCipherSuite() error { } c.cipherSuite = hs.suite.id + // [UTLS SECTION BEGIN] + // Disable unsupported godebug package + // if c.config.CipherSuites == nil && rsaKexCiphers[hs.suite.id] { + // tlsrsakex.IncNonDefault() + // } + // [UTLS SECTION END] + for _, id := range hs.clientHello.cipherSuites { if id == TLS_FALLBACK_SCSV { // The client is doing a fallback connection. See RFC 7507. @@ -870,9 +884,12 @@ func (c *Conn) processCertsFromClient(certificate Certificate) error { c.sendAlert(alertBadCertificate) return errors.New("tls: failed to parse client certificate: " + err.Error()) } - if certs[i].PublicKeyAlgorithm == x509.RSA && certs[i].PublicKey.(*rsa.PublicKey).N.BitLen() > maxRSAKeySize { - c.sendAlert(alertBadCertificate) - return fmt.Errorf("tls: client sent certificate containing RSA key larger than %d bits", maxRSAKeySize) + if certs[i].PublicKeyAlgorithm == x509.RSA { + n := certs[i].PublicKey.(*rsa.PublicKey).N.BitLen() + if max, ok := checkKeySize(n); !ok { + c.sendAlert(alertBadCertificate) + return fmt.Errorf("tls: client sent certificate containing RSA key larger than %d bits", max) + } } } diff --git a/handshake_server_test.go b/handshake_server_test.go index 04abdcca..c0a86a49 100644 --- a/handshake_server_test.go +++ b/handshake_server_test.go @@ -27,6 +27,7 @@ import ( ) func testClientHello(t *testing.T, serverConfig *Config, m handshakeMessage) { + t.Helper() testClientHelloFailure(t, serverConfig, m, "") } @@ -52,23 +53,32 @@ func testClientHelloFailure(t *testing.T, serverConfig *Config, m handshakeMessa ctx := context.Background() conn := Server(s, serverConfig) ch, err := conn.readClientHello(ctx) - hs := serverHandshakeState{ - c: conn, - ctx: ctx, - clientHello: ch, - } - if err == nil { + if err == nil && conn.vers == VersionTLS13 { + hs := serverHandshakeStateTLS13{ + c: conn, + ctx: ctx, + clientHello: ch, + } err = hs.processClientHello() - } - if err == nil { - err = hs.pickCipherSuite() + } else if err == nil { + hs := serverHandshakeState{ + c: conn, + ctx: ctx, + clientHello: ch, + } + err = hs.processClientHello() + if err == nil { + err = hs.pickCipherSuite() + } } s.Close() if len(expectedSubStr) == 0 { if err != nil && err != io.EOF { + t.Helper() t.Errorf("Got error: %s; expected to succeed", err) } } else if err == nil || !strings.Contains(err.Error(), expectedSubStr) { + t.Helper() t.Errorf("Got error: %v; expected to match substring '%s'", err, expectedSubStr) } } @@ -389,21 +399,22 @@ func TestClose(t *testing.T) { func TestVersion(t *testing.T) { serverConfig := &Config{ Certificates: testConfig.Certificates, - MaxVersion: VersionTLS11, + MaxVersion: VersionTLS13, } clientConfig := &Config{ InsecureSkipVerify: true, - MinVersion: VersionTLS10, + MinVersion: VersionTLS12, } state, _, err := testHandshake(t, clientConfig, serverConfig) if err != nil { t.Fatalf("handshake failed: %s", err) } - if state.Version != VersionTLS11 { + if state.Version != VersionTLS13 { t.Fatalf("incorrect version %x, should be %x", state.Version, VersionTLS11) } clientConfig.MinVersion = 0 + serverConfig.MaxVersion = VersionTLS11 _, _, err = testHandshake(t, clientConfig, serverConfig) if err == nil { t.Fatalf("expected failure to connect with TLS 1.0/1.1") @@ -487,17 +498,17 @@ func testCrossVersionResume(t *testing.T, version uint16) { InsecureSkipVerify: true, ClientSessionCache: NewLRUClientSessionCache(1), ServerName: "servername", - MinVersion: VersionTLS10, + MinVersion: VersionTLS12, } - // Establish a session at TLS 1.1. - clientConfig.MaxVersion = VersionTLS11 + // Establish a session at TLS 1.3. + clientConfig.MaxVersion = VersionTLS13 _, _, err := testHandshake(t, clientConfig, serverConfig) if err != nil { t.Fatalf("handshake failed: %s", err) } - // The client session cache now contains a TLS 1.1 session. + // The client session cache now contains a TLS 1.3 session. state, _, err := testHandshake(t, clientConfig, serverConfig) if err != nil { t.Fatalf("handshake failed: %s", err) @@ -507,7 +518,7 @@ func testCrossVersionResume(t *testing.T, version uint16) { } // Test that the server will decline to resume at a lower version. - clientConfig.MaxVersion = VersionTLS10 + clientConfig.MaxVersion = VersionTLS12 state, _, err = testHandshake(t, clientConfig, serverConfig) if err != nil { t.Fatalf("handshake failed: %s", err) @@ -516,7 +527,7 @@ func testCrossVersionResume(t *testing.T, version uint16) { t.Fatalf("handshake resumed at a lower version") } - // The client session cache now contains a TLS 1.0 session. + // The client session cache now contains a TLS 1.2 session. state, _, err = testHandshake(t, clientConfig, serverConfig) if err != nil { t.Fatalf("handshake failed: %s", err) @@ -526,7 +537,7 @@ func testCrossVersionResume(t *testing.T, version uint16) { } // Test that the server will decline to resume at a higher version. - clientConfig.MaxVersion = VersionTLS11 + clientConfig.MaxVersion = VersionTLS13 state, _, err = testHandshake(t, clientConfig, serverConfig) if err != nil { t.Fatalf("handshake failed: %s", err) @@ -1170,6 +1181,7 @@ func TestServerResumptionDisabled(t *testing.T) { func TestFallbackSCSV(t *testing.T) { serverConfig := Config{ Certificates: testConfig.Certificates, + MinVersion: VersionTLS11, } test := &serverTest{ name: "FallbackSCSV", diff --git a/handshake_server_tls13.go b/handshake_server_tls13.go index 43192b3d..f0e4b70a 100644 --- a/handshake_server_tls13.go +++ b/handshake_server_tls13.go @@ -165,6 +165,9 @@ func (hs *serverHandshakeStateTLS13) processClientHello() error { if !hasAESGCMHardwareSupport || !aesgcmPreferred(hs.clientHello.cipherSuites) { preferenceList = defaultCipherSuitesTLS13NoAES } + if needFIPS() { + preferenceList = defaultCipherSuitesTLS13FIPS + } for _, suiteID := range preferenceList { hs.suite = mutualCipherSuiteTLS13(hs.clientHello.cipherSuites, suiteID) if hs.suite != nil { @@ -213,6 +216,8 @@ GroupSelection: clientKeyShare = &hs.clientHello.keyShares[0] } + // [uTLS SECTION BEGIN] + // ported from cloudflare/go if _, ok := curveForCurveID(selectedGroup); selectedGroup != X25519 && curveIdToCirclScheme(selectedGroup) == nil && !ok { c.sendAlert(alertInternalError) return errors.New("tls: CurvePreferences includes unsupported curve") @@ -241,6 +246,7 @@ GroupSelection: c.sendAlert(alertIllegalParameter) return errors.New("tls: invalid client key share") } + // [uTLS SECTION END] selectedProto, err := negotiateALPN(c.config.NextProtos, hs.clientHello.alpnProtocols, c.quic != nil) if err != nil { @@ -250,8 +256,15 @@ GroupSelection: c.clientProtocol = selectedProto if c.quic != nil { + // RFC 9001 Section 4.2: Clients MUST NOT offer TLS versions older than 1.3. + for _, v := range hs.clientHello.supportedVersions { + if v < VersionTLS13 { + c.sendAlert(alertProtocolVersion) + return errors.New("tls: client offered TLS version older than TLS 1.3") + } + } + // RFC 9001 Section 8.2. if hs.clientHello.quicTransportParameters == nil { - // RFC 9001 Section 8.2. c.sendAlert(alertMissingExtension) return errors.New("tls: client did not send a quic_transport_parameters extension") } diff --git a/internal/boring/notboring.go b/internal/boring/notboring.go index ebc2fd50..913afd5d 100644 --- a/internal/boring/notboring.go +++ b/internal/boring/notboring.go @@ -11,6 +11,10 @@ func NewGCMTLS(_ cipher.Block) (cipher.AEAD, error) { return nil, errors.New("boring not implemented") } +func NewGCMTLS13(_ cipher.Block) (cipher.AEAD, error) { + return nil, errors.New("boring not implemented") +} + func Unreachable() { // do nothing } diff --git a/key_agreement.go b/key_agreement.go index 3c733454..ab103880 100644 --- a/key_agreement.go +++ b/key_agreement.go @@ -169,7 +169,7 @@ type ecdheKeyAgreement struct { func (ka *ecdheKeyAgreement) generateServerKeyExchange(config *Config, cert *Certificate, clientHello *clientHelloMsg, hello *serverHelloMsg) (*serverKeyExchangeMsg, error) { var curveID CurveID for _, c := range clientHello.supportedCurves { - if config.supportsCurve(c) && curveIdToCirclScheme(c) == nil { + if config.supportsCurve(c) && curveIdToCirclScheme(c) == nil { // [uTLS] ported from cloudflare/go curveID = c break } diff --git a/notboring.go b/notboring.go index bb8f61c1..82dd5977 100644 --- a/notboring.go +++ b/notboring.go @@ -15,3 +15,5 @@ func fipsCurvePreferences(c *Config) []CurveID { panic("fipsCurvePreferences") } func fipsCipherSuites(c *Config) []uint16 { panic("fipsCipherSuites") } var fipsSupportedSignatureAlgorithms []SignatureScheme + +var defaultCipherSuitesTLS13FIPS []uint16 diff --git a/prf.go b/prf.go index c2a024f4..c525facd 100644 --- a/prf.go +++ b/prf.go @@ -252,13 +252,20 @@ func (h *finishedHash) discardHandshakeBuffer() { h.buffer = nil } -// noExportedKeyingMaterial is used as a value of +// noEKMBecauseRenegotiation is used as a value of // ConnectionState.ekm when renegotiation is enabled and thus // we wish to fail all key-material export requests. -func noExportedKeyingMaterial(label string, context []byte, length int) ([]byte, error) { +func noEKMBecauseRenegotiation(label string, context []byte, length int) ([]byte, error) { return nil, errors.New("crypto/tls: ExportKeyingMaterial is unavailable when renegotiation is enabled") } +// noEKMBecauseNoEMS is used as a value of ConnectionState.ekm when Extended +// Master Secret is not negotiated and thus we wish to fail all key-material +// export requests. +func noEKMBecauseNoEMS(label string, context []byte, length int) ([]byte, error) { + return nil, errors.New("crypto/tls: ExportKeyingMaterial is unavailable when neither TLS 1.3 nor Extended Master Secret are negotiated; override with GODEBUG=tlsunsafeekm=1") +} + // ekmFromMasterSecret generates exported keying material as defined in RFC 5705. func ekmFromMasterSecret(version uint16, suite *cipherSuite, masterSecret, clientRandom, serverRandom []byte) func(string, []byte, int) ([]byte, error) { return func(label string, context []byte, length int) ([]byte, error) { diff --git a/quic.go b/quic.go index ba5c2af0..3518169b 100644 --- a/quic.go +++ b/quic.go @@ -46,7 +46,7 @@ type QUICConn struct { sessionTicketSent bool } -// A QUICConfig configures a QUICConn. +// A QUICConfig configures a [QUICConn]. type QUICConfig struct { TLSConfig *Config } @@ -163,7 +163,7 @@ func newQUICConn(conn *Conn) *QUICConn { } // Start starts the client or server handshake protocol. -// It may produce connection events, which may be read with NextEvent. +// It may produce connection events, which may be read with [QUICConn.NextEvent]. // // Start must be called at most once. func (q *QUICConn) Start(ctx context.Context) error { @@ -182,7 +182,7 @@ func (q *QUICConn) Start(ctx context.Context) error { } // NextEvent returns the next event occurring on the connection. -// It returns an event with a Kind of QUICNoEvent when no events are available. +// It returns an event with a Kind of [QUICNoEvent] when no events are available. func (q *QUICConn) NextEvent() QUICEvent { qs := q.conn.quic if last := qs.nextEvent - 1; last >= 0 && len(qs.events[last].Data) > 0 { @@ -214,7 +214,7 @@ func (q *QUICConn) Close() error { } // HandleData handles handshake bytes received from the peer. -// It may produce connection events, which may be read with NextEvent. +// It may produce connection events, which may be read with [QUICConn.NextEvent]. func (q *QUICConn) HandleData(level QUICEncryptionLevel, data []byte) error { c := q.conn if c.in.level != level { @@ -258,7 +258,7 @@ type QUICSessionTicketOptions struct { } // SendSessionTicket sends a session ticket to the client. -// It produces connection events, which may be read with NextEvent. +// It produces connection events, which may be read with [QUICConn.NextEvent]. // Currently, it can only be called once. func (q *QUICConn) SendSessionTicket(opts QUICSessionTicketOptions) error { c := q.conn @@ -283,7 +283,7 @@ func (q *QUICConn) ConnectionState() ConnectionState { // SetTransportParameters sets the transport parameters to send to the peer. // // Server connections may delay setting the transport parameters until after -// receiving the client's transport parameters. See QUICTransportParametersRequired. +// receiving the client's transport parameters. See [QUICTransportParametersRequired]. func (q *QUICConn) SetTransportParameters(params []byte) { if params == nil { params = []byte{} diff --git a/ticket.go b/ticket.go index a5548804..182411e7 100644 --- a/ticket.go +++ b/ticket.go @@ -69,7 +69,7 @@ type SessionState struct { // To allow different layers in a protocol stack to share this field, // applications must only append to it, not replace it, and must use entries // that can be recognized even if out of order (for example, by starting - // with a id and version prefix). + // with an id and version prefix). Extra [][]byte // EarlyData indicates whether the ticket can be used for 0-RTT in a QUIC @@ -305,7 +305,7 @@ func (c *Conn) sessionState() (*SessionState, error) { }, nil } -// EncryptTicket encrypts a ticket with the Config's configured (or default) +// EncryptTicket encrypts a ticket with the [Config]'s configured (or default) // session ticket keys. It can be used as a [Config.WrapSession] implementation. func (c *Config) EncryptTicket(cs ConnectionState, ss *SessionState) ([]byte, error) { ticketKeys := c.ticketKeys(nil) diff --git a/tls.go b/tls.go index c4f6f390..4168eeed 100644 --- a/tls.go +++ b/tls.go @@ -73,7 +73,7 @@ func (l *listener) Accept() (net.Conn, error) { } // NewListener creates a Listener which accepts connections from an inner -// Listener and wraps each connection with Server. +// Listener and wraps each connection with [Server]. // The configuration config must be non-nil and must include // at least one certificate or else set GetCertificate. func NewListener(inner net.Listener, config *Config) net.Listener { @@ -111,10 +111,10 @@ func (timeoutError) Temporary() bool { return true } // handshake as a whole. // // DialWithDialer interprets a nil configuration as equivalent to the zero -// configuration; see the documentation of Config for the defaults. +// configuration; see the documentation of [Config] for the defaults. // // DialWithDialer uses context.Background internally; to specify the context, -// use Dialer.DialContext with NetDialer set to the desired dialer. +// use [Dialer.DialContext] with NetDialer set to the desired dialer. func DialWithDialer(dialer *net.Dialer, network, addr string, config *Config) (*Conn, error) { return dial(context.Background(), dialer, network, addr, config) } @@ -191,10 +191,10 @@ type Dialer struct { // Dial connects to the given network address and initiates a TLS // handshake, returning the resulting TLS connection. // -// The returned Conn, if any, will always be of type *Conn. +// The returned [Conn], if any, will always be of type *[Conn]. // // Dial uses context.Background internally; to specify the context, -// use DialContext. +// use [Dialer.DialContext]. func (d *Dialer) Dial(network, addr string) (net.Conn, error) { return d.DialContext(context.Background(), network, addr) } @@ -214,7 +214,7 @@ func (d *Dialer) netDialer() *net.Dialer { // connected, any expiration of the context will not affect the // connection. // -// The returned Conn, if any, will always be of type *Conn. +// The returned [Conn], if any, will always be of type *[Conn]. func (d *Dialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) { c, err := dial(ctx, d.netDialer(), network, addr, d.Config) if err != nil { @@ -358,7 +358,7 @@ func parsePrivateKey(der []byte) (crypto.PrivateKey, error) { } if key, err := x509.ParsePKCS8PrivateKey(der); err == nil { switch key := key.(type) { - case *rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey, circlSign.PrivateKey: + case *rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey, circlSign.PrivateKey: // [uTLS] ported from cloudflare/go return key, nil default: return nil, errors.New("tls: found unknown private key type in PKCS#8 wrapping") diff --git a/tls_test.go b/tls_test.go index 3d6f0bea..cecc32f3 100644 --- a/tls_test.go +++ b/tls_test.go @@ -1297,7 +1297,8 @@ func TestClientHelloInfo_SupportsCertificate(t *testing.T) { SignatureSchemes: []SignatureScheme{PKCS1WithSHA1}, SupportedVersions: []uint16{VersionTLS13, VersionTLS12}, config: &Config{ - MaxVersion: VersionTLS12, + CipherSuites: []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256}, + MaxVersion: VersionTLS12, }, }, ""}, // Check that mutual version selection works. @@ -1374,6 +1375,7 @@ func TestClientHelloInfo_SupportsCertificate(t *testing.T) { SupportedPoints: []uint8{pointFormatUncompressed}, SignatureSchemes: []SignatureScheme{Ed25519}, SupportedVersions: []uint16{VersionTLS10}, + config: &Config{MinVersion: VersionTLS10}, }, "doesn't support Ed25519"}, {ed25519Cert, &ClientHelloInfo{ CipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, @@ -1388,10 +1390,14 @@ func TestClientHelloInfo_SupportsCertificate(t *testing.T) { SupportedCurves: []CurveID{CurveP256}, // only relevant for ECDHE support SupportedPoints: []uint8{pointFormatUncompressed}, SupportedVersions: []uint16{VersionTLS10}, + config: &Config{MinVersion: VersionTLS10}, }, ""}, {rsaCert, &ClientHelloInfo{ CipherSuites: []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256}, SupportedVersions: []uint16{VersionTLS12}, + config: &Config{ + CipherSuites: []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256}, + }, }, ""}, // static RSA fallback } for i, tt := range tests { @@ -1493,24 +1499,21 @@ func TestCipherSuites(t *testing.T) { if len(cipherSuitesPreferenceOrderNoAES) != len(cipherSuitesPreferenceOrder) { t.Errorf("cipherSuitesPreferenceOrderNoAES is not the same size as cipherSuitesPreferenceOrder") } + if len(defaultCipherSuites) >= len(defaultCipherSuitesWithRSAKex) { + t.Errorf("defaultCipherSuitesWithRSAKex should be longer than defaultCipherSuites") + } - // Check that disabled suites are at the end of the preference lists, and - // that they are marked insecure. - for i, id := range disabledCipherSuites { - offset := len(cipherSuitesPreferenceOrder) - len(disabledCipherSuites) - if cipherSuitesPreferenceOrder[offset+i] != id { - t.Errorf("disabledCipherSuites[%d]: not at the end of cipherSuitesPreferenceOrder", i) - } - if cipherSuitesPreferenceOrderNoAES[offset+i] != id { - t.Errorf("disabledCipherSuites[%d]: not at the end of cipherSuitesPreferenceOrderNoAES", i) - } - c := CipherSuiteByID(id) - if c == nil { - t.Errorf("%#04x: no CipherSuite entry", id) - continue - } - if !c.Insecure { - t.Errorf("%#04x: disabled by default but not marked insecure", id) + // Check that disabled suites are marked insecure. + for _, badSuites := range []map[uint16]bool{disabledCipherSuites, rsaKexCiphers} { + for id := range badSuites { + c := CipherSuiteByID(id) + if c == nil { + t.Errorf("%#04x: no CipherSuite entry", id) + continue + } + if !c.Insecure { + t.Errorf("%#04x: disabled by default but not marked insecure", id) + } } }