crypto/internal/fips140/entropy: add CPU jitter-based entropy source

Heavily inspired by the BoringSSL implementation.

Change-Id: I6a6a6964b22826d54700c8b3d555054163cef5fe
Co-authored-by: Daniel Morsing <daniel.morsing@gmail.com>
Cq-Include-Trybots: luci.golang.try:gotip-linux-s390x,gotip-linux-ppc64_power10,gotip-linux-ppc64le_power10,gotip-linux-ppc64le_power8,gotip-linux-arm,gotip-darwin-arm64_15,gotip-windows-arm64,gotip-freebsd-amd64
Reviewed-on: https://go-review.googlesource.com/c/go/+/703015
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Roland Shoemaker <roland@golang.org>
Reviewed-by: Daniel McCarney <daniel@binaryparadox.net>
Auto-Submit: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Cherry Mui <cherryyz@google.com>
This commit is contained in:
Filippo Valsorda 2025-09-11 21:04:05 +02:00 committed by Gopher Robot
parent db4fade759
commit fc88e18b4a
10 changed files with 758 additions and 15 deletions

View file

@ -3,9 +3,11 @@
// license that can be found in the LICENSE file.
// Package entropy provides the passive entropy source for the FIPS 140-3
// module. It is only used in FIPS mode by [crypto/internal/fips140/drbg.Read].
// module. It is only used in FIPS mode by [crypto/internal/fips140/drbg.Read]
// from the FIPS 140-3 Go Cryptographic Module v1.0.0. Later versions of the
// module have an internal CPU jitter-based entropy source.
//
// This complies with IG 9.3.A, Additional Comment 12, which until January 1,
// This complied with IG 9.3.A, Additional Comment 12, which until January 1,
// 2026 allows new modules to meet an [earlier version] of Resolution 2(b):
// "A software module that contains an approved DRBG that receives a LOAD
// command (or its logical equivalent) with entropy obtained from [...] inside

View file

