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 golang#70514 to track the tests.

For golang#70123

Change-Id: I6f67de83da37dd1e94e620b7f4f4f6aabe040c41
Reviewed-on: https://go-review.googlesource.com/c/go/+/631018
Reviewed-by: Dmitri Shuralyov <[email protected]>
Auto-Submit: Filippo Valsorda <[email protected]>
Reviewed-by: Roland Shoemaker <[email protected]>
Reviewed-by: Daniel McCarney <[email protected]>
LUCI-TryBot-Result: Go LUCI <[email protected]>
  • Loading branch information
FiloSottile authored and AravindhKudiyarasan-ET committed Dec 11, 2024
1 parent 194de8f commit b2df703
Show file tree
Hide file tree
Showing 19 changed files with 904 additions and 57 deletions.
9 changes: 8 additions & 1 deletion src/crypto/cipher/cfb.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
package cipher

import (
"crypto/internal/alias"
"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
163 changes: 158 additions & 5 deletions src/crypto/cipher/gcm.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
package cipher

import (
"crypto/internal/alias"
"crypto/internal/fips140/aes"
"crypto/internal/fips140/aes/gcm"
"crypto/internal/fips140/alias"
"crypto/internal/fips140only"
"crypto/subtle"
"errors"
"internal/byteorder"
Expand Down Expand Up @@ -83,7 +86,10 @@ type gcm struct {
// 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) {
return newGCMWithNonceAndTagSize(cipher, gcmStandardNonceSize, gcmTagSize)
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)
}

// NewGCMWithNonceSize returns the given 128-bit, block cipher wrapped in Galois
Expand All @@ -94,7 +100,10 @@ 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) {
return newGCMWithNonceAndTagSize(cipher, size, gcmTagSize)
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)
}

// NewGCMWithTagSize returns the given 128-bit, block cipher wrapped in Galois
Expand All @@ -106,10 +115,154 @@ 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) {
return newGCMWithNonceAndTagSize(cipher, gcmStandardNonceSize, tagSize)
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
// AEAD interface value with type *gcm.GCM even if the *gcm.GCM is nil.
g, err := gcm.New(c, nonceSize, tagSize)
if err != nil {
return nil, err
}
return g, nil
}

// NewGCMWithRandomNonce returns the given cipher wrapped in Galois Counter
// Mode, with randomly-generated nonces. The cipher must have been created by
// [aes.NewCipher].
//
// It generates a random 96-bit nonce, which is prepended to the ciphertext by Seal,
// and is extracted from the ciphertext by Open. The NonceSize of the AEAD is zero,
// while the Overhead is 28 bytes (the combination of nonce size and tag size).
//
// A given key MUST NOT be used to encrypt more than 2^32 messages, to limit the
// risk of a random nonce collision to negligible levels.
func NewGCMWithRandomNonce(cipher Block) (AEAD, error) {
c, ok := cipher.(*aes.Block)
if !ok {
return nil, errors.New("cipher: NewGCMWithRandomNonce requires aes.Block")
}
g, err := gcm.New(c, gcmStandardNonceSize, gcmTagSize)
if err != nil {
return nil, err
}
return gcmWithRandomNonce{g}, nil
}

type gcmWithRandomNonce struct {
*gcm.GCM
}

func (g gcmWithRandomNonce) NonceSize() int {
return 0
}

func (g gcmWithRandomNonce) Overhead() int {
return gcmStandardNonceSize + gcmTagSize
}

func (g gcmWithRandomNonce) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
if len(nonce) != 0 {
panic("crypto/cipher: non-empty nonce passed to GCMWithRandomNonce")
}

ret, out := sliceForAppend(dst, gcmStandardNonceSize+len(plaintext)+gcmTagSize)
if alias.InexactOverlap(out, plaintext) {
panic("crypto/cipher: invalid buffer overlap of output and input")
}
if alias.AnyOverlap(out, additionalData) {
panic("crypto/cipher: invalid buffer overlap of output and additional data")
}
nonce = out[:gcmStandardNonceSize]
ciphertext := out[gcmStandardNonceSize:]

