From 2b8dbb35b0d6a5601ae9b6f1d1de106774251214 Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Mon, 15 Sep 2025 18:58:04 +0200 Subject: [PATCH] crypto,testing/cryptotest: ignore random io.Reader params, add SetGlobalRandom First, we centralize all random bytes generation through drbg.Read. The rest of the FIPS 140-3 module can't use external functions anyway, so drbg.Read needs to have all the logic. Then, make sure that the crypto/... tree uses drbg.Read (or the new crypto/internal/rand.Reader wrapper) instead of crypto/rand, so it is unaffected by applications setting crypto/rand.Reader. Next, pass all unspecified random io.Reader parameters through the new crypto/internal/rand.CustomReader, which just redirects to drbg.Read unless GODEBUG=cryptocustomrand=1 is set. Move all the calls to MaybeReadByte there, since it's only needed for these custom Readers. Finally, add testing/cryptotest.SetGlobalRandom which sets crypto/rand.Reader to a locked deterministic source and overrides drbg.Read. This way SetGlobalRandom should affect all cryptographic randomness in the standard library. Fixes #70942 Co-authored-by: qiulaidongfeng <2645477756@qq.com> Change-Id: I6a6a69641311d9fac318abcc6d79677f0e406100 Reviewed-on: https://go-review.googlesource.com/c/go/+/724480 Reviewed-by: Nicholas Husin Auto-Submit: Filippo Valsorda Reviewed-by: Nicholas Husin Reviewed-by: Roland Shoemaker LUCI-TryBot-Result: Go LUCI --- api/next/70942.txt | 1 + doc/godebug.md | 5 + .../6-stdlib/99-minor/crypto/dsa/70924.md | 4 + .../6-stdlib/99-minor/crypto/ecdh/70924.md | 4 + .../6-stdlib/99-minor/crypto/ecdsa/70924.md | 4 + .../6-stdlib/99-minor/crypto/ed25519/70924.md | 4 + .../6-stdlib/99-minor/crypto/rand/70924.md | 4 + .../6-stdlib/99-minor/crypto/rsa/70924.md | 4 + .../99-minor/testing/cryptotest/70942.md | 4 + src/crypto/dsa/dsa.go | 12 +- src/crypto/ecdh/ecdh.go | 6 +- src/crypto/ecdh/nist.go | 11 +- src/crypto/ecdh/x25519.go | 8 +- src/crypto/ecdsa/ecdsa.go | 69 +++--- src/crypto/ecdsa/ecdsa_legacy.go | 5 + src/crypto/ed25519/ed25519.go | 30 ++- src/crypto/hpke/kem.go | 2 +- src/crypto/hpke/pq.go | 5 +- src/crypto/internal/fips140/drbg/rand.go | 41 ++-- src/crypto/internal/fips140/rsa/pkcs1v22.go | 10 +- src/crypto/internal/rand/rand.go | 65 ++++++ src/crypto/internal/rand/rand_fipsv1.0.go | 13 ++ src/crypto/internal/rand/rand_fipsv2.0.go | 16 ++ src/crypto/internal/sysrand/rand.go | 3 + src/crypto/mlkem/mlkem.go | 8 +- src/crypto/rand/rand.go | 28 +-- src/crypto/rand/util.go | 12 +- src/crypto/rsa/pkcs1v15.go | 17 +- src/crypto/rsa/rsa.go | 20 +- src/crypto/tls/handshake_test.go | 4 + src/go/build/deps_test.go | 4 + src/internal/godebugs/table.go | 1 + src/runtime/metrics/doc.go | 5 + src/testing/cryptotest/rand.go | 76 +++++++ src/testing/cryptotest/rand_test.go | 202 ++++++++++++++++++ src/testing/testing.go | 9 +- 36 files changed, 591 insertions(+), 125 deletions(-) create mode 100644 api/next/70942.txt create mode 100644 doc/next/6-stdlib/99-minor/crypto/dsa/70924.md create mode 100644 doc/next/6-stdlib/99-minor/crypto/ecdh/70924.md create mode 100644 doc/next/6-stdlib/99-minor/crypto/ecdsa/70924.md create mode 100644 doc/next/6-stdlib/99-minor/crypto/ed25519/70924.md create mode 100644 doc/next/6-stdlib/99-minor/crypto/rand/70924.md create mode 100644 doc/next/6-stdlib/99-minor/crypto/rsa/70924.md create mode 100644 doc/next/6-stdlib/99-minor/testing/cryptotest/70942.md create mode 100644 src/crypto/internal/rand/rand.go create mode 100644 src/crypto/internal/rand/rand_fipsv1.0.go create mode 100644 src/crypto/internal/rand/rand_fipsv2.0.go create mode 100644 src/testing/cryptotest/rand.go create mode 100644 src/testing/cryptotest/rand_test.go diff --git a/api/next/70942.txt b/api/next/70942.txt new file mode 100644 index 0000000000..ac212d9c29 --- /dev/null +++ b/api/next/70942.txt @@ -0,0 +1 @@ +pkg testing/cryptotest, func SetGlobalRandom(*testing.T, uint64) #70942 diff --git a/doc/godebug.md b/doc/godebug.md index 0d3354bc0f..d6bb18603c 100644 --- a/doc/godebug.md +++ b/doc/godebug.md @@ -178,6 +178,11 @@ includes these key/value pairs in the goroutine status header of runtime tracebacks and debug=2 runtime/pprof stack dumps. This format may change in the future. (see go.dev/issue/76349) +Go 1.26 added a new `cryptocustomrand` setting that controls whether most crypto/... +APIs ignore the random `io.Reader` parameter. For Go 1.26, it defaults +to `cryptocustomrand=0`, ignoring the random parameters. Using `cryptocustomrand=1` +reverts to the pre-Go 1.26 behavior. + ### Go 1.25 Go 1.25 added a new `decoratemappings` setting that controls whether the Go diff --git a/doc/next/6-stdlib/99-minor/crypto/dsa/70924.md b/doc/next/6-stdlib/99-minor/crypto/dsa/70924.md new file mode 100644 index 0000000000..0d99de895f --- /dev/null +++ b/doc/next/6-stdlib/99-minor/crypto/dsa/70924.md @@ -0,0 +1,4 @@ +The random parameter to [GenerateKey] is now ignored. +Instead, it now always uses a secure source of cryptographically random bytes. +For deterministic testing, use the new [testing/cryptotest.SetGlobalRandom] function. +The new GODEBUG setting `cryptocustomrand=1` temporarily restores the old behavior. diff --git a/doc/next/6-stdlib/99-minor/crypto/ecdh/70924.md b/doc/next/6-stdlib/99-minor/crypto/ecdh/70924.md new file mode 100644 index 0000000000..e70325ca7a --- /dev/null +++ b/doc/next/6-stdlib/99-minor/crypto/ecdh/70924.md @@ -0,0 +1,4 @@ +The random parameter to [Curve.GenerateKey] is now ignored. +Instead, it now always uses a secure source of cryptographically random bytes. +For deterministic testing, use the new [testing/cryptotest.SetGlobalRandom] function. +The new GODEBUG setting `cryptocustomrand=1` temporarily restores the old behavior. diff --git a/doc/next/6-stdlib/99-minor/crypto/ecdsa/70924.md b/doc/next/6-stdlib/99-minor/crypto/ecdsa/70924.md new file mode 100644 index 0000000000..15344cbf22 --- /dev/null +++ b/doc/next/6-stdlib/99-minor/crypto/ecdsa/70924.md @@ -0,0 +1,4 @@ +The random parameter to [GenerateKey], [SignASN1], [Sign], and [PrivateKey.Sign] is now ignored. +Instead, they now always use a secure source of cryptographically random bytes. +For deterministic testing, use the new [testing/cryptotest.SetGlobalRandom] function. +The new GODEBUG setting `cryptocustomrand=1` temporarily restores the old behavior. diff --git a/doc/next/6-stdlib/99-minor/crypto/ed25519/70924.md b/doc/next/6-stdlib/99-minor/crypto/ed25519/70924.md new file mode 100644 index 0000000000..885e425473 --- /dev/null +++ b/doc/next/6-stdlib/99-minor/crypto/ed25519/70924.md @@ -0,0 +1,4 @@ +If the random parameter to [GenerateKey] is nil, GenerateKey now always uses a +secure source of cryptographically random bytes, instead of [crypto/rand.Reader] +(which could have been overridden). The new GODEBUG setting `cryptocustomrand=1` +temporarily restores the old behavior. diff --git a/doc/next/6-stdlib/99-minor/crypto/rand/70924.md b/doc/next/6-stdlib/99-minor/crypto/rand/70924.md new file mode 100644 index 0000000000..dfdbaa3a92 --- /dev/null +++ b/doc/next/6-stdlib/99-minor/crypto/rand/70924.md @@ -0,0 +1,4 @@ +The random parameter to [Prime] is now ignored. +Instead, it now always uses a secure source of cryptographically random bytes. +For deterministic testing, use the new [testing/cryptotest.SetGlobalRandom] function. +The new GODEBUG setting `cryptocustomrand=1` temporarily restores the old behavior. diff --git a/doc/next/6-stdlib/99-minor/crypto/rsa/70924.md b/doc/next/6-stdlib/99-minor/crypto/rsa/70924.md new file mode 100644 index 0000000000..195e3ef11d --- /dev/null +++ b/doc/next/6-stdlib/99-minor/crypto/rsa/70924.md @@ -0,0 +1,4 @@ +The random parameter to [GenerateKey], [GenerateMultiPrimeKey], and [EncryptPKCS1v15] is now ignored. +Instead, they now always use a secure source of cryptographically random bytes. +For deterministic testing, use the new [testing/cryptotest.SetGlobalRandom] function. +The new GODEBUG setting `cryptocustomrand=1` temporarily restores the old behavior. diff --git a/doc/next/6-stdlib/99-minor/testing/cryptotest/70942.md b/doc/next/6-stdlib/99-minor/testing/cryptotest/70942.md new file mode 100644 index 0000000000..b8d5913094 --- /dev/null +++ b/doc/next/6-stdlib/99-minor/testing/cryptotest/70942.md @@ -0,0 +1,4 @@ +The new [SetGlobalRandom] function configures a global, deterministic +cryptographic randomness source for the duration of the test. It affects +crypto/rand, and all implicit sources of cryptographic randomness in the +`crypto/...` packages. diff --git a/src/crypto/dsa/dsa.go b/src/crypto/dsa/dsa.go index ecc4c82bb5..6724f861b7 100644 --- a/src/crypto/dsa/dsa.go +++ b/src/crypto/dsa/dsa.go @@ -19,7 +19,7 @@ import ( "math/big" "crypto/internal/fips140only" - "crypto/internal/randutil" + "crypto/internal/rand" ) // Parameters represents the domain parameters for a key. These parameters can @@ -209,14 +209,18 @@ func fermatInverse(k, P *big.Int) *big.Int { // to the byte-length of the subgroup. This function does not perform that // truncation itself. // +// Since Go 1.26, a secure source of random bytes is always used, and the Reader is +// ignored unless GODEBUG=cryptocustomrand=1 is set. This setting will be removed +// in a future Go release. Instead, use [testing/cryptotest.SetGlobalRandom]. +// // 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) { +func Sign(random io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err error) { if fips140only.Enforced() { return nil, nil, errors.New("crypto/dsa: use of DSA is not allowed in FIPS 140-only mode") } - randutil.MaybeReadByte(rand) + random = rand.CustomReader(random) // FIPS 186-3, section 4.6 @@ -232,7 +236,7 @@ func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err err k := new(big.Int) buf := make([]byte, n) for { - _, err = io.ReadFull(rand, buf) + _, err = io.ReadFull(random, buf) if err != nil { return } diff --git a/src/crypto/ecdh/ecdh.go b/src/crypto/ecdh/ecdh.go index 82daacf473..3f85a28336 100644 --- a/src/crypto/ecdh/ecdh.go +++ b/src/crypto/ecdh/ecdh.go @@ -18,9 +18,9 @@ import ( type Curve interface { // GenerateKey generates a random PrivateKey. // - // Most applications should use [crypto/rand.Reader] as rand. Note that the - // returned key does not depend deterministically on the bytes read from rand, - // and may change between calls and/or between versions. + // Since Go 1.26, a secure source of random bytes is always used, and rand + // is ignored unless GODEBUG=cryptocustomrand=1 is set. This setting will be + // removed in a future Go release. Instead, use [testing/cryptotest.SetGlobalRandom]. GenerateKey(rand io.Reader) (*PrivateKey, error) // NewPrivateKey checks that key is valid and returns a PrivateKey. diff --git a/src/crypto/ecdh/nist.go b/src/crypto/ecdh/nist.go index 0d58196842..de7348d923 100644 --- a/src/crypto/ecdh/nist.go +++ b/src/crypto/ecdh/nist.go @@ -9,6 +9,7 @@ import ( "crypto/internal/boring" "crypto/internal/fips140/ecdh" "crypto/internal/fips140only" + "crypto/internal/rand" "errors" "io" ) @@ -25,8 +26,8 @@ func (c *nistCurve) String() string { return c.name } -func (c *nistCurve) GenerateKey(rand io.Reader) (*PrivateKey, error) { - if boring.Enabled && rand == boring.RandReader { +func (c *nistCurve) GenerateKey(r io.Reader) (*PrivateKey, error) { + if boring.Enabled && r == boring.RandReader { key, bytes, err := boring.GenerateKeyECDH(c.name) if err != nil { return nil, err @@ -44,11 +45,13 @@ func (c *nistCurve) GenerateKey(rand io.Reader) (*PrivateKey, error) { return k, nil } - if fips140only.Enforced() && !fips140only.ApprovedRandomReader(rand) { + r = rand.CustomReader(r) + + if fips140only.Enforced() && !fips140only.ApprovedRandomReader(r) { 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(r) if err != nil { return nil, err } diff --git a/src/crypto/ecdh/x25519.go b/src/crypto/ecdh/x25519.go index 3ad13f3e73..21a921aa12 100644 --- a/src/crypto/ecdh/x25519.go +++ b/src/crypto/ecdh/x25519.go @@ -8,7 +8,7 @@ import ( "bytes" "crypto/internal/fips140/edwards25519/field" "crypto/internal/fips140only" - "crypto/internal/randutil" + "crypto/internal/rand" "errors" "io" ) @@ -34,13 +34,13 @@ func (c *x25519Curve) String() string { return "X25519" } -func (c *x25519Curve) GenerateKey(rand io.Reader) (*PrivateKey, error) { +func (c *x25519Curve) GenerateKey(r io.Reader) (*PrivateKey, error) { if fips140only.Enforced() { return nil, errors.New("crypto/ecdh: use of X25519 is not allowed in FIPS 140-only mode") } + r = rand.CustomReader(r) key := make([]byte, x25519PrivateKeySize) - randutil.MaybeReadByte(rand) - if _, err := io.ReadFull(rand, key); err != nil { + if _, err := io.ReadFull(r, key); err != nil { return nil, err } return c.NewPrivateKey(key) diff --git a/src/crypto/ecdsa/ecdsa.go b/src/crypto/ecdsa/ecdsa.go index 9d965c4e7b..aee15b9283 100644 --- a/src/crypto/ecdsa/ecdsa.go +++ b/src/crypto/ecdsa/ecdsa.go @@ -27,7 +27,7 @@ import ( "crypto/internal/fips140cache" "crypto/internal/fips140hash" "crypto/internal/fips140only" - "crypto/internal/randutil" + "crypto/internal/rand" "crypto/sha512" "crypto/subtle" "errors" @@ -310,31 +310,31 @@ func privateKeyBytes[P ecdsa.Point[P]](c *ecdsa.Curve[P], priv *PrivateKey) ([]b // the bit-length of the private key's curve order, the hash will be truncated // to that length. It returns the ASN.1 encoded signature, like [SignASN1]. // -// If rand is not nil, the signature is randomized. Most applications should use -// [crypto/rand.Reader] as rand. Note that the returned signature does not -// depend deterministically on the bytes read from rand, and may change between -// calls and/or between versions. +// If random is not nil, the signature is randomized. Most applications should use +// [crypto/rand.Reader] as random, but unless GODEBUG=cryptocustomrand=1 is set, a +// secure source of random bytes is always used, and the actual Reader is ignored. +// The GODEBUG setting will be removed in a future Go release. Instead, use +// [testing/cryptotest.SetGlobalRandom]. // -// If rand is nil, Sign will produce a deterministic signature according to RFC +// If random is nil, Sign will produce a deterministic signature according to RFC // 6979. When producing a deterministic signature, opts.HashFunc() must be the // function used to produce digest and priv.Curve must be one of // [elliptic.P224], [elliptic.P256], [elliptic.P384], or [elliptic.P521]. -func (priv *PrivateKey) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { - if rand == nil { +func (priv *PrivateKey) Sign(random io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { + if random == nil { return signRFC6979(priv, digest, opts) } - return SignASN1(rand, priv, digest) + random = rand.CustomReader(random) + return SignASN1(random, priv, digest) } // GenerateKey generates a new ECDSA private key for the specified curve. // -// Most applications should use [crypto/rand.Reader] as rand. Note that the -// returned key does not depend deterministically on the bytes read from rand, -// and may change between calls and/or between versions. -func GenerateKey(c elliptic.Curve, rand io.Reader) (*PrivateKey, error) { - randutil.MaybeReadByte(rand) - - if boring.Enabled && rand == boring.RandReader { +// Since Go 1.26, a secure source of random bytes is always used, and the Reader is +// ignored unless GODEBUG=cryptocustomrand=1 is set. This setting will be removed +// in a future Go release. Instead, use [testing/cryptotest.SetGlobalRandom]. +func GenerateKey(c elliptic.Curve, r io.Reader) (*PrivateKey, error) { + if boring.Enabled && r == boring.RandReader { x, y, d, err := boring.GenerateKeyECDSA(c.Params().Name) if err != nil { return nil, err @@ -343,17 +343,19 @@ func GenerateKey(c elliptic.Curve, rand io.Reader) (*PrivateKey, error) { } boring.UnreachableExceptTests() + r = rand.CustomReader(r) + switch c.Params() { case elliptic.P224().Params(): - return generateFIPS(c, ecdsa.P224(), rand) + return generateFIPS(c, ecdsa.P224(), r) case elliptic.P256().Params(): - return generateFIPS(c, ecdsa.P256(), rand) + return generateFIPS(c, ecdsa.P256(), r) case elliptic.P384().Params(): - return generateFIPS(c, ecdsa.P384(), rand) + return generateFIPS(c, ecdsa.P384(), r) case elliptic.P521().Params(): - return generateFIPS(c, ecdsa.P521(), rand) + return generateFIPS(c, ecdsa.P521(), r) default: - return generateLegacy(c, rand) + return generateLegacy(c, r) } } @@ -373,13 +375,12 @@ func generateFIPS[P ecdsa.Point[P]](curve elliptic.Curve, c *ecdsa.Curve[P], ran // private key's curve order, the hash will be truncated to that length. It // returns the ASN.1 encoded signature. // -// The signature is randomized. Most applications should use [crypto/rand.Reader] -// as rand. Note that the returned signature does not depend deterministically on -// the bytes read from rand, and may change between calls and/or between versions. -func SignASN1(rand io.Reader, priv *PrivateKey, hash []byte) ([]byte, error) { - randutil.MaybeReadByte(rand) - - if boring.Enabled && rand == boring.RandReader { +// The signature is randomized. Since Go 1.26, a secure source of random bytes +// is always used, and the Reader is ignored unless GODEBUG=cryptocustomrand=1 +// is set. This setting will be removed in a future Go release. Instead, use +// [testing/cryptotest.SetGlobalRandom]. +func SignASN1(r io.Reader, priv *PrivateKey, hash []byte) ([]byte, error) { + if boring.Enabled && r == boring.RandReader { b, err := boringPrivateKey(priv) if err != nil { return nil, err @@ -388,17 +389,19 @@ func SignASN1(rand io.Reader, priv *PrivateKey, hash []byte) ([]byte, error) { } boring.UnreachableExceptTests() + r = rand.CustomReader(r) + switch priv.Curve.Params() { case elliptic.P224().Params(): - return signFIPS(ecdsa.P224(), priv, rand, hash) + return signFIPS(ecdsa.P224(), priv, r, hash) case elliptic.P256().Params(): - return signFIPS(ecdsa.P256(), priv, rand, hash) + return signFIPS(ecdsa.P256(), priv, r, hash) case elliptic.P384().Params(): - return signFIPS(ecdsa.P384(), priv, rand, hash) + return signFIPS(ecdsa.P384(), priv, r, hash) case elliptic.P521().Params(): - return signFIPS(ecdsa.P521(), priv, rand, hash) + return signFIPS(ecdsa.P521(), priv, r, hash) default: - return signLegacy(priv, rand, hash) + return signLegacy(priv, r, hash) } } diff --git a/src/crypto/ecdsa/ecdsa_legacy.go b/src/crypto/ecdsa/ecdsa_legacy.go index f6b4401bd7..2fb1b21a60 100644 --- a/src/crypto/ecdsa/ecdsa_legacy.go +++ b/src/crypto/ecdsa/ecdsa_legacy.go @@ -61,6 +61,11 @@ var errZeroParam = errors.New("zero parameter") // private key's curve order, the hash will be truncated to that length. It // returns the signature as a pair of integers. Most applications should use // [SignASN1] instead of dealing directly with r, s. +// +// The signature is randomized. Since Go 1.26, a secure source of random bytes +// is always used, and the Reader is ignored unless GODEBUG=cryptocustomrand=1 +// is set. This setting will be removed in a future Go release. Instead, use +// [testing/cryptotest.SetGlobalRandom]. func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err error) { sig, err := SignASN1(rand, priv, hash) if err != nil { diff --git a/src/crypto/ed25519/ed25519.go b/src/crypto/ed25519/ed25519.go index 26b4882b13..f09dabe23e 100644 --- a/src/crypto/ed25519/ed25519.go +++ b/src/crypto/ed25519/ed25519.go @@ -17,12 +17,15 @@ package ed25519 import ( "crypto" + "crypto/internal/fips140/drbg" "crypto/internal/fips140/ed25519" "crypto/internal/fips140cache" "crypto/internal/fips140only" + "crypto/internal/rand" cryptorand "crypto/rand" "crypto/subtle" "errors" + "internal/godebug" "io" "strconv" ) @@ -135,18 +138,31 @@ type Options struct { // HashFunc returns o.Hash. func (o *Options) HashFunc() crypto.Hash { return o.Hash } -// GenerateKey generates a public/private key pair using entropy from rand. -// If rand is nil, [crypto/rand.Reader] will be used. +var cryptocustomrand = godebug.New("cryptocustomrand") + +// GenerateKey generates a public/private key pair using entropy from random. +// +// If random is nil, a secure random source is used. (Before Go 1.26, a custom +// [crypto/rand.Reader] was used if set by the application. That behavior can be +// restored with GODEBUG=cryptocustomrand=1. This setting will be removed in a +// future Go release. Instead, use [testing/cryptotest.SetGlobalRandom].) // // The output of this function is deterministic, and equivalent to reading -// [SeedSize] bytes from rand, and passing them to [NewKeyFromSeed]. -func GenerateKey(rand io.Reader) (PublicKey, PrivateKey, error) { - if rand == nil { - rand = cryptorand.Reader +// [SeedSize] bytes from random, and passing them to [NewKeyFromSeed]. +func GenerateKey(random io.Reader) (PublicKey, PrivateKey, error) { + if random == nil { + if cryptocustomrand.Value() == "1" { + random = cryptorand.Reader + if _, ok := random.(drbg.DefaultReader); !ok { + cryptocustomrand.IncNonDefault() + } + } else { + random = rand.Reader + } } seed := make([]byte, SeedSize) - if _, err := io.ReadFull(rand, seed); err != nil { + if _, err := io.ReadFull(random, seed); err != nil { return nil, nil, err } diff --git a/src/crypto/hpke/kem.go b/src/crypto/hpke/kem.go index c30f79bad4..7633aa2b71 100644 --- a/src/crypto/hpke/kem.go +++ b/src/crypto/hpke/kem.go @@ -6,7 +6,7 @@ package hpke import ( "crypto/ecdh" - "crypto/rand" + "crypto/internal/rand" "errors" "internal/byteorder" ) diff --git a/src/crypto/hpke/pq.go b/src/crypto/hpke/pq.go index 322f937ae8..a79dadf58f 100644 --- a/src/crypto/hpke/pq.go +++ b/src/crypto/hpke/pq.go @@ -8,8 +8,9 @@ import ( "bytes" "crypto" "crypto/ecdh" + "crypto/internal/fips140/drbg" + "crypto/internal/rand" "crypto/mlkem" - "crypto/rand" "crypto/sha3" "errors" "internal/byteorder" @@ -246,7 +247,7 @@ func NewHybridPrivateKey(pq crypto.Decapsulator, t ecdh.KeyExchanger) (PrivateKe func (kem *hybridKEM) GenerateKey() (PrivateKey, error) { seed := make([]byte, 32) - rand.Read(seed) + drbg.Read(seed) return kem.NewPrivateKey(seed) } diff --git a/src/crypto/internal/fips140/drbg/rand.go b/src/crypto/internal/fips140/drbg/rand.go index cec697c7ab..949e74ac60 100644 --- a/src/crypto/internal/fips140/drbg/rand.go +++ b/src/crypto/internal/fips140/drbg/rand.go @@ -11,7 +11,6 @@ package drbg import ( entropy "crypto/internal/entropy/v1.0.0" "crypto/internal/fips140" - "crypto/internal/randutil" "crypto/internal/sysrand" "io" "sync" @@ -63,6 +62,15 @@ var drbgPool = sync.Pool{ // uses an SP 800-90A Rev. 1 Deterministic Random Bit Generator (DRBG). // Otherwise, it uses the operating system's random number generator. func Read(b []byte) { + if testingReader != nil { + fips140.RecordNonApproved() + // Avoid letting b escape in the non-testing case. + bb := make([]byte, len(b)) + testingReader.Read(bb) + copy(b, bb) + return + } + if !fips140.Enabled { sysrand.Read(b) return @@ -101,36 +109,33 @@ func Read(b []byte) { } } +var testingReader io.Reader + +// SetTestingReader sets a global, deterministic cryptographic randomness source +// for testing purposes. Its Read method must never return an error, it must +// never return short, and it must be safe for concurrent use. +// +// This is only intended to be used by the testing/cryptotest package. +func SetTestingReader(r io.Reader) { + testingReader = r +} + // 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. +// +// Any Reader that implements this interface is assumed to +// call [Read] as its Read method. 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 diff --git a/src/crypto/internal/fips140/rsa/pkcs1v22.go b/src/crypto/internal/fips140/rsa/pkcs1v22.go index 29c47069a3..4a043213fd 100644 --- a/src/crypto/internal/fips140/rsa/pkcs1v22.go +++ b/src/crypto/internal/fips140/rsa/pkcs1v22.go @@ -272,8 +272,8 @@ func SignPSS(rand io.Reader, priv *PrivateKey, hash hash.Hash, hashed []byte, sa checkApprovedHash(hash) // Note that while we don't commit to deterministic execution with respect - // to the rand stream, we also don't apply MaybeReadByte, so per Hyrum's Law - // it's probably relied upon by some. It's a tolerable promise because a + // to the rand stream, we also never applied MaybeReadByte, so per Hyrum's + // Law it's probably relied upon by some. It's a tolerable promise because a // well-specified number of random bytes is included in the signature, in a // well-specified way. @@ -286,7 +286,7 @@ func SignPSS(rand io.Reader, priv *PrivateKey, hash hash.Hash, hashed []byte, sa fips140.RecordNonApproved() } salt := make([]byte, saltLength) - if err := drbg.ReadWithReaderDeterministic(rand, salt); err != nil { + if err := drbg.ReadWithReader(rand, salt); err != nil { return nil, err } @@ -372,7 +372,7 @@ func checkApprovedHash(hash hash.Hash) { // EncryptOAEP encrypts the given message with RSAES-OAEP. func EncryptOAEP(hash, mgfHash hash.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 + // to the random stream, we also never applied MaybeReadByte, so per Hyrum's // Law it's probably relied upon by some. It's a tolerable promise because a // well-specified number of random bytes is included in the ciphertext, in a // well-specified way. @@ -402,7 +402,7 @@ func EncryptOAEP(hash, mgfHash hash.Hash, random io.Reader, pub *PublicKey, msg db[len(db)-len(msg)-1] = 1 copy(db[len(db)-len(msg):], msg) - if err := drbg.ReadWithReaderDeterministic(random, seed); err != nil { + if err := drbg.ReadWithReader(random, seed); err != nil { return nil, err } diff --git a/src/crypto/internal/rand/rand.go b/src/crypto/internal/rand/rand.go new file mode 100644 index 0000000000..3a780952e7 --- /dev/null +++ b/src/crypto/internal/rand/rand.go @@ -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 rand + +import ( + "crypto/internal/boring" + "crypto/internal/fips140/drbg" + "crypto/internal/randutil" + "internal/godebug" + "io" + _ "unsafe" +) + +type reader struct { + drbg.DefaultReader +} + +func (r reader) Read(b []byte) (n int, err error) { + if boring.Enabled { + if _, err := boring.RandReader.Read(b); err != nil { + panic("crypto/rand: boring RandReader failed: " + err.Error()) + } + return len(b), nil + } + drbg.Read(b) + return len(b), nil +} + +// Reader is an io.Reader that calls [drbg.Read]. +// +// It should be used internally instead of [crypto/rand.Reader], because the +// latter can be set by applications outside of tests. These applications then +// risk breaking between Go releases, if the way the Reader is used changes. +var Reader io.Reader = reader{} + +// SetTestingReader overrides all calls to [drbg.Read]. The Read method of +// r must never return an error or return short. +// +// SetTestingReader panics when building against Go Cryptographic Module v1.0.0. +// +// SetTestingReader is pulled by [testing/cryptotest.setGlobalRandom] via go:linkname. +// +//go:linkname SetTestingReader crypto/internal/rand.SetTestingReader +func SetTestingReader(r io.Reader) { + fips140SetTestingReader(r) +} + +var cryptocustomrand = godebug.New("cryptocustomrand") + +// CustomReader returns [Reader] or, only if the GODEBUG setting +// "cryptocustomrand=1" is set, the provided io.Reader. +// +// If returning a non-default Reader, it calls [randutil.MaybeReadByte] on it. +func CustomReader(r io.Reader) io.Reader { + if cryptocustomrand.Value() == "1" { + if _, ok := r.(drbg.DefaultReader); !ok { + randutil.MaybeReadByte(r) + cryptocustomrand.IncNonDefault() + } + return r + } + return Reader +} diff --git a/src/crypto/internal/rand/rand_fipsv1.0.go b/src/crypto/internal/rand/rand_fipsv1.0.go new file mode 100644 index 0000000000..29eba7e0bc --- /dev/null +++ b/src/crypto/internal/rand/rand_fipsv1.0.go @@ -0,0 +1,13 @@ +// 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. + +//go:build fips140v1.0 + +package rand + +import "io" + +func fips140SetTestingReader(r io.Reader) { + panic("cryptotest.SetGlobalRandom is not supported when building against Go Cryptographic Module v1.0.0") +} diff --git a/src/crypto/internal/rand/rand_fipsv2.0.go b/src/crypto/internal/rand/rand_fipsv2.0.go new file mode 100644 index 0000000000..0dc18e7883 --- /dev/null +++ b/src/crypto/internal/rand/rand_fipsv2.0.go @@ -0,0 +1,16 @@ +// 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. + +//go:build !fips140v1.0 + +package rand + +import ( + "crypto/internal/fips140/drbg" + "io" +) + +func fips140SetTestingReader(r io.Reader) { + drbg.SetTestingReader(r) +} diff --git a/src/crypto/internal/sysrand/rand.go b/src/crypto/internal/sysrand/rand.go index 034bf61715..5f3977aadd 100644 --- a/src/crypto/internal/sysrand/rand.go +++ b/src/crypto/internal/sysrand/rand.go @@ -31,6 +31,9 @@ var testingOnlyFailRead bool // system. It always fills b entirely and crashes the program irrecoverably if // an error is encountered. The operating system APIs are documented to never // return an error on all but legacy Linux systems. +// +// Note that Read is not affected by [testing/cryptotest.SetGlobalRand], and it +// should not be used directly by algorithm implementations. func Read(b []byte) { if firstUse.CompareAndSwap(false, true) { // First use of randomness. Start timer to warn about diff --git a/src/crypto/mlkem/mlkem.go b/src/crypto/mlkem/mlkem.go index 176b79673b..b652e3bae9 100644 --- a/src/crypto/mlkem/mlkem.go +++ b/src/crypto/mlkem/mlkem.go @@ -43,7 +43,7 @@ type DecapsulationKey768 struct { } // GenerateKey768 generates a new decapsulation key, drawing random bytes from -// the default crypto/rand source. The decapsulation key must be kept secret. +// a secure source. The decapsulation key must be kept secret. func GenerateKey768() (*DecapsulationKey768, error) { key, err := mlkem.GenerateKey768() if err != nil { @@ -118,7 +118,7 @@ func (ek *EncapsulationKey768) Bytes() []byte { } // Encapsulate generates a shared key and an associated ciphertext from an -// encapsulation key, drawing random bytes from the default crypto/rand source. +// encapsulation key, drawing random bytes from a secure source. // // The shared key must be kept secret. // @@ -135,7 +135,7 @@ type DecapsulationKey1024 struct { } // GenerateKey1024 generates a new decapsulation key, drawing random bytes from -// the default crypto/rand source. The decapsulation key must be kept secret. +// a secure source. The decapsulation key must be kept secret. func GenerateKey1024() (*DecapsulationKey1024, error) { key, err := mlkem.GenerateKey1024() if err != nil { @@ -210,7 +210,7 @@ func (ek *EncapsulationKey1024) Bytes() []byte { } // Encapsulate generates a shared key and an associated ciphertext from an -// encapsulation key, drawing random bytes from the default crypto/rand source. +// encapsulation key, drawing random bytes from a secure source. // // The shared key must be kept secret. // diff --git a/src/crypto/rand/rand.go b/src/crypto/rand/rand.go index 1ca16caa95..004e6b6fed 100644 --- a/src/crypto/rand/rand.go +++ b/src/crypto/rand/rand.go @@ -8,11 +8,14 @@ package rand import ( "crypto/internal/boring" - "crypto/internal/fips140" "crypto/internal/fips140/drbg" - "crypto/internal/sysrand" + "crypto/internal/rand" "io" _ "unsafe" + + // Ensure the go:linkname from testing/cryptotest to + // crypto/internal/rand.SetTestingReader works. + _ "crypto/internal/rand" ) // Reader is a global, shared instance of a cryptographically @@ -35,21 +38,7 @@ func init() { Reader = boring.RandReader return } - Reader = &reader{} -} - -type reader struct { - drbg.DefaultReader -} - -func (r *reader) Read(b []byte) (n int, err error) { - boring.Unreachable() - if fips140.Enabled { - drbg.Read(b) - } else { - sysrand.Read(b) - } - return len(b), nil + Reader = rand.Reader } // fatal is [runtime.fatal], pushed via linkname. @@ -68,8 +57,9 @@ func Read(b []byte) (n int, err error) { // through a potentially overridden Reader, so we special-case the default // case which we can keep non-escaping, and in the general case we read into // a heap buffer and copy from it. - if r, ok := Reader.(*reader); ok { - _, err = r.Read(b) + if _, ok := Reader.(drbg.DefaultReader); ok { + boring.Unreachable() + drbg.Read(b) } else { bb := make([]byte, len(b)) _, err = io.ReadFull(Reader, bb) diff --git a/src/crypto/rand/util.go b/src/crypto/rand/util.go index 8c92851975..7cb9b47b4a 100644 --- a/src/crypto/rand/util.go +++ b/src/crypto/rand/util.go @@ -6,7 +6,7 @@ package rand import ( "crypto/internal/fips140only" - "crypto/internal/randutil" + "crypto/internal/rand" "errors" "io" "math/big" @@ -14,7 +14,11 @@ import ( // 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. -func Prime(rand io.Reader, bits int) (*big.Int, error) { +// +// Since Go 1.26, a secure source of random bytes is always used, and the Reader is +// ignored unless GODEBUG=cryptocustomrand=1 is set. This setting will be removed +// in a future Go release. Instead, use [testing/cryptotest.SetGlobalRandom]. +func Prime(r io.Reader, bits int) (*big.Int, error) { if fips140only.Enforced() { return nil, errors.New("crypto/rand: use of Prime is not allowed in FIPS 140-only mode") } @@ -22,7 +26,7 @@ func Prime(rand io.Reader, bits int) (*big.Int, error) { return nil, errors.New("crypto/rand: prime size must be at least 2-bit") } - randutil.MaybeReadByte(rand) + r = rand.CustomReader(r) b := uint(bits % 8) if b == 0 { @@ -33,7 +37,7 @@ func Prime(rand io.Reader, bits int) (*big.Int, error) { p := new(big.Int) for { - if _, err := io.ReadFull(rand, bytes); err != nil { + if _, err := io.ReadFull(r, bytes); err != nil { return nil, err } diff --git a/src/crypto/rsa/pkcs1v15.go b/src/crypto/rsa/pkcs1v15.go index caf68957e2..0f216e0193 100644 --- a/src/crypto/rsa/pkcs1v15.go +++ b/src/crypto/rsa/pkcs1v15.go @@ -8,7 +8,7 @@ import ( "crypto/internal/boring" "crypto/internal/fips140/rsa" "crypto/internal/fips140only" - "crypto/internal/randutil" + "crypto/internal/rand" "crypto/subtle" "errors" "io" @@ -36,12 +36,11 @@ type PKCS1v15DecryptOptions struct { // scheme from PKCS #1 v1.5. The message must be no longer than the // length of the public modulus minus 11 bytes. // -// The random parameter is used as a source of entropy to ensure that -// encrypting the same message twice doesn't result in the same -// ciphertext. Most applications should use [crypto/rand.Reader] -// as random. Note that the returned ciphertext does not depend -// deterministically on the bytes read from random, and may change -// between calls and/or between versions. +// The random parameter is used as a source of entropy to ensure that encrypting +// the same message twice doesn't result in the same ciphertext. Since Go 1.26, +// a secure source of random bytes is always used, and the Reader is ignored +// unless GODEBUG=cryptocustomrand=1 is set. This setting will be removed in a +// future Go release. Instead, use [testing/cryptotest.SetGlobalRandom]. // // Deprecated: PKCS #1 v1.5 encryption is dangerous and should not be used. // See [draft-irtf-cfrg-rsa-guidance-05] for more information. Use @@ -57,8 +56,6 @@ func EncryptPKCS1v15(random io.Reader, pub *PublicKey, msg []byte) ([]byte, erro return nil, err } - randutil.MaybeReadByte(random) - k := pub.Size() if len(msg) > k-11 { return nil, ErrMessageTooLong @@ -73,6 +70,8 @@ func EncryptPKCS1v15(random io.Reader, pub *PublicKey, msg []byte) ([]byte, erro } boring.UnreachableExceptTests() + random = rand.CustomReader(random) + // EM = 0x00 || 0x02 || PS || 0x00 || M em := make([]byte, k) em[1] = 2 diff --git a/src/crypto/rsa/rsa.go b/src/crypto/rsa/rsa.go index 5680d8b541..62f2de30b0 100644 --- a/src/crypto/rsa/rsa.go +++ b/src/crypto/rsa/rsa.go @@ -48,8 +48,8 @@ import ( "crypto/internal/fips140/bigmod" "crypto/internal/fips140/rsa" "crypto/internal/fips140only" - "crypto/internal/randutil" - "crypto/rand" + "crypto/internal/rand" + cryptorand "crypto/rand" "crypto/subtle" "errors" "fmt" @@ -304,9 +304,9 @@ func checkPublicKeySize(k *PublicKey) error { // If bits is less than 1024, [GenerateKey] returns an error. See the "[Minimum // key size]" section for further details. // -// Most applications should use [crypto/rand.Reader] as rand. Note that the -// returned key does not depend deterministically on the bytes read from rand, -// and may change between calls and/or between versions. +// Since Go 1.26, a secure source of random bytes is always used, and the Reader is +// ignored unless GODEBUG=cryptocustomrand=1 is set. This setting will be removed +// in a future Go release. Instead, use [testing/cryptotest.SetGlobalRandom]. // // [Minimum key size]: https://pkg.go.dev/crypto/rsa#hdr-Minimum_key_size func GenerateKey(random io.Reader, bits int) (*PrivateKey, error) { @@ -350,6 +350,8 @@ func GenerateKey(random io.Reader, bits int) (*PrivateKey, error) { return key, nil } + random = rand.CustomReader(random) + 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") } @@ -415,6 +417,10 @@ func GenerateKey(random io.Reader, bits int) (*PrivateKey, error) { // This package does not implement CRT optimizations for multi-prime RSA, so the // keys with more than two primes will have worse performance. // +// Since Go 1.26, a secure source of random bytes is always used, and the Reader is +// ignored unless GODEBUG=cryptocustomrand=1 is set. This setting will be removed +// in a future Go release. Instead, use [testing/cryptotest.SetGlobalRandom]. +// // Deprecated: The use of this function with a number of primes different from // two is not recommended for the above security, compatibility, and performance // reasons. Use [GenerateKey] instead. @@ -428,7 +434,7 @@ func GenerateMultiPrimeKey(random io.Reader, nprimes int, bits int) (*PrivateKey return nil, errors.New("crypto/rsa: multi-prime RSA is not allowed in FIPS 140-only mode") } - randutil.MaybeReadByte(random) + random = rand.CustomReader(random) priv := new(PrivateKey) priv.E = 65537 @@ -473,7 +479,7 @@ NextSetOfPrimes: } for i := 0; i < nprimes; i++ { var err error - primes[i], err = rand.Prime(random, todo/(nprimes-i)) + primes[i], err = cryptorand.Prime(random, todo/(nprimes-i)) if err != nil { return nil, err } diff --git a/src/crypto/tls/handshake_test.go b/src/crypto/tls/handshake_test.go index 3e2c566308..6e15459a9a 100644 --- a/src/crypto/tls/handshake_test.go +++ b/src/crypto/tls/handshake_test.go @@ -448,6 +448,10 @@ func runMain(m *testing.M) int { os.Exit(1) } + // TODO(filippo): deprecate Config.Rand, and regenerate handshake recordings + // to use cryptotest.SetGlobalRandom instead. + os.Setenv("GODEBUG", "cryptocustomrand=1,"+os.Getenv("GODEBUG")) + testConfig = &Config{ Time: func() time.Time { return time.Unix(0, 0) }, Rand: zeroSource{}, diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go index e329c8a172..d58bd294cd 100644 --- a/src/go/build/deps_test.go +++ b/src/go/build/deps_test.go @@ -560,6 +560,7 @@ var depsRules = ` < crypto/cipher < crypto/internal/boring < crypto/boring + < crypto/internal/rand < crypto/aes, crypto/des, crypto/rc4, @@ -713,6 +714,9 @@ var depsRules = ` log/slog, testing < testing/slogtest; + testing, crypto/rand + < testing/cryptotest; + FMT, crypto/sha256, encoding/binary, encoding/json, go/ast, go/parser, go/token, internal/godebug, math/rand, encoding/hex diff --git a/src/internal/godebugs/table.go b/src/internal/godebugs/table.go index f707fc34f2..8f6d8bbdda 100644 --- a/src/internal/godebugs/table.go +++ b/src/internal/godebugs/table.go @@ -29,6 +29,7 @@ var All = []Info{ {Name: "allowmultiplevcs", Package: "cmd/go"}, {Name: "asynctimerchan", Package: "time", Changed: 23, Old: "1"}, {Name: "containermaxprocs", Package: "runtime", Changed: 25, Old: "0"}, + {Name: "cryptocustomrand", Package: "crypto", Changed: 26, Old: "1"}, {Name: "dataindependenttiming", Package: "crypto/subtle", Opaque: true}, {Name: "decoratemappings", Package: "runtime", Opaque: true, Changed: 25, Old: "0"}, {Name: "embedfollowsymlinks", Package: "cmd/go"}, diff --git a/src/runtime/metrics/doc.go b/src/runtime/metrics/doc.go index 8f908f5b52..ca032f51b1 100644 --- a/src/runtime/metrics/doc.go +++ b/src/runtime/metrics/doc.go @@ -271,6 +271,11 @@ Below is the full list of supported metrics, ordered lexicographically. package due to a non-default GODEBUG=containermaxprocs=... setting. + /godebug/non-default-behavior/cryptocustomrand:events + The number of non-default behaviors executed by the crypto + package due to a non-default GODEBUG=cryptocustomrand=... + setting. + /godebug/non-default-behavior/embedfollowsymlinks:events The number of non-default behaviors executed by the cmd/go package due to a non-default GODEBUG=embedfollowsymlinks=... diff --git a/src/testing/cryptotest/rand.go b/src/testing/cryptotest/rand.go new file mode 100644 index 0000000000..d00732d42f --- /dev/null +++ b/src/testing/cryptotest/rand.go @@ -0,0 +1,76 @@ +// 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 cryptotest provides deterministic random source testing. +package cryptotest + +import ( + cryptorand "crypto/rand" + "internal/byteorder" + "io" + mathrand "math/rand/v2" + "sync" + "testing" + + // Import unsafe and crypto/rand, which imports crypto/internal/rand, + // for the crypto/internal/rand.SetTestingReader go:linkname. + _ "crypto/rand" + _ "unsafe" +) + +//go:linkname randSetTestingReader crypto/internal/rand.SetTestingReader +func randSetTestingReader(r io.Reader) + +//go:linkname testingCheckParallel testing.checkParallel +func testingCheckParallel(t *testing.T) + +// SetGlobalRandom sets a global, deterministic cryptographic randomness source +// for the duration of test t. It affects crypto/rand, and all implicit sources +// of cryptographic randomness in the crypto/... packages. +// +// SetGlobalRandom may be called multiple times in the same test to reset the +// random stream or change the seed. +// +// Because SetGlobalRandom affects the whole process, it cannot be used in +// parallel tests or tests with parallel ancestors. +// +// Note that the way cryptographic algorithms use randomness is generally not +// specified and may change over time. Thus, if a test expects a specific output +// from a cryptographic function, it may fail in the future even if it uses +// SetGlobalRandom. +// +// SetGlobalRandom is not supported when building against the Go Cryptographic +// Module v1.0.0 (i.e. when [crypto/fips140.Version] returns "v1.0.0"). +func SetGlobalRandom(t *testing.T, seed uint64) { + if t == nil { + panic("cryptotest: SetGlobalRandom called with a nil *testing.T") + } + if !testing.Testing() { + panic("cryptotest: SetGlobalRandom used in a non-test binary") + } + testingCheckParallel(t) + + var s [32]byte + byteorder.LEPutUint64(s[:8], seed) + r := &lockedReader{r: mathrand.NewChaCha8(s)} + + randSetTestingReader(r) + previous := cryptorand.Reader + cryptorand.Reader = r + t.Cleanup(func() { + cryptorand.Reader = previous + randSetTestingReader(nil) + }) +} + +type lockedReader struct { + sync.Mutex + r *mathrand.ChaCha8 +} + +func (lr *lockedReader) Read(b []byte) (n int, err error) { + lr.Lock() + defer lr.Unlock() + return lr.r.Read(b) +} diff --git a/src/testing/cryptotest/rand_test.go b/src/testing/cryptotest/rand_test.go new file mode 100644 index 0000000000..bf18c1b533 --- /dev/null +++ b/src/testing/cryptotest/rand_test.go @@ -0,0 +1,202 @@ +// 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. + +//go:build !fips140v1.0 + +package cryptotest + +import ( + "bytes" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/mlkem" + "crypto/rand" + "encoding/hex" + "testing" +) + +func TestSetGlobalRandom(t *testing.T) { + seed1, _ := hex.DecodeString("6ae6783f4fbde91b6eb88b73a48ed247dbe5882e2579683432c1bfc525454add" + + "0cd87274d67084caaf0e0d36c8496db7fef55fe0e125750aa608d5e20ffc2d12") + + t.Run("rand.Read", func(t *testing.T) { + buf := make([]byte, 64) + + t.Run("seed 1", func(t *testing.T) { + SetGlobalRandom(t, 1) + rand.Read(buf) + if !bytes.Equal(buf, seed1) { + t.Errorf("rand.Read with seed 1 = %x; want %x", buf, seed1) + } + + rand.Read(buf) + if bytes.Equal(buf, seed1) { + t.Errorf("rand.Read with seed 1 returned same output twice: %x", buf) + } + + SetGlobalRandom(t, 1) + rand.Read(buf) + if !bytes.Equal(buf, seed1) { + t.Errorf("rand.Read with seed 1 after reset = %x; want %x", buf, seed1) + } + + SetGlobalRandom(t, 1) + }) + + rand.Read(buf) + if bytes.Equal(buf, seed1) { + t.Errorf("rand.Read returned seeded output after test end") + } + + t.Run("seed 2", func(t *testing.T) { + SetGlobalRandom(t, 2) + rand.Read(buf) + if bytes.Equal(buf, seed1) { + t.Errorf("rand.Read with seed 2 = %x; want different from %x", buf, seed1) + } + }) + }) + + t.Run("rand.Reader", func(t *testing.T) { + buf := make([]byte, 64) + + t.Run("seed 1", func(t *testing.T) { + SetGlobalRandom(t, 1) + rand.Reader.Read(buf) + if !bytes.Equal(buf, seed1) { + t.Errorf("rand.Reader.Read with seed 1 = %x; want %x", buf, seed1) + } + + SetGlobalRandom(t, 1) + }) + + rand.Reader.Read(buf) + if bytes.Equal(buf, seed1) { + t.Errorf("rand.Reader.Read returned seeded output after test end") + } + + oldReader := rand.Reader + t.Cleanup(func() { rand.Reader = oldReader }) + rand.Reader = bytes.NewReader(bytes.Repeat([]byte{5}, 64)) + + t.Run("seed 1 again", func(t *testing.T) { + SetGlobalRandom(t, 1) + rand.Reader.Read(buf) + if !bytes.Equal(buf, seed1) { + t.Errorf("rand.Reader.Read with seed 1 = %x; want %x", buf, seed1) + } + }) + + rand.Reader.Read(buf) + if !bytes.Equal(buf, bytes.Repeat([]byte{5}, 64)) { + t.Errorf("rand.Reader not restored") + } + }) + + // A direct internal use of drbg.Read. + t.Run("mlkem.GenerateKey768", func(t *testing.T) { + exp, err := mlkem.NewDecapsulationKey768(seed1) + if err != nil { + t.Fatalf("mlkem.NewDecapsulationKey768: %v", err) + } + + SetGlobalRandom(t, 1) + got, err := mlkem.GenerateKey768() + if err != nil { + t.Fatalf("mlkem.GenerateKey768: %v", err) + } + + if gotBytes := got.Bytes(); !bytes.Equal(gotBytes, exp.Bytes()) { + t.Errorf("mlkem.GenerateKey768 with seed 1 = %x; want %x", gotBytes, exp.Bytes()) + } + }) + + // An ignored passed-in Reader. + t.Run("ecdsa.GenerateKey", func(t *testing.T) { + exp, err := ecdsa.ParseRawPrivateKey(elliptic.P384(), seed1[:48]) + if err != nil { + t.Fatalf("ecdsa.ParseRawPrivateKey: %v", err) + } + + SetGlobalRandom(t, 1) + got, err := ecdsa.GenerateKey(elliptic.P384(), bytes.NewReader([]byte("this reader is ignored"))) + if err != nil { + t.Fatalf("ecdsa.GenerateKey: %v", err) + } + + if !got.Equal(exp) { + t.Errorf("ecdsa.GenerateKey with seed 1 = %x; want %x", got.D.Bytes(), exp.D.Bytes()) + } + }) + + // The passed-in Reader is used if cryptocustomrand=1 is set, + // and MaybeReadByte is called on it. + t.Run("cryptocustomrand=1", func(t *testing.T) { + t.Setenv("GODEBUG", "cryptocustomrand=1") + + buf := make([]byte, 49) + buf[0] = 42 + for i := 2; i < 49; i++ { + buf[i] = 1 + } + + exp1, err := ecdsa.ParseRawPrivateKey(elliptic.P384(), buf[:48]) + if err != nil { + t.Fatalf("ecdsa.ParseRawPrivateKey: %v", err) + } + exp2, err := ecdsa.ParseRawPrivateKey(elliptic.P384(), buf[1:49]) + if err != nil { + t.Fatalf("ecdsa.ParseRawPrivateKey: %v", err) + } + + seen := [2]bool{} + for i := 0; i < 1000; i++ { + r := bytes.NewReader(buf) + got, err := ecdsa.GenerateKey(elliptic.P384(), r) + if err != nil { + t.Fatalf("ecdsa.GenerateKey: %v", err) + } + switch { + case got.Equal(exp1): + seen[0] = true + case got.Equal(exp2): + seen[1] = true + default: + t.Fatalf("ecdsa.GenerateKey with custom reader = %x; want %x or %x", got.D.Bytes(), exp1.D.Bytes(), exp2.D.Bytes()) + } + if seen[0] && seen[1] { + break + } + } + if !seen[0] || !seen[1] { + t.Errorf("ecdsa.GenerateKey with custom reader did not produce both expected keys") + } + + // Again, with SetGlobalRandom. + SetGlobalRandom(t, 1) + + seen = [2]bool{} + for i := 0; i < 1000; i++ { + r := bytes.NewReader(buf) + got, err := ecdsa.GenerateKey(elliptic.P384(), r) + if err != nil { + t.Fatalf("ecdsa.GenerateKey: %v", err) + } + switch { + case got.Equal(exp1): + seen[0] = true + case got.Equal(exp2): + seen[1] = true + default: + t.Fatalf("ecdsa.GenerateKey with custom reader and SetGlobalRandom = %x; want %x or %x", got.D.Bytes(), exp1.D.Bytes(), exp2.D.Bytes()) + } + if seen[0] && seen[1] { + break + } + } + if !seen[0] || !seen[1] { + t.Errorf("ecdsa.GenerateKey with custom reader and SetGlobalRandom did not produce both expected keys") + } + }) +} diff --git a/src/testing/testing.go b/src/testing/testing.go index 0d1d08ca89..34b45b41b9 100644 --- a/src/testing/testing.go +++ b/src/testing/testing.go @@ -1749,7 +1749,7 @@ func pcToName(pc uintptr) string { return frame.Function } -const parallelConflict = `testing: test using t.Setenv or t.Chdir can not use t.Parallel` +const parallelConflict = `testing: test using t.Setenv, t.Chdir, or cryptotest.SetGlobalRandom can not use t.Parallel` // Parallel signals that this test is to be run in parallel with (and only with) // other parallel tests. When a test is run multiple times due to use of @@ -1820,6 +1820,13 @@ func (t *T) Parallel() { t.lastRaceErrors.Store(int64(race.Errors())) } +// checkParallel is called by [testing/cryptotest.SetGlobalRandom]. +// +//go:linkname checkParallel testing.checkParallel +func checkParallel(t *T) { + t.checkParallel() +} + func (t *T) checkParallel() { // Non-parallel subtests that have parallel ancestors may still // run in parallel with other tests: they are only non-parallel -- 2.52.0