crypto/internal/fips140/drbg: fix Wasm stub

This partially reverts CL 758361. The result is functionally equivalent,
but the previous readFromEntropy function was actually reading from a
DRBG which in turn is seeded from the Entropy Source (and mixed with the
system RNG), not directly from the Entropy Source. I'm sorry to be
pedantic about this but

  1. the whole randomness and entropy machinery is already very complex
     and we should avoid any confusion;

  2. this is the kind of code that auditors might read, and they have a
     dreadful sense of humor.

I also slightly prefer having fewer levels of indirection, and we
already have the getEntropy function to stub out.

Updates #78321

Change-Id: Ic95bbb0061b7d519f2a1e80c667f4f8b6a6a6964
Reviewed-on: https://go-review.googlesource.com/c/go/+/774221
Auto-Submit: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Roland Shoemaker <roland@golang.org>
Reviewed-by: Cherry Mui <cherryyz@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Filippo Valsorda 2026-05-05 14:13:11 +02:00 committed by Gopher Robot
parent 6f19c3b459
commit 1bd98fab2c
3 changed files with 54 additions and 62 deletions

View file

@ -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:]
}
}

View file

@ -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")
}

View file

@ -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