0
0
mirror of https://github.com/XTLS/REALITY.git synced 2025-08-24 15:38:36 +00:00

crypto/tls: FIPS 140-3 mode

Consolidates handling of FIPS 140-3 considerations for the tls package.
Considerations specific to certificates are now handled in tls instead
of x509 to limit the area-of-effect of FIPS as much as possible.
Boringcrypto specific prefixes are renamed as appropriate.

For #69536

Co-authored-by: Filippo Valsorda <filippo@golang.org>
Change-Id: I1b1fef83c3599e4c9b98ad81db582ac93253030b
Reviewed-on: https://go-review.googlesource.com/c/go/+/629675
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Russ Cox <rsc@golang.org>
Auto-Submit: Filippo Valsorda <filippo@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
yuhan6665 2025-05-04 23:10:11 -04:00
parent 0f3d0f2e71
commit 9cb8914b9f
11 changed files with 142 additions and 32 deletions

View File

@ -15,6 +15,8 @@ import (
"fmt" "fmt"
"hash" "hash"
"io" "io"
"github.com/xtls/reality/fips140tls"
) )
// verifyHandshakeSignature verifies a signature against pre-hashed // 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 // Pick signature scheme in the peer's preference order, as our
// preference order is not configurable. // preference order is not configurable.
for _, preferredAlg := range peerAlgs { for _, preferredAlg := range peerAlgs {
if needFIPS() && !isSupportedSignatureAlgorithm(preferredAlg, defaultSupportedSignatureAlgorithmsFIPS) { if fips140tls.Required() && !isSupportedSignatureAlgorithm(preferredAlg, defaultSupportedSignatureAlgorithmsFIPS) {
continue continue
} }
if isSupportedSignatureAlgorithm(preferredAlg, supportedAlgs) { if isSupportedSignatureAlgorithm(preferredAlg, supportedAlgs) {

View File

@ -25,6 +25,8 @@ import (
"sync" "sync"
"time" "time"
_ "unsafe" // for linkname _ "unsafe" // for linkname
"github.com/xtls/reality/fips140tls"
) )
const ( const (
@ -1085,12 +1087,12 @@ func (c *Config) time() time.Time {
func (c *Config) cipherSuites() []uint16 { func (c *Config) cipherSuites() []uint16 {
if c.CipherSuites == nil { if c.CipherSuites == nil {
if needFIPS() { if fips140tls.Required() {
return defaultCipherSuitesFIPS return defaultCipherSuitesFIPS
} }
return defaultCipherSuites() return defaultCipherSuites()
} }
if needFIPS() { if fips140tls.Required() {
cipherSuites := slices.Clone(c.CipherSuites) cipherSuites := slices.Clone(c.CipherSuites)
return slices.DeleteFunc(cipherSuites, func(id uint16) bool { return slices.DeleteFunc(cipherSuites, func(id uint16) bool {
return !slices.Contains(defaultCipherSuitesFIPS, id) return !slices.Contains(defaultCipherSuitesFIPS, id)
@ -1114,7 +1116,7 @@ const roleServer = false
func (c *Config) supportedVersions(isClient bool) []uint16 { func (c *Config) supportedVersions(isClient bool) []uint16 {
versions := make([]uint16, 0, len(supportedVersions)) versions := make([]uint16, 0, len(supportedVersions))
for _, v := range supportedVersions { for _, v := range supportedVersions {
if needFIPS() && !slices.Contains(defaultSupportedVersionsFIPS, v) { if fips140tls.Required() && !slices.Contains(defaultSupportedVersionsFIPS, v) {
continue continue
} }
if (c == nil || c.MinVersion == 0) && v < VersionTLS12 { if (c == nil || c.MinVersion == 0) && v < VersionTLS12 {
@ -1160,12 +1162,12 @@ func (c *Config) curvePreferences(version uint16) []CurveID {
var curvePreferences []CurveID var curvePreferences []CurveID
if c != nil && len(c.CurvePreferences) != 0 { if c != nil && len(c.CurvePreferences) != 0 {
curvePreferences = slices.Clone(c.CurvePreferences) curvePreferences = slices.Clone(c.CurvePreferences)
if needFIPS() { if fips140tls.Required() {
return slices.DeleteFunc(curvePreferences, func(c CurveID) bool { return slices.DeleteFunc(curvePreferences, func(c CurveID) bool {
return !slices.Contains(defaultCurvePreferencesFIPS, c) return !slices.Contains(defaultCurvePreferencesFIPS, c)
}) })
} }
} else if needFIPS() { } else if fips140tls.Required() {
curvePreferences = slices.Clone(defaultCurvePreferencesFIPS) curvePreferences = slices.Clone(defaultCurvePreferencesFIPS)
} else { } else {
curvePreferences = defaultCurvePreferences() curvePreferences = defaultCurvePreferences()
@ -1637,7 +1639,7 @@ func unexpectedMessageError(wanted, got any) error {
// supportedSignatureAlgorithms returns the supported signature algorithms. // supportedSignatureAlgorithms returns the supported signature algorithms.
func supportedSignatureAlgorithms() []SignatureScheme { func supportedSignatureAlgorithms() []SignatureScheme {
if !needFIPS() { if !fips140tls.Required() {
return defaultSupportedSignatureAlgorithms return defaultSupportedSignatureAlgorithms
} }
return defaultSupportedSignatureAlgorithmsFIPS return defaultSupportedSignatureAlgorithmsFIPS
@ -1666,3 +1668,56 @@ func (e *CertificateVerificationError) Error() string {
func (e *CertificateVerificationError) Unwrap() error { func (e *CertificateVerificationError) Unwrap() error {
return e.Err 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
}

View File

@ -89,7 +89,9 @@ var defaultCipherSuitesTLS13NoAES = []uint16{
TLS_AES_256_GCM_SHA384, 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{ var defaultSupportedVersionsFIPS = []uint16{
VersionTLS12, VersionTLS12,

View File

@ -14,6 +14,7 @@ import (
"github.com/xtls/reality/randutil" "github.com/xtls/reality/randutil"
// "github.com/xtls/reality/sysrand" // "github.com/xtls/reality/sysrand"
"crypto/fips140"
"crypto/rand" "crypto/rand"
"io" "io"
"sync" "sync"
@ -33,10 +34,10 @@ var drbgs = sync.Pool{
// uses an SP 800-90A Rev. 1 Deterministic Random Bit Generator (DRBG). // uses an SP 800-90A Rev. 1 Deterministic Random Bit Generator (DRBG).
// Otherwise, it uses the operating system's random number generator. // Otherwise, it uses the operating system's random number generator.
func Read(b []byte) { func Read(b []byte) {
// if !fips140.Enabled { if !fips140.Enabled() {
// rand.Read(b) rand.Read(b)
// return return
// } }
// At every read, 128 random bits from the operating system are mixed as // 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. // additional input, to make the output as strong as non-FIPS randomness.

37
fips140tls/fipstls.go Normal file
View File

@ -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)
}

8
go.mod
View File

@ -1,9 +1,9 @@
module github.com/xtls/reality module github.com/xtls/reality
go 1.23 go 1.24
require ( require (
github.com/pires/go-proxyproto v0.7.0 github.com/pires/go-proxyproto v0.8.1
golang.org/x/crypto v0.26.0 golang.org/x/crypto v0.37.0
golang.org/x/sys v0.24.0 golang.org/x/sys v0.32.0
) )

6
go.sum
View File

@ -1,6 +1,12 @@
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs= 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.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 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= 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 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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=

View File

@ -22,6 +22,7 @@ import (
"time" "time"
"github.com/xtls/reality/byteorder" "github.com/xtls/reality/byteorder"
"github.com/xtls/reality/fips140tls"
"github.com/xtls/reality/hpke" "github.com/xtls/reality/hpke"
"github.com/xtls/reality/mlkem" "github.com/xtls/reality/mlkem"
"github.com/xtls/reality/tls13" "github.com/xtls/reality/tls13"
@ -142,7 +143,7 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCon
if len(hello.supportedVersions) == 1 { if len(hello.supportedVersions) == 1 {
hello.cipherSuites = nil hello.cipherSuites = nil
} }
if needFIPS() { if fips140tls.Required() {
hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13FIPS...) hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13FIPS...)
} else if hasAESGCMHardwareSupport { } else if hasAESGCMHardwareSupport {
hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13...) hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13...)
@ -1105,8 +1106,13 @@ func (c *Conn) verifyServerCertificate(certificates [][]byte) error {
for _, cert := range certs[1:] { for _, cert := range certs[1:] {
opts.Intermediates.AddCert(cert) opts.Intermediates.AddCert(cert)
} }
var err error chains, err := certs[0].Verify(opts)
c.verifiedChains, 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 { if err != nil {
c.sendAlert(alertBadCertificate) c.sendAlert(alertBadCertificate)
return &CertificateVerificationError{UnverifiedCertificates: certs, Err: err} return &CertificateVerificationError{UnverifiedCertificates: certs, Err: err}
@ -1123,8 +1129,13 @@ func (c *Conn) verifyServerCertificate(certificates [][]byte) error {
for _, cert := range certs[1:] { for _, cert := range certs[1:] {
opts.Intermediates.AddCert(cert) opts.Intermediates.AddCert(cert)
} }
var err error chains, err := certs[0].Verify(opts)
c.verifiedChains, 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 { if err != nil {
c.sendAlert(alertBadCertificate) c.sendAlert(alertBadCertificate)
return &CertificateVerificationError{UnverifiedCertificates: certs, Err: err} return &CertificateVerificationError{UnverifiedCertificates: certs, Err: err}

View File

@ -910,7 +910,11 @@ func (c *Conn) processCertsFromClient(certificate Certificate) error {
return &CertificateVerificationError{UnverifiedCertificates: certs, Err: err} 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 c.peerCertificates = certs

View File

@ -22,6 +22,7 @@ import (
"time" "time"
"github.com/xtls/reality/byteorder" "github.com/xtls/reality/byteorder"
"github.com/xtls/reality/fips140tls"
"github.com/xtls/reality/mlkem" "github.com/xtls/reality/mlkem"
"github.com/xtls/reality/tls13" "github.com/xtls/reality/tls13"
) )
@ -215,7 +216,7 @@ func (hs *serverHandshakeStateTLS13) processClientHello() error {
if !hasAESGCMHardwareSupport || !aesgcmPreferred(hs.clientHello.cipherSuites) { if !hasAESGCMHardwareSupport || !aesgcmPreferred(hs.clientHello.cipherSuites) {
preferenceList = defaultCipherSuitesTLS13NoAES preferenceList = defaultCipherSuitesTLS13NoAES
} }
if needFIPS() { if fips140tls.Required() {
preferenceList = defaultCipherSuitesTLS13FIPS preferenceList = defaultCipherSuitesTLS13FIPS
} }
for _, suiteID := range preferenceList { for _, suiteID := range preferenceList {

View File

@ -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 }