// The AEAD interface allows using plaintext[:0] or ciphertext[:0] as dst.
//
// This is kind of a problem when trying to prepend or trim a nonce, because the
// actual AES-GCTR blocks end up overlapping but not exactly.
//
// In Open, we write the output *before* the input, so unless we do something
// weird like working through a chunk of block backwards, it works out.
//
// In Seal, we could work through the input backwards or intentionally load
// ahead before writing.
//
// However, the crypto/internal/fips140/aes/gcm APIs also check for exact overlap,
// so for now we just do a memmove if we detect overlap.
//
// ┌───────────────────────────┬ ─ ─
// │PPPPPPPPPPPPPPPPPPPPPPPPPPP│ │
// └▽─────────────────────────▲┴ ─ ─
// ╲ Seal ╲
// ╲ Open ╲
// ┌───▼─────────────────────────△──┐
// │NN|CCCCCCCCCCCCCCCCCCCCCCCCCCC|T│
// └────────────────────────────────┘
//
if alias.AnyOverlap(out, plaintext) {
copy(ciphertext, plaintext)
plaintext = ciphertext[:len(plaintext)]
}

gcm.SealWithRandomNonce(g.GCM, nonce, ciphertext, plaintext, additionalData)
return ret
}

func (g gcmWithRandomNonce) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
if len(nonce) != 0 {
panic("crypto/cipher: non-empty nonce passed to GCMWithRandomNonce")
}
if len(ciphertext) < gcmStandardNonceSize+gcmTagSize {
return nil, errOpen
}

ret, out := sliceForAppend(dst, len(ciphertext)-gcmStandardNonceSize-gcmTagSize)
if alias.InexactOverlap(out, ciphertext) {
panic("crypto/cipher: invalid buffer overlap of output and input")
}
if alias.AnyOverlap(out, additionalData) {
panic("crypto/cipher: invalid buffer overlap of output and additional data")
}
// See the discussion in Seal. Note that if there is any overlap at this
// point, it's because out = ciphertext, so out must have enough capacity
// even if we sliced the tag off. Also note how [AEAD] specifies that "the
// contents of dst, up to its capacity, may be overwritten".
if alias.AnyOverlap(out, ciphertext) {
nonce = make([]byte, gcmStandardNonceSize)
copy(nonce, ciphertext)
copy(out[:len(ciphertext)], ciphertext[gcmStandardNonceSize:])
ciphertext = out[:len(ciphertext)-gcmStandardNonceSize]
} else {
nonce = ciphertext[:gcmStandardNonceSize]
ciphertext = ciphertext[gcmStandardNonceSize:]
}

_, err := g.GCM.Open(out[:0], nonce, ciphertext, additionalData)
if err != nil {
return nil, err
}
return ret, nil
}

// gcmAble is an interface implemented by ciphers that have a specific optimized
// implementation of GCM. crypto/aes doesn't use this anymore, and we'd like to
// eventually remove it.
type gcmAble interface {
NewGCM(nonceSize, tagSize int) (AEAD, error)
}

func newGCMWithNonceAndTagSize(cipher Block, nonceSize, tagSize int) (AEAD, error) {
func newGCMFallback(cipher Block, nonceSize, tagSize int) (AEAD, error) {
if tagSize < gcmMinimumTagSize || tagSize > gcmBlockSize {
return nil, errors.New("cipher: incorrect tag size given to GCM")
}
Expand Down
7 changes: 6 additions & 1 deletion src/crypto/cipher/ofb.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
package cipher

import (
"crypto/internal/alias"
"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
12 changes: 11 additions & 1 deletion src/crypto/des/cipher.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ package des

import (
"crypto/cipher"
"crypto/internal/alias"
"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
13 changes: 12 additions & 1 deletion src/crypto/ecdh/x25519.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
package ecdh

import (
"crypto/internal/edwards25519/field"
"bytes"
"crypto/internal/fips140/edwards25519/field"
"crypto/internal/fips140only"
"crypto/internal/randutil"
"errors"
"io"
Expand Down Expand Up @@ -33,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 @@ -42,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 @@ -65,6 +73,9 @@ func (c *x25519Curve) privateKeyToPublicKey(key *PrivateKey) *PublicKey {
}

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
Loading

0 comments on commit b2df703

Please sign in to comment.