acvptool: add hmacDRBG support

Change-Id: I63ecaaaa8ec339688c586a4b2d44e4b91b910b8f
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/49305
Commit-Queue: Adam Langley <agl@google.com>
Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/util/fipstools/acvp/ACVP.md b/util/fipstools/acvp/ACVP.md
index c9570c6..d1255ca 100644
--- a/util/fipstools/acvp/ACVP.md
+++ b/util/fipstools/acvp/ACVP.md
@@ -66,6 +66,8 @@
 | CMAC-AES             | Number output bytes, key, message | MAC |
 | CMAC-AES/verify      | Key, message, claimed MAC | One-byte success flag |
 | ctrDRBG/AES-256      | Output length, entropy, personalisation, ad1, ad2, nonce | Output |
+| ctrDRBG-reseed/AES-256| Output length, entropy, personalisation, reseedAD, reseedEntropy, ad1, ad2, nonce | Output |
+| ctrDRBG-pr/AES-256   | Output length, entropy, personalisation, ad1, entropy1, ad2, entropy2, nonce | Output |
 | ECDH/&lt;CURVE&gt;   | X, Y, private key | X, Y, shared key |
 | ECDSA/keyGen         | Curve name | Private key, X, Y |
 | ECDSA/keyVer         | Curve name, X, Y | Single-byte valid flag |
@@ -79,6 +81,9 @@
 | HMAC-SHA2-384        | Value to hash, key        | Digest  |
 | HMAC-SHA2-512        | Value to hash, key        | Digest  |
 | HMAC-SHA2-512/256    | Value to hash, key        | Digest  |
+| hmacDRBG/&lt;HASH&gt;| Output length, entropy, personalisation, ad1, ad2, nonce | Output |
+| hmacDRBG-reseed/&lt;HASH&gt;| Output length, entropy, personalisation, reseedAD, reseedEntropy, ad1, ad2, nonce | Output |
+| hmacDRBG-pr/&lt;HASH&gt;| Output length, entropy, personalisation, ad1, entropy1, ad2, entropy2, nonce | Output |
 | KDF-counter          | Number output bytes, PRF name, counter location string, key, number of counter bits | Counter, output |
 | RSA/keyGen           | Modulus bit-size | e, p, q, n, d |
 | RSA/sigGen/&lt;HASH&gt;/pkcs1v1.5 | Modulus bit-size | n, e, signature |
diff --git a/util/fipstools/acvp/acvptool/subprocess/drbg.go b/util/fipstools/acvp/acvptool/subprocess/drbg.go
index b9a1cb8..d2f7572 100644
--- a/util/fipstools/acvp/acvptool/subprocess/drbg.go
+++ b/util/fipstools/acvp/acvptool/subprocess/drbg.go
@@ -40,18 +40,20 @@
 	AdditionalDataBits    uint64 `json:"additionalInputLen"`
 	RetBits               uint64 `json:"returnedBitsLen"`
 	Tests                 []struct {
-		ID                 uint64 `json:"tcId"`
-		EntropyHex         string `json:"entropyInput"`
-		NonceHex           string `json:"nonce"`
-		PersonalizationHex string `json:"persoString"`
-		Other              []struct {
-			AdditionalDataHex string `json:"additionalInput"`
-			EntropyHex        string `json:"entropyInput"`
-			Use               string `json:"intendedUse"`
-		} `json:"otherInput"`
+		ID                 uint64           `json:"tcId"`
+		EntropyHex         string           `json:"entropyInput"`
+		NonceHex           string           `json:"nonce"`
+		PersonalizationHex string           `json:"persoString"`
+		Other              []drbgOtherInput `json:"otherInput"`
 	} `json:"tests"`
 }
 
+type drbgOtherInput struct {
+	Use               string `json:"intendedUse"`
+	AdditionalDataHex string `json:"additionalInput"`
+	EntropyHex        string `json:"entropyInput"`
+}
+
 type drbgTestGroupResponse struct {
 	ID    uint64             `json:"tgId"`
 	Tests []drbgTestResponse `json:"tests"`
@@ -90,14 +92,6 @@
 			return nil, fmt.Errorf("test group %d specifies mode %q, which is not supported for the %s algorithm", group.ID, group.Mode, d.algo)
 		}
 
