acvp: move hash iterations into modulewrapper.

In cases where the RPC from acvptool to modulewrapper is expensive,
these iterated tests take excessive amounts of time. By moving the
inner loop into the module wrapper the number of round-trips is reduced
by 1000×.

Change-Id: Ic047db071239492e416a08cab60d6a7e2905e8dc
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/47364
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/util/fipstools/acvp/ACVP.md b/util/fipstools/acvp/ACVP.md
index 2e82f15..201bcf9 100644
--- a/util/fipstools/acvp/ACVP.md
+++ b/util/fipstools/acvp/ACVP.md
@@ -90,9 +90,15 @@
 | SHA2-384             | Value to hash             | Digest  |
 | SHA2-512             | Value to hash             | Digest  |
 | SHA2-512/256         | Value to hash             | Digest  |
+| SHA-1/MCT            | Initial seed¹             | Digest  |
+| SHA2-224/MCT         | Initial seed¹             | Digest  |
+| SHA2-256/MCT         | Initial seed¹             | Digest  |
+| SHA2-384/MCT         | Initial seed¹             | Digest  |
+| SHA2-512/MCT         | Initial seed¹             | Digest  |
+| SHA2-512/256/MCT     | Initial seed¹             | Digest  |
 | TLSKDF/&lt;1.0\|1.2&gt;/&lt;HASH&gt; | Number output bytes, secret, label, seed1, seed2 | Output |
 
