Skip to content

Commit

Permalink
crypto: implement fips140=only mode
Browse files Browse the repository at this point in the history
Running the test suite in this mode is definitely not an option. Testing
this will probably look like a very long test that tries all functions.
Filed #70514 to track the tests.

For #70123

Change-Id: I6f67de83da37dd1e94e620b7f4f4f6aabe040c41
Reviewed-on: https://go-review.googlesource.com/c/go/+/631018
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Auto-Submit: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Roland Shoemaker <roland@golang.org>
Reviewed-by: Daniel McCarney <daniel@binaryparadox.net>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
  • Loading branch information
FiloSottile authored and gopherbot committed Nov 22, 2024
1 parent 07b4266 commit b299e9a
Show file tree
Hide file tree
Showing 19 changed files with 271 additions and 3 deletions.
7 changes: 7 additions & 0 deletions src/crypto/cipher/cfb.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package cipher

import (
"crypto/internal/fips140/alias"
"crypto/internal/fips140only"
"crypto/subtle"
)

Expand Down Expand Up @@ -54,13 +55,19 @@ func (x *cfb) XORKeyStream(dst, src []byte) {
// using the given [Block]. The iv must be the same length as the [Block]'s block
// size.
func NewCFBEncrypter(block Block, iv []byte) Stream {
if fips140only.Enabled {
panic("crypto/cipher: use of CFB is not allowed in FIPS 140-only mode")
}
return newCFB(block, iv, false)
}

// NewCFBDecrypter returns a [Stream] which decrypts with cipher feedback mode,
// using the given [Block]. The iv must be the same length as the [Block]'s block
// size.
func NewCFBDecrypter(block Block, iv []byte) Stream {
if fips140only.Enabled {
panic("crypto/cipher: use of CFB is not allowed in FIPS 140-only mode")
}
return newCFB(block, iv, true)
}

Expand Down
13 changes: 13 additions & 0 deletions src/crypto/cipher/gcm.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"crypto/internal/fips140/aes"
"crypto/internal/fips140/aes/gcm"
"crypto/internal/fips140/alias"
"crypto/internal/fips140only"
"crypto/subtle"
"errors"
"internal/byteorder"
Expand All @@ -27,6 +28,9 @@ const (
// 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.
func NewGCM(cipher Block) (AEAD, error) {
if fips140only.Enabled {
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)
}

Expand All @@ -38,6 +42,9 @@ func NewGCM(cipher Block) (AEAD, error) {
// cryptosystem that uses non-standard nonce lengths. All other users should use
// [NewGCM], which is faster and more resistant to misuse.
func NewGCMWithNonceSize(cipher Block, size int) (AEAD, error) {
if fips140only.Enabled {
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)
}

Expand All @@ -50,12 +57,18 @@ func NewGCMWithNonceSize(cipher Block, size int) (AEAD, error) {
// cryptosystem that uses non-standard tag lengths. All other users should use
// [NewGCM], which is more resistant to misuse.
func NewGCMWithTagSize(cipher Block, tagSize int) (AEAD, error) {
if fips140only.Enabled {
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)
}

func newGCM(cipher Block, nonceSize, tagSize int) (AEAD, error) {
c, ok := cipher.(*aes.Block)
if !ok {
if fips140only.Enabled {
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)
}
// We don't return gcm.New directly, because it would always return a non-nil
Expand Down
5 changes: 5 additions & 0 deletions src/crypto/cipher/ofb.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package cipher

import (
"crypto/internal/fips140/alias"
"crypto/internal/fips140only"
"crypto/subtle"
)

Expand All @@ -22,6 +23,10 @@ type ofb struct {
// in output feedback mode. The initialization vector iv's length must be equal
// to b's block size.
func NewOFB(b Block, iv []byte) Stream {
if fips140only.Enabled {
panic("crypto/cipher: use of OFB is not allowed in FIPS 140-only mode")
}

blockSize := b.BlockSize()
if len(iv) != blockSize {
panic("cipher.NewOFB: IV length must equal block size")
Expand Down
10 changes: 10 additions & 0 deletions src/crypto/des/cipher.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ package des
import (
"crypto/cipher"
"crypto/internal/fips140/alias"
"crypto/internal/fips140only"
"errors"
"internal/byteorder"
"strconv"
)
Expand All @@ -27,6 +29,10 @@ type desCipher struct {

// NewCipher creates and returns a new [cipher.Block].
func NewCipher(key []byte) (cipher.Block, error) {
if fips140only.Enabled {
return nil, errors.New("crypto/des: use of DES is not allowed in FIPS 140-only mode")
}

if len(key) != 8 {
return nil, KeySizeError(len(key))
}
Expand Down Expand Up @@ -71,6 +77,10 @@ type tripleDESCipher struct {

// NewTripleDESCipher creates and returns a new [cipher.Block].
func NewTripleDESCipher(key []byte) (cipher.Block, error) {
if fips140only.Enabled {
return nil, errors.New("crypto/des: use of TripleDES is not allowed in FIPS 140-only mode")
}

if len(key) != 24 {
return nil, KeySizeError(len(key))
}
Expand Down
17 changes: 17 additions & 0 deletions src/crypto/dsa/dsa.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"io"
"math/big"

"crypto/internal/fips140only"
"crypto/internal/randutil"
)

Expand Down Expand Up @@ -63,6 +64,10 @@ const numMRTests = 64
// GenerateParameters puts a random, valid set of DSA parameters into params.
// This function can take many seconds, even on fast machines.
func GenerateParameters(params *Parameters, rand io.Reader, sizes ParameterSizes) error {
if fips140only.Enabled {
return errors.New("crypto/dsa: use of DSA is not allowed in FIPS 140-only mode")
}

// This function doesn't follow FIPS 186-3 exactly in that it doesn't
// use a verification seed to generate the primes. The verification
// seed doesn't appear to be exported or used by other code and
Expand Down Expand Up @@ -157,6 +162,10 @@ GeneratePrimes:
// GenerateKey generates a public&private key pair. The Parameters of the
// [PrivateKey] must already be valid (see [GenerateParameters]).
func GenerateKey(priv *PrivateKey, rand io.Reader) error {
if fips140only.Enabled {
return errors.New("crypto/dsa: use of DSA is not allowed in FIPS 140-only mode")
}

if priv.P == nil || priv.Q == nil || priv.G == nil {
return errors.New("crypto/dsa: parameters not set up before generating key")
}
Expand Down Expand Up @@ -203,6 +212,10 @@ func fermatInverse(k, P *big.Int) *big.Int {
// Be aware that calling Sign with an attacker-controlled [PrivateKey] may
// require an arbitrary amount of CPU.
func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err error) {
if fips140only.Enabled {
return nil, nil, errors.New("crypto/dsa: use of DSA is not allowed in FIPS 140-only mode")
}

randutil.MaybeReadByte(rand)

// FIPS 186-3, section 4.6
Expand Down Expand Up @@ -271,6 +284,10 @@ 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
// truncation itself.
func Verify(pub *PublicKey, hash []byte, r, s *big.Int) bool {
if fips140only.Enabled {
panic("crypto/dsa: use of DSA is not allowed in FIPS 140-only mode")
}

// FIPS 186-3, section 4.7

if pub.P.Sign() == 0 {
Expand Down
10 changes: 10 additions & 0 deletions src/crypto/ecdh/x25519.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package ecdh
import (
"bytes"
"crypto/internal/fips140/edwards25519/field"
"crypto/internal/fips140only"
"crypto/internal/randutil"
"errors"
"io"
Expand Down Expand Up @@ -34,6 +35,9 @@ func (c *x25519Curve) String() string {
}

func (c *x25519Curve) GenerateKey(rand io.Reader) (*PrivateKey, error) {
if fips140only.Enabled {
return nil, errors.New("crypto/ecdh: use of X25519 is not allowed in FIPS 140-only mode")
}
key := make([]byte, x25519PrivateKeySize)
randutil.MaybeReadByte(rand)
if _, err := io.ReadFull(rand, key); err != nil {
Expand All @@ -43,6 +47,9 @@ func (c *x25519Curve) GenerateKey(rand io.Reader) (*PrivateKey, error) {
}

func (c *x25519Curve) NewPrivateKey(key []byte) (*PrivateKey, error) {
if fips140only.Enabled {
return nil, errors.New("crypto/ecdh: use of X25519 is not allowed in FIPS 140-only mode")
}
if len(key) != x25519PrivateKeySize {
return nil, errors.New("crypto/ecdh: invalid private key size")
}
Expand All @@ -60,6 +67,9 @@ func (c *x25519Curve) NewPrivateKey(key []byte) (*PrivateKey, error) {
}

func (c *x25519Curve) NewPublicKey(key []byte) (*PublicKey, error) {
if fips140only.Enabled {
return nil, errors.New("crypto/ecdh: use of X25519 is not allowed in FIPS 140-only mode")
}
if len(key) != x25519PublicKeySize {
return nil, errors.New("crypto/ecdh: invalid public key")
}
Expand Down
13 changes: 13 additions & 0 deletions src/crypto/ecdsa/ecdsa_legacy.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package ecdsa

import (
"crypto/elliptic"
"crypto/internal/fips140only"
"errors"
"io"
"math/big"
Expand All @@ -19,6 +20,10 @@ import (
// deprecated custom curves.

func generateLegacy(c elliptic.Curve, rand io.Reader) (*PrivateKey, error) {
if fips140only.Enabled {
return nil, errors.New("crypto/ecdsa: use of custom curves is not allowed in FIPS 140-only mode")
}

k, err := randFieldElement(c, rand)
if err != nil {
return nil, err
Expand Down Expand Up @@ -76,6 +81,10 @@ 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) {
if fips140only.Enabled {
return nil, errors.New("crypto/ecdsa: use of custom curves is not allowed in FIPS 140-only mode")
}

c := priv.Curve

// A cheap version of hedged signatures, for the deprecated path.
Expand Down Expand Up @@ -144,6 +153,10 @@ func Verify(pub *PublicKey, hash []byte, r, s *big.Int) bool {
}

func verifyLegacy(pub *PublicKey, hash []byte, sig []byte) bool {
if fips140only.Enabled {
panic("crypto/ecdsa: use of custom curves is not allowed in FIPS 140-only mode")
}

rBytes, sBytes, err := parseSignature(sig)
if err != nil {
return false
Expand Down
7 changes: 7 additions & 0 deletions src/crypto/ed25519/ed25519.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package ed25519
import (
"crypto"
"crypto/internal/fips140/ed25519"
"crypto/internal/fips140only"
cryptorand "crypto/rand"
"crypto/subtle"
"errors"
Expand Down Expand Up @@ -103,6 +104,9 @@ func (priv PrivateKey) Sign(rand io.Reader, message []byte, opts crypto.SignerOp
case hash == crypto.SHA512: // Ed25519ph
return ed25519.SignPH(k, message, context)
case hash == crypto.Hash(0) && context != "": // Ed25519ctx
if fips140only.Enabled {
return nil, errors.New("crypto/ed25519: use of Ed25519ctx is not allowed in FIPS 140-only mode")
}
return ed25519.SignCtx(k, message, context)
case hash == crypto.Hash(0): // Ed25519
return ed25519.Sign(k, message), nil
Expand Down Expand Up @@ -219,6 +223,9 @@ func VerifyWithOptions(publicKey PublicKey, message, sig []byte, opts *Options)
case opts.Hash == crypto.SHA512: // Ed25519ph
return ed25519.VerifyPH(k, message, sig, opts.Context)
case opts.Hash == crypto.Hash(0) && opts.Context != "": // Ed25519ctx
if fips140only.Enabled {
return errors.New("crypto/ed25519: use of Ed25519ctx is not allowed in FIPS 140-only mode")
}
return ed25519.VerifyCtx(k, message, sig, opts.Context)
case opts.Hash == crypto.Hash(0): // Ed25519
return ed25519.Verify(k, message, sig)
Expand Down
25 changes: 25 additions & 0 deletions src/crypto/hkdf/hkdf.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package hkdf

import (
"crypto/internal/fips140/hkdf"
"crypto/internal/fips140only"
"errors"
"hash"
)
Expand All @@ -17,6 +18,9 @@ import (
// Expand invocations and different context values. Most common scenarios,
// including the generation of multiple keys, should use [Key] instead.
func Extract[H hash.Hash](h func() H, secret, salt []byte) ([]byte, error) {
if err := checkFIPS140Only(h, secret); err != nil {
return nil, err
}
return hkdf.Extract(h, secret, salt), nil
}

Expand All @@ -28,6 +32,10 @@ func Extract[H hash.Hash](h func() H, secret, salt []byte) ([]byte, error) {
// random or pseudorandom cryptographically strong key. See RFC 5869, Section
// 3.3. Most common scenarios will want to use [Key] instead.
func Expand[H hash.Hash](h func() H, pseudorandomKey []byte, info string, keyLength int) ([]byte, error) {
if err := checkFIPS140Only(h, pseudorandomKey); err != nil {
return nil, err
}

limit := h().Size() * 255
if keyLength > limit {
return nil, errors.New("hkdf: requested key length too large")
Expand All @@ -40,10 +48,27 @@ func Expand[H hash.Hash](h func() H, pseudorandomKey []byte, info string, keyLen
// returning a []byte of length keyLength that can be used as cryptographic key.
// Salt and info can be nil.
func Key[Hash hash.Hash](h func() Hash, secret, salt []byte, info string, keyLength int) ([]byte, error) {
if err := checkFIPS140Only(h, secret); err != nil {
return nil, err
}

limit := h().Size() * 255
if keyLength > limit {
return nil, errors.New("hkdf: requested key length too large")
}

return hkdf.Key(h, secret, salt, info, keyLength), nil
}

func checkFIPS140Only[H hash.Hash](h func() H, key []byte) error {
if !fips140only.Enabled {
return nil
}
if len(key) < 112/8 {
return errors.New("crypto/hkdf: use of keys shorter than 112 bits is not allowed in FIPS 140-only mode")
}
if !fips140only.ApprovedHash(h()) {
return errors.New("crypto/hkdf: use of hash functions other than SHA-2 or SHA-3 is not allowed in FIPS 140-only mode")
}
return nil
}
9 changes: 9 additions & 0 deletions src/crypto/hmac/hmac.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ package hmac
import (
"crypto/internal/boring"
"crypto/internal/fips140/hmac"
"crypto/internal/fips140only"
"crypto/subtle"
"hash"
)
Expand All @@ -42,6 +43,14 @@ func New(h func() hash.Hash, key []byte) hash.Hash {
}
// BoringCrypto did not recognize h, so fall through to standard Go code.
}
if fips140only.Enabled {
if len(key) < 112/8 {
panic("crypto/hmac: use of keys shorter than 112 bits is not allowed in FIPS 140-only mode")
}
if !fips140only.ApprovedHash(h()) {
panic("crypto/hmac: use of hash functions other than SHA-2 or SHA-3 is not allowed in FIPS 140-only mode")
}
}
return hmac.New(h, key)
}

Expand Down
26 changes: 26 additions & 0 deletions src/crypto/internal/fips140only/fips140only.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// 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 fips140only

import (
"crypto/internal/fips140/sha256"
"crypto/internal/fips140/sha3"
"crypto/internal/fips140/sha512"
"hash"
"internal/godebug"
)

// Enabled reports whether FIPS 140-only mode is enabled, in which non-approved
// cryptography returns an error or panics.
var Enabled = godebug.New("#fips140").Value() == "only"

func ApprovedHash(h hash.Hash) bool {
switch h.(type) {
case *sha256.Digest, *sha512.Digest, *sha3.Digest:
return true
default:
return false
}
}
Loading

0 comments on commit b299e9a

Please sign in to comment.