]> Cypherpunks repositories - gostls13.git/commitdiff
crypto/internal/fips140test: add ctr DRBG ACVP tests
authorDaniel McCarney <daniel@binaryparadox.net>
Thu, 2 Jan 2025 20:09:39 +0000 (15:09 -0500)
committerGopher Robot <gobot@golang.org>
Mon, 10 Feb 2025 21:54:29 +0000 (13:54 -0800)
Adds ACVP test coverage for the SP 800-90A rev 1 ctrDRBG algorithm based
on the NIST spec:
  https://pages.nist.gov/ACVP/draft-vassilev-acvp-drbg.html#section-7.2

The implementation in our FIPS module is a minimal implementation
tailored to the specific needs of stdlib crypto. As a result we
customize the ACVP capability registration so that:

* predResistanceEnabled is false
* only mode AES-256 is supported
* for that mode,
  * derFuncEnabled is false
  * persoStringLen is 0 to disable personalization
  * additionalInputLen is 384 to match the [48]byte argument in our API

Other capability values are chosen based on Table 4's ctrDRBG AES-256
w/o `derFuncEnabled` row:
  https://pages.nist.gov/ACVP/draft-vassilev-acvp-drbg.html#section-7.4

We do enable reseed in the capability, necessitating two acvptool
commands: one that expects only 6 args and doesn't reseed
("ctrDRBG/AES-256"), and one that expects 8 args and does
("ctrDRBG-reseed/AES-256").

Updates #69642

Change-Id: I0f01a2f9496f45b130ee7d10916708093236f473
Reviewed-on: https://go-review.googlesource.com/c/go/+/639795
Reviewed-by: Roland Shoemaker <roland@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Auto-Submit: Filippo Valsorda <filippo@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>

src/crypto/internal/fips140test/acvp_capabilities.json
src/crypto/internal/fips140test/acvp_test.config.json
src/crypto/internal/fips140test/acvp_test.go

index 5b318500874d950f2f49a309c24eecda61d04c84..e2a49530fa5a82b6e08d463eea332cf94b6760a7 100644 (file)
@@ -44,6 +44,8 @@
   {"algorithm":"hmacDRBG","revision":"1.0","predResistanceEnabled":[false],"reseedImplemented":false,"capabilities":[{"mode":"SHA3-384","derFuncEnabled":false,"entropyInputLen":[256],"nonceLen":[128],"persoStringLen":[256],"additionalInputLen":[0],"returnedBitsLen":384}]},
   {"algorithm":"hmacDRBG","revision":"1.0","predResistanceEnabled":[false],"reseedImplemented":false,"capabilities":[{"mode":"SHA3-512","derFuncEnabled":false,"entropyInputLen":[256],"nonceLen":[128],"persoStringLen":[256],"additionalInputLen":[0],"returnedBitsLen":512}]},
 
+  {"algorithm":"ctrDRBG","revision":"1.0","predResistanceEnabled":[false],"reseedImplemented":true,"capabilities":[{"mode":"AES-256","derFuncEnabled":false,"entropyInputLen":[384],"nonceLen":[0],"persoStringLen":[0],"additionalInputLen":[384],"returnedBitsLen":128}]},
+
   {"algorithm":"EDDSA","mode":"keyGen","revision":"1.0","curve":["ED-25519"]},
   {"algorithm":"EDDSA","mode":"keyVer","revision":"1.0","curve":["ED-25519"]},
   {"algorithm":"EDDSA","mode":"sigGen","revision":"1.0","pure":true,"preHash":true,"contextLength":[{"min":0,"max":255,"increment":1}],"curve":["ED-25519"]},
index 2339c478c8087055622ec384e033e82ba0a0ee90..10f73faac775aec633364edf130346fda9d3dc19 100644 (file)
@@ -34,6 +34,8 @@
 
   {"Wrapper": "go", "In": "vectors/hmacDRBG.bz2", "Out": "expected/hmacDRBG.bz2"},
 
+  {"Wrapper": "go", "In": "vectors/ctrDRBG.bz2", "Out": "expected/ctrDRBG.bz2"},
+
   {"Wrapper": "go", "In": "vectors/EDDSA.bz2", "Out": "expected/EDDSA.bz2"},
 
   {"Wrapper": "go", "In": "vectors/ECDSA.bz2", "Out": "expected/ECDSA.bz2"},
@@ -46,5 +48,6 @@
 
   {"Wrapper": "go", "In": "vectors/TLS-v1.2.bz2", "Out": "expected/TLS-v1.2.bz2"},
   {"Wrapper": "go", "In": "vectors/TLS-v1.3.bz2", "Out": "expected/TLS-v1.3.bz2"},