-		if group.PredictionResistance {
-			return nil, fmt.Errorf("Test group %d specifies prediction-resistance mode, which is not supported", group.ID)
-		}
-
-		if group.Reseed {
-			return nil, fmt.Errorf("Test group %d requests re-seeding, which is not supported", group.ID)
-		}
-
 		if group.RetBits%8 != 0 {
 			return nil, fmt.Errorf("Test group %d requests %d-bit outputs, but fractional-bytes are not supported", group.ID, group.RetBits)
 		}
@@ -118,26 +112,38 @@
 				return nil, fmt.Errorf("failed to extract personalization hex from test case %d/%d: %s", group.ID, test.ID, err)
 			}
 
-			const numAdditionalInputs = 2
-			if len(test.Other) != numAdditionalInputs {
-				return nil, fmt.Errorf("test case %d/%d provides %d additional inputs, but subprocess only expects %d", group.ID, test.ID, len(test.Other), numAdditionalInputs)
-			}
-
-			var additionalInputs [numAdditionalInputs][]byte
-			for i, other := range test.Other {
-				if other.Use != "generate" {
-					return nil, fmt.Errorf("other %d from test case %d/%d has use %q, but expected 'generate'", i, group.ID, test.ID, other.Use)
-				}
-				additionalInputs[i], err = extractField(other.AdditionalDataHex, group.AdditionalDataBits)
-				if err != nil {
-					return nil, fmt.Errorf("failed to extract additional input %d from test case %d/%d: %s", i, group.ID, test.ID, err)
-				}
-			}
-
 			outLen := group.RetBits / 8
 			var outLenBytes [4]byte
 			binary.LittleEndian.PutUint32(outLenBytes[:], uint32(outLen))
-			result, err := m.Transact(d.algo+"/"+group.Mode, 1, outLenBytes[:], ent, perso, additionalInputs[0], additionalInputs[1], nonce)
+
+			var result [][]byte
+			if group.PredictionResistance {
+				var a1, a2, a3, a4 []byte
+				if err := extractOtherInputs(test.Other, []drbgOtherInputExpectations{
+					{"generate", group.AdditionalDataBits, &a1, group.EntropyBits, &a2},
+					{"generate", group.AdditionalDataBits, &a3, group.EntropyBits, &a4}}); err != nil {
+					return nil, fmt.Errorf("failed to parse other inputs from test case %d/%d: %s", group.ID, test.ID, err)
+				}
+				result, err = m.Transact(d.algo+"-pr/"+group.Mode, 1, outLenBytes[:], ent, perso, a1, a2, a3, a4, nonce)
+			} else if group.Reseed {
+				var a1, a2, a3, a4 []byte
+				if err := extractOtherInputs(test.Other, []drbgOtherInputExpectations{
+					{"reSeed", group.AdditionalDataBits, &a1, group.EntropyBits, &a2},
+					{"generate", group.AdditionalDataBits, &a3, 0, nil},
+					{"generate", group.AdditionalDataBits, &a4, 0, nil}}); err != nil {
+					return nil, fmt.Errorf("failed to parse other inputs from test case %d/%d: %s", group.ID, test.ID, err)
+				}
+				result, err = m.Transact(d.algo+"-reseed/"+group.Mode, 1, outLenBytes[:], ent, perso, a1, a2, a3, a4, nonce)
+			} else {
+				var a1, a2 []byte
+				if err := extractOtherInputs(test.Other, []drbgOtherInputExpectations{
+					{"generate", group.AdditionalDataBits, &a1, 0, nil},
+					{"generate", group.AdditionalDataBits, &a2, 0, nil}}); err != nil {
+					return nil, fmt.Errorf("failed to parse other inputs from test case %d/%d: %s", group.ID, test.ID, err)
+				}
+				result, err = m.Transact(d.algo+"/"+group.Mode, 1, outLenBytes[:], ent, perso, a1, a2, nonce)
+			}
+
 			if err != nil {
 				return nil, fmt.Errorf("DRBG operation failed: %s", err)
 			}
@@ -159,6 +165,52 @@
 	return ret, nil
 }
 
