crypto/fips140: add WithoutEnforcement

WithoutEnforcement lets programs running under GODEBUG=fips140=only
selectively opt out of strict enforcement. This is especially helpful
for non-critical uses of cryptography routines like SHA-1 for content
addressable storage backends (E.g. git).

Fixes #74630

Change-Id: Iabba1f5eb63498db98047aca45e09c5dccf2fbdf
Reviewed-on: https://go-review.googlesource.com/c/go/+/723720
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Auto-Submit: Filippo Valsorda <filippo@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Roland Shoemaker <roland@golang.org>
This commit is contained in:
Daniel Morsing 2025-11-24 13:08:10 +00:00 committed by Gopher Robot
parent e2cae9ecdf
commit 86bbea0cfa
32 changed files with 252 additions and 61 deletions

2
api/next/74630.txt Normal file
View file

@ -0,0 +1,2 @@
pkg crypto/fips140, func Enforced() bool #74630
pkg crypto/fips140, func WithoutEnforcement(func()) #74630

View file

@ -0,0 +1,2 @@
The new [WithoutEnforcement] and [Enforced] functions now allow running
in `GODEBUG=fips140=only` mode while selectively disabling the strict FIPS 140-3 checks.

View file

@ -54,7 +54,7 @@ func NewCBCEncrypter(b Block, iv []byte) BlockMode {
if b, ok := b.(*aes.Block); ok { if b, ok := b.(*aes.Block); ok {
return aes.NewCBCEncrypter(b, [16]byte(iv)) return aes.NewCBCEncrypter(b, [16]byte(iv))
} }
if fips140only.Enabled { if fips140only.Enforced() {
panic("crypto/cipher: use of CBC with non-AES ciphers is not allowed in FIPS 140-only mode") panic("crypto/cipher: use of CBC with non-AES ciphers is not allowed in FIPS 140-only mode")
} }
if cbc, ok := b.(cbcEncAble); ok { if cbc, ok := b.(cbcEncAble); ok {
@ -133,7 +133,7 @@ func NewCBCDecrypter(b Block, iv []byte) BlockMode {
if b, ok := b.(*aes.Block); ok { if b, ok := b.(*aes.Block); ok {
return aes.NewCBCDecrypter(b, [16]byte(iv)) return aes.NewCBCDecrypter(b, [16]byte(iv))
} }
if fips140only.Enabled { if fips140only.Enforced() {
panic("crypto/cipher: use of CBC with non-AES ciphers is not allowed in FIPS 140-only mode") panic("crypto/cipher: use of CBC with non-AES ciphers is not allowed in FIPS 140-only mode")
} }
if cbc, ok := b.(cbcDecAble); ok { if cbc, ok := b.(cbcDecAble); ok {

View file

@ -61,7 +61,7 @@ func (x *cfb) XORKeyStream(dst, src []byte) {
// CFB is also unoptimized and not validated as part of the FIPS 140-3 module. // CFB is also unoptimized and not validated as part of the FIPS 140-3 module.
// If an unauthenticated [Stream] mode is required, use [NewCTR] instead. // If an unauthenticated [Stream] mode is required, use [NewCTR] instead.
func NewCFBEncrypter(block Block, iv []byte) Stream { func NewCFBEncrypter(block Block, iv []byte) Stream {
if fips140only.Enabled { if fips140only.Enforced() {
panic("crypto/cipher: use of CFB is not allowed in FIPS 140-only mode") panic("crypto/cipher: use of CFB is not allowed in FIPS 140-only mode")
} }
return newCFB(block, iv, false) return newCFB(block, iv, false)
@ -77,7 +77,7 @@ func NewCFBEncrypter(block Block, iv []byte) Stream {
// CFB is also unoptimized and not validated as part of the FIPS 140-3 module. // CFB is also unoptimized and not validated as part of the FIPS 140-3 module.
// If an unauthenticated [Stream] mode is required, use [NewCTR] instead. // If an unauthenticated [Stream] mode is required, use [NewCTR] instead.
func NewCFBDecrypter(block Block, iv []byte) Stream { func NewCFBDecrypter(block Block, iv []byte) Stream {
if fips140only.Enabled { if fips140only.Enforced() {
panic("crypto/cipher: use of CFB is not allowed in FIPS 140-only mode") panic("crypto/cipher: use of CFB is not allowed in FIPS 140-only mode")
} }
return newCFB(block, iv, true) return newCFB(block, iv, true)

View file

@ -42,7 +42,7 @@ func NewCTR(block Block, iv []byte) Stream {
if block, ok := block.(*aes.Block); ok { if block, ok := block.(*aes.Block); ok {
return aesCtrWrapper{aes.NewCTR(block, iv)} return aesCtrWrapper{aes.NewCTR(block, iv)}
} }
if fips140only.Enabled { if fips140only.Enforced() {
panic("crypto/cipher: use of CTR with non-AES ciphers is not allowed in FIPS 140-only mode") panic("crypto/cipher: use of CTR with non-AES ciphers is not allowed in FIPS 140-only mode")
} }
if ctr, ok := block.(ctrAble); ok { if ctr, ok := block.(ctrAble); ok {

View file

@ -28,7 +28,7 @@ const (
// An exception is when the underlying [Block] was created by aes.NewCipher // An exception is when the underlying [Block] was created by aes.NewCipher
// on systems with hardware support for AES. See the [crypto/aes] package documentation for details. // on systems with hardware support for AES. See the [crypto/aes] package documentation for details.
func NewGCM(cipher Block) (AEAD, error) { func NewGCM(cipher Block) (AEAD, error) {
if fips140only.Enabled { if fips140only.Enforced() {
return nil, errors.New("crypto/cipher: use of GCM with arbitrary IVs is not allowed in FIPS 140-only mode, use NewGCMWithRandomNonce") return nil, errors.New("crypto/cipher: use of GCM with arbitrary IVs is not allowed in FIPS 140-only mode, use NewGCMWithRandomNonce")
} }
return newGCM(cipher, gcmStandardNonceSize, gcmTagSize) return newGCM(cipher, gcmStandardNonceSize, gcmTagSize)
@ -42,7 +42,7 @@ func NewGCM(cipher Block) (AEAD, error) {
// cryptosystem that uses non-standard nonce lengths. All other users should use // cryptosystem that uses non-standard nonce lengths. All other users should use
// [NewGCM], which is faster and more resistant to misuse. // [NewGCM], which is faster and more resistant to misuse.
func NewGCMWithNonceSize(cipher Block, size int) (AEAD, error) { func NewGCMWithNonceSize(cipher Block, size int) (AEAD, error) {
if fips140only.Enabled { if fips140only.Enforced() {
return nil, errors.New("crypto/cipher: use of GCM with arbitrary IVs is not allowed in FIPS 140-only mode, use NewGCMWithRandomNonce") return nil, errors.New("crypto/cipher: use of GCM with arbitrary IVs is not allowed in FIPS 140-only mode, use NewGCMWithRandomNonce")
} }
return newGCM(cipher, size, gcmTagSize) return newGCM(cipher, size, gcmTagSize)
@ -57,7 +57,7 @@ func NewGCMWithNonceSize(cipher Block, size int) (AEAD, error) {
// cryptosystem that uses non-standard tag lengths. All other users should use // cryptosystem that uses non-standard tag lengths. All other users should use
// [NewGCM], which is more resistant to misuse. // [NewGCM], which is more resistant to misuse.
func NewGCMWithTagSize(cipher Block, tagSize int) (AEAD, error) { func NewGCMWithTagSize(cipher Block, tagSize int) (AEAD, error) {
if fips140only.Enabled { if fips140only.Enforced() {
return nil, errors.New("crypto/cipher: use of GCM with arbitrary IVs is not allowed in FIPS 140-only mode, use NewGCMWithRandomNonce") return nil, errors.New("crypto/cipher: use of GCM with arbitrary IVs is not allowed in FIPS 140-only mode, use NewGCMWithRandomNonce")
} }
return newGCM(cipher, gcmStandardNonceSize, tagSize) return newGCM(cipher, gcmStandardNonceSize, tagSize)
@ -66,7 +66,7 @@ func NewGCMWithTagSize(cipher Block, tagSize int) (AEAD, error) {
func newGCM(cipher Block, nonceSize, tagSize int) (AEAD, error) { func newGCM(cipher Block, nonceSize, tagSize int) (AEAD, error) {
c, ok := cipher.(*aes.Block) c, ok := cipher.(*aes.Block)
if !ok { if !ok {
if fips140only.Enabled { if fips140only.Enforced() {
return nil, errors.New("crypto/cipher: use of GCM with non-AES ciphers is not allowed in FIPS 140-only mode") return nil, errors.New("crypto/cipher: use of GCM with non-AES ciphers is not allowed in FIPS 140-only mode")
} }
return newGCMFallback(cipher, nonceSize, tagSize) return newGCMFallback(cipher, nonceSize, tagSize)

View file

@ -29,7 +29,7 @@ type ofb struct {
// OFB is also unoptimized and not validated as part of the FIPS 140-3 module. // OFB is also unoptimized and not validated as part of the FIPS 140-3 module.
// If an unauthenticated [Stream] mode is required, use [NewCTR] instead. // If an unauthenticated [Stream] mode is required, use [NewCTR] instead.
func NewOFB(b Block, iv []byte) Stream { func NewOFB(b Block, iv []byte) Stream {
if fips140only.Enabled { if fips140only.Enforced() {
panic("crypto/cipher: use of OFB is not allowed in FIPS 140-only mode") panic("crypto/cipher: use of OFB is not allowed in FIPS 140-only mode")
} }

View file

@ -29,7 +29,7 @@ type desCipher struct {
// NewCipher creates and returns a new [cipher.Block]. // NewCipher creates and returns a new [cipher.Block].
func NewCipher(key []byte) (cipher.Block, error) { func NewCipher(key []byte) (cipher.Block, error) {
if fips140only.Enabled { if fips140only.Enforced() {
return nil, errors.New("crypto/des: use of DES is not allowed in FIPS 140-only mode") return nil, errors.New("crypto/des: use of DES is not allowed in FIPS 140-only mode")
} }
@ -77,7 +77,7 @@ type tripleDESCipher struct {
// NewTripleDESCipher creates and returns a new [cipher.Block]. // NewTripleDESCipher creates and returns a new [cipher.Block].
func NewTripleDESCipher(key []byte) (cipher.Block, error) { func NewTripleDESCipher(key []byte) (cipher.Block, error) {
if fips140only.Enabled { if fips140only.Enforced() {
return nil, errors.New("crypto/des: use of TripleDES is not allowed in FIPS 140-only mode") return nil, errors.New("crypto/des: use of TripleDES is not allowed in FIPS 140-only mode")
} }

View file

@ -64,7 +64,7 @@ const numMRTests = 64
// GenerateParameters puts a random, valid set of DSA parameters into params. // GenerateParameters puts a random, valid set of DSA parameters into params.
// This function can take many seconds, even on fast machines. // This function can take many seconds, even on fast machines.
func GenerateParameters(params *Parameters, rand io.Reader, sizes ParameterSizes) error { func GenerateParameters(params *Parameters, rand io.Reader, sizes ParameterSizes) error {
if fips140only.Enabled { if fips140only.Enforced() {
return errors.New("crypto/dsa: use of DSA is not allowed in FIPS 140-only mode") return errors.New("crypto/dsa: use of DSA is not allowed in FIPS 140-only mode")
} }
@ -162,7 +162,7 @@ GeneratePrimes:
// GenerateKey generates a public&private key pair. The Parameters of the // GenerateKey generates a public&private key pair. The Parameters of the
// [PrivateKey] must already be valid (see [GenerateParameters]). // [PrivateKey] must already be valid (see [GenerateParameters]).
func GenerateKey(priv *PrivateKey, rand io.Reader) error { func GenerateKey(priv *PrivateKey, rand io.Reader) error {
if fips140only.Enabled { if fips140only.Enforced() {
return errors.New("crypto/dsa: use of DSA is not allowed in FIPS 140-only mode") return errors.New("crypto/dsa: use of DSA is not allowed in FIPS 140-only mode")
} }
@ -212,7 +212,7 @@ func fermatInverse(k, P *big.Int) *big.Int {
// Be aware that calling Sign with an attacker-controlled [PrivateKey] may // Be aware that calling Sign with an attacker-controlled [PrivateKey] may
// require an arbitrary amount of CPU. // require an arbitrary amount of CPU.
func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err error) { func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err error) {
if fips140only.Enabled { if fips140only.Enforced() {
return nil, nil, errors.New("crypto/dsa: use of DSA is not allowed in FIPS 140-only mode") return nil, nil, errors.New("crypto/dsa: use of DSA is not allowed in FIPS 140-only mode")
} }
@ -284,7 +284,7 @@ func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err err
// to the byte-length of the subgroup. This function does not perform that // to the byte-length of the subgroup. This function does not perform that
// truncation itself. // truncation itself.
func Verify(pub *PublicKey, hash []byte, r, s *big.Int) bool { func Verify(pub *PublicKey, hash []byte, r, s *big.Int) bool {
if fips140only.Enabled { if fips140only.Enforced() {
panic("crypto/dsa: use of DSA is not allowed in FIPS 140-only mode") panic("crypto/dsa: use of DSA is not allowed in FIPS 140-only mode")
} }

View file

@ -44,7 +44,7 @@ func (c *nistCurve) GenerateKey(rand io.Reader) (*PrivateKey, error) {
return k, nil return k, nil
} }
if fips140only.Enabled && !fips140only.ApprovedRandomReader(rand) { if fips140only.Enforced() && !fips140only.ApprovedRandomReader(rand) {
return nil, errors.New("crypto/ecdh: only crypto/rand.Reader is allowed in FIPS 140-only mode") return nil, errors.New("crypto/ecdh: only crypto/rand.Reader is allowed in FIPS 140-only mode")
} }

View file

@ -35,7 +35,7 @@ func (c *x25519Curve) String() string {
} }
func (c *x25519Curve) GenerateKey(rand io.Reader) (*PrivateKey, error) { func (c *x25519Curve) GenerateKey(rand io.Reader) (*PrivateKey, error) {
if fips140only.Enabled { if fips140only.Enforced() {
return nil, errors.New("crypto/ecdh: use of X25519 is not allowed in FIPS 140-only mode") return nil, errors.New("crypto/ecdh: use of X25519 is not allowed in FIPS 140-only mode")
} }
key := make([]byte, x25519PrivateKeySize) key := make([]byte, x25519PrivateKeySize)
@ -47,7 +47,7 @@ func (c *x25519Curve) GenerateKey(rand io.Reader) (*PrivateKey, error) {
} }
func (c *x25519Curve) NewPrivateKey(key []byte) (*PrivateKey, error) { func (c *x25519Curve) NewPrivateKey(key []byte) (*PrivateKey, error) {
if fips140only.Enabled { if fips140only.Enforced() {
return nil, errors.New("crypto/ecdh: use of X25519 is not allowed in FIPS 140-only mode") return nil, errors.New("crypto/ecdh: use of X25519 is not allowed in FIPS 140-only mode")
} }
if len(key) != x25519PrivateKeySize { if len(key) != x25519PrivateKeySize {
@ -67,7 +67,7 @@ func (c *x25519Curve) NewPrivateKey(key []byte) (*PrivateKey, error) {
} }
func (c *x25519Curve) NewPublicKey(key []byte) (*PublicKey, error) { func (c *x25519Curve) NewPublicKey(key []byte) (*PublicKey, error) {
if fips140only.Enabled { if fips140only.Enforced() {
return nil, errors.New("crypto/ecdh: use of X25519 is not allowed in FIPS 140-only mode") return nil, errors.New("crypto/ecdh: use of X25519 is not allowed in FIPS 140-only mode")
} }
if len(key) != x25519PublicKeySize { if len(key) != x25519PublicKeySize {

View file

@ -358,7 +358,7 @@ func GenerateKey(c elliptic.Curve, rand io.Reader) (*PrivateKey, error) {
} }
func generateFIPS[P ecdsa.Point[P]](curve elliptic.Curve, c *ecdsa.Curve[P], rand io.Reader) (*PrivateKey, error) { func generateFIPS[P ecdsa.Point[P]](curve elliptic.Curve, c *ecdsa.Curve[P], rand io.Reader) (*PrivateKey, error) {
if fips140only.Enabled && !fips140only.ApprovedRandomReader(rand) { if fips140only.Enforced() && !fips140only.ApprovedRandomReader(rand) {
return nil, errors.New("crypto/ecdsa: only crypto/rand.Reader is allowed in FIPS 140-only mode") return nil, errors.New("crypto/ecdsa: only crypto/rand.Reader is allowed in FIPS 140-only mode")
} }
privateKey, err := ecdsa.GenerateKey(c, rand) privateKey, err := ecdsa.GenerateKey(c, rand)
@ -403,7 +403,7 @@ func SignASN1(rand io.Reader, priv *PrivateKey, hash []byte) ([]byte, error) {
} }
func signFIPS[P ecdsa.Point[P]](c *ecdsa.Curve[P], priv *PrivateKey, rand io.Reader, hash []byte) ([]byte, error) { func signFIPS[P ecdsa.Point[P]](c *ecdsa.Curve[P], priv *PrivateKey, rand io.Reader, hash []byte) ([]byte, error) {
if fips140only.Enabled && !fips140only.ApprovedRandomReader(rand) { if fips140only.Enforced() && !fips140only.ApprovedRandomReader(rand) {
return nil, errors.New("crypto/ecdsa: only crypto/rand.Reader is allowed in FIPS 140-only mode") return nil, errors.New("crypto/ecdsa: only crypto/rand.Reader is allowed in FIPS 140-only mode")
} }
k, err := privateKeyToFIPS(c, priv) k, err := privateKeyToFIPS(c, priv)
@ -448,7 +448,7 @@ func signFIPSDeterministic[P ecdsa.Point[P]](c *ecdsa.Curve[P], hashFunc crypto.
return nil, err return nil, err
} }
h := fips140hash.UnwrapNew(hashFunc.New) h := fips140hash.UnwrapNew(hashFunc.New)
if fips140only.Enabled && !fips140only.ApprovedHash(h()) { if fips140only.Enforced() && !fips140only.ApprovedHash(h()) {
return nil, errors.New("crypto/ecdsa: use of hash functions other than SHA-2 or SHA-3 is not allowed in FIPS 140-only mode") return nil, errors.New("crypto/ecdsa: use of hash functions other than SHA-2 or SHA-3 is not allowed in FIPS 140-only mode")
} }
sig, err := ecdsa.SignDeterministic(c, h, k, hash) sig, err := ecdsa.SignDeterministic(c, h, k, hash)

View file

@ -20,7 +20,7 @@ import (
// deprecated custom curves. // deprecated custom curves.
func generateLegacy(c elliptic.Curve, rand io.Reader) (*PrivateKey, error) { func generateLegacy(c elliptic.Curve, rand io.Reader) (*PrivateKey, error) {
if fips140only.Enabled { if fips140only.Enforced() {
return nil, errors.New("crypto/ecdsa: use of custom curves is not allowed in FIPS 140-only mode") return nil, errors.New("crypto/ecdsa: use of custom curves is not allowed in FIPS 140-only mode")
} }
@ -81,7 +81,7 @@ func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err err
} }
func signLegacy(priv *PrivateKey, csprng io.Reader, hash []byte) (sig []byte, err error) { func signLegacy(priv *PrivateKey, csprng io.Reader, hash []byte) (sig []byte, err error) {
if fips140only.Enabled { if fips140only.Enforced() {
return nil, errors.New("crypto/ecdsa: use of custom curves is not allowed in FIPS 140-only mode") return nil, errors.New("crypto/ecdsa: use of custom curves is not allowed in FIPS 140-only mode")
} }
@ -153,7 +153,7 @@ func Verify(pub *PublicKey, hash []byte, r, s *big.Int) bool {
} }
func verifyLegacy(pub *PublicKey, hash []byte, sig []byte) bool { func verifyLegacy(pub *PublicKey, hash []byte, sig []byte) bool {
if fips140only.Enabled { if fips140only.Enforced() {
panic("crypto/ecdsa: use of custom curves is not allowed in FIPS 140-only mode") panic("crypto/ecdsa: use of custom curves is not allowed in FIPS 140-only mode")
} }

View file

@ -110,7 +110,7 @@ func (priv PrivateKey) Sign(rand io.Reader, message []byte, opts crypto.SignerOp
case hash == crypto.SHA512: // Ed25519ph case hash == crypto.SHA512: // Ed25519ph
return ed25519.SignPH(k, message, context) return ed25519.SignPH(k, message, context)
case hash == crypto.Hash(0) && context != "": // Ed25519ctx case hash == crypto.Hash(0) && context != "": // Ed25519ctx
if fips140only.Enabled { if fips140only.Enforced() {
return nil, errors.New("crypto/ed25519: use of Ed25519ctx is not allowed in FIPS 140-only mode") return nil, errors.New("crypto/ed25519: use of Ed25519ctx is not allowed in FIPS 140-only mode")
} }
return ed25519.SignCtx(k, message, context) return ed25519.SignCtx(k, message, context)
@ -230,7 +230,7 @@ func VerifyWithOptions(publicKey PublicKey, message, sig []byte, opts *Options)
case opts.Hash == crypto.SHA512: // Ed25519ph case opts.Hash == crypto.SHA512: // Ed25519ph
return ed25519.VerifyPH(k, message, sig, opts.Context) return ed25519.VerifyPH(k, message, sig, opts.Context)
case opts.Hash == crypto.Hash(0) && opts.Context != "": // Ed25519ctx case opts.Hash == crypto.Hash(0) && opts.Context != "": // Ed25519ctx
if fips140only.Enabled { if fips140only.Enforced() {
return errors.New("crypto/ed25519: use of Ed25519ctx is not allowed in FIPS 140-only mode") return errors.New("crypto/ed25519: use of Ed25519ctx is not allowed in FIPS 140-only mode")
} }
return ed25519.VerifyCtx(k, message, sig, opts.Context) return ed25519.VerifyCtx(k, message, sig, opts.Context)

View file

@ -0,0 +1,47 @@
// Copyright 2025 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 fips140
import (
"internal/godebug"
_ "unsafe" // for linkname
)
// WithoutEnforcement disables strict FIPS 140-3 enforcement while executing f.
// Calling WithoutEnforcement without strict enforcement enabled
// (GODEBUG=fips140=only is not set or already inside of a call to
// WithoutEnforcement) is a no-op.
//
// WithoutEnforcement is inherited by any goroutines spawned while executing f.
//
// As this disables enforcement, it should be applied carefully to tightly
// scoped functions.
func WithoutEnforcement(f func()) {
if !Enabled() || !Enforced() {
f()
return
}
setBypass()
defer unsetBypass()
f()
}
var enabled = godebug.New("fips140").Value() == "only"
// Enforced indicates if strict FIPS 140-3 enforcement is enabled. Strict
// enforcement is enabled when a program is run with GODEBUG=fips140=only and
// enforcement has not been disabled by a call to [WithoutEnforcement].
func Enforced() bool {
return enabled && !isBypassed()
}
//go:linkname setBypass
func setBypass()
//go:linkname isBypassed
func isBypassed() bool
//go:linkname unsetBypass
func unsetBypass()

View file

@ -0,0 +1,46 @@
// Copyright 2025 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 fips140_test
import (
"crypto/internal/cryptotest"
"internal/testenv"
"path/filepath"
"strings"
"testing"
)
func TestWithoutEnforcement(t *testing.T) {
testenv.MustHaveExec(t)
testenv.MustHaveGoBuild(t)
cryptotest.MustSupportFIPS140(t)
tool, _ := testenv.GoTool()
tmpdir := t.TempDir()
binFile := filepath.Join(tmpdir, "fips140.test")
cmd := testenv.Command(t, tool, "test", "-c", "-o", binFile, "./testdata")
out, err := cmd.CombinedOutput()
if err != nil {
t.Log(string(out))
t.Errorf("Could not build enforcement tests")
}
cmd = testenv.Command(t, binFile, "-test.list", ".")
list, err := cmd.CombinedOutput()
if err != nil {
t.Log(string(out))
t.Errorf("Could not get enforcement test list")
}
for test := range strings.Lines(string(list)) {
test = strings.TrimSpace(test)
t.Run(test, func(t *testing.T) {
cmd = testenv.Command(t, binFile, "-test.run", "^"+test+"$")
cmd.Env = append(cmd.Env, "GODEBUG=fips140=only")
out, err := cmd.CombinedOutput()
if err != nil {
t.Error(string(out))
}
})
}
}

View file

@ -0,0 +1,65 @@
// Copyright 2025 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 fips140_test
import (
"crypto/des"
"crypto/fips140"
"testing"
)
func expectAllowed(t *testing.T, why string, expected bool) {
t.Helper()
result := isAllowed()
if result != expected {
t.Fatalf("%v: expected: %v, got: %v", why, expected, result)
}
}
func isAllowed() bool {
_, err := des.NewCipher(make([]byte, 8))
return err == nil
}
func TestDisabled(t *testing.T) {
expectAllowed(t, "before enforcement disabled", false)
fips140.WithoutEnforcement(func() {
expectAllowed(t, "inside WithoutEnforcement", true)
})
// make sure that bypass doesn't live on after returning
expectAllowed(t, "after WithoutEnforcement", false)
}
func TestNested(t *testing.T) {
expectAllowed(t, "before enforcement bypass", false)
fips140.WithoutEnforcement(func() {
fips140.WithoutEnforcement(func() {
expectAllowed(t, "inside nested WithoutEnforcement", true)
})
expectAllowed(t, "inside nested WithoutEnforcement", true)
})
expectAllowed(t, "after enforcement bypass", false)
}
func TestGoroutineInherit(t *testing.T) {
ch := make(chan bool, 2)
expectAllowed(t, "before enforcement bypass", false)
fips140.WithoutEnforcement(func() {
go func() {
ch <- isAllowed()
}()
})
allowed := <-ch
if !allowed {
t.Fatal("goroutine didn't inherit enforcement bypass")
}
go func() {
ch <- isAllowed()
}()
allowed = <-ch
if allowed {
t.Fatal("goroutine inherited bypass after WithoutEnforcement return")
}
}

View file

@ -71,7 +71,7 @@ func Key[Hash hash.Hash](h func() Hash, secret, salt []byte, info string, keyLen
} }
func checkFIPS140Only[Hash hash.Hash](h func() Hash, key []byte) error { func checkFIPS140Only[Hash hash.Hash](h func() Hash, key []byte) error {
if !fips140only.Enabled { if !fips140only.Enforced() {
return nil return nil
} }
if len(key) < 112/8 { if len(key) < 112/8 {

View file

@ -45,7 +45,7 @@ func New(h func() hash.Hash, key []byte) hash.Hash {
// BoringCrypto did not recognize h, so fall through to standard Go code. // BoringCrypto did not recognize h, so fall through to standard Go code.
} }
h = fips140hash.UnwrapNew(h) h = fips140hash.UnwrapNew(h)
if fips140only.Enabled { if fips140only.Enforced() {
if len(key) < 112/8 { if len(key) < 112/8 {
panic("crypto/hmac: use of keys shorter than 112 bits is not allowed in FIPS 140-only mode") panic("crypto/hmac: use of keys shorter than 112 bits is not allowed in FIPS 140-only mode")
} }

View file

@ -5,18 +5,20 @@
package fips140only package fips140only
import ( import (
"crypto/fips140"
"crypto/internal/fips140/drbg" "crypto/internal/fips140/drbg"
"crypto/internal/fips140/sha256" "crypto/internal/fips140/sha256"
"crypto/internal/fips140/sha3" "crypto/internal/fips140/sha3"
"crypto/internal/fips140/sha512" "crypto/internal/fips140/sha512"
"hash" "hash"
"internal/godebug"
"io" "io"
) )
// Enabled reports whether FIPS 140-only mode is enabled, in which non-approved // Enforced reports whether FIPS 140-only mode is enabled and enforced, in which non-approved
// cryptography returns an error or panics. // cryptography returns an error or panics.
var Enabled = godebug.New("fips140").Value() == "only" func Enforced() bool {
return fips140.Enforced()
}
func ApprovedHash(h hash.Hash) bool { func ApprovedHash(h hash.Hash) bool {
switch h.(type) { switch h.(type) {

View file

@ -124,7 +124,7 @@ func (d *digest) Size() int { return Size }
func (d *digest) BlockSize() int { return BlockSize } func (d *digest) BlockSize() int { return BlockSize }
func (d *digest) Write(p []byte) (nn int, err error) { func (d *digest) Write(p []byte) (nn int, err error) {
if fips140only.Enabled { if fips140only.Enforced() {
return 0, errors.New("crypto/md5: use of MD5 is not allowed in FIPS 140-only mode") return 0, errors.New("crypto/md5: use of MD5 is not allowed in FIPS 140-only mode")
} }
// Note that we currently call block or blockGeneric // Note that we currently call block or blockGeneric
@ -173,7 +173,7 @@ func (d *digest) Sum(in []byte) []byte {
} }
func (d *digest) checkSum() [Size]byte { func (d *digest) checkSum() [Size]byte {
if fips140only.Enabled { if fips140only.Enforced() {
panic("crypto/md5: use of MD5 is not allowed in FIPS 140-only mode") panic("crypto/md5: use of MD5 is not allowed in FIPS 140-only mode")
} }

View file

@ -39,7 +39,7 @@ import (
// Setting keyLength to a value outside of this range will result in an error. // Setting keyLength to a value outside of this range will result in an error.
func Key[Hash hash.Hash](h func() Hash, password string, salt []byte, iter, keyLength int) ([]byte, error) { func Key[Hash hash.Hash](h func() Hash, password string, salt []byte, iter, keyLength int) ([]byte, error) {
fh := fips140hash.UnwrapNew(h) fh := fips140hash.UnwrapNew(h)
if fips140only.Enabled { if fips140only.Enforced() {
if keyLength < 112/8 { if keyLength < 112/8 {
return nil, errors.New("crypto/pbkdf2: use of keys shorter than 112 bits is not allowed in FIPS 140-only mode") return nil, errors.New("crypto/pbkdf2: use of keys shorter than 112 bits is not allowed in FIPS 140-only mode")
} }

View file

@ -15,7 +15,7 @@ import (
// Prime returns a number of the given bit length that is prime with high probability. // Prime returns a number of the given bit length that is prime with high probability.
// Prime will return error for any error returned by rand.Read or if bits < 2. // Prime will return error for any error returned by rand.Read or if bits < 2.
func Prime(rand io.Reader, bits int) (*big.Int, error) { func Prime(rand io.Reader, bits int) (*big.Int, error) {
if fips140only.Enabled { if fips140only.Enforced() {
return nil, errors.New("crypto/rand: use of Prime is not allowed in FIPS 140-only mode") return nil, errors.New("crypto/rand: use of Prime is not allowed in FIPS 140-only mode")
} }
if bits < 2 { if bits < 2 {

View file

@ -31,7 +31,7 @@ func (k KeySizeError) Error() string {
// NewCipher creates and returns a new [Cipher]. The key argument should be the // NewCipher creates and returns a new [Cipher]. The key argument should be the
// RC4 key, at least 1 byte and at most 256 bytes. // RC4 key, at least 1 byte and at most 256 bytes.
func NewCipher(key []byte) (*Cipher, error) { func NewCipher(key []byte) (*Cipher, error) {
if fips140only.Enabled { if fips140only.Enforced() {
return nil, errors.New("crypto/rc4: use of RC4 is not allowed in FIPS 140-only mode") return nil, errors.New("crypto/rc4: use of RC4 is not allowed in FIPS 140-only mode")
} }
k := len(key) k := len(key)

View file

@ -84,10 +84,10 @@ func SignPSS(rand io.Reader, priv *PrivateKey, hash crypto.Hash, digest []byte,
if err := checkFIPS140OnlyPrivateKey(priv); err != nil { if err := checkFIPS140OnlyPrivateKey(priv); err != nil {
return nil, err return nil, err
} }
if fips140only.Enabled && !fips140only.ApprovedHash(h) { if fips140only.Enforced() && !fips140only.ApprovedHash(h) {
return nil, errors.New("crypto/rsa: use of hash functions other than SHA-2 or SHA-3 is not allowed in FIPS 140-only mode") return nil, errors.New("crypto/rsa: use of hash functions other than SHA-2 or SHA-3 is not allowed in FIPS 140-only mode")
} }
if fips140only.Enabled && !fips140only.ApprovedRandomReader(rand) { if fips140only.Enforced() && !fips140only.ApprovedRandomReader(rand) {
return nil, errors.New("crypto/rsa: only crypto/rand.Reader is allowed in FIPS 140-only mode") return nil, errors.New("crypto/rsa: only crypto/rand.Reader is allowed in FIPS 140-only mode")
} }
@ -97,7 +97,7 @@ func SignPSS(rand io.Reader, priv *PrivateKey, hash crypto.Hash, digest []byte,
} }
saltLength := opts.saltLength() saltLength := opts.saltLength()
if fips140only.Enabled && saltLength > h.Size() { if fips140only.Enforced() && saltLength > h.Size() {
return nil, errors.New("crypto/rsa: use of PSS salt longer than the hash is not allowed in FIPS 140-only mode") return nil, errors.New("crypto/rsa: use of PSS salt longer than the hash is not allowed in FIPS 140-only mode")
} }
switch saltLength { switch saltLength {
@ -149,7 +149,7 @@ func VerifyPSS(pub *PublicKey, hash crypto.Hash, digest []byte, sig []byte, opts
if err := checkFIPS140OnlyPublicKey(pub); err != nil { if err := checkFIPS140OnlyPublicKey(pub); err != nil {
return err return err
} }
if fips140only.Enabled && !fips140only.ApprovedHash(h) { if fips140only.Enforced() && !fips140only.ApprovedHash(h) {
return errors.New("crypto/rsa: use of hash functions other than SHA-2 or SHA-3 is not allowed in FIPS 140-only mode") return errors.New("crypto/rsa: use of hash functions other than SHA-2 or SHA-3 is not allowed in FIPS 140-only mode")
} }
@ -159,7 +159,7 @@ func VerifyPSS(pub *PublicKey, hash crypto.Hash, digest []byte, sig []byte, opts
} }
saltLength := opts.saltLength() saltLength := opts.saltLength()
if fips140only.Enabled && saltLength > h.Size() { if fips140only.Enforced() && saltLength > h.Size() {
return errors.New("crypto/rsa: use of PSS salt longer than the hash is not allowed in FIPS 140-only mode") return errors.New("crypto/rsa: use of PSS salt longer than the hash is not allowed in FIPS 140-only mode")
} }
switch saltLength { switch saltLength {
@ -234,10 +234,10 @@ func encryptOAEP(hash hash.Hash, mgfHash hash.Hash, random io.Reader, pub *Publi
if err := checkFIPS140OnlyPublicKey(pub); err != nil { if err := checkFIPS140OnlyPublicKey(pub); err != nil {
return nil, err return nil, err
} }
if fips140only.Enabled && !fips140only.ApprovedHash(hash) { if fips140only.Enforced() && !fips140only.ApprovedHash(hash) {
return nil, errors.New("crypto/rsa: use of hash functions other than SHA-2 or SHA-3 is not allowed in FIPS 140-only mode") return nil, errors.New("crypto/rsa: use of hash functions other than SHA-2 or SHA-3 is not allowed in FIPS 140-only mode")
} }
if fips140only.Enabled && !fips140only.ApprovedRandomReader(random) { if fips140only.Enforced() && !fips140only.ApprovedRandomReader(random) {
return nil, errors.New("crypto/rsa: only crypto/rand.Reader is allowed in FIPS 140-only mode") return nil, errors.New("crypto/rsa: only crypto/rand.Reader is allowed in FIPS 140-only mode")
} }
@ -291,7 +291,7 @@ func decryptOAEP(hash, mgfHash hash.Hash, priv *PrivateKey, ciphertext []byte, l
if err := checkFIPS140OnlyPrivateKey(priv); err != nil { if err := checkFIPS140OnlyPrivateKey(priv); err != nil {
return nil, err return nil, err
} }
if fips140only.Enabled { if fips140only.Enforced() {
if !fips140only.ApprovedHash(hash) || !fips140only.ApprovedHash(mgfHash) { if !fips140only.ApprovedHash(hash) || !fips140only.ApprovedHash(mgfHash) {
return nil, errors.New("crypto/rsa: use of hash functions other than SHA-2 or SHA-3 is not allowed in FIPS 140-only mode") return nil, errors.New("crypto/rsa: use of hash functions other than SHA-2 or SHA-3 is not allowed in FIPS 140-only mode")
} }
@ -341,7 +341,7 @@ func SignPKCS1v15(random io.Reader, priv *PrivateKey, hash crypto.Hash, hashed [
if err := checkFIPS140OnlyPrivateKey(priv); err != nil { if err := checkFIPS140OnlyPrivateKey(priv); err != nil {
return nil, err return nil, err
} }
if fips140only.Enabled && !fips140only.ApprovedHash(fips140hash.Unwrap(hash.New())) { if fips140only.Enforced() && !fips140only.ApprovedHash(fips140hash.Unwrap(hash.New())) {
return nil, errors.New("crypto/rsa: use of hash functions other than SHA-2 or SHA-3 is not allowed in FIPS 140-only mode") return nil, errors.New("crypto/rsa: use of hash functions other than SHA-2 or SHA-3 is not allowed in FIPS 140-only mode")
} }
@ -387,7 +387,7 @@ func VerifyPKCS1v15(pub *PublicKey, hash crypto.Hash, hashed []byte, sig []byte)
if err := checkFIPS140OnlyPublicKey(pub); err != nil { if err := checkFIPS140OnlyPublicKey(pub); err != nil {
return err return err
} }
if fips140only.Enabled && !fips140only.ApprovedHash(fips140hash.Unwrap(hash.New())) { if fips140only.Enforced() && !fips140only.ApprovedHash(fips140hash.Unwrap(hash.New())) {
return errors.New("crypto/rsa: use of hash functions other than SHA-2 or SHA-3 is not allowed in FIPS 140-only mode") return errors.New("crypto/rsa: use of hash functions other than SHA-2 or SHA-3 is not allowed in FIPS 140-only mode")
} }
@ -415,7 +415,7 @@ func fipsError2[T any](x T, err error) (T, error) {
} }
func checkFIPS140OnlyPublicKey(pub *PublicKey) error { func checkFIPS140OnlyPublicKey(pub *PublicKey) error {
if !fips140only.Enabled { if !fips140only.Enforced() {
return nil return nil
} }
if pub.N == nil { if pub.N == nil {
@ -437,7 +437,7 @@ func checkFIPS140OnlyPublicKey(pub *PublicKey) error {
} }
func checkFIPS140OnlyPrivateKey(priv *PrivateKey) error { func checkFIPS140OnlyPrivateKey(priv *PrivateKey) error {
if !fips140only.Enabled { if !fips140only.Enforced() {
return nil return nil
} }
if err := checkFIPS140OnlyPublicKey(&priv.PublicKey); err != nil { if err := checkFIPS140OnlyPublicKey(&priv.PublicKey); err != nil {

View file

@ -49,7 +49,7 @@ type PKCS1v15DecryptOptions struct {
// //
// [draft-irtf-cfrg-rsa-guidance-05]: https://www.ietf.org/archive/id/draft-irtf-cfrg-rsa-guidance-05.html#name-rationale // [draft-irtf-cfrg-rsa-guidance-05]: https://www.ietf.org/archive/id/draft-irtf-cfrg-rsa-guidance-05.html#name-rationale
func EncryptPKCS1v15(random io.Reader, pub *PublicKey, msg []byte) ([]byte, error) { func EncryptPKCS1v15(random io.Reader, pub *PublicKey, msg []byte) ([]byte, error) {
if fips140only.Enabled { if fips140only.Enforced() {
return nil, errors.New("crypto/rsa: use of PKCS#1 v1.5 encryption is not allowed in FIPS 140-only mode") return nil, errors.New("crypto/rsa: use of PKCS#1 v1.5 encryption is not allowed in FIPS 140-only mode")
} }
@ -212,7 +212,7 @@ func DecryptPKCS1v15SessionKey(random io.Reader, priv *PrivateKey, ciphertext []
// access patterns. If the plaintext was valid then index contains the index of // access patterns. If the plaintext was valid then index contains the index of
// the original message in em, to allow constant time padding removal. // the original message in em, to allow constant time padding removal.
func decryptPKCS1v15(priv *PrivateKey, ciphertext []byte) (valid int, em []byte, index int, err error) { func decryptPKCS1v15(priv *PrivateKey, ciphertext []byte) (valid int, em []byte, index int, err error) {
if fips140only.Enabled { if fips140only.Enforced() {
return 0, nil, 0, errors.New("crypto/rsa: use of PKCS#1 v1.5 encryption is not allowed in FIPS 140-only mode") return 0, nil, 0, errors.New("crypto/rsa: use of PKCS#1 v1.5 encryption is not allowed in FIPS 140-only mode")
} }

View file

@ -350,13 +350,13 @@ func GenerateKey(random io.Reader, bits int) (*PrivateKey, error) {
return key, nil return key, nil
} }
if fips140only.Enabled && bits < 2048 { if fips140only.Enforced() && bits < 2048 {
return nil, errors.New("crypto/rsa: use of keys smaller than 2048 bits is not allowed in FIPS 140-only mode") return nil, errors.New("crypto/rsa: use of keys smaller than 2048 bits is not allowed in FIPS 140-only mode")
} }
if fips140only.Enabled && bits%2 == 1 { if fips140only.Enforced() && bits%2 == 1 {
return nil, errors.New("crypto/rsa: use of keys with odd size is not allowed in FIPS 140-only mode") return nil, errors.New("crypto/rsa: use of keys with odd size is not allowed in FIPS 140-only mode")
} }
if fips140only.Enabled && !fips140only.ApprovedRandomReader(random) { if fips140only.Enforced() && !fips140only.ApprovedRandomReader(random) {
return nil, errors.New("crypto/rsa: only crypto/rand.Reader is allowed in FIPS 140-only mode") return nil, errors.New("crypto/rsa: only crypto/rand.Reader is allowed in FIPS 140-only mode")
} }
@ -424,7 +424,7 @@ func GenerateMultiPrimeKey(random io.Reader, nprimes int, bits int) (*PrivateKey
if nprimes == 2 { if nprimes == 2 {
return GenerateKey(random, bits) return GenerateKey(random, bits)
} }
if fips140only.Enabled { if fips140only.Enforced() {
return nil, errors.New("crypto/rsa: multi-prime RSA is not allowed in FIPS 140-only mode") return nil, errors.New("crypto/rsa: multi-prime RSA is not allowed in FIPS 140-only mode")
} }

View file

@ -126,7 +126,7 @@ func (d *digest) Size() int { return Size }
func (d *digest) BlockSize() int { return BlockSize } func (d *digest) BlockSize() int { return BlockSize }
func (d *digest) Write(p []byte) (nn int, err error) { func (d *digest) Write(p []byte) (nn int, err error) {
if fips140only.Enabled { if fips140only.Enforced() {
return 0, errors.New("crypto/sha1: use of SHA-1 is not allowed in FIPS 140-only mode") return 0, errors.New("crypto/sha1: use of SHA-1 is not allowed in FIPS 140-only mode")
} }
boring.Unreachable() boring.Unreachable()
@ -161,7 +161,7 @@ func (d *digest) Sum(in []byte) []byte {
} }
func (d *digest) checkSum() [Size]byte { func (d *digest) checkSum() [Size]byte {
if fips140only.Enabled { if fips140only.Enforced() {
panic("crypto/sha1: use of SHA-1 is not allowed in FIPS 140-only mode") panic("crypto/sha1: use of SHA-1 is not allowed in FIPS 140-only mode")
} }
@ -205,7 +205,7 @@ func (d *digest) ConstantTimeSum(in []byte) []byte {
} }
func (d *digest) constSum() [Size]byte { func (d *digest) constSum() [Size]byte {
if fips140only.Enabled { if fips140only.Enforced() {
panic("crypto/sha1: use of SHA-1 is not allowed in FIPS 140-only mode") panic("crypto/sha1: use of SHA-1 is not allowed in FIPS 140-only mode")
} }
@ -274,7 +274,7 @@ func Sum(data []byte) [Size]byte {
if boring.Enabled { if boring.Enabled {
return boring.SHA1(data) return boring.SHA1(data)
} }
if fips140only.Enabled { if fips140only.Enforced() {
panic("crypto/sha1: use of SHA-1 is not allowed in FIPS 140-only mode") panic("crypto/sha1: use of SHA-1 is not allowed in FIPS 140-only mode")
} }
var d digest var d digest

View file

@ -537,7 +537,7 @@ var depsRules = `
< crypto/internal/fips140/edwards25519 < crypto/internal/fips140/edwards25519
< crypto/internal/fips140/ed25519 < crypto/internal/fips140/ed25519
< crypto/internal/fips140/rsa < crypto/internal/fips140/rsa
< FIPS < crypto/fips140; < crypto/fips140 < FIPS;
crypto !< FIPS; crypto !< FIPS;

22
src/runtime/fipsbypass.go Normal file
View file

@ -0,0 +1,22 @@
// Copyright 2025 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 runtime
import _ "unsafe"
//go:linkname fips140_setBypass crypto/fips140.setBypass
func fips140_setBypass() {
getg().fipsOnlyBypass = true
}
//go:linkname fips140_unsetBypass crypto/fips140.unsetBypass
func fips140_unsetBypass() {
getg().fipsOnlyBypass = false
}
//go:linkname fips140_isBypassed crypto/fips140.isBypassed
func fips140_isBypassed() bool {
return getg().fipsOnlyBypass
}

View file

@ -4481,6 +4481,7 @@ func gdestroy(gp *g) {
gp.labels = nil gp.labels = nil
gp.timer = nil gp.timer = nil
gp.bubble = nil gp.bubble = nil
gp.fipsOnlyBypass = false
if gcBlackenEnabled != 0 && gp.gcAssistBytes > 0 { if gcBlackenEnabled != 0 && gp.gcAssistBytes > 0 {
// Flush assist credit to the global pool. This gives // Flush assist credit to the global pool. This gives
@ -5325,6 +5326,9 @@ func newproc1(fn *funcval, callergp *g, callerpc uintptr, parked bool, waitreaso
traceRelease(trace) traceRelease(trace)
} }
// fips140 bubble
newg.fipsOnlyBypass = callergp.fipsOnlyBypass
// Set up race context. // Set up race context.
if raceenabled { if raceenabled {
newg.racectx = racegostart(callerpc) newg.racectx = racegostart(callerpc)

View file

@ -545,6 +545,7 @@ type g struct {
runnableTime int64 // the amount of time spent runnable, cleared when running, only used when tracking runnableTime int64 // the amount of time spent runnable, cleared when running, only used when tracking
lockedm muintptr lockedm muintptr
fipsIndicator uint8 fipsIndicator uint8
fipsOnlyBypass bool
syncSafePoint bool // set if g is stopped at a synchronous safe point. syncSafePoint bool // set if g is stopped at a synchronous safe point.
runningCleanups atomic.Bool runningCleanups atomic.Bool
sig uint32 sig uint32