acvp: add support for AES-ECB and AES-CBC.

Change-Id: I685701304576a519e68a13d22bd557fdbf5a84fb
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/36884
Commit-Queue: David Benjamin <davidben@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
new file mode 100644
index 0000000..a5ed2cb
--- /dev/null
+++ b/util/fipstools/acvp/acvptool/subprocess/block.go
@@ -0,0 +1,252 @@
+package subprocess
+
+import (
+	"encoding/hex"
+	"encoding/json"
+	"fmt"
+)
+
+// 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
+	hasIV     bool
+	m         *Subprocess
+}
+
+type blockCipherVectorSet struct {
+	Groups []blockCipherTestGroup `json:"testGroups"`
+}
+
+type blockCipherTestGroup struct {
+	ID        uint64 `json:"tgId"`
+	Type      string `json:"testType"`
+	Direction string `json:"direction"`
+	KeyBits   int    `json:"keylen"`
+	Tests     []struct {
+		ID            uint64 `json:"tcId"`
+		PlaintextHex  string `json:"pt"`
+		CiphertextHex string `json:"ct"`
+		IVHex         string `json:"iv"`
+		KeyHex        string `json:"key"`
+	} `json:"tests"`
+}
+
+type blockCipherTestGroupResponse struct {
+	ID    uint64                    `json:"tgId"`
+	Tests []blockCipherTestResponse `json:"tests"`
+}
+
+type blockCipherTestResponse struct {
+	ID            uint64                 `json:"tcId"`
+	CiphertextHex string                 `json:"ct,omitempty"`
+	PlaintextHex  string                 `json:"pt,omitempty"`
+	MCTResults    []blockCipherMCTResult `json:"resultsArray,omitempty"`
+}
+
+type blockCipherMCTResult struct {
+	KeyHex        string `json:"key"`
+	PlaintextHex  string `json:"pt"`
+	CiphertextHex string `json:"ct"`
+	IVHex         string `json:"iv,omitempty"`
+}
+
+func (b *blockCipher) Process(vectorSet []byte) (interface{}, error) {
+	var parsed blockCipherVectorSet
+	if err := json.Unmarshal(vectorSet, &parsed); err != nil {
+		return nil, err
+	}
+
+	var ret []blockCipherTestGroupResponse
+	// See
+	// http://usnistgov.github.io/ACVP/artifacts/draft-celi-acvp-block-ciph-00.html#rfc.section.5.2
+	// for details about the tests.
+	for _, group := range parsed.Groups {
+		response := blockCipherTestGroupResponse{
+			ID: group.ID,
+		}
+
+		var encrypt bool
+		switch group.Direction {
+		case "encrypt":
+			encrypt = true
+		case "decrypt":
+			encrypt = false
+		default:
+			return nil, fmt.Errorf("test group %d has unknown direction %q", group.ID, group.Direction)
+		}
+
+		op := b.algo + "/encrypt"
+		if !encrypt {
+			op = b.algo + "/decrypt"
+		}
+
+		var mct bool
+		switch group.Type {
+		case "AFT":
+			mct = false
+		case "MCT":
+			mct = true
+		default:
+			return nil, fmt.Errorf("test group %d has unknown type %q", group.ID, group.Type)
+		}
+
+		if group.KeyBits%8 != 0 {
+			return nil, fmt.Errorf("test group %d contains non-byte-multiple key length %d", group.ID, group.KeyBits)
+		}
+		keyBytes := group.KeyBits / 8
+
+		for _, test := range group.Tests {
+			if len(test.KeyHex) != keyBytes*2 {
+				return nil, fmt.Errorf("test case %d/%d contains key %q of length %d, but expected %d-bit key", group.ID, test.ID, test.KeyHex, len(test.KeyHex), group.KeyBits)
+			}
+
+			key, err := hex.DecodeString(test.KeyHex)
+			if err != nil {
+				return nil, fmt.Errorf("failed to decode hex in test case %d/%d: %s", group.ID, test.ID, err)
+			}
+
+			var inputHex string
+			if encrypt {
+				inputHex = test.PlaintextHex
+			} else {
+				inputHex = test.CiphertextHex
+			}
+
+			input, err := hex.DecodeString(inputHex)
+			if err != nil {
+				return nil, fmt.Errorf("failed to decode hex in test case %d/%d: %s", group.ID, test.ID, err)
+			}
+
+			if len(input)%b.blockSize != 0 {
+				return nil, fmt.Errorf("test case %d/%d has input of length %d, but expected multiple of %d", group.ID, test.ID, len(input), b.blockSize)
+			}
+
+			var iv []byte
+			if b.hasIV {
+				if iv, err = hex.DecodeString(test.IVHex); err != nil {
+					return nil, fmt.Errorf("failed to decode hex in test case %d/%d: %s", group.ID, test.ID, err)
+				}
+				if len(iv) != b.blockSize {
+					return nil, fmt.Errorf("test case %d/%d has IV of length %d, but expected %d", group.ID, test.ID, len(iv), b.blockSize)
+				}
+			}
+
+			testResp := blockCipherTestResponse{ID: test.ID}
+			if !mct {
+				var result [][]byte
+				var err error
+
+				if b.hasIV {
+					result, err = b.m.transact(op, 1, key, input, iv)
+				} else {
+					result, err = b.m.transact(op, 1, key, input)
+				}
+				if err != nil {
+					panic("block operation failed: " + err.Error())
+				}
+
+				if encrypt {
+					testResp.CiphertextHex = hex.EncodeToString(result[0])
+				} else {
+					testResp.PlaintextHex = hex.EncodeToString(result[0])
+				}
+			} else {
+				for i := 0; i < 100; i++ {
+					var iteration blockCipherMCTResult
+					iteration.KeyHex = hex.EncodeToString(key)
+					if encrypt {
+						iteration.PlaintextHex = hex.EncodeToString(input)
+					} else {
+						iteration.CiphertextHex = hex.EncodeToString(input)
+					}
+
+					var result, prevResult []byte
+					if !b.hasIV {
+						for j := 0; j < 1000; j++ {
+							prevResult = input
+							result, err := b.m.transact(op, 1, key, input)
+							if err != nil {
+								panic("block operation failed")
+							}
+							input = result[0]
+						}
+						result = input
+					} else {
+						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 := b.m.transact(op, 1, key, input, iv)
+							if err != nil {
+								panic("block operation failed")
+							}
+							result = results[0]
+
+							prevInput = input
+							if j == 0 {
+								input = iv
+							} else {
+								input = prevResult
+							}
+						}
+					}
+
+					if encrypt {
+						iteration.CiphertextHex = hex.EncodeToString(result)
+					} else {
+						iteration.PlaintextHex = hex.EncodeToString(result)
+					}
+
+					switch keyBytes {
+					case 16:
+						for i := range key {
+							key[i] ^= result[i]
+						}
+					case 24:
+						for i := 0; i < 8; i++ {
+							key[i] ^= prevResult[i+8]
+						}
+						for i := range result {
+							key[i+8] ^= result[i]
+						}
+					case 32:
+						for i, b := range prevResult {
+							key[i] ^= b
+						}
+						for i, b := range result {
+							key[i+16] ^= b
+						}
+					default:
+						panic("unhandled key length")
+					}
+
+					if !b.hasIV {
+						input = result
+					} else {
+						iv = result
+						input = prevResult
+					}
+
+					testResp.MCTResults = append(testResp.MCTResults, iteration)
+				}
+			}
+
+			response.Tests = append(response.Tests, testResp)
+		}
+
+		ret = append(ret, response)
+	}
+
+	return ret, nil
+}
diff --git a/util/fipstools/acvp/acvptool/subprocess/subprocess.go b/util/fipstools/acvp/acvptool/subprocess/subprocess.go
index 7ab1c4b..404569f 100644
--- a/util/fipstools/acvp/acvptool/subprocess/subprocess.go
+++ b/util/fipstools/acvp/acvptool/subprocess/subprocess.go
@@ -43,11 +43,13 @@
 	}
 
 	m.primitives = map[string]primitive{
-		"SHA-1":    &hashPrimitive{"SHA-1", 20, m},
-		"SHA2-224": &hashPrimitive{"SHA2-224", 28, m},
-		"SHA2-256": &hashPrimitive{"SHA2-256", 32, m},
-		"SHA2-384": &hashPrimitive{"SHA2-384", 48, m},
-		"SHA2-512": &hashPrimitive{"SHA2-512", 64, m},
+		"SHA-1":        &hashPrimitive{"SHA-1", 20, m},
+		"SHA2-224":     &hashPrimitive{"SHA2-224", 28, m},
+		"SHA2-256":     &hashPrimitive{"SHA2-256", 32, m},
+		"SHA2-384":     &hashPrimitive{"SHA2-384", 48, m},
+		"SHA2-512":     &hashPrimitive{"SHA2-512", 64, m},
+		"ACVP-AES-ECB": &blockCipher{"AES", 16, false, m},
+		"ACVP-AES-CBC": &blockCipher{"AES-CBC", 16, true, m},
 	}
 
 	return m, nil
