crypto: use provided random Reader in FIPS mode

This removes the difference in behavior between FIPS mode on and off.

Instead of the sentinel type we could have moved the Reader to the
drbg package and checked for equality, but then we would have locked the
crypto/rand.Reader implementation to the one in the FIPS module (which
we might have to support for years).

In internal/ed25519.GenerateKey we remove the random parameter entirely,
since that function is not actually used by crypto/ed25519.GenerateKey,
which instead commits to being deterministic.

Fixes #70772

Change-Id: Ic1c7ca2c1cd59eb9cd090a8b235c0ce218921ac5
Reviewed-on: https://go-review.googlesource.com/c/go/+/635195
Reviewed-by: Roland Shoemaker <roland@golang.org>
Auto-Submit: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Russ Cox <rsc@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Filippo Valsorda 2024-12-11 14:50:00 +01:00 committed by Gopher Robot
parent 3104b6adbb
commit c93477b5e5
15 changed files with 94 additions and 84 deletions

View file

@ -8,6 +8,7 @@ import (
"bytes" "bytes"
"crypto/internal/boring" "crypto/internal/boring"
"crypto/internal/fips140/ecdh" "crypto/internal/fips140/ecdh"
"crypto/internal/fips140only"
"errors" "errors"
"io" "io"
) )
@ -43,6 +44,10 @@ func (c *nistCurve) GenerateKey(rand io.Reader) (*PrivateKey, error) {
return k, nil return k, nil
} }
if fips140only.Enabled && !fips140only.ApprovedRandomReader(rand) {
return nil, errors.New("crypto/ecdh: only crypto/rand.Reader is allowed in FIPS 140-only mode")
}
privateKey, err := c.generate(rand) privateKey, err := c.generate(rand)
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -21,6 +21,7 @@ import (
"crypto/internal/boring" "crypto/internal/boring"
"crypto/internal/boring/bbig" "crypto/internal/boring/bbig"
"crypto/internal/fips140/ecdsa" "crypto/internal/fips140/ecdsa"
"crypto/internal/fips140only"
"crypto/internal/randutil" "crypto/internal/randutil"
"crypto/sha512" "crypto/sha512"
"crypto/subtle" "crypto/subtle"
@ -182,6 +183,9 @@ 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) {
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)
if err != nil { if err != nil {
return nil, err return nil, err
@ -228,6 +232,9 @@ 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) {
return nil, errors.New("crypto/ecdsa: only crypto/rand.Reader is allowed in FIPS 140-only mode")
}
// privateKeyToFIPS is very slow in FIPS mode because it performs a // privateKeyToFIPS is very slow in FIPS mode because it performs a
// Sign+Verify cycle per FIPS 140-3 IG 10.3.A. We should find a way to cache // Sign+Verify cycle per FIPS 140-3 IG 10.3.A. We should find a way to cache
// it or attach it to the PrivateKey. // it or attach it to the PrivateKey.

View file

@ -7,7 +7,9 @@ package drbg
import ( import (
"crypto/internal/entropy" "crypto/internal/entropy"
"crypto/internal/fips140" "crypto/internal/fips140"
"crypto/internal/randutil"
"crypto/internal/sysrand" "crypto/internal/sysrand"
"io"
"sync" "sync"
) )
@ -56,3 +58,38 @@ func Read(b []byte) {
b = b[size:] b = b[size:]
} }
} }
// DefaultReader is a sentinel type, embedded in the default
// [crypto/rand.Reader], used to recognize it when passed to
// APIs that accept a rand io.Reader.
type DefaultReader interface{ defaultReader() }
// ReadWithReader uses Reader to fill b with cryptographically secure random
// bytes. It is intended for use in APIs that expose a rand io.Reader.
//
// If Reader is not the default Reader from crypto/rand,
// [randutil.MaybeReadByte] and [fips140.RecordNonApproved] are called.
func ReadWithReader(r io.Reader, b []byte) error {
if _, ok := r.(DefaultReader); ok {
Read(b)
return nil
}
fips140.RecordNonApproved()
randutil.MaybeReadByte(r)
_, err := io.ReadFull(r, b)
return err
}
// ReadWithReaderDeterministic is like ReadWithReader, but it doesn't call
// [randutil.MaybeReadByte] on non-default Readers.
func ReadWithReaderDeterministic(r io.Reader, b []byte) error {
if _, ok := r.(DefaultReader); ok {
Read(b)
return nil
}
fips140.RecordNonApproved()
_, err := io.ReadFull(r, b)
return err
}

