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>
+++ /dev/null
-// 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
-
-// runtime_arg0 is declared in tls.go without a body.
-// It's provided by package runtime,
-// but the go command doesn't know that.
-// Having this assembly file keeps the go command
-// from complaining about the missing body
-// (because the implementation might be here).
+++ /dev/null
-// 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 fipstls allows control over whether crypto/tls requires FIPS-approved settings.
-// This package only exists with GOEXPERIMENT=boringcrypto, but the effects are independent
-// of the use of BoringCrypto.
-package fipstls
-
-import (
- "internal/stringslite"
- "sync/atomic"
-)
-
-var required atomic.Bool
-
-// Force forces crypto/tls to restrict TLS configurations to FIPS-approved settings.
-// By design, this call is impossible to undo (except in tests).
-//
-// Note that this call has an effect even in programs using
-// standard crypto (that is, even when Enabled = false).
-func Force() {
- required.Store(true)
-}
-
-// Abandon allows non-FIPS-approved settings.
-// If called from a non-test binary, it panics.
-func Abandon() {
- // Note: Not using boring.UnreachableExceptTests because we want
- // this test to happen even when boring.Enabled = false.
- name := runtime_arg0()
- // Allow _test for Go command, .test for Bazel,
- // NaClMain for NaCl (where all binaries run as NaClMain),
- // and empty string for Windows (where runtime_arg0 can't easily find the name).
- // Since this is an internal package, testing that this isn't used on the
- // other operating systems should suffice to catch any mistakes.
- if !stringslite.HasSuffix(name, "_test") && !stringslite.HasSuffix(name, ".test") && name != "NaClMain" && name != "" {
- panic("fipstls: invalid use of Abandon in " + name)
- }
- required.Store(false)
-}
-
-// provided by runtime
-func runtime_arg0() string
-
-// Required reports whether FIPS-approved settings are required.
-func Required() bool {
- return required.Load()
-}
"crypto/ed25519"
"crypto/elliptic"
"crypto/rsa"
+ "crypto/tls/internal/fips140tls"
"errors"
"fmt"
"hash"
// 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) {
+++ /dev/null
-// 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()
-}
"crypto/rand"
"crypto/rsa"
"crypto/sha512"
+ "crypto/tls/internal/fips140tls"
"crypto/x509"
"errors"
"fmt"
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)
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 {
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()
// supportedSignatureAlgorithms returns the supported signature algorithms.
func supportedSignatureAlgorithms() []SignatureScheme {
- if !needFIPS() {
+ if !fips140tls.Required() {
return defaultSupportedSignatureAlgorithms
}
return defaultSupportedSignatureAlgorithmsFIPS
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
+}
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,
// 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/ecdsa"
"crypto/elliptic"
- "crypto/internal/boring/fipstls"
"crypto/rand"
"crypto/rsa"
+ "crypto/tls/internal/fips140tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"internal/obscuretestdata"
+ "internal/testenv"
"math/big"
"net"
"runtime"
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) {
t.Run(name, func(t *testing.T) {
serverConfig := testConfig.Clone()
test(t, "VersionTLS12", VersionTLS12, "")
test(t, "VersionTLS13", VersionTLS13, "")
- t.Run("fipstls", func(t *testing.T) {
- fipstls.Force()
- defer fipstls.Abandon()
+ t.Run("fips140tls", func(t *testing.T) {
+ fips140tls.Force()
+ defer fips140tls.TestingOnlyAbandon()
test(t, "VersionTLS10", VersionTLS10, "supported versions")
test(t, "VersionTLS11", VersionTLS11, "supported versions")
test(t, "VersionTLS12", VersionTLS12, "")
})
}
-func isBoringVersion(v uint16) bool {
+func isFIPSVersion(v uint16) bool {
return v == VersionTLS12 || v == VersionTLS13
}
-func isBoringCipherSuite(id uint16) bool {
+func isFIPSCipherSuite(id uint16) bool {
switch id {
case TLS_AES_128_GCM_SHA256,
TLS_AES_256_GCM_SHA384,
return false
}
-func isBoringCurve(id CurveID) bool {
+func isFIPSCurve(id CurveID) bool {
switch id {
case CurveP256, CurveP384:
return true
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 {
default:
return false
return true
}
-func TestBoringServerCipherSuites(t *testing.T) {
+func TestFIPSServerCipherSuites(t *testing.T) {
serverConfig := testConfig.Clone()
serverConfig.Certificates = make([]Certificate, 1)
}
testClientHello(t, serverConfig, clientHello)
- t.Run("fipstls", func(t *testing.T) {
- fipstls.Force()
- defer fipstls.Abandon()
+ t.Run("fips140tls", func(t *testing.T) {
+ fips140tls.Force()
+ defer fips140tls.TestingOnlyAbandon()
msg := ""
- if !isBoringCipherSuite(id) {
+ if !isFIPSCipherSuite(id) {
msg = "no cipher suite supported by both client and server"
}
testClientHelloFailure(t, serverConfig, clientHello, msg)
}
}
-func TestBoringServerCurves(t *testing.T) {
+func TestFIPSServerCurves(t *testing.T) {
serverConfig := testConfig.Clone()
serverConfig.BuildNameToCertificate()
t.Fatalf("got error: %v, expected success", err)
}
- // With fipstls forced, bad curves should be rejected.
- t.Run("fipstls", func(t *testing.T) {
- fipstls.Force()
- defer fipstls.Abandon()
+ // With fips140tls forced, bad curves should be rejected.
+ t.Run("fips140tls", func(t *testing.T) {
+ fips140tls.Force()
+ defer fips140tls.TestingOnlyAbandon()
_, _, err := testHandshake(t, clientConfig, serverConfig)
- if err != nil && isBoringCurve(curveid) {
+ if err != nil && isFIPSCurve(curveid) {
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")
}
})
}
}
-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)
client := Client(c, clientConfig)
server := Server(s, serverConfig)
return
}
-func TestBoringServerSignatureAndHash(t *testing.T) {
+func TestFIPSServerSignatureAndHash(t *testing.T) {
defer func() {
testingOnlyForceClientHelloSignatureAlgorithms = nil
}()
// 1.3, and the ECDSA ones bind to the curve used.
serverConfig.MaxVersion = VersionTLS12
- clientErr, serverErr := boringHandshake(t, testConfig, serverConfig)
+ clientErr, serverErr := fipsHandshake(t, testConfig, serverConfig)
if clientErr != nil {
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.
- t.Run("fipstls", func(t *testing.T) {
- fipstls.Force()
- defer fipstls.Abandon()
- clientErr, _ := boringHandshake(t, testConfig, serverConfig)
- if isBoringSignatureScheme(sigHash) {
+ // With fips140tls forced, bad curves should be rejected.
+ t.Run("fips140tls", func(t *testing.T) {
+ fips140tls.Force()
+ defer fips140tls.TestingOnlyAbandon()
+ clientErr, _ := fipsHandshake(t, testConfig, serverConfig)
+ if isFIPSSignatureScheme(sigHash) {
if clientErr != nil {
t.Fatalf("expected handshake with %#x to succeed; err=%v", sigHash, clientErr)
}
}
}
-func TestBoringClientHello(t *testing.T) {
+func TestFIPSClientHello(t *testing.T) {
// Test that no matter what we put in the client config,
// the client does not offer non-FIPS configurations.
- fipstls.Force()
- defer fipstls.Abandon()
+ fips140tls.Force()
+ defer fips140tls.TestingOnlyAbandon()
c, s := net.Pipe()
defer c.Close()
t.Fatalf("unexpected message type %T", msg)
}
- if !isBoringVersion(hello.vers) {
+ if !isFIPSVersion(hello.vers) {
t.Errorf("client vers=%#x", hello.vers)
}
for _, v := range hello.supportedVersions {
- if !isBoringVersion(v) {
+ if !isFIPSVersion(v) {
t.Errorf("client offered disallowed version %#x", v)
}
}
for _, id := range hello.cipherSuites {
- if !isBoringCipherSuite(id) {
+ if !isFIPSCipherSuite(id) {
t.Errorf("client offered disallowed suite %#x", id)
}
}
for _, id := range hello.supportedCurves {
- if !isBoringCurve(id) {
+ if !isFIPSCurve(id) {
t.Errorf("client offered disallowed curve %d", id)
}
}
for _, sigHash := range hello.supportedSignatureAlgorithms {
- if !isBoringSignatureScheme(sigHash) {
+ if !isFIPSSignatureScheme(sigHash) {
t.Errorf("client offered disallowed signature-and-hash %v", sigHash)
}
}
}
-func TestBoringCertAlgs(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.
- if runtime.GOOS == "nacl" || runtime.GOARCH == "arm" || runtime.GOOS == "js" {
+func TestFIPSCertAlgs(t *testing.T) {
+ // arm and wasm time out generating keys. Nothing in this test is
+ // 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)
}
// Set up some roots, intermediate CAs, and leaf certs with various algorithms.
// X_Y is X signed by Y.
- R1 := boringCert(t, "R1", boringRSAKey(t, 2048), nil, boringCertCA|boringCertFIPSOK)
- R2 := boringCert(t, "R2", boringRSAKey(t, 512), nil, boringCertCA)
+ R1 := fipsCert(t, "R1", fipsRSAKey(t, 2048), nil, fipsCertCA|fipsCertFIPSOK)
+ R2 := fipsCert(t, "R2", fipsRSAKey(t, 512), nil, fipsCertCA)
+ R3 := fipsCert(t, "R3", fipsRSAKey(t, 4096), nil, fipsCertCA|fipsCertFIPSOK)
+
+ M1_R1 := fipsCert(t, "M1_R1", fipsECDSAKey(t, elliptic.P256()), R1, fipsCertCA|fipsCertFIPSOK)
+ M2_R1 := fipsCert(t, "M2_R1", fipsECDSAKey(t, elliptic.P224()), R1, fipsCertCA)
- M1_R1 := boringCert(t, "M1_R1", boringECDSAKey(t, elliptic.P256()), R1, boringCertCA|boringCertFIPSOK)
- M2_R1 := boringCert(t, "M2_R1", boringECDSAKey(t, elliptic.P224()), R1, boringCertCA)
+ I_R1 := fipsCert(t, "I_R1", fipsRSAKey(t, 3072), R1, fipsCertCA|fipsCertFIPSOK)
+ I_R2 := fipsCert(t, "I_R2", I_R1.key, R2, fipsCertCA|fipsCertFIPSOK)
+ I_M1 := fipsCert(t, "I_M1", I_R1.key, M1_R1, fipsCertCA|fipsCertFIPSOK)
+ I_M2 := fipsCert(t, "I_M2", I_R1.key, M2_R1, fipsCertCA|fipsCertFIPSOK)
- I_R1 := boringCert(t, "I_R1", boringRSAKey(t, 3072), R1, boringCertCA|boringCertFIPSOK)
- I_R2 := boringCert(t, "I_R2", I_R1.key, R2, boringCertCA|boringCertFIPSOK)
- I_M1 := boringCert(t, "I_M1", I_R1.key, M1_R1, boringCertCA|boringCertFIPSOK)
- I_M2 := boringCert(t, "I_M2", I_R1.key, M2_R1, boringCertCA|boringCertFIPSOK)
+ I_R3 := fipsCert(t, "I_R3", fipsRSAKey(t, 3072), R3, fipsCertCA|fipsCertFIPSOK)
+ fipsCert(t, "I_R3", I_R3.key, R3, fipsCertCA|fipsCertFIPSOK)
- L1_I := boringCert(t, "L1_I", boringECDSAKey(t, elliptic.P384()), I_R1, boringCertLeaf|boringCertFIPSOK)
- L2_I := boringCert(t, "L2_I", boringRSAKey(t, 1024), I_R1, boringCertLeaf)
+ 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
testServerCert := func(t *testing.T, desc string, pool *x509.CertPool, key interface{}, list [][]byte, ok bool) {
serverConfig.Certificates = []Certificate{{Certificate: list, PrivateKey: key}}
serverConfig.BuildNameToCertificate()
- clientErr, _ := boringHandshake(t, clientConfig, serverConfig)
+ clientErr, _ := fipsHandshake(t, clientConfig, serverConfig)
if (clientErr == nil) == ok {
if ok {
serverConfig.ClientCAs = pool
serverConfig.ClientAuth = RequireAndVerifyClientCert
- _, serverErr := boringHandshake(t, clientConfig, serverConfig)
+ _, serverErr := fipsHandshake(t, clientConfig, serverConfig)
if (serverErr == nil) == ok {
if ok {
r1pool.AddCert(R1.cert)
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)
- fipstls.Force()
+ fips140tls.Force()
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)
- fipstls.Abandon()
+ fips140tls.TestingOnlyAbandon()
if t.Failed() {
t.Fatal("basic test failed, skipping exhaustive test")
reachableFIPS := map[string]bool{leaf.parentOrg: leaf.fipsOK}
list := [][]byte{leaf.der}
listName := leaf.name
- addList := func(cond int, c *boringCertificate) {
+ addList := func(cond int, c *fipsCertificate) {
if cond != 0 {
list = append(list, c.der)
listName += "," + c.name
rootName := ","
shouldVerify := false
shouldVerifyFIPS := false
- addRoot := func(cond int, c *boringCertificate) {
+ addRoot := func(cond int, c *fipsCertificate) {
if cond != 0 {
rootName += "," + c.name
pool.AddCert(c.cert)
rootName = rootName[1:] // strip leading comma
testServerCert(t, listName+"->"+rootName[1:], 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)
testClientCert(t, listName+"->"+rootName[1:]+" (fips, client cert)", pool, leaf.key, list, shouldVerifyFIPS)
- fipstls.Abandon()
+ fips140tls.TestingOnlyAbandon()
}
}
}
}
const (
- boringCertCA = iota
- boringCertLeaf
- boringCertFIPSOK = 0x80
+ fipsCertCA = iota
+ fipsCertLeaf
+ 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)
if err != nil {
t.Fatal(err)
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)
if err != nil {
t.Fatal(err)
return k
}
-type boringCertificate struct {
+type fipsCertificate struct {
name string
org string
parentOrg string
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
parentOrg := ""
if i := strings.Index(org, "_"); i >= 0 {
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
BasicConstraintsValid: true,
}
- if mode&^boringCertFIPSOK == boringCertLeaf {
+ if mode&^fipsCertFIPSOK == fipsCertLeaf {
tmpl.DNSNames = []string{"example.com"}
} else {
tmpl.IsCA = true
}
var pub interface{}
+ var desc string
switch k := key.(type) {
case *rsa.PrivateKey:
pub = &k.PublicKey
+ desc = fmt.Sprintf("RSA-%d", k.N.BitLen())
case *ecdsa.PrivateKey:
pub = &k.PublicKey
+ desc = "ECDSA-" + k.Curve.Params().Name
default:
t.Fatalf("invalid key %T", key)
}
t.Fatal(err)
}
- fipsOK := mode&boringCertFIPSOK != 0
- return &boringCertificate{name, org, parentOrg, der, cert, key, fipsOK}
+ fips140tls.Force()
+ 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
// new source file and not modifying any existing source files.
import (
- "crypto/internal/boring/fipstls"
"crypto/internal/boring/sig"
+ "crypto/tls/internal/fips140tls"
)
func init() {
- fipstls.Force()
+ fips140tls.Force()
sig.FIPSOnly()
}
package fipsonly
import (
- "crypto/internal/boring/fipstls"
+ "crypto/tls/internal/fips140tls"
"testing"
)
func Test(t *testing.T) {
- if !fipstls.Required() {
- t.Fatal("fipstls.Required() = false, must be true")
+ if !fips140tls.Required() {
+ t.Fatal("fips140tls.Required() = false, must be true")
}
}
"crypto/internal/hpke"
"crypto/rsa"
"crypto/subtle"
+ "crypto/tls/internal/fips140tls"
"crypto/x509"
"errors"
"fmt"
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...)
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.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.IncNonDefault()
}
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}
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}
"crypto/ed25519"
"crypto/rsa"
"crypto/subtle"
+ "crypto/tls/internal/fips140tls"
"crypto/x509"
"errors"
"fmt"
}
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.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.IncNonDefault()
}
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
"crypto/internal/fips140/mlkem"
"crypto/internal/fips140/tls13"
"crypto/rsa"
+ "crypto/tls/internal/fips140tls"
"errors"
"hash"
"internal/byteorder"
if !hasAESGCMHardwareSupport || !aesgcmPreferred(hs.clientHello.cipherSuites) {
preferenceList = defaultCipherSuitesTLS13NoAES
}
- if needFIPS() {
+ if fips140tls.Required() {
preferenceList = defaultCipherSuitesTLS13FIPS
}
for _, suiteID := range preferenceList {
--- /dev/null
+// 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)
+}
+++ /dev/null
-// 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 }
+++ /dev/null
-// 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 x509
-
-import (
- "crypto/ecdsa"
- "crypto/elliptic"
- "crypto/internal/boring/fipstls"
- "crypto/rsa"
-)
-
-// boringAllowCert reports whether c is allowed to be used
-// in a certificate chain by the current fipstls enforcement setting.
-// It is called for each leaf, intermediate, and root certificate.
-func boringAllowCert(c *Certificate) bool {
- if !fipstls.Required() {
- return true
- }
-
- // The key must be RSA 2048, RSA 3072, RSA 4096,
- // or ECDSA P-256, P-384, P-521.
- switch k := c.PublicKey.(type) {
- default:
- return false
- case *rsa.PublicKey:
- if size := k.N.BitLen(); size != 2048 && size != 3072 && size != 4096 {
- return false
- }
- case *ecdsa.PublicKey:
- if k.Curve != elliptic.P256() && k.Curve != elliptic.P384() && k.Curve != elliptic.P521() {
- return false
- }
- }
- return true
-}
+++ /dev/null
-// 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 x509
-
-import (
- "crypto/ecdsa"
- "crypto/elliptic"
- "crypto/internal/boring/fipstls"
- "crypto/rand"
- "crypto/rsa"
- "crypto/x509/pkix"
- "fmt"
- "math/big"
- "strings"
- "testing"
- "time"
-)
-
-const (
- boringCertCA = iota
- boringCertLeaf
- boringCertFIPSOK = 0x80
-)
-
-func boringRSAKey(t *testing.T, size int) *rsa.PrivateKey {
- t.Helper()
- k, err := rsa.GenerateKey(rand.Reader, size)
- if err != nil {
- t.Fatal(err)
- }
- return k
-}
-
-func boringECDSAKey(t *testing.T, curve elliptic.Curve) *ecdsa.PrivateKey {
- t.Helper()
- k, err := ecdsa.GenerateKey(curve, rand.Reader)
- if err != nil {
- t.Fatal(err)
- }
- return k
-}
-
-type boringCertificate struct {
- name string
- org string
- parentOrg string
- der []byte
- cert *Certificate
- key interface{}
- fipsOK bool
-}
-
-func TestBoringAllowCert(t *testing.T) {
- R1 := testBoringCert(t, "R1", boringRSAKey(t, 2048), nil, boringCertCA|boringCertFIPSOK)
- R2 := testBoringCert(t, "R2", boringRSAKey(t, 512), nil, boringCertCA)
- R3 := testBoringCert(t, "R3", boringRSAKey(t, 4096), nil, boringCertCA|boringCertFIPSOK)
-
- M1_R1 := testBoringCert(t, "M1_R1", boringECDSAKey(t, elliptic.P256()), R1, boringCertCA|boringCertFIPSOK)
- M2_R1 := testBoringCert(t, "M2_R1", boringECDSAKey(t, elliptic.P224()), R1, boringCertCA)
-
- I_R1 := testBoringCert(t, "I_R1", boringRSAKey(t, 3072), R1, boringCertCA|boringCertFIPSOK)
- testBoringCert(t, "I_R2", I_R1.key, R2, boringCertCA|boringCertFIPSOK)
- testBoringCert(t, "I_M1", I_R1.key, M1_R1, boringCertCA|boringCertFIPSOK)
- testBoringCert(t, "I_M2", I_R1.key, M2_R1, boringCertCA|boringCertFIPSOK)
-
- I_R3 := testBoringCert(t, "I_R3", boringRSAKey(t, 3072), R3, boringCertCA|boringCertFIPSOK)
- testBoringCert(t, "I_R3", I_R3.key, R3, boringCertCA|boringCertFIPSOK)
-
- testBoringCert(t, "L1_I", boringECDSAKey(t, elliptic.P384()), I_R1, boringCertLeaf|boringCertFIPSOK)
- testBoringCert(t, "L2_I", boringRSAKey(t, 1024), I_R1, boringCertLeaf)
-}
-
-func testBoringCert(t *testing.T, name string, key interface{}, parent *boringCertificate, mode int) *boringCertificate {
- org := name
- parentOrg := ""
- if i := strings.Index(org, "_"); i >= 0 {
- org = org[:i]
- parentOrg = name[i+1:]
- }
- tmpl := &Certificate{
- SerialNumber: big.NewInt(1),
- Subject: pkix.Name{
- Organization: []string{org},
- },
- NotBefore: time.Unix(0, 0),
- NotAfter: time.Unix(0, 0),
-
- KeyUsage: KeyUsageKeyEncipherment | KeyUsageDigitalSignature,
- ExtKeyUsage: []ExtKeyUsage{ExtKeyUsageServerAuth, ExtKeyUsageClientAuth},
- BasicConstraintsValid: true,
- }
- if mode&^boringCertFIPSOK == boringCertLeaf {
- tmpl.DNSNames = []string{"example.com"}
- } else {
- tmpl.IsCA = true
- tmpl.KeyUsage |= KeyUsageCertSign
- }
-
- var pcert *Certificate
- var pkey interface{}
- if parent != nil {
- pcert = parent.cert
- pkey = parent.key
- } else {
- pcert = tmpl
- pkey = key
- }
-
- var pub interface{}
- var desc string
- switch k := key.(type) {
- case *rsa.PrivateKey:
- pub = &k.PublicKey
- desc = fmt.Sprintf("RSA-%d", k.N.BitLen())
- case *ecdsa.PrivateKey:
- pub = &k.PublicKey
- desc = "ECDSA-" + k.Curve.Params().Name
- default:
- t.Fatalf("invalid key %T", key)
- }
-
- der, err := CreateCertificate(rand.Reader, tmpl, pcert, pub, pkey)
- if err != nil {
- t.Fatal(err)
- }
- cert, err := ParseCertificate(der)
- if err != nil {
- t.Fatal(err)
- }
-
- // Tell isBoringCertificate to enforce FIPS restrictions for this check.
- fipstls.Force()
- defer fipstls.Abandon()
-
- fipsOK := mode&boringCertFIPSOK != 0
- if boringAllowCert(cert) != fipsOK {
- t.Errorf("boringAllowCert(cert with %s key) = %v, want %v", desc, !fipsOK, fipsOK)
- }
- return &boringCertificate{name, org, parentOrg, der, cert, key, fipsOK}
-}
+++ /dev/null
-// 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 x509
-
-func boringAllowCert(c *Certificate) bool { return true }
}
}
- if !boringAllowCert(c) {
- // IncompatibleUsage is not quite right here,
- // but it's also the "no chains found" error
- // and is close enough.
- return CertificateInvalidError{c, IncompatibleUsage, ""}
- }
-
return nil
}
FIPS < crypto/internal/fips140/check/checktest;
+ FIPS, sync/atomic < crypto/tls/internal/fips140tls;
+
NONE < crypto/internal/boring/sig, crypto/internal/boring/syso;
- sync/atomic < crypto/internal/boring/bcache, crypto/internal/boring/fipstls;
- crypto/internal/boring/sig, crypto/internal/boring/fipstls < crypto/tls/fipsonly;
+ sync/atomic < crypto/internal/boring/bcache, crypto/internal/boring/fips140tls;
+ crypto/internal/boring/sig, crypto/tls/internal/fips140tls < crypto/tls/fipsonly;
# CRYPTO is core crypto algorithms - no cgo, fmt, net.
FIPS,
< crypto/x509/internal/macos
< crypto/x509/pkix;
- crypto/internal/boring/fipstls, crypto/x509/pkix
+ crypto/tls/internal/fips140tls, crypto/x509/pkix
< crypto/x509
< crypto/tls;