+
   {"Wrapper": "go", "In": "vectors/kdf-components.bz2", "Out": "expected/kdf-components.bz2"}
 ]
index 8c51538cabd9ab4e43cec60c32d8ab67dfdaab34..62a7dee6eb4510bb92b76ebd806fbd0bcf9faece 100644 (file)
@@ -26,6 +26,7 @@ import (
        "crypto/internal/fips140"
        "crypto/internal/fips140/aes"
        "crypto/internal/fips140/aes/gcm"
+       "crypto/internal/fips140/drbg"
        "crypto/internal/fips140/ecdh"
        "crypto/internal/fips140/ecdsa"
        "crypto/internal/fips140/ed25519"
@@ -125,7 +126,9 @@ var (
        // SSH KDF algorithm capabilities:
        //   https://pages.nist.gov/ACVP/draft-celi-acvp-kdf-ssh.html#section-7.2
        // ECDH algorithm capabilities:
-       //   https://pages.nist.gov/ACVP/draft-hammett-acvp-kas-ssc-ecc.html
+       //   https://pages.nist.gov/ACVP/draft-hammett-acvp-kas-ssc-ecc.html#section-7.3
+       // HMAC DRBG and CTR DRBG algorithm capabilities:
+       //   https://pages.nist.gov/ACVP/draft-vassilev-acvp-drbg.html#section-7.2
        //go:embed acvp_capabilities.json
        capabilitiesJson []byte
 
@@ -259,6 +262,9 @@ var (
                "ECDH/P-256": cmdEcdhAftVal(ecdh.P256()),
                "ECDH/P-384": cmdEcdhAftVal(ecdh.P384()),
                "ECDH/P-521": cmdEcdhAftVal(ecdh.P521()),
+
+               "ctrDRBG/AES-256":        cmdCtrDrbgAft(),
+               "ctrDRBG-reseed/AES-256": cmdCtrDrbgReseedAft(),
        }
 )
 
@@ -1103,39 +1109,6 @@ func cmdMlKem1024DecapAft() command {
        }
 }
 
-func cmdHmacDrbgAft(h func() fips140.Hash) command {
-       return command{
-               requiredArgs: 6, // Output length, entropy, personalization, ad1, ad2, nonce
-               handler: func(args [][]byte) ([][]byte, error) {
-                       outLen := binary.LittleEndian.Uint32(args[0])
-                       entropy := args[1]
-                       personalization := args[2]
-                       ad1 := args[3]
-                       ad2 := args[4]
-                       nonce := args[5]
-
-                       // Our capabilities describe no additional data support.
-                       if len(ad1) != 0 || len(ad2) != 0 {
-                               return nil, errors.New("additional data not supported")
-                       }
-
-                       // Our capabilities describe no prediction resistance (requires reseed) and no reseed.
-                       // So the test procedure is:
-                       //   * Instantiate DRBG
-                       //   * Generate but don't output
-                       //   * Generate output
-                       //   * Uninstantiate
-                       // See Table 7 in draft-vassilev-acvp-drbg
-                       out := make([]byte, outLen)
-                       drbg := ecdsa.TestingOnlyNewDRBG(h, entropy, nonce, personalization)
-                       drbg.Generate(out)
-                       drbg.Generate(out)
-
-                       return [][]byte{out}, nil
-               },
-       }
-}
-
 func lookupCurve(name string) (elliptic.Curve, error) {
        var c elliptic.Curve
 
@@ -1460,6 +1433,152 @@ func cmdEcdhAftVal[P ecdh.Point[P]](curve *ecdh.Curve[P]) command {
        }
 }
 
