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:
Daniel McCarney 2024-11-18 22:18:56 +01:00 committed by Gopher Robot
parent f189b9184a
commit 9abc9d7132
12 changed files with 220 additions and 119 deletions

View file

@ -11,6 +11,7 @@ import (
"crypto/ed25519" "crypto/ed25519"
"crypto/elliptic" "crypto/elliptic"
"crypto/rsa" "crypto/rsa"
"crypto/tls/internal/fips140tls"
"errors" "errors"
"fmt" "fmt"
"hash" "hash"
@ -242,7 +243,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

@ -1,15 +0,0 @@
// 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 tls
import "crypto/internal/boring/fipstls"
// needFIPS returns fipstls.Required(), which is not available without the
// boringcrypto build tag.
func needFIPS() bool {
return fipstls.Required()
}

View file

@ -15,6 +15,7 @@ import (
"crypto/rand" "crypto/rand"
"crypto/rsa" "crypto/rsa"
"crypto/sha512" "crypto/sha512"
"crypto/tls/internal/fips140tls"
"crypto/x509" "crypto/x509"
"errors" "errors"
"fmt" "fmt"
@ -1061,12 +1062,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)
@ -1092,7 +1093,7 @@ var tls10server = godebug.New("tls10server")
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 {
@ -1140,12 +1141,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()
@ -1617,7 +1618,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
@ -1646,3 +1647,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

@ -90,7 +90,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

@ -2,21 +2,20 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build boringcrypto
package tls package tls
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
"crypto/elliptic" "crypto/elliptic"
"crypto/internal/boring/fipstls"
"crypto/rand" "crypto/rand"
"crypto/rsa" "crypto/rsa"
"crypto/tls/internal/fips140tls"
"crypto/x509" "crypto/x509"
"crypto/x509/pkix" "crypto/x509/pkix"
"encoding/pem" "encoding/pem"
"fmt" "fmt"
"internal/obscuretestdata" "internal/obscuretestdata"
"internal/testenv"
"math/big" "math/big"
"net" "net"
"runtime" "runtime"
@ -50,7 +49,7 @@ func generateKeyShare(group CurveID) keyShare {
return keyShare{group: group, data: key.PublicKey().Bytes()} return keyShare{group: group, data: key.PublicKey().Bytes()}
} }
func TestBoringServerProtocolVersion(t *testing.T) { func TestFIPSServerProtocolVersion(t *testing.T) {
test := func(t *testing.T, name string, v uint16, msg string) { test := func(t *testing.T, name string, v uint16, msg string) {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
serverConfig := testConfig.Clone() serverConfig := testConfig.Clone()
@ -79,9 +78,9 @@ func TestBoringServerProtocolVersion(t *testing.T) {
test(t, "VersionTLS12", VersionTLS12, "") test(t, "VersionTLS12", VersionTLS12, "")
test(t, "VersionTLS13", VersionTLS13, "") test(t, "VersionTLS13", VersionTLS13, "")
t.Run("fipstls", func(t *testing.T) { t.Run("fips140tls", func(t *testing.T) {
fipstls.Force() fips140tls.Force()
defer fipstls.Abandon() defer fips140tls.TestingOnlyAbandon()
test(t, "VersionTLS10", VersionTLS10, "supported versions") test(t, "VersionTLS10", VersionTLS10, "supported versions")
test(t, "VersionTLS11", VersionTLS11, "supported versions") test(t, "VersionTLS11", VersionTLS11, "supported versions")
test(t, "VersionTLS12", VersionTLS12, "") test(t, "VersionTLS12", VersionTLS12, "")
@ -89,11 +88,11 @@ func TestBoringServerProtocolVersion(t *testing.T) {
}) })
} }
func isBoringVersion(v uint16) bool { func isFIPSVersion(v uint16) bool {
return v == VersionTLS12 || v == VersionTLS13 return v == VersionTLS12 || v == VersionTLS13
} }
func isBoringCipherSuite(id uint16) bool { func isFIPSCipherSuite(id uint16) bool {
switch id { switch id {
case TLS_AES_128_GCM_SHA256, case TLS_AES_128_GCM_SHA256,
TLS_AES_256_GCM_SHA384, TLS_AES_256_GCM_SHA384,
@ -106,7 +105,7 @@ func isBoringCipherSuite(id uint16) bool {
return false return false
} }
func isBoringCurve(id CurveID) bool { func isFIPSCurve(id CurveID) bool {
switch id { switch id {
case CurveP256, CurveP384: case CurveP256, CurveP384:
return true return true
@ -123,7 +122,7 @@ func isECDSA(id uint16) bool {
return false // TLS 1.3 cipher suites are not tied to the signature algorithm. return false // TLS 1.3 cipher suites are not tied to the signature algorithm.
} }
func isBoringSignatureScheme(alg SignatureScheme) bool { func isFIPSSignatureScheme(alg SignatureScheme) bool {
switch alg { switch alg {
default: default:
return false return false
@ -140,7 +139,7 @@ func isBoringSignatureScheme(alg SignatureScheme) bool {
return true return true
} }
func TestBoringServerCipherSuites(t *testing.T) { func TestFIPSServerCipherSuites(t *testing.T) {
serverConfig := testConfig.Clone() serverConfig := testConfig.Clone()
serverConfig.Certificates = make([]Certificate, 1) serverConfig.Certificates = make([]Certificate, 1)
@ -170,11 +169,11 @@ func TestBoringServerCipherSuites(t *testing.T) {
} }
testClientHello(t, serverConfig, clientHello) testClientHello(t, serverConfig, clientHello)
t.Run("fipstls", func(t *testing.T) { t.Run("fips140tls", func(t *testing.T) {
fipstls.Force() fips140tls.Force()
defer fipstls.Abandon() defer fips140tls.TestingOnlyAbandon()
msg := "" msg := ""
if !isBoringCipherSuite(id) { if !isFIPSCipherSuite(id) {
msg = "no cipher suite supported by both client and server" msg = "no cipher suite supported by both client and server"
} }
testClientHelloFailure(t, serverConfig, clientHello, msg) testClientHelloFailure(t, serverConfig, clientHello, msg)
@ -183,7 +182,7 @@ func TestBoringServerCipherSuites(t *testing.T) {
} }
} }
func TestBoringServerCurves(t *testing.T) { func TestFIPSServerCurves(t *testing.T) {
serverConfig := testConfig.Clone() serverConfig := testConfig.Clone()
serverConfig.BuildNameToCertificate() serverConfig.BuildNameToCertificate()
@ -199,14 +198,14 @@ func TestBoringServerCurves(t *testing.T) {
t.Fatalf("got error: %v, expected success", err) t.Fatalf("got error: %v, expected success", err)
} }
// With fipstls forced, bad curves should be rejected. // With fips140tls forced, bad curves should be rejected.
t.Run("fipstls", func(t *testing.T) { t.Run("fips140tls", func(t *testing.T) {
fipstls.Force() fips140tls.Force()
defer fipstls.Abandon() defer fips140tls.TestingOnlyAbandon()
_, _, err := testHandshake(t, clientConfig, serverConfig) _, _, err := testHandshake(t, clientConfig, serverConfig)
if err != nil && isBoringCurve(curveid) { if err != nil && isFIPSCurve(curveid) {
t.Fatalf("got error: %v, expected success", err) t.Fatalf("got error: %v, expected success", err)
} else if err == nil && !isBoringCurve(curveid) { } else if err == nil && !isFIPSCurve(curveid) {
t.Fatalf("got success, expected error") t.Fatalf("got success, expected error")
} }
}) })
@ -214,7 +213,7 @@ func TestBoringServerCurves(t *testing.T) {
} }
} }
func boringHandshake(t *testing.T, clientConfig, serverConfig *Config) (clientErr, serverErr error) { func fipsHandshake(t *testing.T, clientConfig, serverConfig *Config) (clientErr, serverErr error) {
c, s := localPipe(t) c, s := localPipe(t)
client := Client(c, clientConfig) client := Client(c, clientConfig)
server := Server(s, serverConfig) server := Server(s, serverConfig)
@ -229,7 +228,7 @@ func boringHandshake(t *testing.T, clientConfig, serverConfig *Config) (clientEr
return return
} }
func TestBoringServerSignatureAndHash(t *testing.T) { func TestFIPSServerSignatureAndHash(t *testing.T) {
defer func() { defer func() {
testingOnlyForceClientHelloSignatureAlgorithms = nil testingOnlyForceClientHelloSignatureAlgorithms = nil
}() }()
@ -261,17 +260,17 @@ func TestBoringServerSignatureAndHash(t *testing.T) {
// 1.3, and the ECDSA ones bind to the curve used. // 1.3, and the ECDSA ones bind to the curve used.
serverConfig.MaxVersion = VersionTLS12 serverConfig.MaxVersion = VersionTLS12
clientErr, serverErr := boringHandshake(t, testConfig, serverConfig) clientErr, serverErr := fipsHandshake(t, testConfig, serverConfig)
if clientErr != nil { if clientErr != nil {
t.Fatalf("expected handshake with %#x to succeed; client error: %v; server error: %v", sigHash, clientErr, serverErr) t.Fatalf("expected handshake with %#x to succeed; client error: %v; server error: %v", sigHash, clientErr, serverErr)
} }
// With fipstls forced, bad curves should be rejected. // With fips140tls forced, bad curves should be rejected.
t.Run("fipstls", func(t *testing.T) { t.Run("fips140tls", func(t *testing.T) {
fipstls.Force() fips140tls.Force()
defer fipstls.Abandon() defer fips140tls.TestingOnlyAbandon()
clientErr, _ := boringHandshake(t, testConfig, serverConfig) clientErr, _ := fipsHandshake(t, testConfig, serverConfig)
if isBoringSignatureScheme(sigHash) { if isFIPSSignatureScheme(sigHash) {
if clientErr != nil { if clientErr != nil {
t.Fatalf("expected handshake with %#x to succeed; err=%v", sigHash, clientErr) t.Fatalf("expected handshake with %#x to succeed; err=%v", sigHash, clientErr)
} }
@ -285,11 +284,11 @@ func TestBoringServerSignatureAndHash(t *testing.T) {
} }
} }
func TestBoringClientHello(t *testing.T) { func TestFIPSClientHello(t *testing.T) {
// Test that no matter what we put in the client config, // Test that no matter what we put in the client config,
// the client does not offer non-FIPS configurations. // the client does not offer non-FIPS configurations.
fipstls.Force() fips140tls.Force()
defer fipstls.Abandon() defer fips140tls.TestingOnlyAbandon()
c, s := net.Pipe() c, s := net.Pipe()
defer c.Close() defer c.Close()
@ -313,52 +312,57 @@ func TestBoringClientHello(t *testing.T) {
t.Fatalf("unexpected message type %T", msg) t.Fatalf("unexpected message type %T", msg)
} }
if !isBoringVersion(hello.vers) { if !isFIPSVersion(hello.vers) {
t.Errorf("client vers=%#x", hello.vers) t.Errorf("client vers=%#x", hello.vers)
} }
for _, v := range hello.supportedVersions { for _, v := range hello.supportedVersions {
if !isBoringVersion(v) { if !isFIPSVersion(v) {
t.Errorf("client offered disallowed version %#x", v) t.Errorf("client offered disallowed version %#x", v)
} }
} }
for _, id := range hello.cipherSuites { for _, id := range hello.cipherSuites {
if !isBoringCipherSuite(id) { if !isFIPSCipherSuite(id) {
t.Errorf("client offered disallowed suite %#x", id) t.Errorf("client offered disallowed suite %#x", id)
} }
} }
for _, id := range hello.supportedCurves { for _, id := range hello.supportedCurves {
if !isBoringCurve(id) { if !isFIPSCurve(id) {
t.Errorf("client offered disallowed curve %d", id) t.Errorf("client offered disallowed curve %d", id)
} }
} }
for _, sigHash := range hello.supportedSignatureAlgorithms { for _, sigHash := range hello.supportedSignatureAlgorithms {
if !isBoringSignatureScheme(sigHash) { if !isFIPSSignatureScheme(sigHash) {
t.Errorf("client offered disallowed signature-and-hash %v", sigHash) t.Errorf("client offered disallowed signature-and-hash %v", sigHash)
} }
} }
} }
func TestBoringCertAlgs(t *testing.T) { func TestFIPSCertAlgs(t *testing.T) {
// NaCl, arm and wasm time out generating keys. Nothing in this test is architecture-specific, so just don't bother on those. // arm and wasm time out generating keys. Nothing in this test is
if runtime.GOOS == "nacl" || runtime.GOARCH == "arm" || runtime.GOOS == "js" { // architecture-specific, so just don't bother on those.
if testenv.CPUIsSlow() {
t.Skipf("skipping on %s/%s because key generation takes too long", runtime.GOOS, runtime.GOARCH) t.Skipf("skipping on %s/%s because key generation takes too long", runtime.GOOS, runtime.GOARCH)
} }
// Set up some roots, intermediate CAs, and leaf certs with various algorithms. // Set up some roots, intermediate CAs, and leaf certs with various algorithms.
// X_Y is X signed by Y. // X_Y is X signed by Y.
R1 := boringCert(t, "R1", boringRSAKey(t, 2048), nil, boringCertCA|boringCertFIPSOK) R1 := fipsCert(t, "R1", fipsRSAKey(t, 2048), nil, fipsCertCA|fipsCertFIPSOK)
R2 := boringCert(t, "R2", boringRSAKey(t, 512), nil, boringCertCA) R2 := fipsCert(t, "R2", fipsRSAKey(t, 512), nil, fipsCertCA)
R3 := fipsCert(t, "R3", fipsRSAKey(t, 4096), nil, fipsCertCA|fipsCertFIPSOK)
M1_R1 := boringCert(t, "M1_R1", boringECDSAKey(t, elliptic.P256()), R1, boringCertCA|boringCertFIPSOK) M1_R1 := fipsCert(t, "M1_R1", fipsECDSAKey(t, elliptic.P256()), R1, fipsCertCA|fipsCertFIPSOK)
M2_R1 := boringCert(t, "M2_R1", boringECDSAKey(t, elliptic.P224()), R1, boringCertCA) M2_R1 := fipsCert(t, "M2_R1", fipsECDSAKey(t, elliptic.P224()), R1, fipsCertCA)
I_R1 := boringCert(t, "I_R1", boringRSAKey(t, 3072), R1, boringCertCA|boringCertFIPSOK) I_R1 := fipsCert(t, "I_R1", fipsRSAKey(t, 3072), R1, fipsCertCA|fipsCertFIPSOK)
I_R2 := boringCert(t, "I_R2", I_R1.key, R2, boringCertCA|boringCertFIPSOK) I_R2 := fipsCert(t, "I_R2", I_R1.key, R2, fipsCertCA|fipsCertFIPSOK)
I_M1 := boringCert(t, "I_M1", I_R1.key, M1_R1, boringCertCA|boringCertFIPSOK) I_M1 := fipsCert(t, "I_M1", I_R1.key, M1_R1, fipsCertCA|fipsCertFIPSOK)
I_M2 := boringCert(t, "I_M2", I_R1.key, M2_R1, boringCertCA|boringCertFIPSOK) I_M2 := fipsCert(t, "I_M2", I_R1.key, M2_R1, fipsCertCA|fipsCertFIPSOK)
L1_I := boringCert(t, "L1_I", boringECDSAKey(t, elliptic.P384()), I_R1, boringCertLeaf|boringCertFIPSOK) I_R3 := fipsCert(t, "I_R3", fipsRSAKey(t, 3072), R3, fipsCertCA|fipsCertFIPSOK)
L2_I := boringCert(t, "L2_I", boringRSAKey(t, 1024), I_R1, boringCertLeaf) fipsCert(t, "I_R3", I_R3.key, R3, fipsCertCA|fipsCertFIPSOK)
L1_I := fipsCert(t, "L1_I", fipsECDSAKey(t, elliptic.P384()), I_R1, fipsCertLeaf|fipsCertFIPSOK)
L2_I := fipsCert(t, "L2_I", fipsRSAKey(t, 1024), I_R1, fipsCertLeaf)
// client verifying server cert // client verifying server cert
testServerCert := func(t *testing.T, desc string, pool *x509.CertPool, key interface{}, list [][]byte, ok bool) { testServerCert := func(t *testing.T, desc string, pool *x509.CertPool, key interface{}, list [][]byte, ok bool) {
@ -371,7 +375,7 @@ func TestBoringCertAlgs(t *testing.T) {
serverConfig.Certificates = []Certificate{{Certificate: list, PrivateKey: key}} serverConfig.Certificates = []Certificate{{Certificate: list, PrivateKey: key}}
serverConfig.BuildNameToCertificate() serverConfig.BuildNameToCertificate()
clientErr, _ := boringHandshake(t, clientConfig, serverConfig) clientErr, _ := fipsHandshake(t, clientConfig, serverConfig)
if (clientErr == nil) == ok { if (clientErr == nil) == ok {
if ok { if ok {
@ -398,7 +402,7 @@ func TestBoringCertAlgs(t *testing.T) {
serverConfig.ClientCAs = pool serverConfig.ClientCAs = pool
serverConfig.ClientAuth = RequireAndVerifyClientCert serverConfig.ClientAuth = RequireAndVerifyClientCert
_, serverErr := boringHandshake(t, clientConfig, serverConfig) _, serverErr := fipsHandshake(t, clientConfig, serverConfig)
if (serverErr == nil) == ok { if (serverErr == nil) == ok {
if ok { if ok {
@ -421,10 +425,10 @@ func TestBoringCertAlgs(t *testing.T) {
r1pool.AddCert(R1.cert) r1pool.AddCert(R1.cert)
testServerCert(t, "basic", r1pool, L2_I.key, [][]byte{L2_I.der, I_R1.der}, true) testServerCert(t, "basic", r1pool, L2_I.key, [][]byte{L2_I.der, I_R1.der}, true)
testClientCert(t, "basic (client cert)", r1pool, L2_I.key, [][]byte{L2_I.der, I_R1.der}, true) testClientCert(t, "basic (client cert)", r1pool, L2_I.key, [][]byte{L2_I.der, I_R1.der}, true)
fipstls.Force() fips140tls.Force()
testServerCert(t, "basic (fips)", r1pool, L2_I.key, [][]byte{L2_I.der, I_R1.der}, false) testServerCert(t, "basic (fips)", r1pool, L2_I.key, [][]byte{L2_I.der, I_R1.der}, false)
testClientCert(t, "basic (fips, client cert)", r1pool, L2_I.key, [][]byte{L2_I.der, I_R1.der}, false) testClientCert(t, "basic (fips, client cert)", r1pool, L2_I.key, [][]byte{L2_I.der, I_R1.der}, false)
fipstls.Abandon() fips140tls.TestingOnlyAbandon()
if t.Failed() { if t.Failed() {
t.Fatal("basic test failed, skipping exhaustive test") t.Fatal("basic test failed, skipping exhaustive test")
@ -445,7 +449,7 @@ func TestBoringCertAlgs(t *testing.T) {
reachableFIPS := map[string]bool{leaf.parentOrg: leaf.fipsOK} reachableFIPS := map[string]bool{leaf.parentOrg: leaf.fipsOK}
list := [][]byte{leaf.der} list := [][]byte{leaf.der}
listName := leaf.name listName := leaf.name
addList := func(cond int, c *boringCertificate) { addList := func(cond int, c *fipsCertificate) {
if cond != 0 { if cond != 0 {
list = append(list, c.der) list = append(list, c.der)
listName += "," + c.name listName += "," + c.name
@ -469,7 +473,7 @@ func TestBoringCertAlgs(t *testing.T) {
rootName := "," rootName := ","
shouldVerify := false shouldVerify := false
shouldVerifyFIPS := false shouldVerifyFIPS := false
addRoot := func(cond int, c *boringCertificate) { addRoot := func(cond int, c *fipsCertificate) {
if cond != 0 { if cond != 0 {
rootName += "," + c.name rootName += "," + c.name
pool.AddCert(c.cert) pool.AddCert(c.cert)
@ -486,22 +490,22 @@ func TestBoringCertAlgs(t *testing.T) {
rootName = rootName[1:] // strip leading comma rootName = rootName[1:] // strip leading comma
testServerCert(t, listName+"->"+rootName[1:], pool, leaf.key, list, shouldVerify) testServerCert(t, listName+"->"+rootName[1:], pool, leaf.key, list, shouldVerify)
testClientCert(t, listName+"->"+rootName[1:]+"(client cert)", pool, leaf.key, list, shouldVerify) testClientCert(t, listName+"->"+rootName[1:]+"(client cert)", pool, leaf.key, list, shouldVerify)
fipstls.Force() fips140tls.Force()
testServerCert(t, listName+"->"+rootName[1:]+" (fips)", pool, leaf.key, list, shouldVerifyFIPS) testServerCert(t, listName+"->"+rootName[1:]+" (fips)", pool, leaf.key, list, shouldVerifyFIPS)
testClientCert(t, listName+"->"+rootName[1:]+" (fips, client cert)", pool, leaf.key, list, shouldVerifyFIPS) testClientCert(t, listName+"->"+rootName[1:]+" (fips, client cert)", pool, leaf.key, list, shouldVerifyFIPS)
fipstls.Abandon() fips140tls.TestingOnlyAbandon()
} }
} }
} }
} }
const ( const (
boringCertCA = iota fipsCertCA = iota
boringCertLeaf fipsCertLeaf
boringCertFIPSOK = 0x80 fipsCertFIPSOK = 0x80
) )
func boringRSAKey(t *testing.T, size int) *rsa.PrivateKey { func fipsRSAKey(t *testing.T, size int) *rsa.PrivateKey {
k, err := rsa.GenerateKey(rand.Reader, size) k, err := rsa.GenerateKey(rand.Reader, size)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -509,7 +513,7 @@ func boringRSAKey(t *testing.T, size int) *rsa.PrivateKey {
return k return k
} }
func boringECDSAKey(t *testing.T, curve elliptic.Curve) *ecdsa.PrivateKey { func fipsECDSAKey(t *testing.T, curve elliptic.Curve) *ecdsa.PrivateKey {
k, err := ecdsa.GenerateKey(curve, rand.Reader) k, err := ecdsa.GenerateKey(curve, rand.Reader)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -517,7 +521,7 @@ func boringECDSAKey(t *testing.T, curve elliptic.Curve) *ecdsa.PrivateKey {
return k return k
} }
type boringCertificate struct { type fipsCertificate struct {
name string name string
org string org string
parentOrg string parentOrg string
@ -527,7 +531,7 @@ type boringCertificate struct {
fipsOK bool fipsOK bool
} }
func boringCert(t *testing.T, name string, key interface{}, parent *boringCertificate, mode int) *boringCertificate { func fipsCert(t *testing.T, name string, key interface{}, parent *fipsCertificate, mode int) *fipsCertificate {
org := name org := name
parentOrg := "" parentOrg := ""
if i := strings.Index(org, "_"); i >= 0 { if i := strings.Index(org, "_"); i >= 0 {
@ -546,7 +550,7 @@ func boringCert(t *testing.T, name string, key interface{}, parent *boringCertif
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
BasicConstraintsValid: true, BasicConstraintsValid: true,
} }
if mode&^boringCertFIPSOK == boringCertLeaf { if mode&^fipsCertFIPSOK == fipsCertLeaf {
tmpl.DNSNames = []string{"example.com"} tmpl.DNSNames = []string{"example.com"}
} else { } else {
tmpl.IsCA = true tmpl.IsCA = true
@ -564,11 +568,14 @@ func boringCert(t *testing.T, name string, key interface{}, parent *boringCertif
} }
var pub interface{} var pub interface{}
var desc string
switch k := key.(type) { switch k := key.(type) {
case *rsa.PrivateKey: case *rsa.PrivateKey:
pub = &k.PublicKey pub = &k.PublicKey
desc = fmt.Sprintf("RSA-%d", k.N.BitLen())
case *ecdsa.PrivateKey: case *ecdsa.PrivateKey:
pub = &k.PublicKey pub = &k.PublicKey
desc = "ECDSA-" + k.Curve.Params().Name
default: default:
t.Fatalf("invalid key %T", key) t.Fatalf("invalid key %T", key)
} }
@ -582,8 +589,15 @@ func boringCert(t *testing.T, name string, key interface{}, parent *boringCertif
t.Fatal(err) t.Fatal(err)
} }
fipsOK := mode&boringCertFIPSOK != 0 fips140tls.Force()
return &boringCertificate{name, org, parentOrg, der, cert, key, fipsOK} defer fips140tls.TestingOnlyAbandon()
fipsOK := mode&fipsCertFIPSOK != 0
if fipsAllowCert(cert) != fipsOK {
t.Errorf("fipsAllowCert(cert with %s key) = %v, want %v", desc, !fipsOK, fipsOK)
}
return &fipsCertificate{name, org, parentOrg, der, cert, key, fipsOK}
} }
// A self-signed test certificate with an RSA key of size 2048, for testing // A self-signed test certificate with an RSA key of size 2048, for testing

View file

@ -19,11 +19,11 @@ package fipsonly
// new source file and not modifying any existing source files. // new source file and not modifying any existing source files.
import ( import (
"crypto/internal/boring/fipstls"
"crypto/internal/boring/sig" "crypto/internal/boring/sig"
"crypto/tls/internal/fips140tls"
) )
func init() { func init() {
fipstls.Force() fips140tls.Force()
sig.FIPSOnly() sig.FIPSOnly()
} }

View file

@ -7,12 +7,12 @@
package fipsonly package fipsonly
import ( import (
"crypto/internal/boring/fipstls" "crypto/tls/internal/fips140tls"
"testing" "testing"
) )
func Test(t *testing.T) { func Test(t *testing.T) {
if !fipstls.Required() { if !fips140tls.Required() {
t.Fatal("fipstls.Required() = false, must be true") t.Fatal("fips140tls.Required() = false, must be true")
} }
} }

View file

@ -15,6 +15,7 @@ import (
"crypto/internal/hpke" "crypto/internal/hpke"
"crypto/rsa" "crypto/rsa"
"crypto/subtle" "crypto/subtle"
"crypto/tls/internal/fips140tls"
"crypto/x509" "crypto/x509"
"errors" "errors"
"fmt" "fmt"
@ -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...)
@ -632,11 +633,11 @@ func (hs *clientHandshakeState) pickCipherSuite() error {
return errors.New("tls: server chose an unconfigured cipher suite") return errors.New("tls: server chose an unconfigured cipher suite")
} }
if hs.c.config.CipherSuites == nil && !needFIPS() && rsaKexCiphers[hs.suite.id] { if hs.c.config.CipherSuites == nil && !fips140tls.Required() && rsaKexCiphers[hs.suite.id] {
tlsrsakex.Value() // ensure godebug is initialized tlsrsakex.Value() // ensure godebug is initialized
tlsrsakex.IncNonDefault() tlsrsakex.IncNonDefault()
} }
if hs.c.config.CipherSuites == nil && !needFIPS() && tdesCiphers[hs.suite.id] { if hs.c.config.CipherSuites == nil && !fips140tls.Required() && tdesCiphers[hs.suite.id] {
tls3des.Value() // ensure godebug is initialized tls3des.Value() // ensure godebug is initialized
tls3des.IncNonDefault() tls3des.IncNonDefault()
} }
@ -1112,8 +1113,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}
@ -1130,8 +1136,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

@ -11,6 +11,7 @@ import (
"crypto/ed25519" "crypto/ed25519"
"crypto/rsa" "crypto/rsa"
"crypto/subtle" "crypto/subtle"
"crypto/tls/internal/fips140tls"
"crypto/x509" "crypto/x509"
"errors" "errors"
"fmt" "fmt"
@ -372,11 +373,11 @@ func (hs *serverHandshakeState) pickCipherSuite() error {
} }
c.cipherSuite = hs.suite.id c.cipherSuite = hs.suite.id
if c.config.CipherSuites == nil && !needFIPS() && rsaKexCiphers[hs.suite.id] { if c.config.CipherSuites == nil && !fips140tls.Required() && rsaKexCiphers[hs.suite.id] {
tlsrsakex.Value() // ensure godebug is initialized tlsrsakex.Value() // ensure godebug is initialized
tlsrsakex.IncNonDefault() tlsrsakex.IncNonDefault()
} }
if c.config.CipherSuites == nil && !needFIPS() && tdesCiphers[hs.suite.id] { if c.config.CipherSuites == nil && !fips140tls.Required() && tdesCiphers[hs.suite.id] {
tls3des.Value() // ensure godebug is initialized tls3des.Value() // ensure godebug is initialized
tls3des.IncNonDefault() tls3des.IncNonDefault()
} }
@ -923,7 +924,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

@ -12,6 +12,7 @@ import (
"crypto/internal/fips140/mlkem" "crypto/internal/fips140/mlkem"
"crypto/internal/fips140/tls13" "crypto/internal/fips140/tls13"
"crypto/rsa" "crypto/rsa"
"crypto/tls/internal/fips140tls"
"errors" "errors"
"hash" "hash"
"internal/byteorder" "internal/byteorder"
@ -162,7 +163,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

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

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 file.
//go:build !boringcrypto
package tls
func needFIPS() bool { return false }