acvp: add 3DES-CBC support

Change-Id: I2e6cc7367b5ca6631329be298fbed7424221a06b
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/43406
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 35c2133..3b468c8 100644
--- a/util/fipstools/acvp/acvptool/subprocess/block.go
+++ b/util/fipstools/acvp/acvptool/subprocess/block.go
@@ -203,6 +203,65 @@
 	return mctResults
 }
 
+// iterate3DESCBC implements "TDES Monte Carlo Test - CBC mode" from the ACVP
+// specification.
+func iterate3DESCBC(transact func(n int, args ...[]byte) ([][]byte, error), encrypt bool, key, input, iv []byte) (mctResults []blockCipherMCTResult) {
+	for i := 0; i < 400; i++ {
+		var iteration blockCipherMCTResult
+		keyHex := hex.EncodeToString(key)
+		iteration.Key1Hex = keyHex[:16]
+		iteration.Key2Hex = keyHex[16:32]
+		iteration.Key3Hex = keyHex[32:]
+
+		if encrypt {
+			iteration.PlaintextHex = hex.EncodeToString(input)
+		} else {
+			iteration.CiphertextHex = hex.EncodeToString(input)
+		}
+		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
+			}
+		}
+
+		if encrypt {
+			iteration.CiphertextHex = hex.EncodeToString(result)
+		} else {
+			iteration.PlaintextHex = hex.EncodeToString(result)
+		}
+
+		keyShuffle3DES(key, result, prevResult, prevPrevResult)
+
+		if encrypt {
+			input = prevResult
+			iv = result
+		}
+
+		mctResults = append(mctResults, iteration)
+	}
+
+	return mctResults
+}
+
 // blockCipher implements an ACVP algorithm by making requests to the subprocess
 // to encrypt and decrypt with a block cipher.
 type blockCipher struct {
diff --git a/util/fipstools/acvp/acvptool/subprocess/subprocess.go b/util/fipstools/acvp/acvptool/subprocess/subprocess.go
index 3c04981..76442fa 100644
--- a/util/fipstools/acvp/acvptool/subprocess/subprocess.go
+++ b/util/fipstools/acvp/acvptool/subprocess/subprocess.go
@@ -80,6 +80,7 @@
 		"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-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 d97ea91..c782f67 100644
--- a/util/fipstools/acvp/modulewrapper/modulewrapper.cc
+++ b/util/fipstools/acvp/modulewrapper/modulewrapper.cc
@@ -259,6 +259,13 @@
         "keyingOption": [1]
       },
       {
+        "algorithm": "ACVP-TDES-CBC",
+        "revision": "1.0",
+        "direction": ["encrypt", "decrypt"],
+        "keyLen": [192],
+        "keyingOption": [1]
+      },
+      {
         "algorithm": "HMAC-SHA-1",
         "revision": "1.0",
         "keyLen": [{
@@ -728,9 +735,9 @@
                     Span<const uint8_t>(out));
 }
 
-template<bool Encrypt>
+template<bool Encrypt, bool HasIV, const EVP_CIPHER* (*cipher_func)()>
 static bool TDES(const Span<const uint8_t> args[]) {
-  const EVP_CIPHER *cipher = EVP_des_ede3();
+  const EVP_CIPHER *cipher = cipher_func();
 
   if (args[0].size() != 24) {
     fprintf(stderr, "Bad key length %u for 3DES.\n",
@@ -742,14 +749,19 @@
             static_cast<unsigned>(args[1].size()));
     return false;
   }
+  if (HasIV && 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> out;
   out.resize(args[1].size());
 
   bssl::ScopedEVP_CIPHER_CTX ctx;
   int out_len, out_len2;
-  if (!EVP_CipherInit_ex(ctx.get(), cipher, nullptr, args[0].data(), nullptr,
-                         Encrypt ? 1 : 0) ||
+  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()) ||
@@ -1016,8 +1028,10 @@
     {"AES-KWP/open", 5, AESPaddedKeyWrapOpen},
     {"AES-CCM/seal", 5, AEADSeal<AESCCMSetup>},
     {"AES-CCM/open", 5, AEADOpen<AESCCMSetup>},
-    {"3DES-ECB/encrypt", 2, TDES<true>},
-    {"3DES-ECB/decrypt", 2, TDES<false>},
+    {"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>},
     {"HMAC-SHA-1", 2, HMAC<EVP_sha1>},
     {"HMAC-SHA2-224", 2, HMAC<EVP_sha224>},
     {"HMAC-SHA2-256", 2, HMAC<EVP_sha256>},