mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
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:
parent
db4fade759
commit
fc88e18b4a
10 changed files with 758 additions and 15 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
202
src/crypto/internal/fips140/entropy/entropy.go
Normal file
202
src/crypto/internal/fips140/entropy/entropy.go
Normal 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
|
||||
}
|
||||
191
src/crypto/internal/fips140/entropy/sha384.go
Normal file
191
src/crypto/internal/fips140/entropy/sha384.go
Normal 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)
|
||||
}
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
21
src/crypto/internal/fips140deps/time/time.go
Normal file
21
src/crypto/internal/fips140deps/time/time.go
Normal 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))
|
||||
}
|
||||
17
src/crypto/internal/fips140deps/time/time_windows.go
Normal file
17
src/crypto/internal/fips140deps/time/time_windows.go
Normal 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()
|
||||
}
|
||||
264
src/crypto/internal/fips140test/entropy_test.go
Normal file
264
src/crypto/internal/fips140test/entropy_test.go
Normal 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]
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue