Source file src/crypto/internal/fips140/drbg/entropy_fips140.go
1 // Copyright 2026 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 //go:build !wasm 6 7 // This file contains reading from from entropy sources in FIPS-140 8 // mode. It uses a scratch buffer in the BSS section (see below), 9 // which usually doesn't cost much, except on Wasm, due to the way 10 // the linear memory works. FIPS-140 mode is not supported on Wasm, 11 // so we just use a build tag to exclude it. (Could also exclude other 12 // platforms that does not support FIPS-140 mode, but as the BSS 13 // variable doesn't cost much, don't bother.) 14 15 package drbg 16 17 import ( 18 entropy "crypto/internal/entropy/v1.0.0" 19 "crypto/internal/sysrand" 20 "sync" 21 "sync/atomic" 22 ) 23 24 // memory is a scratch buffer that is accessed between samples by the entropy 25 // source to expose it to memory access timings. 26 // 27 // We reuse it and share it between Seed calls to avoid the significant (~500µs) 28 // cost of zeroing a new allocation every time. The entropy source accesses it 29 // using atomics (and doesn't care about its contents). 30 // 31 // It should end up in the .noptrbss section, and become backed by physical pages 32 // at first use. This ensures that programs that do not use the FIPS 140-3 module 33 // do not incur any memory use or initialization penalties. 34 var memory entropy.ScratchBuffer 35 36 func getEntropy() *[SeedSize]byte { 37 var retries int 38 seed, err := entropy.Seed(&memory) 39 for err != nil { 40 // The CPU jitter-based SP 800-90B entropy source has a non-negligible 41 // chance of failing the startup health tests. 42 // 43 // Each time it does, it enters a permanent failure state, and we 44 // restart it anew. This is not expected to happen more than a few times 45 // in a row. 46 if retries++; retries > 100 { 47 panic("fips140/drbg: failed to obtain initial entropy") 48 } 49 seed, err = entropy.Seed(&memory) 50 } 51 return &seed 52 } 53 54 // getEntropy is very slow (~500µs), so we don't want it on the hot path. 55 // We keep both a persistent DRBG instance and a pool of additional instances. 56 // Occasional uses will use drbgInstance, even if the pool was emptied since the 57 // last use. Frequent concurrent uses will fill the pool and use it. 58 var drbgInstance atomic.Pointer[Counter] 59 var drbgPool = sync.Pool{ 60 New: func() any { 61 return NewCounter(getEntropy()) 62 }, 63 } 64 65 func readFromEntropy(b []byte) { 66 // At every read, 128 random bits from the operating system are mixed as 67 // additional input, to make the output as strong as non-FIPS randomness. 68 // This is not credited as entropy for FIPS purposes, as allowed by Section 69 // 8.7.2: "Note that a DRBG does not rely on additional input to provide 70 // entropy, even though entropy could be provided in the additional input". 71 additionalInput := new([SeedSize]byte) 72 sysrand.Read(additionalInput[:16]) 73 74 drbg := drbgInstance.Swap(nil) 75 if drbg == nil { 76 drbg = drbgPool.Get().(*Counter) 77 } 78 defer func() { 79 if !drbgInstance.CompareAndSwap(nil, drbg) { 80 drbgPool.Put(drbg) 81 } 82 }() 83 84 for len(b) > 0 { 85 size := min(len(b), maxRequestSize) 86 if reseedRequired := drbg.Generate(b[:size], additionalInput); reseedRequired { 87 // See SP 800-90A Rev. 1, Section 9.3.1, Steps 6-8, as explained in 88 // Section 9.3.2: if Generate reports a reseed is required, the 89 // additional input is passed to Reseed along with the entropy and 90 // then nulled before the next Generate call. 91 drbg.Reseed(getEntropy(), additionalInput) 92 additionalInput = nil 93 continue 94 } 95 b = b[size:] 96 } 97 } 98