+type drbgOtherInputExpectations struct {
+	use                   string
+	additionalInputBitLen uint64
+	additionalInputOut    *[]byte
+	entropyBitLen         uint64
+	entropyOut            *[]byte
+}
+
+func extractOtherInputs(inputs []drbgOtherInput, expected []drbgOtherInputExpectations) (err error) {
+	if len(inputs) != len(expected) {
+		return fmt.Errorf("found %d other inputs but %d were expected", len(inputs), len(expected))
+	}
+
+	for i := range inputs {
+		input, expect := &inputs[i], &expected[i]
+
+		if input.Use != expect.use {
+			return fmt.Errorf("other input #%d has type %q but expected %q", i, input.Use, expect.use)
+		}
+
+		if expect.additionalInputBitLen == 0 {
+			if len(input.AdditionalDataHex) != 0 {
+				return fmt.Errorf("other input #%d has unexpected additional input", i)
+			}
+		} else {
+			*expect.additionalInputOut, err = extractField(input.AdditionalDataHex, expect.additionalInputBitLen)
+			if err != nil {
+				return err
+			}
+		}
+
+		if expect.entropyBitLen == 0 {
+			if len(input.EntropyHex) != 0 {
+				return fmt.Errorf("other input #%d has unexpected entropy value", i)
+			}
+		} else {
+			*expect.entropyOut, err = extractField(input.EntropyHex, expect.entropyBitLen)
+			if err != nil {
+				return err
+			}
+		}
+	}
+
+	return nil
+}
+
 // validate the length and hex of a JSON field in test vectors
 func extractField(fieldHex string, bits uint64) ([]byte, error) {
 	if uint64(len(fieldHex))*4 != bits {
diff --git a/util/fipstools/acvp/acvptool/test/expected/hmacDRBG.bz2 b/util/fipstools/acvp/acvptool/test/expected/hmacDRBG.bz2
new file mode 100644
index 0000000..286ba0c
--- /dev/null
+++ b/util/fipstools/acvp/acvptool/test/expected/hmacDRBG.bz2
Binary files differ
diff --git a/util/fipstools/acvp/acvptool/test/tests.json b/util/fipstools/acvp/acvptool/test/tests.json
index dfbaac5..afd6b8d 100644
--- a/util/fipstools/acvp/acvptool/test/tests.json
+++ b/util/fipstools/acvp/acvptool/test/tests.json
@@ -18,6 +18,7 @@
 {"Wrapper": "modulewrapper", "In": "vectors/HMAC-SHA2-256.bz2", "Out": "expected/HMAC-SHA2-256.bz2"},
 {"Wrapper": "modulewrapper", "In": "vectors/HMAC-SHA2-384.bz2", "Out": "expected/HMAC-SHA2-384.bz2"},
 {"Wrapper": "modulewrapper", "In": "vectors/HMAC-SHA2-512.bz2", "Out": "expected/HMAC-SHA2-512.bz2"},
+{"Wrapper": "testmodulewrapper", "In": "vectors/hmacDRBG.bz2", "Out": "expected/hmacDRBG.bz2"},
 {"Wrapper": "testmodulewrapper", "In": "vectors/KAS-KDF.bz2", "Out": "expected/KAS-KDF.bz2"},
 {"Wrapper": "modulewrapper", "In": "vectors/KAS-ECC-SSC.bz2"},
 {"Wrapper": "modulewrapper", "In": "vectors/KAS-FFC-SSC.bz2"},
diff --git a/util/fipstools/acvp/acvptool/test/vectors/hmacDRBG.bz2 b/util/fipstools/acvp/acvptool/test/vectors/hmacDRBG.bz2
new file mode 100644
index 0000000..6bfaa4e
--- /dev/null
+++ b/util/fipstools/acvp/acvptool/test/vectors/hmacDRBG.bz2
Binary files differ
diff --git a/util/fipstools/acvp/acvptool/testmodulewrapper/hmac_drbg.go b/util/fipstools/acvp/acvptool/testmodulewrapper/hmac_drbg.go
new file mode 100644
index 0000000..46fae69
--- /dev/null
+++ b/util/fipstools/acvp/acvptool/testmodulewrapper/hmac_drbg.go
@@ -0,0 +1,98 @@
+// Copyright (c) 2021, Google Inc.
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+package main
+
+import (
+	"crypto/hmac"
+	"crypto/sha256"
+)
+
+// See SP 800-90Ar1, section 10.1.2
+type HMACDRBGSHA256 struct {
+	k, v [32]byte
+}
+
+func NewHMACDRBG(entropy, nonce, personalisation []byte) *HMACDRBGSHA256 {
+	ret := new(HMACDRBGSHA256)
+	ret.init(entropy, nonce, personalisation)
+	return ret
+}
+
+func (drbg *HMACDRBGSHA256) init(entropy, nonce, personalisation []byte) {
+	for i := range drbg.k {
+		drbg.k[i] = 0
+	}
+	for i := range drbg.v {
+		drbg.v[i] = 1
+	}
+
+	seed := make([]byte, 0, len(entropy)+len(nonce)+len(personalisation))
+	seed = append(seed, entropy...)
+	seed = append(seed, nonce...)
+	seed = append(seed, personalisation...)
+	drbg.update(seed)
+}
+
+func (drbg *HMACDRBGSHA256) update(data []byte) {
+	buf := make([]byte, 0, len(drbg.v)+1+len(data))
+	buf = append(buf, drbg.v[:]...)
+	buf = append(buf, 0)
+	buf = append(buf, data...)
+
+	mac := hmac.New(sha256.New, drbg.k[:])
+	mac.Write(buf)
+	mac.Sum(drbg.k[:0])
+
+	mac = hmac.New(sha256.New, drbg.k[:])
+	mac.Write(drbg.v[:])
+	mac.Sum(drbg.v[:0])
+
+	if len(data) > 0 {
+		copy(buf, drbg.v[:])
+		buf[len(drbg.v)] = 1
+
+		mac = hmac.New(sha256.New, drbg.k[:])
+		mac.Write(buf)
+		mac.Sum(drbg.k[:0])
+
+		mac = hmac.New(sha256.New, drbg.k[:])
+		mac.Write(drbg.v[:])
+		mac.Sum(drbg.v[:0])
+	}
+}
+
+func (drbg *HMACDRBGSHA256) Reseed(entropy, additionalInput []byte) {
+	buf := make([]byte, 0, len(entropy)+len(additionalInput))
+	buf = append(buf, entropy...)
+	buf = append(buf, additionalInput...)
+	drbg.update(buf)
+}
+
+func (drbg *HMACDRBGSHA256) Generate(out []byte, additionalInput []byte) {
+	if len(additionalInput) > 0 {
+		drbg.update(additionalInput)
+	}
+
+	done := 0
+	for done < len(out) {
+		mac := hmac.New(sha256.New, drbg.k[:])
+		mac.Write(drbg.v[:])
+		mac.Sum(drbg.v[:0])
+
+		done += copy(out[done:], drbg.v[:])
+	}
+
+	drbg.update(additionalInput)
+}
diff --git a/util/fipstools/acvp/acvptool/testmodulewrapper/hmac_drbg_test.go b/util/fipstools/acvp/acvptool/testmodulewrapper/hmac_drbg_test.go
new file mode 100644
index 0000000..ca6304d
--- /dev/null
+++ b/util/fipstools/acvp/acvptool/testmodulewrapper/hmac_drbg_test.go
@@ -0,0 +1,103 @@
+// Copyright (c) 2021, Google Inc.
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+package main
+
+import (
+	"encoding/hex"
+	"testing"
+)
+
+/*
+[SHA-256]
+[PredictionResistance = False]
+[EntropyInputLen = 256]
+[NonceLen = 128]
+[PersonalizationStringLen = 0]
+[AdditionalInputLen = 0]
+[ReturnedBitsLen = 1024]
+
+COUNT = 0
+EntropyInput = 06032cd5eed33f39265f49ecb142c511da9aff2af71203bffaf34a9ca5bd9c0d
+Nonce = 0e66f71edc43e42a45ad3c6fc6cdc4df
+PersonalizationString =
+** INSTANTIATE:
+	V   = 81e0d8830ed2d16f9b288a1cb289c5fab3f3c5c28131be7cafedcc7734604d34
+	Key = 17dc11c2389f5eeb9d0f6a5148a1ea83ee8a828f4f140ac78272a0da435fa121
+EntropyInputReseed = 01920a4e669ed3a85ae8a33b35a74ad7fb2a6bb4cf395ce00334a9c9a5a5d552
+AdditionalInputReseed =
+** RESEED:
+	V   = c246fa97570ba2b9d9e5b453fe4632366f146fbd8491146563eb463c9eafe50c
+	Key = ca43e73325de43c41d7e0a7a3163fb04061b09fcee4c7b8884e969e3bdfdff9a
+AdditionalInput =
+** GENERATE (FIRST CALL):
+	V   = df67d0816d6a8f3b73ba7638ea113bef0e33a1da451272ef1472211fb31c1cd6
+	Key = 8be4c7f9f249d5af2c6345a8f07af14be1d7adc2b9892286ffe37760d8aa5a1b
+AdditionalInput =
+ReturnedBits = 76fc79fe9b50beccc991a11b5635783a83536add03c157fb30645e611c2898bb2b1bc215000209208cd506cb28da2a51bdb03826aaf2bd2335d576d519160842e7158ad0949d1a9ec3e66ea1b1a064b005de914eac2e9d4f2d72a8616a80225422918250ff66a41bd2f864a6a38cc5b6499dc43f7f2bd09e1e0f8f5885935124
+** GENERATE (SECOND CALL):
+	V   = 80524881711e89a61e6fe7169581e50fb9ad642f3dff48fba5773352fa04cec3
+	Key = 5ed31bc06cc4f3a97f7f34929b0558b0c34de1f4bd1cef456a8364140e2d9f41
+*/
+
+func TestHMACDRBG(t *testing.T) {
+	drbg := NewHMACDRBG(fromHex("06032cd5eed33f39265f49ecb142c511da9aff2af71203bffaf34a9ca5bd9c0d"),
+		fromHex("0e66f71edc43e42a45ad3c6fc6cdc4df"),
+		nil)
+
+	drbg.Reseed(fromHex("01920a4e669ed3a85ae8a33b35a74ad7fb2a6bb4cf395ce00334a9c9a5a5d552"), nil)
+
+	var out [1024 / 8]byte
+	drbg.Generate(out[:], nil)
+	drbg.Generate(out[:], nil)
+
+	if hex.EncodeToString(out[:]) != "76fc79fe9b50beccc991a11b5635783a83536add03c157fb30645e611c2898bb2b1bc215000209208cd506cb28da2a51bdb03826aaf2bd2335d576d519160842e7158ad0949d1a9ec3e66ea1b1a064b005de914eac2e9d4f2d72a8616a80225422918250ff66a41bd2f864a6a38cc5b6499dc43f7f2bd09e1e0f8f5885935124" {
+		t.Errorf("Incorrect result: %x", out)
+	}
+}
+
+/*
+EntropyInput = 6c1f4bffc476e488fb57eb80dc106cf2b417bad22b196baa6346958256db490f
+Nonce = 5f1b92223e3909e43677da2f588a6d19
+PersonalizationString =
+AdditionalInput = e6cd940610375e504fa80406120b34d498b022393436e910c0ba2560603fd066
+EntropyInputPR = abaca65695bd5d289880453850fc8289b76f78b43f970ed32f4125a941165515
+AdditionalInput = d20082c5bdf6f6711af391e7d01046b9d3610827de63aa2671a5f5ad07b90841
+EntropyInputPR = 4a39b666cf861816d7d82ef6e23f70f149d74d9bd499eea19b622e751c43d839
+ReturnedBits = d3c36e4ae25ff21a95a157a89f13eb976362a695ea755f0465ed4a7bb20c5cb3
+*/
+
+func TestHMACDRBGPredictionResistance(t *testing.T) {
+	drbg := NewHMACDRBG(fromHex("6c1f4bffc476e488fb57eb80dc106cf2b417bad22b196baa6346958256db490f"),
+		fromHex("5f1b92223e3909e43677da2f588a6d19"),
+		nil)
+
+	var out [32]byte
+	drbg.Reseed(fromHex("abaca65695bd5d289880453850fc8289b76f78b43f970ed32f4125a941165515"), fromHex("e6cd940610375e504fa80406120b34d498b022393436e910c0ba2560603fd066"))
+	drbg.Generate(out[:], nil)
+	drbg.Reseed(fromHex("4a39b666cf861816d7d82ef6e23f70f149d74d9bd499eea19b622e751c43d839"), fromHex("d20082c5bdf6f6711af391e7d01046b9d3610827de63aa2671a5f5ad07b90841"))
+	drbg.Generate(out[:], nil)
+
+	if hex.EncodeToString(out[:]) != "d3c36e4ae25ff21a95a157a89f13eb976362a695ea755f0465ed4a7bb20c5cb3" {
+		t.Errorf("Incorrect result: %x", out)
+	}
+}
+
+func fromHex(h string) []byte {
+	ret, err := hex.DecodeString(h)
+	if err != nil {
+		panic(err)
+	}
+	return ret
+}
diff --git a/util/fipstools/acvp/acvptool/testmodulewrapper/testmodulewrapper.go b/util/fipstools/acvp/acvptool/testmodulewrapper/testmodulewrapper.go
index e989461..08a0fd8 100644
--- a/util/fipstools/acvp/acvptool/testmodulewrapper/testmodulewrapper.go
+++ b/util/fipstools/acvp/acvptool/testmodulewrapper/testmodulewrapper.go
@@ -1,3 +1,17 @@
+// Copyright (c) 2021, Google Inc.
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
 // testmodulewrapper is a modulewrapper binary that works with acvptool and
 // implements the primitives that BoringSSL's modulewrapper doesn't, so that
 // we have something that can exercise all the code in avcptool.
@@ -21,11 +35,13 @@
 )
 
 var handlers = map[string]func([][]byte) error{
-	"getConfig":       getConfig,
-	"KDF-counter":     kdfCounter,
-	"AES-XTS/encrypt": xtsEncrypt,
-	"AES-XTS/decrypt": xtsDecrypt,
-	"HKDF/SHA2-256":   hkdfMAC,
+	"getConfig":                getConfig,
+	"KDF-counter":              kdfCounter,
+	"AES-XTS/encrypt":          xtsEncrypt,
+	"AES-XTS/decrypt":          xtsDecrypt,
+	"HKDF/SHA2-256":            hkdfMAC,
+	"hmacDRBG-reseed/SHA2-256": hmacDRBGReseed,
+	"hmacDRBG-pr/SHA2-256":     hmacDRBGPredictionResistance,
 }
 
 func getConfig(args [][]byte) error {
@@ -104,6 +120,28 @@
 		}],
 		"l": 256,
 		"z": [256, 384]
