diff --git a/auth.go b/auth.go index d595925..c389cd4 100644 --- a/auth.go +++ b/auth.go @@ -15,6 +15,8 @@ import ( "fmt" "hash" "io" + + "github.com/xtls/reality/fips140tls" ) // verifyHandshakeSignature verifies a signature against pre-hashed @@ -242,7 +244,7 @@ func selectSignatureScheme(vers uint16, c *Certificate, peerAlgs []SignatureSche // Pick signature scheme in the peer's preference order, as our // preference order is not configurable. for _, preferredAlg := range peerAlgs { - if needFIPS() && !isSupportedSignatureAlgorithm(preferredAlg, defaultSupportedSignatureAlgorithmsFIPS) { + if fips140tls.Required() && !isSupportedSignatureAlgorithm(preferredAlg, defaultSupportedSignatureAlgorithmsFIPS) { continue } if isSupportedSignatureAlgorithm(preferredAlg, supportedAlgs) { diff --git a/common.go b/common.go index 895ee85..61381c9 100644 --- a/common.go +++ b/common.go @@ -25,6 +25,8 @@ import ( "sync" "time" _ "unsafe" // for linkname + + "github.com/xtls/reality/fips140tls" ) const ( @@ -1085,12 +1087,12 @@ func (c *Config) time() time.Time { func (c *Config) cipherSuites() []uint16 { if c.CipherSuites == nil { - if needFIPS() { + if fips140tls.Required() { return defaultCipherSuitesFIPS } return defaultCipherSuites() } - if needFIPS() { + if fips140tls.Required() { cipherSuites := slices.Clone(c.CipherSuites) return slices.DeleteFunc(cipherSuites, func(id uint16) bool { return !slices.Contains(defaultCipherSuitesFIPS, id) @@ -1114,7 +1116,7 @@ const roleServer = false func (c *Config) supportedVersions(isClient bool) []uint16 { versions := make([]uint16, 0, len(supportedVersions)) for _, v := range supportedVersions { - if needFIPS() && !slices.Contains(defaultSupportedVersionsFIPS, v) { + if fips140tls.Required() && !slices.Contains(defaultSupportedVersionsFIPS, v) { continue } if (c == nil || c.MinVersion == 0) && v < VersionTLS12 { @@ -1160,12 +1162,12 @@ func (c *Config) curvePreferences(version uint16) []CurveID { var curvePreferences []CurveID if c != nil && len(c.CurvePreferences) != 0 { curvePreferences = slices.Clone(c.CurvePreferences) - if needFIPS() { + if fips140tls.Required() { return slices.DeleteFunc(curvePreferences, func(c CurveID) bool { return !slices.Contains(defaultCurvePreferencesFIPS, c) }) } - } else if needFIPS() { + } else if fips140tls.Required() { curvePreferences = slices.Clone(defaultCurvePreferencesFIPS) } else { curvePreferences = defaultCurvePreferences() @@ -1637,7 +1639,7 @@ func unexpectedMessageError(wanted, got any) error { // supportedSignatureAlgorithms returns the supported signature algorithms. func supportedSignatureAlgorithms() []SignatureScheme { - if !needFIPS() { + if !fips140tls.Required() { return defaultSupportedSignatureAlgorithms } return defaultSupportedSignatureAlgorithmsFIPS @@ -1666,3 +1668,56 @@ func (e *CertificateVerificationError) Error() string { func (e *CertificateVerificationError) Unwrap() error { return e.Err } + +// fipsAllowedChains returns chains that are allowed to be used in a TLS connection +// based on the current fips140tls enforcement setting. +// +// If fips140tls is not required, the chains are returned as-is with no processing. +// Otherwise, the returned chains are filtered to only those allowed by FIPS 140-3. +// If this results in no chains it returns an error. +func fipsAllowedChains(chains [][]*x509.Certificate) ([][]*x509.Certificate, error) { + if !fips140tls.Required() { + return chains, nil + } + + permittedChains := make([][]*x509.Certificate, 0, len(chains)) + for _, chain := range chains { + if fipsAllowChain(chain) { + permittedChains = append(permittedChains, chain) + } + } + + if len(permittedChains) == 0 { + return nil, errors.New("tls: no FIPS compatible certificate chains found") + } + + return permittedChains, nil +} + +func fipsAllowChain(chain []*x509.Certificate) bool { + if len(chain) == 0 { + return false + } + + for _, cert := range chain { + if !fipsAllowCert(cert) { + return false + } + } + + return true +} + +func fipsAllowCert(c *x509.Certificate) bool { + // The key must be RSA 2048, RSA 3072, RSA 4096, + // or ECDSA P-256, P-384, P-521. + switch k := c.PublicKey.(type) { + case *rsa.PublicKey: + size := k.N.BitLen() + return size == 2048 || size == 3072 || size == 4096 + case *ecdsa.PublicKey: + return k.Curve == elliptic.P256() || k.Curve == elliptic.P384() || k.Curve == elliptic.P521() + } + + return false +} \ No newline at end of file diff --git a/defaults.go b/defaults.go index 19dc10e..f2d9ff3 100644 --- a/defaults.go +++ b/defaults.go @@ -89,7 +89,9 @@ var defaultCipherSuitesTLS13NoAES = []uint16{ TLS_AES_256_GCM_SHA384, } -// The FIPS-only policies below match BoringSSL's ssl_policy_fips_202205. +// The FIPS-only policies below match BoringSSL's +// ssl_compliance_policy_fips_202205, which is based on NIST SP 800-52r2. +// https://cs.opensource.google/boringssl/boringssl/+/master:ssl/ssl_lib.cc;l=3289;drc=ea7a88fa var defaultSupportedVersionsFIPS = []uint16{ VersionTLS12, diff --git a/drbg/rand.go b/drbg/rand.go index 7fe0b57..a3255fe 100644 --- a/drbg/rand.go +++ b/drbg/rand.go @@ -14,6 +14,7 @@ import ( "github.com/xtls/reality/randutil" // "github.com/xtls/reality/sysrand" + "crypto/fips140" "crypto/rand" "io" "sync" @@ -33,10 +34,10 @@ var drbgs = sync.Pool{ // uses an SP 800-90A Rev. 1 Deterministic Random Bit Generator (DRBG). // Otherwise, it uses the operating system's random number generator. func Read(b []byte) { - // if !fips140.Enabled { - // rand.Read(b) - // return - // } + if !fips140.Enabled() { + rand.Read(b) + return + } // At every read, 128 random bits from the operating system are mixed as // additional input, to make the output as strong as non-FIPS randomness. diff --git a/fips140tls/fipstls.go b/fips140tls/fipstls.go new file mode 100644 index 0000000..3cf9b0f --- /dev/null +++ b/fips140tls/fipstls.go @@ -0,0 +1,37 @@ +// Copyright 2024 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. + +// Package fips140tls controls whether crypto/tls requires FIPS-approved settings. +package fips140tls + +import ( + "crypto/fips140" + "sync/atomic" +) + +var required atomic.Bool + +func init() { + if fips140.Enabled() { + Force() + } +} + +// Force forces crypto/tls to restrict TLS configurations to FIPS-approved settings. +// By design, this call is impossible to undo (except in tests). +func Force() { + required.Store(true) +} + +// Required reports whether FIPS-approved settings are required. +// +// Required is true if FIPS 140-3 mode is enabled with GODEBUG=fips140=on, or if +// the crypto/tls/fipsonly package is imported by a Go+BoringCrypto build. +func Required() bool { + return required.Load() +} + +func TestingOnlyAbandon() { + required.Store(false) +} \ No newline at end of file diff --git a/go.mod b/go.mod index f3e384f..ca25db1 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,9 @@ module github.com/xtls/reality -go 1.23 +go 1.24 require ( - github.com/pires/go-proxyproto v0.7.0 - golang.org/x/crypto v0.26.0 - golang.org/x/sys v0.24.0 + github.com/pires/go-proxyproto v0.8.1 + golang.org/x/crypto v0.37.0 + golang.org/x/sys v0.32.0 ) diff --git a/go.sum b/go.sum index 68bcfd9..be439aa 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,12 @@ github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs= github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4= +github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0= +github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU= golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= diff --git a/handshake_client.go b/handshake_client.go index e6a9420..781cfb7 100644 --- a/handshake_client.go +++ b/handshake_client.go @@ -22,6 +22,7 @@ import ( "time" "github.com/xtls/reality/byteorder" + "github.com/xtls/reality/fips140tls" "github.com/xtls/reality/hpke" "github.com/xtls/reality/mlkem" "github.com/xtls/reality/tls13" @@ -142,7 +143,7 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCon if len(hello.supportedVersions) == 1 { hello.cipherSuites = nil } - if needFIPS() { + if fips140tls.Required() { hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13FIPS...) } else if hasAESGCMHardwareSupport { hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13...) @@ -1105,8 +1106,13 @@ func (c *Conn) verifyServerCertificate(certificates [][]byte) error { for _, cert := range certs[1:] { opts.Intermediates.AddCert(cert) } - var err error - c.verifiedChains, err = certs[0].Verify(opts) + chains, err := certs[0].Verify(opts) + if err != nil { + c.sendAlert(alertBadCertificate) + return &CertificateVerificationError{UnverifiedCertificates: certs, Err: err} + } + + c.verifiedChains, err = fipsAllowedChains(chains) if err != nil { c.sendAlert(alertBadCertificate) return &CertificateVerificationError{UnverifiedCertificates: certs, Err: err} @@ -1123,8 +1129,13 @@ func (c *Conn) verifyServerCertificate(certificates [][]byte) error { for _, cert := range certs[1:] { opts.Intermediates.AddCert(cert) } - var err error - c.verifiedChains, err = certs[0].Verify(opts) + chains, err := certs[0].Verify(opts) + if err != nil { + c.sendAlert(alertBadCertificate) + return &CertificateVerificationError{UnverifiedCertificates: certs, Err: err} + } + + c.verifiedChains, err = fipsAllowedChains(chains) if err != nil { c.sendAlert(alertBadCertificate) return &CertificateVerificationError{UnverifiedCertificates: certs, Err: err} diff --git a/handshake_server.go b/handshake_server.go index 3201520..6958c95 100644 --- a/handshake_server.go +++ b/handshake_server.go @@ -910,7 +910,11 @@ func (c *Conn) processCertsFromClient(certificate Certificate) error { return &CertificateVerificationError{UnverifiedCertificates: certs, Err: err} } - c.verifiedChains = chains + c.verifiedChains, err = fipsAllowedChains(chains) + if err != nil { + c.sendAlert(alertBadCertificate) + return &CertificateVerificationError{UnverifiedCertificates: certs, Err: err} + } } c.peerCertificates = certs diff --git a/handshake_server_tls13.go b/handshake_server_tls13.go index 3f0f465..be75a1f 100644 --- a/handshake_server_tls13.go +++ b/handshake_server_tls13.go @@ -22,6 +22,7 @@ import ( "time" "github.com/xtls/reality/byteorder" + "github.com/xtls/reality/fips140tls" "github.com/xtls/reality/mlkem" "github.com/xtls/reality/tls13" ) @@ -215,7 +216,7 @@ func (hs *serverHandshakeStateTLS13) processClientHello() error { if !hasAESGCMHardwareSupport || !aesgcmPreferred(hs.clientHello.cipherSuites) { preferenceList = defaultCipherSuitesTLS13NoAES } - if needFIPS() { + if fips140tls.Required() { preferenceList = defaultCipherSuitesTLS13FIPS } for _, suiteID := range preferenceList { diff --git a/notboring.go b/notboring.go deleted file mode 100644 index 5fe954f..0000000 --- a/notboring.go +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright 2022 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-Go file. - -//go:build !boringcrypto - -package reality - -func needFIPS() bool { return false }