View file

@ -10,7 +10,6 @@ import (
"crypto/internal/fips140/drbg" "crypto/internal/fips140/drbg"
"crypto/internal/fips140/nistec" "crypto/internal/fips140/nistec"
"crypto/internal/fips140deps/byteorder" "crypto/internal/fips140deps/byteorder"
"crypto/internal/randutil"
"errors" "errors"
"io" "io"
"math/bits" "math/bits"
@ -137,8 +136,6 @@ var p521Order = []byte{0x01, 0xff,
} }
// GenerateKey generates a new ECDSA private key pair for the specified curve. // GenerateKey generates a new ECDSA private key pair for the specified curve.
//
// In FIPS mode, rand is ignored.
func GenerateKey[P Point[P]](c *Curve[P], rand io.Reader) (*PrivateKey, error) { func GenerateKey[P Point[P]](c *Curve[P], rand io.Reader) (*PrivateKey, error) {
fips140.RecordApproved() fips140.RecordApproved()
// This procedure is equivalent to Key Pair Generation by Testing // This procedure is equivalent to Key Pair Generation by Testing
@ -146,18 +143,13 @@ func GenerateKey[P Point[P]](c *Curve[P], rand io.Reader) (*PrivateKey, error) {
for { for {
key := make([]byte, len(c.N)) key := make([]byte, len(c.N))
if fips140.Enabled { if err := drbg.ReadWithReader(rand, key); err != nil {
drbg.Read(key)
} else {
randutil.MaybeReadByte(rand)
if _, err := io.ReadFull(rand, key); err != nil {
return nil, err return nil, err
} }
// In tests, rand will return all zeros and NewPrivateKey will reject // In tests, rand will return all zeros and NewPrivateKey will reject
// the zero key as it generates the identity as a public key. This also // the zero key as it generates the identity as a public key. This also
// makes this function consistent with crypto/elliptic.GenerateKey. // makes this function consistent with crypto/elliptic.GenerateKey.
key[1] ^= 0x42 key[1] ^= 0x42
}
// Mask off any excess bits if the size of the underlying field is not a // Mask off any excess bits if the size of the underlying field is not a
// whole number of bytes, which is only the case for P-521. // whole number of bytes, which is only the case for P-521.

View file

@ -54,7 +54,8 @@ func testHash() []byte {
func fipsPCT[P Point[P]](c *Curve[P], k *PrivateKey) error { func fipsPCT[P Point[P]](c *Curve[P], k *PrivateKey) error {
return fips140.PCT("ECDSA PCT", func() error { return fips140.PCT("ECDSA PCT", func() error {
hash := testHash() hash := testHash()
sig, err := Sign(c, sha512.New, k, nil, hash) drbg := newDRBG(sha512.New, k.d, bits2octets(P256(), hash), nil)
sig, err := sign(c, k, drbg, hash)
if err != nil { if err != nil {
return err return err
} }

View file

@ -10,7 +10,6 @@ import (
"crypto/internal/fips140/bigmod" "crypto/internal/fips140/bigmod"
"crypto/internal/fips140/drbg" "crypto/internal/fips140/drbg"
"crypto/internal/fips140/nistec" "crypto/internal/fips140/nistec"
"crypto/internal/randutil"
"errors" "errors"
"io" "io"
"sync" "sync"
@ -187,20 +186,11 @@ func NewPublicKey[P Point[P]](c *Curve[P], Q []byte) (*PublicKey, error) {
} }
// GenerateKey generates a new ECDSA private key pair for the specified curve. // GenerateKey generates a new ECDSA private key pair for the specified curve.
//
// In FIPS mode, rand is ignored.
func GenerateKey[P Point[P]](c *Curve[P], rand io.Reader) (*PrivateKey, error) { func GenerateKey[P Point[P]](c *Curve[P], rand io.Reader) (*PrivateKey, error) {
fips140.RecordApproved() fips140.RecordApproved()
k, Q, err := randomPoint(c, func(b []byte) error { k, Q, err := randomPoint(c, func(b []byte) error {
if fips140.Enabled { return drbg.ReadWithReader(rand, b)
drbg.Read(b)
return nil
} else {
randutil.MaybeReadByte(rand)
_, err := io.ReadFull(rand, b)
return err
}
}) })
if err != nil { if err != nil {
return nil, err return nil, err
@ -281,8 +271,6 @@ type Signature struct {
// the hash function H) using the private key, priv. If the hash is longer than // the hash function H) using the private key, priv. If the hash is longer than
// the bit-length of the private key's curve order, the hash will be truncated // the bit-length of the private key's curve order, the hash will be truncated
// to that length. // to that length.
//
// The signature is randomized. If FIPS mode is enabled, rand is ignored.
func Sign[P Point[P], H fips140.Hash](c *Curve[P], h func() H, priv *PrivateKey, rand io.Reader, hash []byte) (*Signature, error) { func Sign[P Point[P], H fips140.Hash](c *Curve[P], h func() H, priv *PrivateKey, rand io.Reader, hash []byte) (*Signature, error) {
if priv.pub.curve != c.curve { if priv.pub.curve != c.curve {
return nil, errors.New("ecdsa: private key does not match curve") return nil, errors.New("ecdsa: private key does not match curve")
@ -296,14 +284,9 @@ func Sign[P Point[P], H fips140.Hash](c *Curve[P], h func() H, priv *PrivateKey,
// advantage of closely resembling Deterministic ECDSA. // advantage of closely resembling Deterministic ECDSA.
Z := make([]byte, len(priv.d)) Z := make([]byte, len(priv.d))
if fips140.Enabled { if err := drbg.ReadWithReader(rand, Z); err != nil {
drbg.Read(Z)
} else {
randutil.MaybeReadByte(rand)
if _, err := io.ReadFull(rand, Z); err != nil {
return nil, err return nil, err
} }
}
// See https://github.com/cfrg/draft-irtf-cfrg-det-sigs-with-noise/issues/6 // See https://github.com/cfrg/draft-irtf-cfrg-det-sigs-with-noise/issues/6
// for the FIPS compliance of this method. In short Z is entropy from the // for the FIPS compliance of this method. In short Z is entropy from the

View file

@ -11,7 +11,6 @@ import (
"crypto/internal/fips140/edwards25519" "crypto/internal/fips140/edwards25519"
"crypto/internal/fips140/sha512" "crypto/internal/fips140/sha512"
"errors" "errors"
"io"
"strconv" "strconv"
) )
@ -61,24 +60,14 @@ func (pub *PublicKey) Bytes() []byte {
} }
// GenerateKey generates a new Ed25519 private key pair. // GenerateKey generates a new Ed25519 private key pair.
// func GenerateKey() (*PrivateKey, error) {
// In FIPS mode, rand is ignored. Otherwise, the output of this function is
// deterministic, and equivalent to reading 32 bytes from rand, and passing them
// to [NewKeyFromSeed].
func GenerateKey(rand io.Reader) (*PrivateKey, error) {
priv := &PrivateKey{} priv := &PrivateKey{}
return generateKey(priv, rand) return generateKey(priv)
} }
func generateKey(priv *PrivateKey, rand io.Reader) (*PrivateKey, error) { func generateKey(priv *PrivateKey) (*PrivateKey, error) {
fips140.RecordApproved() fips140.RecordApproved()
if fips140.Enabled {
drbg.Read(priv.seed[:]) drbg.Read(priv.seed[:])
} else {
if _, err := io.ReadFull(rand, priv.seed[:]); err != nil {
return nil, err
}
}
precomputePrivateKey(priv) precomputePrivateKey(priv)
if err := fipsPCT(priv); err != nil { if err := fipsPCT(priv); err != nil {
// This clearly can't happen, but FIPS 140-3 requires that we check. // This clearly can't happen, but FIPS 140-3 requires that we check.

View file

@ -8,15 +8,12 @@ import (
"crypto/internal/fips140" "crypto/internal/fips140"
"crypto/internal/fips140/bigmod" "crypto/internal/fips140/bigmod"
"crypto/internal/fips140/drbg" "crypto/internal/fips140/drbg"
"crypto/internal/randutil"
"errors" "errors"
"io" "io"
) )
// GenerateKey generates a new RSA key pair of the given bit size. // GenerateKey generates a new RSA key pair of the given bit size.
// bits must be at least 128. // bits must be at least 128.
//
// When operating in FIPS mode, rand is ignored.
func GenerateKey(rand io.Reader, bits int) (*PrivateKey, error) { func GenerateKey(rand io.Reader, bits int) (*PrivateKey, error) {
if bits < 128 { if bits < 128 {
return nil, errors.New("rsa: key too small") return nil, errors.New("rsa: key too small")
@ -94,7 +91,7 @@ func GenerateKey(rand io.Reader, bits int) (*PrivateKey, error) {
} }
// randomPrime returns a random prime number of the given bit size following // randomPrime returns a random prime number of the given bit size following
// the process in FIPS 186-5, Appendix A.1.3. rand is ignored in FIPS mode. // the process in FIPS 186-5, Appendix A.1.3.
func randomPrime(rand io.Reader, bits int) ([]byte, error) { func randomPrime(rand io.Reader, bits int) ([]byte, error) {
if bits < 64 { if bits < 64 {
return nil, errors.New("rsa: prime size must be at least 32-bit") return nil, errors.New("rsa: prime size must be at least 32-bit")
@ -102,14 +99,9 @@ func randomPrime(rand io.Reader, bits int) ([]byte, error) {
b := make([]byte, (bits+7)/8) b := make([]byte, (bits+7)/8)
for { for {
if fips140.Enabled { if err := drbg.ReadWithReader(rand, b); err != nil {
drbg.Read(b)
} else {
randutil.MaybeReadByte(rand)
if _, err := io.ReadFull(rand, b); err != nil {
return nil, err return nil, err
} }
}
if excess := len(b)*8 - bits; excess != 0 { if excess := len(b)*8 - bits; excess != 0 {
b[0] >>= excess b[0] >>= excess
} }

View file

@ -264,8 +264,6 @@ func PSSMaxSaltLength(pub *PublicKey, hash fips140.Hash) (int, error) {
} }
// SignPSS calculates the signature of hashed using RSASSA-PSS. // SignPSS calculates the signature of hashed using RSASSA-PSS.
//
// In FIPS mode, rand is ignored and can be nil.
func SignPSS(rand io.Reader, priv *PrivateKey, hash fips140.Hash, hashed []byte, saltLength int) ([]byte, error) { func SignPSS(rand io.Reader, priv *PrivateKey, hash fips140.Hash, hashed []byte, saltLength int) ([]byte, error) {
fipsSelfTest() fipsSelfTest()
fips140.RecordApproved() fips140.RecordApproved()
@ -286,13 +284,9 @@ func SignPSS(rand io.Reader, priv *PrivateKey, hash fips140.Hash, hashed []byte,
fips140.RecordNonApproved() fips140.RecordNonApproved()
} }
salt := make([]byte, saltLength) salt := make([]byte, saltLength)
if fips140.Enabled { if err := drbg.ReadWithReaderDeterministic(rand, salt); err != nil {
drbg.Read(salt)
} else {
if _, err := io.ReadFull(rand, salt); err != nil {
return nil, err return nil, err
} }
}
emBits := priv.pub.N.BitLen() - 1 emBits := priv.pub.N.BitLen() - 1
em, err := emsaPSSEncode(hashed, emBits, salt, hash) em, err := emsaPSSEncode(hashed, emBits, salt, hash)
@ -374,8 +368,6 @@ func checkApprovedHash(hash fips140.Hash) {
} }
// EncryptOAEP encrypts the given message with RSAES-OAEP. // EncryptOAEP encrypts the given message with RSAES-OAEP.
//
// In FIPS mode, random is ignored and can be nil.
func EncryptOAEP(hash, mgfHash fips140.Hash, random io.Reader, pub *PublicKey, msg []byte, label []byte) ([]byte, error) { func EncryptOAEP(hash, mgfHash fips140.Hash, random io.Reader, pub *PublicKey, msg []byte, label []byte) ([]byte, error) {
// Note that while we don't commit to deterministic execution with respect // Note that while we don't commit to deterministic execution with respect
// to the random stream, we also don't apply MaybeReadByte, so per Hyrum's // to the random stream, we also don't apply MaybeReadByte, so per Hyrum's
@ -408,14 +400,9 @@ func EncryptOAEP(hash, mgfHash fips140.Hash, random io.Reader, pub *PublicKey, m
db[len(db)-len(msg)-1] = 1 db[len(db)-len(msg)-1] = 1
copy(db[len(db)-len(msg):], msg) copy(db[len(db)-len(msg):], msg)
if fips140.Enabled { if err := drbg.ReadWithReaderDeterministic(random, seed); err != nil {
drbg.Read(seed)
} else {
_, err := io.ReadFull(random, seed)
if err != nil {
return nil, err return nil, err
} }
}
mgf1XOR(db, mgfHash, seed) mgf1XOR(db, mgfHash, seed)
mgf1XOR(seed, mgfHash, db) mgf1XOR(seed, mgfHash, db)

View file

@ -5,11 +5,13 @@
package fips140only package fips140only
import ( import (
"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" "internal/godebug"
"io"
) )
// Enabled reports whether FIPS 140-only mode is enabled, in which non-approved // Enabled reports whether FIPS 140-only mode is enabled, in which non-approved
@ -24,3 +26,8 @@ func ApprovedHash(h hash.Hash) bool {
return false return false
} }
} }
func ApprovedRandomReader(r io.Reader) bool {
_, ok := r.(drbg.DefaultReader)
return ok
}

View file

@ -85,7 +85,7 @@ func TestConditionals(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
ecdsa.SignDeterministic(ecdsa.P256(), sha256.New, kDSA, make([]byte, 32)) ecdsa.SignDeterministic(ecdsa.P256(), sha256.New, kDSA, make([]byte, 32))
k25519, err := ed25519.GenerateKey(rand.Reader) k25519, err := ed25519.GenerateKey()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View file

@ -38,7 +38,9 @@ func init() {
Reader = &reader{} Reader = &reader{}
} }
type reader struct{} type reader struct {
drbg.DefaultReader
}
func (r *reader) Read(b []byte) (n int, err error) { func (r *reader) Read(b []byte) (n int, err error) {
boring.Unreachable() boring.Unreachable()

View file

@ -17,6 +17,9 @@ import (
const ( const (
// PSSSaltLengthAuto causes the salt in a PSS signature to be as large // PSSSaltLengthAuto causes the salt in a PSS signature to be as large
// as possible when signing, and to be auto-detected when verifying. // as possible when signing, and to be auto-detected when verifying.
//
// When signing in FIPS 140-3 mode, the salt length is capped at the length
// of the hash function used in the signature.
PSSSaltLengthAuto = 0 PSSSaltLengthAuto = 0
// PSSSaltLengthEqualsHash causes the salt length to equal the length // PSSSaltLengthEqualsHash causes the salt length to equal the length
// of the hash used in the signature. // of the hash used in the signature.
@ -67,6 +70,9 @@ func SignPSS(rand io.Reader, priv *PrivateKey, hash crypto.Hash, digest []byte,
if fips140only.Enabled && !fips140only.ApprovedHash(hash.New()) { if fips140only.Enabled && !fips140only.ApprovedHash(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")
} }
if fips140only.Enabled && !fips140only.ApprovedRandomReader(rand) {
return nil, errors.New("crypto/rsa: only crypto/rand.Reader is allowed in FIPS 140-only mode")
}
if opts != nil && opts.Hash != 0 { if opts != nil && opts.Hash != 0 {
hash = opts.Hash hash = opts.Hash
@ -188,6 +194,9 @@ func EncryptOAEP(hash hash.Hash, random io.Reader, pub *PublicKey, msg []byte, l
if fips140only.Enabled && !fips140only.ApprovedHash(hash) { if fips140only.Enabled && !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) {
return nil, errors.New("crypto/rsa: only crypto/rand.Reader is allowed in FIPS 140-only mode")
}
defer hash.Reset() defer hash.Reset()

View file

@ -322,6 +322,9 @@ func GenerateKey(random io.Reader, bits int) (*PrivateKey, error) {
if fips140only.Enabled && bits%2 == 1 { if fips140only.Enabled && 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) {
return nil, errors.New("crypto/rsa: only crypto/rand.Reader is allowed in FIPS 140-only mode")
}
k, err := rsa.GenerateKey(random, bits) k, err := rsa.GenerateKey(random, bits)
if err != nil { if err != nil {

View file

@ -10,7 +10,6 @@ import (
"crypto" "crypto"
"crypto/internal/boring" "crypto/internal/boring"
"crypto/internal/cryptotest" "crypto/internal/cryptotest"
"crypto/internal/fips140"
"crypto/rand" "crypto/rand"
. "crypto/rsa" . "crypto/rsa"
"crypto/sha1" "crypto/sha1"
@ -782,9 +781,6 @@ type testEncryptOAEPStruct struct {
} }
func TestEncryptOAEP(t *testing.T) { func TestEncryptOAEP(t *testing.T) {
if fips140.Enabled {
t.Skip("FIPS mode overrides the deterministic random source")
}
sha1 := sha1.New() sha1 := sha1.New()
n := new(big.Int) n := new(big.Int)
for i, test := range testEncryptOAEPData { for i, test := range testEncryptOAEPData {