@ -9,21 +9,53 @@
package drbg
import (
"crypto/internal/entropy"
"crypto/internal/fips140"
"crypto/internal/fips140/entropy"
"crypto/internal/randutil"
"crypto/internal/sysrand"
"io"
"sync"
"sync/atomic"
)
var drbgs = sync.Pool{
// memory is a scratch buffer that is accessed between samples by the entropy
// source to expose it to memory access timings.
//
// We reuse it and share it between Seed calls to avoid the significant (~500µs)
// cost of zeroing a new allocation every time. The entropy source accesses it
// using atomics (and doesn't care about its contents).
//
// It should end up in the .noptrbss section, and become backed by physical pages
// at first use. This ensures that programs that do not use the FIPS 140-3 module
// do not incur any memory use or initialization penalties.
var memory entropy.ScratchBuffer
func getEntropy() *[SeedSize]byte {
var retries int
seed, err := entropy.Seed(&memory)
for err != nil {
// The CPU jitter-based SP 800-90B entropy source has a non-negligible
// chance of failing the startup health tests.
//
// Each time it does, it enters a permanent failure state, and we
// restart it anew. This is not expected to happen more than a few times
// in a row.
if retries++; retries > 100 {
panic("fips140/drbg: failed to obtain initial entropy")
}
seed, err = entropy.Seed(&memory)
}
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 {
var c *Counter
entropy.Depleted(func(seed *[48]byte) {
c = NewCounter(seed)
})
return c
return NewCounter(getEntropy())
},
}
@ -44,8 +76,15 @@ func Read(b []byte) {
additionalInput := new([SeedSize]byte)
sysrand.Read(additionalInput[:16])
drbg := drbgs.Get().(*Counter)
defer drbgs.Put(drbg)
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)
@ -54,9 +93,7 @@ func Read(b []byte) {
// 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.
entropy.Depleted(func(seed *[48]byte) {
drbg.Reseed(seed, additionalInput)
})
drbg.Reseed(getEntropy(), additionalInput)
additionalInput = nil
continue
}

View file

@ -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.
// Package entropy implements a CPU jitter-based SP 800-90B entropy source.
package entropy
import (
"crypto/internal/fips140deps/time"
"errors"
"sync/atomic"
"unsafe"
)
// Version returns the version of the entropy source.
//
// This is independent of the FIPS 140-3 module version, in order to reuse the
// ESV certificate across module versions.
func Version() string {
return "v1.0.0"
}
// ScratchBuffer is a large buffer that will be written to using atomics, to
// generate noise from memory access timings. Its contents do not matter.
type ScratchBuffer [1 << 25]byte
// Seed returns a 384-bit seed with full entropy.
//
// memory is passed in to allow changing the allocation strategy without
// modifying the frozen and certified entropy source in this package.
//
// Seed returns an error if the entropy source startup health tests fail, which
// has a non-negligible chance of happening.
func Seed(memory *ScratchBuffer) ([48]byte, error) {
// Collect w = 1024 samples, each certified to provide no less than h = 0.5
// bits of entropy, for a total of hᵢₙ = w × h = 512 bits of entropy, over
// nᵢₙ = w × n = 8192 bits of input data.
var samples [1024]byte
if err := Samples(samples[:], memory); err != nil {
return [48]byte{}, err
}
// Use a vetted unkeyed conditioning component, SHA-384, with nw = 384 and
// nₒᵤₜ = 384. Per the formula in SP 800-90B Section 3.1.5.1.2, the output
// entropy hₒᵤₜ is:
//
// sage: n_in = 8192
// sage: n_out = 384
// sage: nw = 384
// sage: h_in = 512
// sage: P_high = 2^(-h_in)
// sage: P_low = (1 - P_high) / (2^n_in - 1)
// sage: n = min(n_out, nw)
// sage: ψ = 2^(n_in - n) * P_low + P_high
// sage: U = 2^(n_in - n) + sqrt(2 * n * 2^(n_in - n) * ln(2))
// sage: ω = U * P_low
// sage: h_out = -log(max(ψ, ω), 2)
// sage: h_out.n()
// 384.000000000000
//
// According to Implementation Guidance D.K, Resolution 19, since
//
// - the conditioning component is vetted,
// - hᵢₙ = 512 ≥ nₒᵤₜ + 64 = 448, and
// - nₒᵤₜ ≤ security strength of SHA-384 = 384 (per SP 800-107 Rev. 1, Table 1),
//
// we can claim the output has full entropy.
return SHA384(&samples), nil
}
// Samples starts a new entropy source, collects the requested number of
// samples, conducts startup health tests, and returns the samples or an error
// if the health tests fail.
//
// The health tests have a non-negligible chance of failing.
func Samples(samples []uint8, memory *ScratchBuffer) error {
if len(samples) < 1024 {
return errors.New("entropy: at least 1024 samples are required for startup health tests")
}
s := newSource(memory)
for range 4 {
// Warm up the source to avoid any initial bias.
_ = s.Sample()
}
for i := range samples {
samples[i] = s.Sample()
}
if err := RepetitionCountTest(samples); err != nil {
return err
}
if err := AdaptiveProportionTest(samples); err != nil {
return err
}
return nil
}
type source struct {
memory *ScratchBuffer
lcgState uint32
previous int64
}
func newSource(memory *ScratchBuffer) *source {
return &source{
memory: memory,
lcgState: uint32(time.HighPrecisionNow()),
previous: time.HighPrecisionNow(),
}
}
// touchMemory performs a write to memory at the given index.
//
// The memory slice is passed in and may be shared across sources e.g. to avoid
// the significant (~500µs) cost of zeroing a new allocation on every [Seed] call.
func touchMemory(memory *ScratchBuffer, idx uint32) {
idx = idx / 4 * 4 // align to 32 bits
u32 := (*uint32)(unsafe.Pointer(&memory[idx]))
last := atomic.LoadUint32(u32)
atomic.SwapUint32(u32, last+13)
}
func (s *source) Sample() uint8 {
// Perform a few memory accesses in an unpredictable pattern to expose the
// next measurement to as much system noise as possible.
memory, lcgState := s.memory, s.lcgState
_ = memory[0] // hoist the nil check out of touchMemory
for range 64 {
lcgState = 1664525*lcgState + 1013904223
// Discard the lower bits, which tend to fall into short cycles.
idx := (lcgState >> 6) & (1<<25 - 1)
touchMemory(memory, idx)
}
s.lcgState = lcgState
t := time.HighPrecisionNow()
sample := t - s.previous
s.previous = t
// Reduce the symbol space to 256 values, assuming most of the entropy is in
// the least-significant bits, which represent the highest-resolution timing
// differences.
return uint8(sample)
}
// RepetitionCountTest implements the repetition count test from SP 800-90B
// Section 4.4.1. It returns an error if any symbol is repeated C = 41 or more
// times in a row.
//
// This C value is calculated from a target failure probability α = 2⁻²⁰ and a
// claimed min-entropy per symbol h = 0.5 bits, using the formula in SP 800-90B
// Section 4.4.1.
//
// sage: α = 2^-20
// sage: H = 0.5
// sage: 1 + ceil(-log(α, 2) / H)
// 41
func RepetitionCountTest(samples []uint8) error {
x := samples[0]
count := 1
for _, y := range samples[1:] {
if y == x {
count++
if count >= 41 {
return errors.New("entropy: repetition count health test failed")
}
} else {
x = y
count = 1
}
}
return nil
}
// AdaptiveProportionTest implements the adaptive proportion test from SP 800-90B
// Section 4.4.2. It returns an error if any symbol appears C = 410 or more
// times in the last W = 512 samples.
//
// This C value is calculated from a target failure probability α = 2⁻²⁰, a
// window size W = 512, and a claimed min-entropy per symbol h = 0.5 bits, using
// the formula in SP 800-90B Section 4.4.2, equivalent to the Microsoft Excel
// formula 1+CRITBINOM(W, power(2,(H)),1α).
//
// sage: from scipy.stats import binom
// sage: α = 2^-20
// sage: H = 0.5
// sage: W = 512
// sage: C = 1 + binom.ppf(1 - α, W, 2**(-H))
// sage: ceil(C)
// 410
func AdaptiveProportionTest(samples []uint8) error {
var counts [256]int
for i, x := range samples {
counts[x]++
if i >= 512 {
counts[samples[i-512]]--
}
if counts[x] >= 410 {
return errors.New("entropy: adaptive proportion health test failed")
}
}
return nil
}

View file

@ -0,0 +1,191 @@
// 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 entropy
import "math/bits"
// This file includes a SHA-384 implementation to insulate the entropy source
// from any changes in the FIPS 140-3 module's crypto/internal/fips140/sha512
// package. We only support 1024-byte inputs.
func SHA384(p *[1024]byte) [48]byte {
h := [8]uint64{
0xcbbb9d5dc1059ed8,
0x629a292a367cd507,
0x9159015a3070dd17,
0x152fecd8f70e5939,
0x67332667ffc00b31,
0x8eb44a8768581511,
0xdb0c2e0d64f98fa7,
0x47b5481dbefa4fa4,
}
sha384Block(&h, (*[128]byte)(p[0:128]))
sha384Block(&h, (*[128]byte)(p[128:256]))
sha384Block(&h, (*[128]byte)(p[256:384]))
sha384Block(&h, (*[128]byte)(p[384:512]))
sha384Block(&h, (*[128]byte)(p[512:640]))
sha384Block(&h, (*[128]byte)(p[640:768]))
sha384Block(&h, (*[128]byte)(p[768:896]))
sha384Block(&h, (*[128]byte)(p[896:1024]))
var padlen [128]byte
padlen[0] = 0x80
bePutUint64(padlen[112+8:], 1024*8)
sha384Block(&h, &padlen)
var digest [48]byte
bePutUint64(digest[0:], h[0])
bePutUint64(digest[8:], h[1])
bePutUint64(digest[16:], h[2])
bePutUint64(digest[24:], h[3])
bePutUint64(digest[32:], h[4])
bePutUint64(digest[40:], h[5])
return digest
}
var _K = [...]uint64{
0x428a2f98d728ae22,
0x7137449123ef65cd,
0xb5c0fbcfec4d3b2f,
0xe9b5dba58189dbbc,
0x3956c25bf348b538,
0x59f111f1b605d019,
0x923f82a4af194f9b,
0xab1c5ed5da6d8118,
0xd807aa98a3030242,
0x12835b0145706fbe,
0x243185be4ee4b28c,
0x550c7dc3d5ffb4e2,
0x72be5d74f27b896f,
0x80deb1fe3b1696b1,
0x9bdc06a725c71235,
0xc19bf174cf692694,
0xe49b69c19ef14ad2,
0xefbe4786384f25e3,
0x0fc19dc68b8cd5b5,
0x240ca1cc77ac9c65,
0x2de92c6f592b0275,
0x4a7484aa6ea6e483,
0x5cb0a9dcbd41fbd4,
0x76f988da831153b5,
0x983e5152ee66dfab,
0xa831c66d2db43210,
0xb00327c898fb213f,
0xbf597fc7beef0ee4,
0xc6e00bf33da88fc2,
0xd5a79147930aa725,
0x06ca6351e003826f,
0x142929670a0e6e70,
0x27b70a8546d22ffc,
0x2e1b21385c26c926,
0x4d2c6dfc5ac42aed,
0x53380d139d95b3df,
0x650a73548baf63de,
0x766a0abb3c77b2a8,
0x81c2c92e47edaee6,
0x92722c851482353b,
0xa2bfe8a14cf10364,
0xa81a664bbc423001,
0xc24b8b70d0f89791,
0xc76c51a30654be30,
0xd192e819d6ef5218,
0xd69906245565a910,
0xf40e35855771202a,
0x106aa07032bbd1b8,
0x19a4c116b8d2d0c8,
0x1e376c085141ab53,
0x2748774cdf8eeb99,
0x34b0bcb5e19b48a8,
0x391c0cb3c5c95a63,
0x4ed8aa4ae3418acb,
0x5b9cca4f7763e373,
0x682e6ff3d6b2b8a3,
0x748f82ee5defb2fc,
0x78a5636f43172f60,
0x84c87814a1f0ab72,
0x8cc702081a6439ec,
0x90befffa23631e28,
0xa4506cebde82bde9,
0xbef9a3f7b2c67915,
0xc67178f2e372532b,
0xca273eceea26619c,
0xd186b8c721c0c207,
0xeada7dd6cde0eb1e,
0xf57d4f7fee6ed178,
0x06f067aa72176fba,
0x0a637dc5a2c898a6,
0x113f9804bef90dae,
0x1b710b35131c471b,
0x28db77f523047d84,
0x32caab7b40c72493,
0x3c9ebe0a15c9bebc,
0x431d67c49c100d4c,
0x4cc5d4becb3e42b6,
0x597f299cfc657e2a,
0x5fcb6fab3ad6faec,
0x6c44198c4a475817,
}
func sha384Block(dh *[8]uint64, p *[128]byte) {
var w [80]uint64
for i := range 80 {
if i < 16 {
w[i] = beUint64(p[i*8:])
} else {
v1 := w[i-2]
t1 := bits.RotateLeft64(v1, -19) ^ bits.RotateLeft64(v1, -61) ^ (v1 >> 6)
v2 := w[i-15]
t2 := bits.RotateLeft64(v2, -1) ^ bits.RotateLeft64(v2, -8) ^ (v2 >> 7)
w[i] = t1 + w[i-7] + t2 + w[i-16]
}
}
a, b, c, d, e, f, g, h := dh[0], dh[1], dh[2], dh[3], dh[4], dh[5], dh[6], dh[7]
for i := range 80 {
t1 := h + (bits.RotateLeft64(e, -14) ^ bits.RotateLeft64(e, -18) ^
bits.RotateLeft64(e, -41)) + ((e & f) ^ (^e & g)) + _K[i] + w[i]
t2 := (bits.RotateLeft64(a, -28) ^ bits.RotateLeft64(a, -34) ^
bits.RotateLeft64(a, -39)) + ((a & b) ^ (a & c) ^ (b & c))
h = g
g = f
f = e
e = d + t1
d = c
c = b
b = a
a = t1 + t2
}
dh[0] += a
dh[1] += b
dh[2] += c
dh[3] += d
dh[4] += e
dh[5] += f
dh[6] += g
dh[7] += h
}
func beUint64(b []byte) uint64 {
_ = b[7] // bounds check hint to compiler; see golang.org/issue/14808
return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 |
uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56
}
func bePutUint64(b []byte, v uint64) {
_ = b[7] // early bounds check to guarantee safety of writes below
b[0] = byte(v >> 56)
b[1] = byte(v >> 48)
b[2] = byte(v >> 40)
b[3] = byte(v >> 32)
b[4] = byte(v >> 24)
b[5] = byte(v >> 16)
b[6] = byte(v >> 8)
b[7] = byte(v)
}

View file

@ -48,6 +48,8 @@ func Supported() error {
}
// See EnableFIPS in cmd/internal/obj/fips.go for commentary.
// Also, js/wasm and windows/386 don't have good enough timers
// for the CPU jitter entropy source.
switch {
case runtime.GOARCH == "wasm",
runtime.GOOS == "windows" && runtime.GOARCH == "386",

View file

@ -88,7 +88,8 @@ func TestImports(t *testing.T) {
}
}
// Ensure that all packages except check and check's dependencies import check.
// Ensure that all packages except check, check's dependencies, and the
// entropy source (which is used only from .../fips140/drbg) import check.
for pkg := range allPackages {
switch pkg {
case "crypto/internal/fips140/check":
@ -99,6 +100,7 @@ func TestImports(t *testing.T) {
case "crypto/internal/fips140/sha3":
case "crypto/internal/fips140/sha256":
case "crypto/internal/fips140/sha512":
case "crypto/internal/fips140/entropy":
default:
if !importCheck[pkg] {
t.Errorf("package %s does not import crypto/internal/fips140/check", pkg)

View file

@ -0,0 +1,21 @@
// 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 !windows
package time
import "time"
var start = time.Now()
// HighPrecisionNow returns a high-resolution timestamp suitable for measuring
// small time differences. It uses the time package's monotonic clock.
//
// Its unit, epoch, and resolution are unspecified, and may change, but can be
// assumed to be sufficiently precise to measure time differences on the order
// of tens to hundreds of nanoseconds.
func HighPrecisionNow() int64 {
return int64(time.Since(start))
}

View file

@ -0,0 +1,17 @@
// 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 time
import "internal/syscall/windows"
// HighPrecisionNow returns a high-resolution timestamp suitable for measuring
// small time differences. It uses Windows' QueryPerformanceCounter.
//
// Its unit, epoch, and resolution are unspecified, and may change, but can be
// assumed to be sufficiently precise to measure time differences on the order
// of tens to hundreds of nanoseconds.
func HighPrecisionNow() int64 {
return windows.QueryPerformanceCounter()
}

View file

@ -0,0 +1,264 @@
// 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 fipstest
import (
"bytes"
"crypto/internal/cryptotest"
"crypto/internal/fips140/drbg"
"crypto/internal/fips140/entropy"
"crypto/sha256"
"crypto/sha512"
"encoding/hex"
"flag"
"fmt"
"internal/testenv"
"io/fs"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
)
var flagEntropySamples = flag.String("entropy-samples", "", "store entropy samples with the provided `suffix`")
var flagNISTSP80090B = flag.Bool("nist-sp800-90b", false, "run NIST SP 800-90B tests (requires docker)")
func TestEntropySamples(t *testing.T) {
cryptotest.MustSupportFIPS140(t)
var seqSamples [1_000_000]uint8
samplesOrTryAgain(t, seqSamples[:])
seqSamplesName := fmt.Sprintf("entropy_samples_sequential_%s_%s_%s_%s_%s.bin", entropy.Version(),
runtime.GOOS, runtime.GOARCH, *flagEntropySamples, time.Now().Format("20060102T150405Z"))
if *flagEntropySamples != "" {
if err := os.WriteFile(seqSamplesName, seqSamples[:], 0644); err != nil {
t.Fatalf("failed to write samples to %q: %v", seqSamplesName, err)
}
t.Logf("wrote %s", seqSamplesName)
}
var restartSamples [1000][1000]uint8
for i := range restartSamples {
var samples [1024]uint8
samplesOrTryAgain(t, samples[:])
copy(restartSamples[i][:], samples[:])
}
restartSamplesName := fmt.Sprintf("entropy_samples_restart_%s_%s_%s_%s_%s.bin", entropy.Version(),
runtime.GOOS, runtime.GOARCH, *flagEntropySamples, time.Now().Format("20060102T150405Z"))
if *flagEntropySamples != "" {
f, err := os.Create(restartSamplesName)
if err != nil {
t.Fatalf("failed to create %q: %v", restartSamplesName, err)
}
for i := range restartSamples {
if _, err := f.Write(restartSamples[i][:]); err != nil {
t.Fatalf("failed to write samples to %q: %v", restartSamplesName, err)
}
}
if err := f.Close(); err != nil {
t.Fatalf("failed to close %q: %v", restartSamplesName, err)
}
t.Logf("wrote %s", restartSamplesName)
}
if *flagNISTSP80090B {
if *flagEntropySamples == "" {
t.Fatalf("-nist-sp800-90b requires -entropy-samples to be set too")
}
// Check if the nist-sp800-90b docker image is already present,
// and build it otherwise.
if err := testenv.Command(t,
"docker", "image", "inspect", "nist-sp800-90b",
).Run(); err != nil {
t.Logf("building nist-sp800-90b docker image")
dockerfile := filepath.Join(t.TempDir(), "Dockerfile.SP800-90B_EntropyAssessment")
if err := os.WriteFile(dockerfile, []byte(NISTSP80090BDockerfile), 0644); err != nil {
t.Fatalf("failed to write Dockerfile: %v", err)
}
out, err := testenv.Command(t,
"docker", "build", "-t", "nist-sp800-90b", "-f", dockerfile, "/var/empty",
).CombinedOutput()
if err != nil {
t.Fatalf("failed to build nist-sp800-90b docker image: %v\n%s", err, out)
}
}
pwd, err := os.Getwd()
if err != nil {
t.Fatalf("failed to get current working directory: %v", err)
}
t.Logf("running ea_non_iid analysis")
out, err := testenv.Command(t,
"docker", "run", "--rm", "-v", fmt.Sprintf("%s:%s", pwd, pwd), "-w", pwd,
"nist-sp800-90b", "ea_non_iid", seqSamplesName, "8",
).CombinedOutput()
if err != nil {
t.Fatalf("ea_non_iid failed: %v\n%s", err, out)
}
t.Logf("\n%s", out)
H_I := string(out)
H_I = strings.TrimSpace(H_I[strings.LastIndexByte(H_I, ' ')+1:])
t.Logf("running ea_restart analysis with H_I = %s", H_I)
out, err = testenv.Command(t,
"docker", "run", "--rm", "-v", fmt.Sprintf("%s:%s", pwd, pwd), "-w", pwd,
"nist-sp800-90b", "ea_restart", restartSamplesName, "8", H_I,
).CombinedOutput()
if err != nil {
t.Fatalf("ea_restart failed: %v\n%s", err, out)
}
t.Logf("\n%s", out)
}
}
var NISTSP80090BDockerfile = `
FROM ubuntu:24.04
RUN apt-get update && apt-get install -y build-essential git \
libbz2-dev libdivsufsort-dev libjsoncpp-dev libgmp-dev libmpfr-dev libssl-dev \
&& rm -rf /var/lib/apt/lists/*
RUN git clone --depth 1 https://github.com/usnistgov/SP800-90B_EntropyAssessment.git
RUN cd SP800-90B_EntropyAssessment && git checkout 8924f158c97e7b805e0f95247403ad4c44b9cd6f
WORKDIR ./SP800-90B_EntropyAssessment/cpp/
RUN make all
RUN cd selftest && ./selftest
RUN cp ea_non_iid ea_restart /usr/local/bin/
`
var memory entropy.ScratchBuffer
// samplesOrTryAgain calls entropy.Samples up to 10 times until it succeeds.
// Samples has a non-negligible chance of failing the health tests, as required
// by SP 800-90B.
func samplesOrTryAgain(t *testing.T, samples []uint8) {
t.Helper()
for range 10 {
if err := entropy.Samples(samples, &memory); err != nil {
t.Logf("entropy.Samples() failed: %v", err)
continue
}
return
}
t.Fatal("entropy.Samples() failed 10 times in a row")
}
func TestEntropySHA384(t *testing.T) {
var input [1024]uint8
for i := range input {
input[i] = uint8(i)
}
want := sha512.Sum384(input[:])
got := entropy.SHA384(&input)
if got != want {
t.Errorf("SHA384() = %x, want %x", got, want)
}
}
func TestEntropyRepetitionCountTest(t *testing.T) {
good := bytes.Repeat(append(bytes.Repeat([]uint8{42}, 40), 1), 100)
if err := entropy.RepetitionCountTest(good); err != nil {
t.Errorf("RepetitionCountTest(good) = %v, want nil", err)
}
bad := bytes.Repeat([]uint8{0}, 40)
bad = append(bad, bytes.Repeat([]uint8{1}, 40)...)
bad = append(bad, bytes.Repeat([]uint8{42}, 41)...)
bad = append(bad, bytes.Repeat([]uint8{2}, 40)...)
if err := entropy.RepetitionCountTest(bad); err == nil {
t.Error("RepetitionCountTest(bad) = nil, want error")
}
bad = bytes.Repeat([]uint8{42}, 41)
if err := entropy.RepetitionCountTest(bad); err == nil {
t.Error("RepetitionCountTest(bad) = nil, want error")
}
}
func TestEntropyAdaptiveProportionTest(t *testing.T) {
good := bytes.Repeat([]uint8{0}, 409)
good = append(good, bytes.Repeat([]uint8{1}, 512-409)...)
good = append(good, bytes.Repeat([]uint8{0}, 409)...)
if err := entropy.AdaptiveProportionTest(good); err != nil {
t.Errorf("AdaptiveProportionTest(good) = %v, want nil", err)
}
// These fall out of the window.
bad := bytes.Repeat([]uint8{1}, 100)
bad = append(bad, bytes.Repeat([]uint8{1, 2, 3, 4, 5, 6}, 100)...)
// These are in the window.
bad = append(bad, bytes.Repeat([]uint8{42}, 410)...)
if err := entropy.AdaptiveProportionTest(bad[:len(bad)-1]); err != nil {
t.Errorf("AdaptiveProportionTest(bad[:len(bad)-1]) = %v, want nil", err)
}
if err := entropy.AdaptiveProportionTest(bad); err == nil {
t.Error("AdaptiveProportionTest(bad) = nil, want error")
}
}
func TestEntropyUnchanged(t *testing.T) {
testenv.MustHaveSource(t)
h := sha256.New()
root := os.DirFS("../fips140/entropy")
if err := fs.WalkDir(root, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
data, err := fs.ReadFile(root, path)
if err != nil {
return err
}
t.Logf("Hashing %s (%d bytes)", path, len(data))
fmt.Fprintf(h, "%s %d\n", path, len(data))
h.Write(data)
return nil
}); err != nil {
t.Fatalf("WalkDir: %v", err)
}
// The crypto/internal/fips140/entropy package is certified as a FIPS 140-3
// entropy source through the Entropy Source Validation program,
// independently of the FIPS 140-3 module. It must not change even across
// FIPS 140-3 module versions, in order to reuse the ESV certificate.
exp := "35976eb8a11678c79777da07aaab5511d4325701f837777df205f6e7b20c6821"
if got := hex.EncodeToString(h.Sum(nil)); got != exp {
t.Errorf("hash of crypto/internal/fips140/entropy = %s, want %s", got, exp)
}
}
func TestEntropyRace(t *testing.T) {
// Check that concurrent calls to Seed don't trigger the race detector.
for range 2 {
go func() {
_, _ = entropy.Seed(&memory)
}()
}
// Same, with the higher-level DRBG. More concurrent calls to hit the Pool.
for range 16 {
go func() {
var b [64]byte
drbg.Read(b[:])
}()
}
}
var sink byte
func BenchmarkEntropySeed(b *testing.B) {
for b.Loop() {
seed, err := entropy.Seed(&memory)
if err != nil {
b.Fatalf("entropy.Seed() failed: %v", err)
}
sink ^= seed[0]
}
}

View file

@ -485,11 +485,16 @@ var depsRules = `
internal/byteorder < crypto/internal/fips140deps/byteorder;
internal/cpu, internal/goarch < crypto/internal/fips140deps/cpu;
internal/godebug < crypto/internal/fips140deps/godebug;
time, internal/syscall/windows < crypto/internal/fips140deps/time;
crypto/internal/fips140deps/time, errors, math/bits, sync/atomic, unsafe
< crypto/internal/fips140/entropy;
STR, hash,
crypto/internal/impl,
crypto/internal/entropy,
crypto/internal/randutil,
crypto/internal/fips140/entropy,
crypto/internal/fips140deps/byteorder,
crypto/internal/fips140deps/cpu,
crypto/internal/fips140deps/godebug