diff --git a/src/crypto/internal/fips/ecdsa/cast.go b/src/crypto/internal/fips/ecdsa/cast.go index ed6d632c1c1..9982e1abc3a 100644 --- a/src/crypto/internal/fips/ecdsa/cast.go +++ b/src/crypto/internal/fips/ecdsa/cast.go @@ -7,6 +7,7 @@ package ecdsa import ( "bytes" "crypto/internal/fips" + "crypto/internal/fips/sha256" "errors" "sync" ) @@ -67,3 +68,33 @@ var fipsSelfTest = sync.OnceFunc(func() { return nil }) }) + +func init() { + fips.CAST("HMAC_DRBG SHA2-256", func() error { + entropy := []byte{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + } + nonce := []byte{ + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + } + personalizationString := []byte{ + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, + } + want := []byte{ + 0xc7, 0xff, 0x15, 0xb9, 0x68, 0x9a, 0x1d, 0x12, + 0x67, 0x67, 0x4b, 0xd4, 0x11, 0x27, 0xf3, 0xa6, + 0xa8, 0x8c, 0x1e, 0xd1, 0x58, 0xee, 0xda, 0x21, + 0x6e, 0x3c, 0xce, 0x84, 0xce, 0x45, 0x5e, 0xdb, + } + c := newDRBG(sha256.New, entropy, nonce, personalizationString) + got := make([]byte, len(want)) + c.Generate(got) + if !bytes.Equal(got, want) { + return errors.New("unexpected result") + } + return nil + }) +} diff --git a/src/crypto/internal/fips/ecdsa/hmacdrbg.go b/src/crypto/internal/fips/ecdsa/hmacdrbg.go new file mode 100644 index 00000000000..dcb9cf6875c --- /dev/null +++ b/src/crypto/internal/fips/ecdsa/hmacdrbg.go @@ -0,0 +1,120 @@ +// Copyright 2024 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 ecdsa + +import ( + "bytes" + "crypto/internal/fips" + "crypto/internal/fips/hmac" +) + +// hmacDRBG is an SP 800-90A Rev. 1 HMAC_DRBG. +// +// It is only intended to be used to generate ECDSA nonces. Since it will be +// instantiated ex-novo for each signature, its Generate function will only be +// invoked once or twice (only for P-256, with probability 2⁻³²). +// +// Per Table 2, it has a reseed interval of 2^48 requests, and a maximum request +// size of 2^19 bits (2^16 bytes, 64 KiB). +type hmacDRBG struct { + newHMAC func(key []byte) *hmac.HMAC + + hK *hmac.HMAC + V []byte + + reseedCounter uint64 +} + +const ( + reseedInterval = 1 << 48 + maxRequestSize = (1 << 19) / 8 +) + +func newDRBG[H fips.Hash](hash func() H, entropy, nonce, personalizationString []byte) *hmacDRBG { + // HMAC_DRBG_Instantiate_algorithm, per Section 10.1.2.3. + fips.RecordApproved() + + d := &hmacDRBG{ + newHMAC: func(key []byte) *hmac.HMAC { + return hmac.New(hash, key) + }, + } + size := hash().Size() + + // K = 0x00 0x00 0x00 ... 0x00 + K := make([]byte, size) + + // V = 0x01 0x01 0x01 ... 0x01 + d.V = bytes.Repeat([]byte{0x01}, size) + + // HMAC_DRBG_Update, per Section 10.1.2.2. + // K = HMAC (K, V || 0x00 || provided_data) + h := hmac.New(hash, K) + h.Write(d.V) + h.Write([]byte{0x00}) + h.Write(entropy) + h.Write(nonce) + h.Write(personalizationString) + K = h.Sum(K[:0]) + // V = HMAC (K, V) + h = hmac.New(hash, K) + h.Write(d.V) + d.V = h.Sum(d.V[:0]) + // K = HMAC (K, V || 0x01 || provided_data). + h.Reset() + h.Write(d.V) + h.Write([]byte{0x01}) + h.Write(entropy) + h.Write(nonce) + h.Write(personalizationString) + K = h.Sum(K[:0]) + // V = HMAC (K, V) + h = hmac.New(hash, K) + h.Write(d.V) + d.V = h.Sum(d.V[:0]) + + d.hK = h + d.reseedCounter = 1 + return d +} + +// Generate produces at most maxRequestSize bytes of random data in out. +func (d *hmacDRBG) Generate(out []byte) { + // HMAC_DRBG_Generate_algorithm, per Section 10.1.2.5. + fips.RecordApproved() + + if len(out) > maxRequestSize { + panic("ecdsa: internal error: request size exceeds maximum") + } + + if d.reseedCounter > reseedInterval { + panic("ecdsa: reseed interval exceeded") + } + + tlen := 0 + for tlen < len(out) { + // V = HMAC_K(V) + // T = T || V + d.hK.Reset() + d.hK.Write(d.V) + d.V = d.hK.Sum(d.V[:0]) + tlen += copy(out[tlen:], d.V) + } + + // Note that if this function shows up on ECDSA-level profiles, this can be + // optimized in the common case by deferring the rest to the next Generate + // call, which will never come in nearly all cases. + + // HMAC_DRBG_Update, per Section 10.1.2.2, without provided_data. + // K = HMAC (K, V || 0x00) + d.hK.Reset() + d.hK.Write(d.V) + d.hK.Write([]byte{0x00}) + K := d.hK.Sum(nil) + // V = HMAC (K, V) + d.hK = d.newHMAC(K) + d.hK.Write(d.V) + d.V = d.hK.Sum(d.V[:0]) +}