Skip to content

Commit 0bc57a3

Browse files
cpugopherbot
authored andcommitted
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 <[email protected]> Reviewed-by: Dmitri Shuralyov <[email protected]> Reviewed-by: Filippo Valsorda <[email protected]> Auto-Submit: Filippo Valsorda <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 2832961 commit 0bc57a3

File tree

3 files changed

+158
-34
lines changed

3 files changed

+158
-34
lines changed

src/crypto/internal/fips140test/acvp_capabilities.json

+2
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
{"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}]},
4545
{"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}]},
4646

47+
{"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}]},
48+
4749
{"algorithm":"EDDSA","mode":"keyGen","revision":"1.0","curve":["ED-25519"]},
4850
{"algorithm":"EDDSA","mode":"keyVer","revision":"1.0","curve":["ED-25519"]},
4951
{"algorithm":"EDDSA","mode":"sigGen","revision":"1.0","pure":true,"preHash":true,"contextLength":[{"min":0,"max":255,"increment":1}],"curve":["ED-25519"]},

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

+3
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434

3535
{"Wrapper": "go", "In": "vectors/hmacDRBG.bz2", "Out": "expected/hmacDRBG.bz2"},
3636

37+
{"Wrapper": "go", "In": "vectors/ctrDRBG.bz2", "Out": "expected/ctrDRBG.bz2"},
38+
3739
{"Wrapper": "go", "In": "vectors/EDDSA.bz2", "Out": "expected/EDDSA.bz2"},
3840

3941
{"Wrapper": "go", "In": "vectors/ECDSA.bz2", "Out": "expected/ECDSA.bz2"},
@@ -46,5 +48,6 @@
4648

4749
{"Wrapper": "go", "In": "vectors/TLS-v1.2.bz2", "Out": "expected/TLS-v1.2.bz2"},
4850
{"Wrapper": "go", "In": "vectors/TLS-v1.3.bz2", "Out": "expected/TLS-v1.3.bz2"},
51+
4952
{"Wrapper": "go", "In": "vectors/kdf-components.bz2", "Out": "expected/kdf-components.bz2"}
5053
]

src/crypto/internal/fips140test/acvp_test.go

+153-34
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"crypto/internal/fips140"
2727
"crypto/internal/fips140/aes"
2828
"crypto/internal/fips140/aes/gcm"
29+
"crypto/internal/fips140/drbg"
2930
"crypto/internal/fips140/ecdh"
3031
"crypto/internal/fips140/ecdsa"
3132
"crypto/internal/fips140/ed25519"
@@ -125,7 +126,9 @@ var (
125126
// SSH KDF algorithm capabilities:
126127
// https://pages.nist.gov/ACVP/draft-celi-acvp-kdf-ssh.html#section-7.2
127128
// ECDH algorithm capabilities:
128-
// https://pages.nist.gov/ACVP/draft-hammett-acvp-kas-ssc-ecc.html
129+
// https://pages.nist.gov/ACVP/draft-hammett-acvp-kas-ssc-ecc.html#section-7.3
130+
// HMAC DRBG and CTR DRBG algorithm capabilities:
131+
// https://pages.nist.gov/ACVP/draft-vassilev-acvp-drbg.html#section-7.2
129132
//go:embed acvp_capabilities.json
130133
capabilitiesJson []byte
131134

@@ -259,6 +262,9 @@ var (
259262
"ECDH/P-256": cmdEcdhAftVal(ecdh.P256()),
260263
"ECDH/P-384": cmdEcdhAftVal(ecdh.P384()),
261264
"ECDH/P-521": cmdEcdhAftVal(ecdh.P521()),
265+
266+
"ctrDRBG/AES-256": cmdCtrDrbgAft(),
267+
"ctrDRBG-reseed/AES-256": cmdCtrDrbgReseedAft(),
262268
}
263269
)
264270

@@ -1103,39 +1109,6 @@ func cmdMlKem1024DecapAft() command {
11031109
}
11041110
}
11051111

1106-
func cmdHmacDrbgAft(h func() fips140.Hash) command {
1107-
return command{
1108-
requiredArgs: 6, // Output length, entropy, personalization, ad1, ad2, nonce
1109-
handler: func(args [][]byte) ([][]byte, error) {
1110-
outLen := binary.LittleEndian.Uint32(args[0])
1111-
entropy := args[1]
1112-
personalization := args[2]
1113-
ad1 := args[3]
1114-
ad2 := args[4]
1115-
nonce := args[5]
1116-
1117-
// Our capabilities describe no additional data support.
1118-
if len(ad1) != 0 || len(ad2) != 0 {
1119-
return nil, errors.New("additional data not supported")
1120-
}
1121-
1122-
// Our capabilities describe no prediction resistance (requires reseed) and no reseed.
1123-
// So the test procedure is:
1124-
// * Instantiate DRBG
1125-
// * Generate but don't output
1126-
// * Generate output
1127-
// * Uninstantiate
1128-
// See Table 7 in draft-vassilev-acvp-drbg
1129-
out := make([]byte, outLen)
1130-
drbg := ecdsa.TestingOnlyNewDRBG(h, entropy, nonce, personalization)
1131-
drbg.Generate(out)
1132-
drbg.Generate(out)
1133-
1134-
return [][]byte{out}, nil
1135-
},
1136-
}
1137-
}
1138-
11391112
func lookupCurve(name string) (elliptic.Curve, error) {
11401113
var c elliptic.Curve
11411114

@@ -1460,6 +1433,152 @@ func cmdEcdhAftVal[P ecdh.Point[P]](curve *ecdh.Curve[P]) command {
14601433
}
14611434
}
14621435

1436+
func cmdHmacDrbgAft(h func() fips140.Hash) command {
1437+
return command{
1438+
requiredArgs: 6, // Output length, entropy, personalization, ad1, ad2, nonce
1439+
handler: func(args [][]byte) ([][]byte, error) {
1440+
outLen := binary.LittleEndian.Uint32(args[0])
1441+
entropy := args[1]
1442+
personalization := args[2]
1443+
ad1 := args[3]
1444+
ad2 := args[4]
1445+
nonce := args[5]
1446+
1447+
// Our capabilities describe no additional data support.
1448+
if len(ad1) != 0 || len(ad2) != 0 {
1449+
return nil, errors.New("additional data not supported")
1450+
}
1451+
1452+
// Our capabilities describe no prediction resistance (requires reseed) and no reseed.
1453+
// So the test procedure is:
1454+
// * Instantiate DRBG
1455+
// * Generate but don't output
1456+
// * Generate output
1457+
// * Uninstantiate
1458+
// See Table 7 in draft-vassilev-acvp-drbg
1459+
out := make([]byte, outLen)
1460+
drbg := ecdsa.TestingOnlyNewDRBG(h, entropy, nonce, personalization)
1461+
drbg.Generate(out)
1462+
drbg.Generate(out)
1463+
1464+
return [][]byte{out}, nil
1465+
},
1466+
}
1467+
}
1468+
1469+
func cmdCtrDrbgAft() command {
1470+
return command{
1471+
requiredArgs: 6, // Output length, entropy, personalization, ad1, ad2, nonce
1472+
handler: func(args [][]byte) ([][]byte, error) {
1473+
return acvpCtrDrbg{
1474+
outLen: binary.LittleEndian.Uint32(args[0]),
1475+
entropy: args[1],
1476+
personalization: args[2],
1477+
ad1: args[3],
1478+
ad2: args[4],
1479+
nonce: args[5],
1480+
}.process()
1481+
},
1482+
}
1483+
}
1484+
1485+
func cmdCtrDrbgReseedAft() command {
1486+
return command{
1487+
requiredArgs: 8, // Output length, entropy, personalization, reseedAD, reseedEntropy, ad1, ad2, nonce
1488+
handler: func(args [][]byte) ([][]byte, error) {
1489+
return acvpCtrDrbg{
1490+
outLen: binary.LittleEndian.Uint32(args[0]),
1491+
entropy: args[1],
1492+
personalization: args[2],
1493+
reseedAd: args[3],
1494+
reseedEntropy: args[4],
1495+
ad1: args[5],
1496+
ad2: args[6],
1497+
nonce: args[7],
1498+
}.process()
1499+
},
1500+
}
1501+
}
1502+
1503+
type acvpCtrDrbg struct {
1504+
outLen uint32
1505+
entropy []byte
1506+
personalization []byte
1507+
ad1 []byte
1508+
ad2 []byte
1509+
nonce []byte
1510+
reseedAd []byte // May be empty for no reseed
1511+
reseedEntropy []byte // May be empty for no reseed
1512+
}
1513+
1514+
func (args acvpCtrDrbg) process() ([][]byte, error) {
1515+
// Our capability describes no personalization support.
1516+
if len(args.personalization) > 0 {
1517+
return nil, errors.New("personalization string not supported")
1518+
}
1519+
1520+
// Our capability describes no derivation function support, so the nonce
1521+
// should be empty.
1522+
if len(args.nonce) > 0 {
1523+
return nil, errors.New("unexpected nonce value")
1524+
}
1525+
1526+
// Our capability describes entropy input len of 384 bits.
1527+
entropy, err := require48Bytes(args.entropy)
1528+
if err != nil {
1529+
return nil, fmt.Errorf("entropy: %w", err)
1530+
}
1531+
1532+
// Our capability describes additional input len of 384 bits.
1533+
ad1, err := require48Bytes(args.ad1)
1534+
if err != nil {
1535+
return nil, fmt.Errorf("AD1: %w", err)
1536+
}
1537+
ad2, err := require48Bytes(args.ad2)
1538+
if err != nil {
1539+
return nil, fmt.Errorf("AD2: %w", err)
1540+
}
1541+
1542+
withReseed := len(args.reseedAd) > 0
1543+
var reseedAd, reseedEntropy *[48]byte
1544+
if withReseed {
1545+
// Ditto RE: entropy and additional data lengths for reseeding.
1546+
if reseedAd, err = require48Bytes(args.reseedAd); err != nil {
1547+
return nil, fmt.Errorf("reseed AD: %w", err)
1548+
}
1549+
if reseedEntropy, err = require48Bytes(args.reseedEntropy); err != nil {
1550+
return nil, fmt.Errorf("reseed entropy: %w", err)
1551+
}
1552+
}
1553+
1554+
// Our capabilities describe no prediction resistance and allow both
1555+
// reseed and no reseed, so the test procedure is:
1556+
// * Instantiate DRBG
1557+
// * Reseed (if enabled)
1558+
// * Generate but don't output
1559+
// * Generate output
1560+
// * Uninstantiate
1561+
// See Table 7 in draft-vassilev-acvp-drbg
1562+
out := make([]byte, args.outLen)
1563+
ctrDrbg := drbg.NewCounter(entropy)
1564+
if withReseed {
1565+
ctrDrbg.Reseed(reseedEntropy, reseedAd)
1566+
}
1567+
ctrDrbg.Generate(out, ad1)
1568+
ctrDrbg.Generate(out, ad2)
1569+
1570+
return [][]byte{out}, nil
1571+
}
1572+
1573+
// Verify input is 48 byte slice, and cast it to a pointer to a fixed-size array
1574+
// of 48 bytes, or return an error.
1575+
func require48Bytes(input []byte) (*[48]byte, error) {
1576+
if inputLen := len(input); inputLen != 48 {
1577+
return nil, fmt.Errorf("invalid length: %d", inputLen)
1578+
}
1579+
return (*[48]byte)(input), nil
1580+
}
1581+
14631582
func TestACVP(t *testing.T) {
14641583
testenv.SkipIfShortAndSlow(t)
14651584

0 commit comments

Comments
 (0)