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  

View as plain text