acvp: move inner MCT loops into subprocess.

The ACVP MCT tests involve a double loop where the inner loop iterates
1000 (AES) or 10000 (3DES) times. This change moves that inner loop
into the subprocess. This significantly reduces the amount of IPC
traffic at the cost of making the subprocesses more complex. The traffic
volume is unimportant when talking over a local pipe, but it's
significant when channels like serial links are used.

Change-Id: Ia9d51335f06b743791f7885d366c8fd2f0f7eaf6
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/43844
Commit-Queue: Adam Langley <alangley@gmail.com>
Commit-Queue: Adam Langley <agl@google.com>
Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/util/fipstools/acvp/acvptool/subprocess/block.go b/util/fipstools/acvp/acvptool/subprocess/block.go
index 3b468c8..3765473 100644
--- a/util/fipstools/acvp/acvptool/subprocess/block.go
+++ b/util/fipstools/acvp/acvptool/subprocess/block.go
@@ -60,24 +60,20 @@
 			iteration.CiphertextHex = hex.EncodeToString(input)
 		}
 
-		var result, prevResult []byte
-		for j := 0; j < 1000; j++ {
-			prevResult = input
-			result, err := transact(1, key, input)
-			if err != nil {
-				panic("block operation failed")
-			}
-			input = result[0]
+		results, err := transact(2, key, input, uint32le(1000))
+		if err != nil {
+			panic(err)
 		}
-		result = input
+		input = results[0]
+		prevResult := results[1]
 
 		if encrypt {
-			iteration.CiphertextHex = hex.EncodeToString(result)
+			iteration.CiphertextHex = hex.EncodeToString(input)
 		} else {
-			iteration.PlaintextHex = hex.EncodeToString(result)
+			iteration.PlaintextHex = hex.EncodeToString(input)
 		}
 
-		aesKeyShuffle(key, result, prevResult)
+		aesKeyShuffle(key, input, prevResult)
 		mctResults = append(mctResults, iteration)
 	}
 
@@ -96,34 +92,16 @@
 			iteration.CiphertextHex = hex.EncodeToString(input)
 		}
 
-		var result, prevResult []byte
 		iteration.IVHex = hex.EncodeToString(iv)
 
-		var prevInput []byte
-		for j := 0; j < 1000; j++ {
-			prevResult = result
-			if j > 0 {
-				if encrypt {
-					iv = result
-				} else {
-					iv = prevInput
-				}
-			}
-
-			results, err := transact(1, key, input, iv)
-			if err != nil {
-				panic("block operation failed")
-			}
-			result = results[0]
-
-			prevInput = input
-			if j == 0 {
-				input = iv
-			} else {
-				input = prevResult
-			}
+		results, err := transact(2, key, input, iv, uint32le(1000))
+		if err != nil {
+			panic("block operation failed")
 		}
 
+		result := results[0]
+		prevResult := results[1]
+
 		if encrypt {
 			iteration.CiphertextHex = hex.EncodeToString(result)
 		} else {
@@ -178,17 +156,13 @@
 			iteration.CiphertextHex = hex.EncodeToString(input)
 		}
 
-		var result, prevResult, prevPrevResult []byte
-		for j := 0; j < 10000; j++ {
-			prevPrevResult = prevResult
-			prevResult = input
-			result, err := transact(1, key, input)
-			if err != nil {
-				panic("block operation failed")
-			}
-			input = result[0]
+		results, err := transact(3, key, input, uint32le(10000))
+		if err != nil {
+			panic("block operation failed")
 		}
-		result = input
+		result := results[0]
+		prevResult := results[1]
+		prevPrevResult := results[2]
 
 		if encrypt {
 			iteration.CiphertextHex = hex.EncodeToString(result)
@@ -198,6 +172,7 @@
 
 		keyShuffle3DES(key, result, prevResult, prevPrevResult)
 		mctResults = append(mctResults, iteration)
+		input = result
 	}
 
 	return mctResults
@@ -220,29 +195,15 @@
 		}
 		iteration.IVHex = hex.EncodeToString(iv)
 
-		var result, prevResult, prevPrevResult []byte
-		for j := 0; j < 10000; j++ {
-			prevPrevResult = prevResult
-			prevResult = result
-			results, err := transact(1, key, input, iv)
-			if err != nil {
-				panic("block operation failed")
-			}
-			result = results[0]
-
-			if encrypt {
-				if j == 0 {
-					input = iv
-				} else {
-					input = prevResult
-				}
-				iv = result
-			} else {
-				iv = input
-				input = result
-			}
+		results, err := transact(3, key, input, iv, uint32le(10000))
+		if err != nil {
+			panic("block operation failed")
 		}
 
+		result := results[0]
+		prevResult := results[1]
+		prevPrevResult := results[2]
+
 		if encrypt {
 			iteration.CiphertextHex = hex.EncodeToString(result)
 		} else {
@@ -254,6 +215,9 @@
 		if encrypt {
 			input = prevResult
 			iv = result
+		} else {
+			iv = prevResult
+			input = result
 		}
 
 		mctResults = append(mctResults, iteration)
@@ -265,8 +229,12 @@
 // blockCipher implements an ACVP algorithm by making requests to the subprocess
 // to encrypt and decrypt with a block cipher.
 type blockCipher struct {
-	algo                    string
-	blockSize               int
+	algo      string
+	blockSize int
+	// numResults is the number of values returned by the wrapper. The one-shot
+	// tests always take the first value as the result, but the mctFunc may use
+	// them all.
+	numResults              int
 	inputsAreBlockMultiples bool
 	hasIV                   bool
 	mctFunc                 func(transact func(n int, args ...[]byte) ([][]byte, error), encrypt bool, key, input, iv []byte) (result []blockCipherMCTResult)
@@ -423,9 +391,9 @@
 				var err error
 
 				if b.hasIV {
-					result, err = m.Transact(op, 1, key, input, iv)
+					result, err = m.Transact(op, b.numResults, key, input, iv, uint32le(1))
 				} else {
-					result, err = m.Transact(op, 1, key, input)
+					result, err = m.Transact(op, b.numResults, key, input, uint32le(1))
 				}
 				if err != nil {
 					panic("block operation failed: " + err.Error())
diff --git a/util/fipstools/acvp/acvptool/subprocess/subprocess.go b/util/fipstools/acvp/acvptool/subprocess/subprocess.go
index 6f450d4..84b54b1 100644
--- a/util/fipstools/acvp/acvptool/subprocess/subprocess.go
+++ b/util/fipstools/acvp/acvptool/subprocess/subprocess.go
@@ -76,11 +76,11 @@
 		"SHA2-256":      &hashPrimitive{"SHA2-256", 32},
 		"SHA2-384":      &hashPrimitive{"SHA2-384", 48},
 		"SHA2-512":      &hashPrimitive{"SHA2-512", 64},
-		"ACVP-AES-ECB":  &blockCipher{"AES", 16, true, false, iterateAES},
-		"ACVP-AES-CBC":  &blockCipher{"AES-CBC", 16, true, true, iterateAESCBC},
-		"ACVP-AES-CTR":  &blockCipher{"AES-CTR", 16, false, true, nil},
-		"ACVP-TDES-ECB": &blockCipher{"3DES-ECB", 8, true, false, iterate3DES},
-		"ACVP-TDES-CBC": &blockCipher{"3DES-CBC", 8, true, true, iterate3DESCBC},
+		"ACVP-AES-ECB":  &blockCipher{"AES", 16, 2, true, false, iterateAES},
+		"ACVP-AES-CBC":  &blockCipher{"AES-CBC", 16, 2, true, true, iterateAESCBC},
+		"ACVP-AES-CTR":  &blockCipher{"AES-CTR", 16, 1, false, true, nil},
+		"ACVP-TDES-ECB": &blockCipher{"3DES-ECB", 8, 3, true, false, iterate3DES},
+		"ACVP-TDES-CBC": &blockCipher{"3DES-CBC", 8, 3, true, true, iterate3DESCBC},
 		"ACVP-AES-GCM":  &aead{"AES-GCM", false},
 		"ACVP-AES-CCM":  &aead{"AES-CCM", true},
 		"ACVP-AES-KW":   &aead{"AES-KW", false},
diff --git a/util/fipstools/acvp/modulewrapper/modulewrapper.cc b/util/fipstools/acvp/modulewrapper/modulewrapper.cc
index 8043497..b1e1dd7 100644
--- a/util/fipstools/acvp/modulewrapper/modulewrapper.cc
+++ b/util/fipstools/acvp/modulewrapper/modulewrapper.cc
@@ -711,6 +711,27 @@
   return WriteReply(STDOUT_FILENO, Span<const uint8_t>(digest));
 }
 
+static uint32_t GetIterations(const Span<const uint8_t> iterations_bytes) {
+  uint32_t iterations;
+  if (iterations_bytes.size() != sizeof(iterations)) {
+    fprintf(stderr,
+            "Expected %u-byte input for number of iterations, but found %u "
+            "bytes.\n",
+            static_cast<unsigned>(sizeof(iterations)),
+            static_cast<unsigned>(iterations_bytes.size()));
+    abort();
+  }
+
+  memcpy(&iterations, iterations_bytes.data(), sizeof(iterations));
+  if (iterations == 0 || iterations == UINT32_MAX) {
+    fprintf(stderr, "Invalid number of iterations: %x.\n",
+            static_cast<unsigned>(iterations));
+    abort();
+  }
+
+  return iterations;
+}
+
 template <int (*SetKey)(const uint8_t *key, unsigned bits, AES_KEY *out),
           void (*Block)(const uint8_t *in, uint8_t *out, const AES_KEY *key)>
 static bool AES(const Span<const uint8_t> args[]) {
@@ -721,13 +742,22 @@
   if (args[1].size() % AES_BLOCK_SIZE != 0) {
     return false;
   }
+  std::vector<uint8_t> result(args[1].begin(), args[1].end());
+  const uint32_t iterations = GetIterations(args[2]);
 
-  std::vector<uint8_t> out;
-  out.resize(args[1].size());
-  for (size_t i = 0; i < args[1].size(); i += AES_BLOCK_SIZE) {
-    Block(args[1].data() + i, &out[i], &key);
+  std::vector<uint8_t> prev_result;
+  for (uint32_t j = 0; j < iterations; j++) {
+    if (j == iterations - 1) {
+      prev_result = result;
+    }
+
+    for (size_t i = 0; i < args[1].size(); i += AES_BLOCK_SIZE) {
+      Block(result.data() + i, result.data() + i, &key);
+    }
   }
-  return WriteReply(STDOUT_FILENO, Span<const uint8_t>(out));
+
+  return WriteReply(STDOUT_FILENO, Span<const uint8_t>(result),
+                    Span<const uint8_t>(prev_result));
 }
 
 template <int (*SetKey)(const uint8_t *key, unsigned bits, AES_KEY *out),
@@ -737,18 +767,46 @@
   if (SetKey(args[0].data(), args[0].size() * 8, &key) != 0) {
     return false;
   }
-  if (args[1].size() % AES_BLOCK_SIZE != 0 ||
+  if (args[1].size() % AES_BLOCK_SIZE != 0 || args[1].empty() ||
       args[2].size() != AES_BLOCK_SIZE) {
     return false;
   }
-  uint8_t iv[AES_BLOCK_SIZE];
-  memcpy(iv, args[2].data(), AES_BLOCK_SIZE);
+  std::vector<uint8_t> input(args[1].begin(), args[1].end());
+  std::vector<uint8_t> iv(args[2].begin(), args[2].end());
+  const uint32_t iterations = GetIterations(args[3]);
 
-  std::vector<uint8_t> out;
-  out.resize(args[1].size());
-  AES_cbc_encrypt(args[1].data(), out.data(), args[1].size(), &key, iv,
-                  Direction);
-  return WriteReply(STDOUT_FILENO, Span<const uint8_t>(out));
+  std::vector<uint8_t> result(input.size());
+  std::vector<uint8_t> prev_result, prev_input;
+
+  for (uint32_t j = 0; j < iterations; j++) {
+    prev_result = result;
+    if (j > 0) {
+      if (Direction == AES_ENCRYPT) {
+        iv = result;
+      } else {
+        iv = prev_input;
+      }
+    }
+
+    // AES_cbc_encrypt will mutate the given IV, but we need it later.
+    uint8_t iv_copy[AES_BLOCK_SIZE];
+    memcpy(iv_copy, iv.data(), sizeof(iv_copy));
+    AES_cbc_encrypt(input.data(), result.data(), input.size(), &key, iv_copy,
+                    Direction);
+
+    if (Direction == AES_DECRYPT) {
+      prev_input = input;
+    }
+
+    if (j == 0) {
+      input = iv;
+    } else {
+      input = prev_result;
+    }
+  }
+
+  return WriteReply(STDOUT_FILENO, Span<const uint8_t>(result),
+                    Span<const uint8_t>(prev_result));
 }
 
 static bool AES_CTR(const Span<const uint8_t> args[]) {
@@ -761,6 +819,10 @@
   }
   uint8_t iv[AES_BLOCK_SIZE];
   memcpy(iv, args[2].data(), AES_BLOCK_SIZE);
+  if (GetIterations(args[3]) != 1) {
+    fprintf(stderr, "Multiple iterations of AES-CTR is not supported.\n");
+    return false;
+  }
 
   std::vector<uint8_t> out;
   out.resize(args[1].size());
@@ -1018,42 +1080,116 @@
                     Span<const uint8_t>(out));
 }
 
-template<bool Encrypt, bool HasIV, const EVP_CIPHER* (*cipher_func)()>
+template <bool Encrypt>
 static bool TDES(const Span<const uint8_t> args[]) {
-  const EVP_CIPHER *cipher = cipher_func();
+  const EVP_CIPHER *cipher = EVP_des_ede3();
 
   if (args[0].size() != 24) {
     fprintf(stderr, "Bad key length %u for 3DES.\n",
             static_cast<unsigned>(args[0].size()));
     return false;
   }
+  bssl::ScopedEVP_CIPHER_CTX ctx;
+  if (!EVP_CipherInit_ex(ctx.get(), cipher, nullptr, args[0].data(), nullptr,
+                         Encrypt ? 1 : 0) ||
+      !EVP_CIPHER_CTX_set_padding(ctx.get(), 0)) {
+    return false;
+  }
+
   if (args[1].size() % 8) {
     fprintf(stderr, "Bad input length %u for 3DES.\n",
             static_cast<unsigned>(args[1].size()));
     return false;
   }
-  if (HasIV && args[2].size() != EVP_CIPHER_iv_length(cipher)) {
+  std::vector<uint8_t> result(args[1].begin(), args[1].end());
+
+  const uint32_t iterations = GetIterations(args[2]);
+  std::vector<uint8_t> prev_result, prev_prev_result;
+
+  for (uint32_t j = 0; j < iterations; j++) {
+    if (j == iterations - 1) {
+      prev_result = result;
+    } else if (iterations >= 2 && j == iterations - 2) {
+      prev_prev_result = result;
+    }
+
+    int out_len;
+    if (!EVP_CipherUpdate(ctx.get(), result.data(), &out_len, result.data(),
+                          result.size()) ||
+        out_len != static_cast<int>(result.size())) {
+      return false;
+    }
+  }
+
+  return WriteReply(STDOUT_FILENO, Span<const uint8_t>(result),
+                    Span<const uint8_t>(prev_result),
+                    Span<const uint8_t>(prev_prev_result));
+}
+
+template <bool Encrypt>
+static bool TDES_CBC(const Span<const uint8_t> args[]) {
+  const EVP_CIPHER *cipher = EVP_des_ede3_cbc();
+
+  if (args[0].size() != 24) {
+    fprintf(stderr, "Bad key length %u for 3DES.\n",
+            static_cast<unsigned>(args[0].size()));
+    return false;
+  }
+
+  if (args[1].size() % 8 || args[1].size() == 0) {
+    fprintf(stderr, "Bad input length %u for 3DES.\n",
+            static_cast<unsigned>(args[1].size()));
+    return false;
+  }
+  std::vector<uint8_t> input(args[1].begin(), args[1].end());
+
+  if (args[2].size() != EVP_CIPHER_iv_length(cipher)) {
     fprintf(stderr, "Bad IV length %u for 3DES.\n",
             static_cast<unsigned>(args[2].size()));
     return false;
   }
+  std::vector<uint8_t> iv(args[2].begin(), args[2].end());
+  const uint32_t iterations = GetIterations(args[3]);
 
-  std::vector<uint8_t> out;
-  out.resize(args[1].size());
-
+  std::vector<uint8_t> result(input.size());
+  std::vector<uint8_t> prev_result, prev_prev_result;
   bssl::ScopedEVP_CIPHER_CTX ctx;
-  int out_len, out_len2;
-  if (!EVP_CipherInit_ex(ctx.get(), cipher, nullptr, args[0].data(),
-                         HasIV ? args[2].data() : nullptr, Encrypt ? 1 : 0) ||
-      !EVP_CIPHER_CTX_set_padding(ctx.get(), 0) ||
-      !EVP_CipherUpdate(ctx.get(), out.data(), &out_len, args[1].data(),
-                        args[1].size()) ||
-      !EVP_CipherFinal_ex(ctx.get(), out.data() + out_len, &out_len2) ||
-      (out_len + out_len2) != static_cast<int>(out.size())) {
+  if (!EVP_CipherInit_ex(ctx.get(), cipher, nullptr, args[0].data(), iv.data(),
+                         Encrypt ? 1 : 0) ||
+      !EVP_CIPHER_CTX_set_padding(ctx.get(), 0)) {
     return false;
   }
 
-  return WriteReply(STDOUT_FILENO, Span<const uint8_t>(out));
+  for (uint32_t j = 0; j < iterations; j++) {
+    prev_prev_result = prev_result;
+    prev_result = result;
+
+    int out_len, out_len2;
+    if (!EVP_CipherInit_ex(ctx.get(), nullptr, nullptr, nullptr, iv.data(),
+                           -1) ||
+        !EVP_CipherUpdate(ctx.get(), result.data(), &out_len, input.data(),
+                          input.size()) ||
+        !EVP_CipherFinal_ex(ctx.get(), result.data() + out_len, &out_len2) ||
+        (out_len + out_len2) != static_cast<int>(result.size())) {
+      return false;
+    }
+
+    if (Encrypt) {
+      if (j == 0) {
+        input = iv;
+      } else {
+        input = prev_result;
+      }
+      iv = result;
+    } else {
+      iv = input;
+      input = result;
+    }
+  }
+
+  return WriteReply(STDOUT_FILENO, Span<const uint8_t>(result),
+                    Span<const uint8_t>(prev_result),
+                    Span<const uint8_t>(prev_prev_result));
 }
 
 template <const EVP_MD *HashFunc()>
@@ -1421,10 +1557,10 @@
     {"SHA2-256", 1, Hash<SHA256, SHA256_DIGEST_LENGTH>},
     {"SHA2-384", 1, Hash<SHA384, SHA256_DIGEST_LENGTH>},
     {"SHA2-512", 1, Hash<SHA512, SHA512_DIGEST_LENGTH>},
-    {"AES/encrypt", 2, AES<AES_set_encrypt_key, AES_encrypt>},
-    {"AES/decrypt", 2, AES<AES_set_decrypt_key, AES_decrypt>},
-    {"AES-CBC/encrypt", 3, AES_CBC<AES_set_encrypt_key, AES_ENCRYPT>},
-    {"AES-CBC/decrypt", 3, AES_CBC<AES_set_decrypt_key, AES_DECRYPT>},
+    {"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>},
+    {"AES-CBC/decrypt", 4, AES_CBC<AES_set_decrypt_key, AES_DECRYPT>},
     {"AES-CTR/encrypt", 3, AES_CTR},
     {"AES-CTR/decrypt", 3, AES_CTR},
     {"AES-GCM/seal", 5, AEADSeal<AESGCMSetup>},
@@ -1435,10 +1571,10 @@
     {"AES-KWP/open", 5, AESPaddedKeyWrapOpen},
     {"AES-CCM/seal", 5, AEADSeal<AESCCMSetup>},
     {"AES-CCM/open", 5, AEADOpen<AESCCMSetup>},
-    {"3DES-ECB/encrypt", 2, TDES<true, false, EVP_des_ede3>},
-    {"3DES-ECB/decrypt", 2, TDES<false, false, EVP_des_ede3>},
-    {"3DES-CBC/encrypt", 3, TDES<true, true, EVP_des_ede3_cbc>},
-    {"3DES-CBC/decrypt", 3, TDES<false, true, EVP_des_ede3_cbc>},
+    {"3DES-ECB/encrypt", 3, TDES<true>},
+    {"3DES-ECB/decrypt", 3, TDES<false>},
+    {"3DES-CBC/encrypt", 4, TDES_CBC<true>},
+    {"3DES-CBC/decrypt", 4, TDES_CBC<false>},
     {"HMAC-SHA-1", 2, HMAC<EVP_sha1>},
     {"HMAC-SHA2-224", 2, HMAC<EVP_sha224>},
     {"HMAC-SHA2-256", 2, HMAC<EVP_sha256>},