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() {