+func cmdHmacDrbgAft(h func() fips140.Hash) command {
+       return command{
+               requiredArgs: 6, // Output length, entropy, personalization, ad1, ad2, nonce
+               handler: func(args [][]byte) ([][]byte, error) {
+                       outLen := binary.LittleEndian.Uint32(args[0])
+                       entropy := args[1]
+                       personalization := args[2]
+                       ad1 := args[3]
+                       ad2 := args[4]
+                       nonce := args[5]
+
+                       // Our capabilities describe no additional data support.
+                       if len(ad1) != 0 || len(ad2) != 0 {
+                               return nil, errors.New("additional data not supported")
+                       }
+
+                       // Our capabilities describe no prediction resistance (requires reseed) and no reseed.
+                       // So the test procedure is:
+                       //   * Instantiate DRBG
+                       //   * Generate but don't output
+                       //   * Generate output
+                       //   * Uninstantiate
+                       // See Table 7 in draft-vassilev-acvp-drbg
+                       out := make([]byte, outLen)
+                       drbg := ecdsa.TestingOnlyNewDRBG(h, entropy, nonce, personalization)
+                       drbg.Generate(out)
+                       drbg.Generate(out)
+
+                       return [][]byte{out}, nil
+               },
+       }
+}
+
+func cmdCtrDrbgAft() command {
+       return command{
+               requiredArgs: 6, // Output length, entropy, personalization, ad1, ad2, nonce
+               handler: func(args [][]byte) ([][]byte, error) {
+                       return acvpCtrDrbg{
+                               outLen:          binary.LittleEndian.Uint32(args[0]),
+                               entropy:         args[1],
+                               personalization: args[2],
+                               ad1:             args[3],
+                               ad2:             args[4],
+                               nonce:           args[5],
+                       }.process()
+               },
+       }
+}
+
+func cmdCtrDrbgReseedAft() command {
+       return command{
+               requiredArgs: 8, // Output length, entropy, personalization, reseedAD, reseedEntropy, ad1, ad2, nonce
+               handler: func(args [][]byte) ([][]byte, error) {
+                       return acvpCtrDrbg{
+                               outLen:          binary.LittleEndian.Uint32(args[0]),
+                               entropy:         args[1],
+                               personalization: args[2],
+                               reseedAd:        args[3],
+                               reseedEntropy:   args[4],
+                               ad1:             args[5],
+                               ad2:             args[6],
+                               nonce:           args[7],
+                       }.process()
+               },
+       }
+}
+
+type acvpCtrDrbg struct {
+       outLen          uint32
+       entropy         []byte
+       personalization []byte
+       ad1             []byte
+       ad2             []byte
+       nonce           []byte
+       reseedAd        []byte // May be empty for no reseed
+       reseedEntropy   []byte // May be empty for no reseed
+}
+
+func (args acvpCtrDrbg) process() ([][]byte, error) {
+       // Our capability describes no personalization support.
+       if len(args.personalization) > 0 {
+               return nil, errors.New("personalization string not supported")
+       }
+
+       // Our capability describes no derivation function support, so the nonce
+       // should be empty.
+       if len(args.nonce) > 0 {
+               return nil, errors.New("unexpected nonce value")
+       }
+
+       // Our capability describes entropy input len of 384 bits.
+       entropy, err := require48Bytes(args.entropy)
+       if err != nil {
+               return nil, fmt.Errorf("entropy: %w", err)
+       }
+
+       // Our capability describes additional input len of 384 bits.
+       ad1, err := require48Bytes(args.ad1)
+       if err != nil {
+               return nil, fmt.Errorf("AD1: %w", err)
+       }
+       ad2, err := require48Bytes(args.ad2)
+       if err != nil {
+               return nil, fmt.Errorf("AD2: %w", err)
+       }
+
+       withReseed := len(args.reseedAd) > 0
+       var reseedAd, reseedEntropy *[48]byte
+       if withReseed {
+               // Ditto RE: entropy and additional data lengths for reseeding.
+               if reseedAd, err = require48Bytes(args.reseedAd); err != nil {
+                       return nil, fmt.Errorf("reseed AD: %w", err)
+               }
+               if reseedEntropy, err = require48Bytes(args.reseedEntropy); err != nil {
+                       return nil, fmt.Errorf("reseed entropy: %w", err)
+               }
+       }
+
+       // Our capabilities describe no prediction resistance and allow both
+       // reseed and no reseed, so the test procedure is:
+       //   * Instantiate DRBG
+       //   * Reseed (if enabled)
+       //   * Generate but don't output
+       //   * Generate output
+       //   * Uninstantiate
+       // See Table 7 in draft-vassilev-acvp-drbg
+       out := make([]byte, args.outLen)
+       ctrDrbg := drbg.NewCounter(entropy)
+       if withReseed {
+               ctrDrbg.Reseed(reseedEntropy, reseedAd)
+       }
+       ctrDrbg.Generate(out, ad1)
+       ctrDrbg.Generate(out, ad2)
+
+       return [][]byte{out}, nil
+}
+
+// Verify input is 48 byte slice, and cast it to a pointer to a fixed-size array
+// of 48 bytes, or return an error.
+func require48Bytes(input []byte) (*[48]byte, error) {
+       if inputLen := len(input); inputLen != 48 {
+               return nil, fmt.Errorf("invalid length: %d", inputLen)
+       }
+       return (*[48]byte)(input), nil
+}
+
 func TestACVP(t *testing.T) {
        testenv.SkipIfShortAndSlow(t)