diff --git a/util/fipstools/acvp/modulewrapper/modulewrapper.cc b/util/fipstools/acvp/modulewrapper/modulewrapper.cc
index 79976dc..f877c75 100644
--- a/util/fipstools/acvp/modulewrapper/modulewrapper.cc
+++ b/util/fipstools/acvp/modulewrapper/modulewrapper.cc
@@ -20,6 +20,7 @@
 #include <unistd.h>
 #include <cstdarg>
 
+#include <openssl/aes.h>
 #include <openssl/sha.h>
 #include <openssl/span.h>
 
@@ -145,6 +146,18 @@
       "  \"messageLength\": [{"
       "    \"min\": 0, \"max\": 65528, \"increment\": 8"
       "  }]"
+      "},"
+      "{"
+      "  \"algorithm\": \"ACVP-AES-ECB\","
+      "  \"revision\": \"1.0\","
+      "  \"direction\": [\"encrypt\", \"decrypt\"],"
+      "  \"keyLen\": [128, 192, 256]"
+      "},"
+      "{"
+      "  \"algorithm\": \"ACVP-AES-CBC\","
+      "  \"revision\": \"1.0\","
+      "  \"direction\": [\"encrypt\", \"decrypt\"],"
+      "  \"keyLen\": [128, 192, 256]"
       "}"
       "]";
   return WriteReply(
@@ -161,6 +174,46 @@
   return WriteReply(STDOUT_FILENO, Span<const uint8_t>(digest));
 }
 
+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[]) {
+  AES_KEY key;
+  if (SetKey(args[0].data(), args[0].size() * 8, &key) != 0) {
+    return false;
+  }
+  if (args[1].size() % AES_BLOCK_SIZE != 0) {
+    return false;
+  }
+
+  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);
+  }
+  return WriteReply(STDOUT_FILENO, Span<const uint8_t>(out));
+}
+
+template <int (*SetKey)(const uint8_t *key, unsigned bits, AES_KEY *out),
+          int Direction>
+static bool AES_CBC(const Span<const uint8_t> args[]) {
+  AES_KEY key;
+  if (SetKey(args[0].data(), args[0].size() * 8, &key) != 0) {
+    return false;
+  }
+  if (args[1].size() % AES_BLOCK_SIZE != 0 ||
+      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> 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));
+}
+
 static constexpr struct {
   const char name[kMaxNameLength + 1];
   uint8_t expected_args;
@@ -172,6 +225,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>},
 };
 
 int main() {