mirror of
https://github.com/golang/go.git
synced 2025-10-19 11:03:18 +00:00
crypto/internal/fips140test: add ctr DRBG ACVP tests
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>
This commit is contained in:
parent
283296195b
commit
0bc57a3e7f
3 changed files with 158 additions and 34 deletions
|
@ -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"]},
|
||||
|
|
|
@ -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"}
|
||||
]
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue