Skip to content

Commit

Permalink
crypto: use provided random Reader in FIPS mode
Browse files Browse the repository at this point in the history
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 <[email protected]>
Auto-Submit: Filippo Valsorda <[email protected]>
Reviewed-by: Russ Cox <[email protected]>
LUCI-TryBot-Result: Go LUCI <[email protected]>
  • Loading branch information
FiloSottile authored and gopherbot committed Dec 11, 2024
1 parent 3104b6a commit c93477b
Show file tree
Hide file tree
Showing 15 changed files with 94 additions and 84 deletions.
5 changes: 5 additions & 0 deletions src/crypto/ecdh/nist.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"bytes"
"crypto/internal/boring"
"crypto/internal/fips140/ecdh"
"crypto/internal/fips140only"
"errors"
"io"
)
Expand Down Expand Up @@ -43,6 +44,10 @@ func (c *nistCurve) GenerateKey(rand io.Reader) (*PrivateKey, error) {
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)
if err != nil {
return nil, err
Expand Down
7 changes: 7 additions & 0 deletions src/crypto/ecdsa/ecdsa.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"crypto/internal/boring"
"crypto/internal/boring/bbig"
"crypto/internal/fips140/ecdsa"
"crypto/internal/fips140only"
"crypto/internal/randutil"
"crypto/sha512"
"crypto/subtle"
Expand Down Expand Up @@ -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) {
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)
if err != nil {
return nil, err
Expand Down Expand Up @@ -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) {
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
// 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.
Expand Down
37 changes: 37 additions & 0 deletions src/crypto/internal/fips140/drbg/rand.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ package drbg
import (
"crypto/internal/entropy"
"crypto/internal/fips140"
"crypto/internal/randutil"
"crypto/internal/sysrand"
"io"
"sync"
)

Expand Down Expand Up @@ -56,3 +58,38 @@ func Read(b []byte) {
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
}
20 changes: 6 additions & 14 deletions src/crypto/internal/fips140/ecdh/ecdh.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"crypto/internal/fips140/drbg"
"crypto/internal/fips140/nistec"
"crypto/internal/fips140deps/byteorder"
"crypto/internal/randutil"
"errors"
"io"
"math/bits"
Expand Down Expand Up @@ -137,27 +136,20 @@ var p521Order = []byte{0x01, 0xff,
}

// 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) {
fips140.RecordApproved()
// This procedure is equivalent to Key Pair Generation by Testing
// Candidates, specified in NIST SP 800-56A Rev. 3, Section 5.6.1.2.2.

for {
key := make([]byte, len(c.N))
if fips140.Enabled {
drbg.Read(key)
} else {
randutil.MaybeReadByte(rand)
if _, err := io.ReadFull(rand, key); err != nil {
return nil, err
}
// 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
// makes this function consistent with crypto/elliptic.GenerateKey.
key[1] ^= 0x42
if err := drbg.ReadWithReader(rand, key); err != nil {
return nil, err
}
// 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
// makes this function consistent with crypto/elliptic.GenerateKey.
key[1] ^= 0x42

// 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.
Expand Down
3 changes: 2 additions & 1 deletion src/crypto/internal/fips140/ecdsa/cast.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ func testHash() []byte {
func fipsPCT[P Point[P]](c *Curve[P], k *PrivateKey) error {
return fips140.PCT("ECDSA PCT", func() error {
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 {
return err
}
Expand Down
23 changes: 3 additions & 20 deletions src/crypto/internal/fips140/ecdsa/ecdsa.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"crypto/internal/fips140/bigmod"
"crypto/internal/fips140/drbg"
"crypto/internal/fips140/nistec"
"crypto/internal/randutil"
"errors"
"io"
"sync"
Expand Down Expand Up @@ -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.
//
// In FIPS mode, rand is ignored.
func GenerateKey[P Point[P]](c *Curve[P], rand io.Reader) (*PrivateKey, error) {
fips140.RecordApproved()

k, Q, err := randomPoint(c, func(b []byte) error {
if fips140.Enabled {
drbg.Read(b)
return nil
} else {
randutil.MaybeReadByte(rand)
_, err := io.ReadFull(rand, b)
return err
}
return drbg.ReadWithReader(rand, b)
})
if err != nil {
return nil, err
Expand Down Expand Up @@ -281,8 +271,6 @@ type Signature struct {
// 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
// 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) {
if priv.pub.curve != c.curve {
return nil, errors.New("ecdsa: private key does not match curve")
Expand All @@ -296,13 +284,8 @@ func Sign[P Point[P], H fips140.Hash](c *Curve[P], h func() H, priv *PrivateKey,
// advantage of closely resembling Deterministic ECDSA.

Z := make([]byte, len(priv.d))
if fips140.Enabled {
drbg.Read(Z)
} else {
randutil.MaybeReadByte(rand)
if _, err := io.ReadFull(rand, Z); err != nil {
return nil, err
}
if err := drbg.ReadWithReader(rand, Z); err != nil {
return nil, err
}

// See https://github.com/cfrg/draft-irtf-cfrg-det-sigs-with-noise/issues/6
Expand Down
19 changes: 4 additions & 15 deletions src/crypto/internal/fips140/ed25519/ed25519.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"crypto/internal/fips140/edwards25519"
"crypto/internal/fips140/sha512"
"errors"
"io"
"strconv"
)

Expand Down Expand Up @@ -61,24 +60,14 @@ func (pub *PublicKey) Bytes() []byte {
}

// GenerateKey generates a new Ed25519 private key pair.
//
// 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) {
func GenerateKey() (*PrivateKey, error) {
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()
if fips140.Enabled {
drbg.Read(priv.seed[:])
} else {
if _, err := io.ReadFull(rand, priv.seed[:]); err != nil {
return nil, err
}
}
drbg.Read(priv.seed[:])
precomputePrivateKey(priv)
if err := fipsPCT(priv); err != nil {
// This clearly can't happen, but FIPS 140-3 requires that we check.
Expand Down
14 changes: 3 additions & 11 deletions src/crypto/internal/fips140/rsa/keygen.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,12 @@ import (
"crypto/internal/fips140"
"crypto/internal/fips140/bigmod"
"crypto/internal/fips140/drbg"
"crypto/internal/randutil"
"errors"
"io"
)

// GenerateKey generates a new RSA key pair of the given bit size.
// bits must be at least 128.
//
// When operating in FIPS mode, rand is ignored.
func GenerateKey(rand io.Reader, bits int) (*PrivateKey, error) {
if bits < 128 {
return nil, errors.New("rsa: key too small")
Expand Down Expand Up @@ -94,21 +91,16 @@ func GenerateKey(rand io.Reader, bits int) (*PrivateKey, error) {
}

// 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) {
if bits < 64 {
return nil, errors.New("rsa: prime size must be at least 32-bit")
}

b := make([]byte, (bits+7)/8)
for {
if fips140.Enabled {
drbg.Read(b)
} else {
randutil.MaybeReadByte(rand)
if _, err := io.ReadFull(rand, b); err != nil {
return nil, err
}
if err := drbg.ReadWithReader(rand, b); err != nil {
return nil, err
}
if excess := len(b)*8 - bits; excess != 0 {
b[0] >>= excess
Expand Down
21 changes: 4 additions & 17 deletions src/crypto/internal/fips140/rsa/pkcs1v22.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,8 +264,6 @@ func PSSMaxSaltLength(pub *PublicKey, hash fips140.Hash) (int, error) {
}

// 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) {
fipsSelfTest()
fips140.RecordApproved()
Expand All @@ -286,12 +284,8 @@ func SignPSS(rand io.Reader, priv *PrivateKey, hash fips140.Hash, hashed []byte,
fips140.RecordNonApproved()
}
salt := make([]byte, saltLength)
if fips140.Enabled {
drbg.Read(salt)
} else {
if _, err := io.ReadFull(rand, salt); err != nil {
return nil, err
}
if err := drbg.ReadWithReaderDeterministic(rand, salt); err != nil {
return nil, err
}

emBits := priv.pub.N.BitLen() - 1
Expand Down Expand Up @@ -374,8 +368,6 @@ func checkApprovedHash(hash fips140.Hash) {
}

// 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) {
// 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
Expand Down Expand Up @@ -408,13 +400,8 @@ func EncryptOAEP(hash, mgfHash fips140.Hash, random io.Reader, pub *PublicKey, m
db[len(db)-len(msg)-1] = 1
copy(db[len(db)-len(msg):], msg)

if fips140.Enabled {
drbg.Read(seed)
} else {
_, err := io.ReadFull(random, seed)
if err != nil {
return nil, err
}
if err := drbg.ReadWithReaderDeterministic(random, seed); err != nil {
return nil, err
}

mgf1XOR(db, mgfHash, seed)
Expand Down
7 changes: 7 additions & 0 deletions src/crypto/internal/fips140only/fips140only.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
package fips140only

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

// Enabled reports whether FIPS 140-only mode is enabled, in which non-approved
Expand All @@ -24,3 +26,8 @@ func ApprovedHash(h hash.Hash) bool {
return false
}
}

func ApprovedRandomReader(r io.Reader) bool {
_, ok := r.(drbg.DefaultReader)
return ok
}
2 changes: 1 addition & 1 deletion src/crypto/internal/fips140test/cast_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ func TestConditionals(t *testing.T) {
t.Fatal(err)
}
ecdsa.SignDeterministic(ecdsa.P256(), sha256.New, kDSA, make([]byte, 32))
k25519, err := ed25519.GenerateKey(rand.Reader)
k25519, err := ed25519.GenerateKey()
if err != nil {
t.Fatal(err)
}
Expand Down
4 changes: 3 additions & 1 deletion src/crypto/rand/rand.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ func init() {
Reader = &reader{}
}

type reader struct{}
type reader struct {
drbg.DefaultReader
}

func (r *reader) Read(b []byte) (n int, err error) {
boring.Unreachable()
Expand Down
Loading

0 comments on commit c93477b

Please sign in to comment.