-¹ The iterated block-cipher tests would result in excessive numbers of round trips if the module wrapper handled only basic operations. Thus some ACVP logic is pushed down for these tests so that the inner loop can be handled locally. Either read the [NIST documentation](https://usnistgov.github.io/ACVP/draft-celi-acvp-symmetric.html#name-monte-carlo-tests-for-block) to understand the iteration count and return values or, probably more fruitfully, see how these functions are handled in the `modulewrapper` directory.
+¹ The iterated tests would result in excessive numbers of round trips if the module wrapper handled only basic operations. Thus some ACVP logic is pushed down for these tests so that the inner loop can be handled locally. Either read the NIST documentation ([block-ciphers](https://usnistgov.github.io/ACVP/draft-celi-acvp-symmetric.html#name-monte-carlo-tests-for-block) [hashes](https://pages.nist.gov/ACVP/draft-celi-acvp-sha.html#name-monte-carlo-tests-for-sha-1)) to understand the iteration count and return values or, probably more fruitfully, see how these functions are handled in the `modulewrapper` directory.
 
 ## Online operation
 
diff --git a/util/fipstools/acvp/acvptool/subprocess/hash.go b/util/fipstools/acvp/acvptool/subprocess/hash.go
index f283352..4c3ddf0 100644
--- a/util/fipstools/acvp/acvptool/subprocess/hash.go
+++ b/util/fipstools/acvp/acvptool/subprocess/hash.go
@@ -62,15 +62,6 @@
 	size int
 }
 
-// hash uses the subprocess to hash msg and returns the digest.
-func (h *hashPrimitive) hash(msg []byte, m Transactable) []byte {
-	result, err := m.Transact(h.algo, 1, msg)
-	if err != nil {
-		panic("hash operation failed: " + err.Error())
-	}
-	return result[0]
-}
-
 func (h *hashPrimitive) Process(vectorSet []byte, m Transactable) (interface{}, error) {
 	var parsed hashTestVectorSet
 	if err := json.Unmarshal(vectorSet, &parsed); err != nil {
@@ -98,9 +89,14 @@
 			// http://usnistgov.github.io/ACVP/artifacts/draft-celi-acvp-sha-00.html#rfc.section.3
 			switch group.Type {
 			case "AFT":
+				result, err := m.Transact(h.algo, 1, msg)
+				if err != nil {
+					panic(h.algo + " hash operation failed: " + err.Error())
+				}
+
 				response.Tests = append(response.Tests, hashTestResponse{
 					ID:        test.ID,
-					DigestHex: hex.EncodeToString(h.hash(msg, m)),
+					DigestHex: hex.EncodeToString(result[0]),
 				})
 
 			case "MCT":
@@ -110,20 +106,15 @@
 
 				testResponse := hashTestResponse{ID: test.ID}
 
-				buf := make([]byte, 3*h.size)
-				var digest []byte
+				digest := msg
 				for i := 0; i < 100; i++ {
-					copy(buf, msg)
-					copy(buf[h.size:], msg)
-					copy(buf[2*h.size:], msg)
-					for j := 0; j < 1000; j++ {
-						digest = h.hash(buf, m)
-						copy(buf, buf[h.size:])
-						copy(buf[2*h.size:], digest)
+					result, err := m.Transact(h.algo+"/MCT", 1, digest)
+					if err != nil {
+						panic(h.algo + " hash operation failed: " + err.Error())
 					}
 
+					digest = result[0]
 					testResponse.MCTResults = append(testResponse.MCTResults, hashMCTResult{hex.EncodeToString(digest)})
-					msg = digest
 				}
 
 				response.Tests = append(response.Tests, testResponse)
diff --git a/util/fipstools/acvp/modulewrapper/modulewrapper.cc b/util/fipstools/acvp/modulewrapper/modulewrapper.cc
index 25da8a1..1974dce 100644
--- a/util/fipstools/acvp/modulewrapper/modulewrapper.cc
+++ b/util/fipstools/acvp/modulewrapper/modulewrapper.cc
@@ -860,6 +860,30 @@
   return write_reply({Span<const uint8_t>(digest)});
 }
 
+template <uint8_t *(*OneShotHash)(const uint8_t *, size_t, uint8_t *),
+          size_t DigestLength>
+static bool HashMCT(const Span<const uint8_t> args[],
+                    ReplyCallback write_reply) {
+  if (args[0].size() != DigestLength) {
+    return false;
+  }
+
+  uint8_t buf[DigestLength * 3];
+  memcpy(buf, args[0].data(), DigestLength);
+  memcpy(buf + DigestLength, args[0].data(), DigestLength);
+  memcpy(buf + 2 * DigestLength, args[0].data(), DigestLength);
+
+  for (size_t i = 0; i < 1000; i++) {
+    uint8_t digest[DigestLength];
+    OneShotHash(buf, sizeof(buf), digest);
+    memmove(buf, buf + DigestLength, DigestLength * 2);
+    memcpy(buf + DigestLength * 2, digest, DigestLength);
+  }
+
+  return write_reply(
+      {Span<const uint8_t>(buf + 2 * DigestLength, DigestLength)});
+}
+
 static uint32_t GetIterations(const Span<const uint8_t> iterations_bytes) {
   uint32_t iterations;
   if (iterations_bytes.size() != sizeof(iterations)) {
@@ -1861,6 +1885,12 @@
     {"SHA2-384", 1, Hash<SHA384, SHA384_DIGEST_LENGTH>},
     {"SHA2-512", 1, Hash<SHA512, SHA512_DIGEST_LENGTH>},
     {"SHA2-512/256", 1, Hash<SHA512_256, SHA512_256_DIGEST_LENGTH>},
+    {"SHA-1/MCT", 1, HashMCT<SHA1, SHA_DIGEST_LENGTH>},
+    {"SHA2-224/MCT", 1, HashMCT<SHA224, SHA224_DIGEST_LENGTH>},
+    {"SHA2-256/MCT", 1, HashMCT<SHA256, SHA256_DIGEST_LENGTH>},
+    {"SHA2-384/MCT", 1, HashMCT<SHA384, SHA384_DIGEST_LENGTH>},
+    {"SHA2-512/MCT", 1, HashMCT<SHA512, SHA512_DIGEST_LENGTH>},
+    {"SHA2-512/256/MCT", 1, HashMCT<SHA512_256, SHA512_256_DIGEST_LENGTH>},
     {"AES/encrypt", 3, AES<AES_set_encrypt_key, AES_encrypt>},
     {"AES/decrypt", 3, AES<AES_set_decrypt_key, AES_decrypt>},
     {"AES-CBC/encrypt", 4, AES_CBC<AES_set_encrypt_key, AES_ENCRYPT>},