+	}, {
+		"algorithm": "hmacDRBG",
+		"revision": "1.0",
+		"predResistanceEnabled": [false, true],
+		"reseedImplemented": true,
+		"capabilities": [{
+			"mode": "SHA2-256",
+			"derFuncEnabled": false,
+			"entropyInputLen": [
+				256
+			],
+			"nonceLen": [
+				128
+			],
+			"persoStringLen": [
+				256
+			],
+			"additionalInputLen": [
+				256
+			],
+			"returnedBitsLen": 256
+		}]
 	}
 ]`))
 }
@@ -246,8 +284,51 @@
 	return reply(ret)
 }
 
+func hmacDRBGReseed(args [][]byte) error {
+	if len(args) != 8 {
+		return fmt.Errorf("hmacDRBG received %d args, wanted 8", len(args))
+	}
+
+	outLenBytes, entropy, personalisation, reseedAdditionalData, reseedEntropy, additionalData1, additionalData2, nonce := args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]
+
+	if len(outLenBytes) != 4 {
+		return fmt.Errorf("uint32 length was %d bytes long", len(outLenBytes))
+	}
+	outLen := binary.LittleEndian.Uint32(outLenBytes)
+	out := make([]byte, outLen)
+
+	drbg := NewHMACDRBG(entropy, nonce, personalisation)
+	drbg.Reseed(reseedEntropy, reseedAdditionalData)
+	drbg.Generate(out, additionalData1)
+	drbg.Generate(out, additionalData2)
+
+	return reply(out)
+}
+
+func hmacDRBGPredictionResistance(args [][]byte) error {
+	if len(args) != 8 {
+		return fmt.Errorf("hmacDRBG received %d args, wanted 8", len(args))
+	}
+
+	outLenBytes, entropy, personalisation, additionalData1, entropy1, additionalData2, entropy2, nonce := args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]
+
+	if len(outLenBytes) != 4 {
+		return fmt.Errorf("uint32 length was %d bytes long", len(outLenBytes))
+	}
+	outLen := binary.LittleEndian.Uint32(outLenBytes)
+	out := make([]byte, outLen)
+
+	drbg := NewHMACDRBG(entropy, nonce, personalisation)
+	drbg.Reseed(entropy1, additionalData1)
+	drbg.Generate(out, nil)
+	drbg.Reseed(entropy2, additionalData2)
+	drbg.Generate(out, nil)
+
+	return reply(out)
+}
+
 const (
-	maxArgs       = 8
+	maxArgs       = 9
 	maxArgLength  = 1 << 20
 	maxNameLength = 30
 )