diff --git a/src/crypto/internal/fips140/drbg/entropy_fips140.go b/src/crypto/internal/fips140/drbg/entropy_fips140.go index d36c8e3f39..9f936a37b8 100644 --- a/src/crypto/internal/fips140/drbg/entropy_fips140.go +++ b/src/crypto/internal/fips140/drbg/entropy_fips140.go @@ -2,24 +2,18 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// Entropy generation in FIPS 140-3 mode uses a scratch buffer in the BSS +// section (see below), which usually doesn't cost much, except on Wasm, due to +// the way the linear memory works. FIPS 140-3 mode is not supported on Wasm, so +// we just use a build tag to exclude it. (Could also exclude other platforms +// that does not support FIPS 140-3 mode, but as the BSS variable doesn't cost +// much, don't bother.) +// //go:build !wasm -// This file contains reading from entropy sources in FIPS-140 -// mode. It uses a scratch buffer in the BSS section (see below), -// which usually doesn't cost much, except on Wasm, due to the way -// the linear memory works. FIPS-140 mode is not supported on Wasm, -// so we just use a build tag to exclude it. (Could also exclude other -// platforms that does not support FIPS-140 mode, but as the BSS -// variable doesn't cost much, don't bother.) - package drbg -import ( - entropy "crypto/internal/entropy/v1.0.0" - "crypto/internal/sysrand" - "sync" - "sync/atomic" -) +import entropy "crypto/internal/entropy/v1.0.0" // memory is a scratch buffer that is accessed between samples by the entropy // source to expose it to memory access timings. @@ -50,48 +44,3 @@ func getEntropy() *[SeedSize]byte { } return &seed } - -// getEntropy is very slow (~500µs), so we don't want it on the hot path. -// We keep both a persistent DRBG instance and a pool of additional instances. -// Occasional uses will use drbgInstance, even if the pool was emptied since the -// last use. Frequent concurrent uses will fill the pool and use it. -var drbgInstance atomic.Pointer[Counter] -var drbgPool = sync.Pool{ - New: func() any { - return NewCounter(getEntropy()) - }, -} - -func readFromEntropy(b []byte) { - // At every read, 128 random bits from the operating system are mixed as - // additional input, to make the output as strong as non-FIPS randomness. - // This is not credited as entropy for FIPS purposes, as allowed by Section - // 8.7.2: "Note that a DRBG does not rely on additional input to provide - // entropy, even though entropy could be provided in the additional input". - additionalInput := new([SeedSize]byte) - sysrand.Read(additionalInput[:16]) - - drbg := drbgInstance.Swap(nil) - if drbg == nil { - drbg = drbgPool.Get().(*Counter) - } - defer func() { - if !drbgInstance.CompareAndSwap(nil, drbg) { - drbgPool.Put(drbg) - } - }() - - for len(b) > 0 { - size := min(len(b), maxRequestSize) - if reseedRequired := drbg.Generate(b[:size], additionalInput); reseedRequired { - // See SP 800-90A Rev. 1, Section 9.3.1, Steps 6-8, as explained in - // Section 9.3.2: if Generate reports a reseed is required, the - // additional input is passed to Reseed along with the entropy and - // then nulled before the next Generate call. - drbg.Reseed(getEntropy(), additionalInput) - additionalInput = nil - continue - } - b = b[size:] - } -} diff --git a/src/crypto/internal/fips140/drbg/entropy_wasm.go b/src/crypto/internal/fips140/drbg/entropy_wasm.go index f2e4cc73b1..894fda974a 100644 --- a/src/crypto/internal/fips140/drbg/entropy_wasm.go +++ b/src/crypto/internal/fips140/drbg/entropy_wasm.go @@ -6,6 +6,6 @@ package drbg -func readFromEntropy(b []byte) { - panic("FIPS-140 entropy generation is not supported on Wasm") +func getEntropy() *[SeedSize]byte { + panic("FIPS 140-3 entropy generation is not supported on Wasm") } diff --git a/src/crypto/internal/fips140/drbg/rand.go b/src/crypto/internal/fips140/drbg/rand.go index 7129bd08a6..ba2d6f7736 100644 --- a/src/crypto/internal/fips140/drbg/rand.go +++ b/src/crypto/internal/fips140/drbg/rand.go @@ -12,8 +12,21 @@ import ( "crypto/internal/fips140" "crypto/internal/sysrand" "io" + "sync" + "sync/atomic" ) +// getEntropy is very slow (~500µs), so we don't want it on the hot path. +// We keep both a persistent DRBG instance and a pool of additional instances. +// Occasional uses will use drbgInstance, even if the pool was emptied since the +// last use. Frequent concurrent uses will fill the pool and use it. +var drbgInstance atomic.Pointer[Counter] +var drbgPool = sync.Pool{ + New: func() any { + return NewCounter(getEntropy()) + }, +} + // Read fills b with cryptographically secure random bytes. In FIPS mode, it // uses an SP 800-90A Rev. 1 Deterministic Random Bit Generator (DRBG). // Otherwise, it uses the operating system's random number generator. @@ -32,7 +45,37 @@ func Read(b []byte) { return } - readFromEntropy(b) + // At every read, 128 random bits from the operating system are mixed as + // additional input, to make the output as strong as non-FIPS randomness. + // This is not credited as entropy for FIPS purposes, as allowed by Section + // 8.7.2: "Note that a DRBG does not rely on additional input to provide + // entropy, even though entropy could be provided in the additional input". + additionalInput := new([SeedSize]byte) + sysrand.Read(additionalInput[:16]) + + drbg := drbgInstance.Swap(nil) + if drbg == nil { + drbg = drbgPool.Get().(*Counter) + } + defer func() { + if !drbgInstance.CompareAndSwap(nil, drbg) { + drbgPool.Put(drbg) + } + }() + + for len(b) > 0 { + size := min(len(b), maxRequestSize) + if reseedRequired := drbg.Generate(b[:size], additionalInput); reseedRequired { + // See SP 800-90A Rev. 1, Section 9.3.1, Steps 6-8, as explained in + // Section 9.3.2: if Generate reports a reseed is required, the + // additional input is passed to Reseed along with the entropy and + // then nulled before the next Generate call. + drbg.Reseed(getEntropy(), additionalInput) + additionalInput = nil + continue + } + b = b[size:] + } } var testingReader io.Reader