acvptool: Add support for DRBG
Change-Id: Ia9dda0826787aea4d63536524074e343ff6c87d9
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/38644
Reviewed-by: Adam Langley <agl@google.com>
Reviewed-by: Gurleen Grewal <gurleengrewal@google.com>
Commit-Queue: Adam Langley <agl@google.com>
diff --git a/util/fipstools/acvp/acvptool/subprocess/drbg.go b/util/fipstools/acvp/acvptool/subprocess/drbg.go
new file mode 100644
index 0000000..b7f93f9
--- /dev/null
+++ b/util/fipstools/acvp/acvptool/subprocess/drbg.go
@@ -0,0 +1,155 @@
+package subprocess
+
+import (
+ "encoding/binary"
+ "encoding/hex"
+ "encoding/json"
+ "fmt"
+)
+
+// The following structures reflect the JSON of ACVP DRBG tests. See
+// https://usnistgov.github.io/ACVP/artifacts/acvp_sub_drbg.html#rfc.section.4
+
+type drbgTestVectorSet struct {
+ Groups []drbgTestGroup `json:"testGroups"`
+}
+
+type drbgTestGroup struct {
+ ID uint64 `json:"tgId"`
+ Mode string `json:"mode"`
+ UseDerivationFunction bool `json:"derFunc,omitempty"`
+ PredictionResistance bool `json:"predResistance"`
+ Reseed bool `json:"reSeed"`
+ EntropyBits uint64 `json:"entropyInputLen"`
+ NonceBits uint64 `json:"nonceLen"`
+ PersonalizationBits uint64 `json:"persoStringLen"`
+ 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"`
+ } `json:"tests"`
+}
+
+type drbgTestGroupResponse struct {
+ ID uint64 `json:"tgId"`
+ Tests []drbgTestResponse `json:"tests"`
+}
+
+type drbgTestResponse struct {
+ ID uint64 `json:"tcId"`
+ OutHex string `json:"returnedBits,omitempty"`
+}
+
+// drbg implements an ACVP algorithm by making requests to the
+// subprocess to generate random bits with the given entropy and other paramaters.
+type drbg struct {
+ // algo is the ACVP name for this algorithm and also the command name
+ // given to the subprocess to generate random bytes.
+ algo string
+ modes map[string]bool // the supported underlying primitives for the DRBG
+ m *Subprocess
+}
+
+func (d *drbg) Process(vectorSet []byte) (interface{}, error) {
+ var parsed drbgTestVectorSet
+ if err := json.Unmarshal(vectorSet, &parsed); err != nil {
+ return nil, err
+ }
+
+ var ret []drbgTestGroupResponse
+ // See
+ // https://usnistgov.github.io/ACVP/artifacts/acvp_sub_drbg.html#rfc.section.4
+ // for details about the tests.
+ for _, group := range parsed.Groups {
+ response := drbgTestGroupResponse{
+ ID: group.ID,
+ }
+
+ if _, ok := d.modes[group.Mode]; !ok {
+ 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)
+ }
+
+ for _, test := range group.Tests {
+ ent, err := extractField(test.EntropyHex, group.EntropyBits)
+ if err != nil {
+ return nil, fmt.Errorf("failed to extract entropy hex from test case %d/%d: %s", group.ID, test.ID, err)
+ }
+
+ nonce, err := extractField(test.NonceHex, group.NonceBits)
+ if err != nil {
+ return nil, fmt.Errorf("failed to extract nonce hex from test case %d/%d: %s", group.ID, test.ID, err)
+ }
+
+ perso, err := extractField(test.PersonalizationHex, group.PersonalizationBits)
+ if err != nil {
+ 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 := d.m.transact(d.algo+"/"+group.Mode, 1, outLenBytes[:], ent, perso, additionalInputs[0], additionalInputs[1], nonce)
+ if err != nil {
+ return nil, fmt.Errorf("DRBG operation failed: %s", err)
+ }
+
+ if l := uint64(len(result[0])); l != outLen {
+ return nil, fmt.Errorf("wrong length DRBG result: %d bytes but wanted %d", l, outLenBytes)
+ }
+
+ // https://usnistgov.github.io/ACVP/artifacts/acvp_sub_drbg.html#rfc.section.4
+ response.Tests = append(response.Tests, drbgTestResponse{
+ ID: test.ID,
+ OutHex: hex.EncodeToString(result[0]),
+ })
+ }
+
+ ret = append(ret, response)
+ }
+
+ return ret, 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 {
+ return nil, fmt.Errorf("expected %d bits but have %d-byte hex string", bits, len(fieldHex))
+ }
+ return hex.DecodeString(fieldHex)
+}
diff --git a/util/fipstools/acvp/acvptool/subprocess/subprocess.go b/util/fipstools/acvp/acvptool/subprocess/subprocess.go
index 1f3ff8e..452a422 100644
--- a/util/fipstools/acvp/acvptool/subprocess/subprocess.go
+++ b/util/fipstools/acvp/acvptool/subprocess/subprocess.go
@@ -61,6 +61,8 @@
"HMAC-SHA2-256": &hmacPrimitive{"HMAC-SHA2-256", 32, m},
"HMAC-SHA2-384": &hmacPrimitive{"HMAC-SHA2-384", 48, m},
"HMAC-SHA2-512": &hmacPrimitive{"HMAC-SHA2-512", 64, m},
+ "ctrDRBG": &drbg{"ctrDRBG", map[string]bool{"AES-128": true, "AES-192": true, "AES-256": true}, m},
+ "hmacDRBG": &drbg{"hmacDRBG", map[string]bool{"SHA-1": true, "SHA2-224": true, "SHA2-256": true, "SHA2-384": true, "SHA2-512": true}, m},
}
return m
diff --git a/util/fipstools/acvp/modulewrapper/modulewrapper.cc b/util/fipstools/acvp/modulewrapper/modulewrapper.cc
index e6d8b21..5f3150d 100644
--- a/util/fipstools/acvp/modulewrapper/modulewrapper.cc
+++ b/util/fipstools/acvp/modulewrapper/modulewrapper.cc
@@ -28,6 +28,8 @@
#include <openssl/sha.h>
#include <openssl/span.h>
+#include "../../../../crypto/fipsmodule/rand/internal.h"
+
static constexpr size_t kMaxArgs = 8;
static constexpr size_t kMaxArgLength = (1 << 20);
static constexpr size_t kMaxNameLength = 30;
@@ -212,6 +214,23 @@
" \"macLen\": [{"
" \"min\": 32, \"max\": 512, \"increment\": 8"
" }]"
+ "},"
+ "{"
+ " \"algorithm\": \"ctrDRBG\","
+ " \"revision\": \"1.0\","
+ " \"predResistanceEnabled\": [false],"
+ " \"reseedImplemented\": false,"
+ " \"capabilities\": [{"
+ " \"mode\": \"AES-256\","
+ " \"derFuncEnabled\": false,"
+ " \"entropyInputLen\": [384],"
+ " \"nonceLen\": [0],"
+ " \"persoStringLen\": [{\"min\": 0, \"max\": 384, \"increment\": 16}],"
+ " \"additionalInputLen\": ["
+ " {\"min\": 0, \"max\": 384, \"increment\": 16}"
+ " ],"
+ " \"returnedBitsLen\": 2048"
+ " }]"
"}"
"]";
return WriteReply(
@@ -280,6 +299,40 @@
return WriteReply(STDOUT_FILENO, Span<const uint8_t>(digest, digest_len));
}
+static bool DRBG(const Span<const uint8_t> args[]) {
+ const auto out_len_bytes = args[0];
+ const auto entropy = args[1];
+ const auto personalisation = args[2];
+ const auto additional_data1 = args[3];
+ const auto additional_data2 = args[4];
+ const auto nonce = args[5];
+
+ uint32_t out_len;
+ if (out_len_bytes.size() != sizeof(out_len) ||
+ entropy.size() != CTR_DRBG_ENTROPY_LEN ||
+ // nonces are not supported
+ nonce.size() != 0) {
+ return false;
+ }
+ memcpy(&out_len, out_len_bytes.data(), sizeof(out_len));
+ if (out_len > (1 << 24)) {
+ return false;
+ }
+ std::vector<uint8_t> out(out_len);
+
+ CTR_DRBG_STATE drbg;
+ if (!CTR_DRBG_init(&drbg, entropy.data(), personalisation.data(),
+ personalisation.size()) ||
+ !CTR_DRBG_generate(&drbg, out.data(), out_len, additional_data1.data(),
+ additional_data1.size()) ||
+ !CTR_DRBG_generate(&drbg, out.data(), out_len, additional_data2.data(),
+ additional_data2.size())) {
+ return false;
+ }
+
+ return WriteReply(STDOUT_FILENO, Span<const uint8_t>(out));
+}
+
static constexpr struct {
const char name[kMaxNameLength + 1];
uint8_t expected_args;
@@ -300,6 +353,7 @@
{"HMAC-SHA2-256", 2, HMAC<EVP_sha256>},
{"HMAC-SHA2-384", 2, HMAC<EVP_sha384>},
{"HMAC-SHA2-512", 2, HMAC<EVP_sha512>},
+ {"ctrDRBG/AES-256", 6, DRBG},
};
int main() {