acvp: add AES-GCM support.

Change-Id: I7636736752ac371fc8d86fbc6bf81ca797ac5092
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/43127
Reviewed-by: Adam Langley <agl@google.com>
Reviewed-by: David Benjamin <davidben@google.com>
Commit-Queue: Adam Langley <agl@google.com>
diff --git a/util/fipstools/acvp/acvptool/subprocess/aead.go b/util/fipstools/acvp/acvptool/subprocess/aead.go
new file mode 100644
index 0000000..0c38b85
--- /dev/null
+++ b/util/fipstools/acvp/acvptool/subprocess/aead.go
@@ -0,0 +1,193 @@
+// Copyright (c) 2020, Google Inc.
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+package subprocess
+
+import (
+	"encoding/hex"
+	"encoding/json"
+	"fmt"
+)
+
+// aead implements an ACVP algorithm by making requests to the subprocess
+// to encrypt and decrypt with an AEAD.
+type aead struct {
+	algo string
+}
+
+type aeadVectorSet struct {
+	Groups []aeadTestGroup `json:"testGroups"`
+}
+
+type aeadTestGroup struct {
+	ID        uint64 `json:"tgId"`
+	Type      string `json:"testType"`
+	Direction string `json:"direction"`
+	KeyBits   int    `json:"keyLen"`
+	TagBits   int    `json:"tagLen"`
+	Tests     []struct {
+		ID            uint64 `json:"tcId"`
+		PlaintextHex  string `json:"pt"`
+		CiphertextHex string `json:"ct"`
+		IVHex         string `json:"iv"`
+		KeyHex        string `json:"key"`
+		AADHex        string `json:"aad"`
+		TagHex        string `json:"tag"`
+	} `json:"tests"`
+}
+
+type aeadTestGroupResponse struct {
+	ID    uint64             `json:"tgId"`
+	Tests []aeadTestResponse `json:"tests"`
+}
+
+type aeadTestResponse struct {
+	ID            uint64  `json:"tcId"`
+	CiphertextHex *string `json:"ct,omitempty"`
+	TagHex        string  `json:"tag,omitempty"`
+	PlaintextHex  *string `json:"pt,omitempty"`
+	Passed        *bool   `json:"testPassed,omitempty"`
+}
+
+func (a *aead) Process(vectorSet []byte, m Transactable) (interface{}, error) {
+	var parsed aeadVectorSet
+	if err := json.Unmarshal(vectorSet, &parsed); err != nil {
+		return nil, err
+	}
+
+	var ret []aeadTestGroupResponse
+	// See draft-celi-acvp-symmetric.html#table-6. (NIST no longer publish HTML
+	// versions of the ACVP documents. You can find fragments in
+	// https://github.com/usnistgov/ACVP.)
+	for _, group := range parsed.Groups {
+		response := aeadTestGroupResponse{
+			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 := a.algo + "/seal"
+		if !encrypt {
+			op = a.algo + "/open"
+		}
+
+		if group.KeyBits%8 != 0 || group.KeyBits < 0 {
+			return nil, fmt.Errorf("test group %d contains non-byte-multiple key length %d", group.ID, group.KeyBits)
+		}
+		keyBytes := group.KeyBits / 8
+
+		if group.TagBits%8 != 0 || group.TagBits < 0 {
+			return nil, fmt.Errorf("test group %d contains non-byte-multiple tag length %d", group.ID, group.TagBits)
+		}
+		tagBytes := group.TagBits / 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 key in test case %d/%d: %s", group.ID, test.ID, err)
+			}
+
+			nonce, err := hex.DecodeString(test.IVHex)
+			if err != nil {
+				return nil, fmt.Errorf("failed to decode nonce in test case %d/%d: %s", group.ID, test.ID, err)
+			}
+
+			aad, err := hex.DecodeString(test.AADHex)
+			if err != nil {
+				return nil, fmt.Errorf("failed to decode aad in test case %d/%d: %s", group.ID, test.ID, err)
+			}
+
+			var tag []byte
+			if !encrypt {
+				if tag, err = hex.DecodeString(test.TagHex); err != nil {
+					return nil, fmt.Errorf("failed to decode tag in test case %d/%d: %s", group.ID, test.ID, err)
+				}
+				if len(tag) != tagBytes {
+					return nil, fmt.Errorf("tag in test case %d/%d is %d bytes long, but should be %d", group.ID, test.ID, len(tag), tagBytes)
+				}
+			} else if len(test.TagHex) != 0 {
+				return nil, fmt.Errorf("test case %d/%d has unexpected tag input", group.ID, test.ID)
+			}
+
+			var inputHex, otherHex string
+			if encrypt {
+				inputHex, otherHex = test.PlaintextHex, test.CiphertextHex
+			} else {
+				inputHex, otherHex = test.CiphertextHex, test.PlaintextHex
+			}
+
+			if len(otherHex) != 0 {
+				return nil, fmt.Errorf("test case %d/%d has unexpected plain/ciphertext input", group.ID, test.ID)
+			}
+
+			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)
+			}
+
+			testResp := aeadTestResponse{ID: test.ID}
+
+			if encrypt {
+				result, err := m.Transact(op, 1, uint32le(uint32(tagBytes)), key, input, nonce, aad)
+				if err != nil {
+					return nil, err
+				}
+
+				if len(result[0]) < tagBytes {
+					return nil, fmt.Errorf("ciphertext from subprocess for test case %d/%d is shorter than the tag (%d vs %d)", group.ID, test.ID, len(result[0]), tagBytes)
+				}
+
+				ciphertext := result[0][:len(result[0])-tagBytes]
+				ciphertextHex := hex.EncodeToString(ciphertext)
+				tag := result[0][len(result[0])-tagBytes:]
+
+				testResp.CiphertextHex = &ciphertextHex
+				testResp.TagHex = hex.EncodeToString(tag)
+			} else {
+				result, err := m.Transact(op, 2, uint32le(uint32(tagBytes)), key, append(input, tag...), nonce, aad)
+				if err != nil {
+					return nil, err
+				}
+
+				if len(result[0]) != 1 || (result[0][0]&0xfe) != 0 {
+					return nil, fmt.Errorf("invalid AEAD status result from subprocess")
+				}
+				passed := result[0][0] == 1
+				testResp.Passed = &passed
+				if passed {
+					plaintextHex := hex.EncodeToString(result[1])
+					testResp.PlaintextHex = &plaintextHex
+				}
+			}
+
+			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 af757d5..a9fbdfc 100644
--- a/util/fipstools/acvp/acvptool/subprocess/subprocess.go
+++ b/util/fipstools/acvp/acvptool/subprocess/subprocess.go
@@ -79,6 +79,7 @@
 		"ACVP-AES-ECB":  &blockCipher{"AES", 16, true, false},
 		"ACVP-AES-CBC":  &blockCipher{"AES-CBC", 16, true, true},
 		"ACVP-AES-CTR":  &blockCipher{"AES-CTR", 16, false, true},
+		"ACVP-AES-GCM":  &aead{"AES-GCM"},
 		"HMAC-SHA-1":    &hmacPrimitive{"HMAC-SHA-1", 20},
 		"HMAC-SHA2-224": &hmacPrimitive{"HMAC-SHA2-224", 28},
 		"HMAC-SHA2-256": &hmacPrimitive{"HMAC-SHA2-256", 32},
diff --git a/util/fipstools/acvp/modulewrapper/modulewrapper.cc b/util/fipstools/acvp/modulewrapper/modulewrapper.cc
index 043d375..2e9598b 100644
--- a/util/fipstools/acvp/modulewrapper/modulewrapper.cc
+++ b/util/fipstools/acvp/modulewrapper/modulewrapper.cc
@@ -22,6 +22,7 @@
 #include <unistd.h>
 #include <cstdarg>
 
+#include <openssl/aead.h>
 #include <openssl/aes.h>
 #include <openssl/bn.h>
 #include <openssl/cmac.h>
@@ -189,6 +190,21 @@
         "keyLen": [128, 192, 256]
       },
       {
+        "algorithm": "ACVP-AES-GCM",
+        "revision": "1.0",
+        "direction": ["encrypt", "decrypt"],
+        "keyLen": [128, 192, 256],
+        "payloadLen": [{
+          "min": 0, "max": 256, "increment": 8
+        }],
+        "aadLen": [{
+          "min": 0, "max": 256, "increment": 8
+        }],
+        "tagLen": [128],
+        "ivLen": [96],
+        "ivGen": "external"
+      },
+      {
         "algorithm": "HMAC-SHA-1",
         "revision": "1.0",
         "keyLen": [{
@@ -411,6 +427,100 @@
   return WriteReply(STDOUT_FILENO, Span<const uint8_t>(out));
 }
 
+static bool AESGCMSetup(EVP_AEAD_CTX *ctx, Span<const uint8_t> tag_len_span,
+                        Span<const uint8_t> key) {
+  uint32_t tag_len_32;
+  if (tag_len_span.size() != sizeof(tag_len_32)) {
+    fprintf(stderr, "Tag size value is %u bytes, not an uint32_t\n",
+            static_cast<unsigned>(tag_len_span.size()));
+    return false;
+  }
+  memcpy(&tag_len_32, tag_len_span.data(), sizeof(tag_len_32));
+
+  const EVP_AEAD *aead;
+  switch (key.size()) {
+    case 16:
+      aead = EVP_aead_aes_128_gcm();
+      break;
+    case 24:
+      aead = EVP_aead_aes_192_gcm();
+      break;
+    case 32:
+      aead = EVP_aead_aes_256_gcm();
+      break;
+    default:
+      fprintf(stderr, "Bad AES-GCM key length %u\n",
+              static_cast<unsigned>(key.size()));
+      return false;
+  }
+
+  if (!EVP_AEAD_CTX_init(ctx, aead, key.data(), key.size(), tag_len_32,
+                         nullptr)) {
+    fprintf(stderr, "Failed to setup AES-GCM with tag length %u\n",
+            static_cast<unsigned>(tag_len_32));
+    return false;
+  }
+
+  return true;
+}
+
+static bool AESGCMSeal(const Span<const uint8_t> args[]) {
+  Span<const uint8_t> tag_len_span = args[0];
+  Span<const uint8_t> key = args[1];
+  Span<const uint8_t> plaintext = args[2];
+  Span<const uint8_t> nonce = args[3];
+  Span<const uint8_t> ad = args[4];
+
+  bssl::ScopedEVP_AEAD_CTX ctx;
+  if (!AESGCMSetup(ctx.get(), tag_len_span, key)) {
+    return false;
+  }
+
+  if (EVP_AEAD_MAX_OVERHEAD + plaintext.size() < EVP_AEAD_MAX_OVERHEAD) {
+    return false;
+  }
+  std::vector<uint8_t> out(EVP_AEAD_MAX_OVERHEAD + plaintext.size());
+
+  size_t out_len;
+  if (!EVP_AEAD_CTX_seal(ctx.get(), out.data(), &out_len, out.size(),
+                         nonce.data(), nonce.size(), plaintext.data(),
+                         plaintext.size(), ad.data(), ad.size())) {
+    return false;
+  }
+
+  out.resize(out_len);
+  return WriteReply(STDOUT_FILENO, Span<const uint8_t>(out));
+}
+
+static bool AESGCMOpen(const Span<const uint8_t> args[]) {
+  Span<const uint8_t> tag_len_span = args[0];
+  Span<const uint8_t> key = args[1];
+  Span<const uint8_t> ciphertext = args[2];
+  Span<const uint8_t> nonce = args[3];
+  Span<const uint8_t> ad = args[4];
+
+  bssl::ScopedEVP_AEAD_CTX ctx;
+  if (!AESGCMSetup(ctx.get(), tag_len_span, key)) {
+    return false;
+  }
+
+  std::vector<uint8_t> out(ciphertext.size());
+  size_t out_len;
+  uint8_t success[1] = {0};
+
+  if (!EVP_AEAD_CTX_open(ctx.get(), out.data(), &out_len, out.size(),
+                         nonce.data(), nonce.size(), ciphertext.data(),
+                         ciphertext.size(), ad.data(), ad.size())) {
+    return WriteReply(STDOUT_FILENO, Span<const uint8_t>(success),
+                      Span<const uint8_t>());
+  }
+
+  out.resize(out_len);
+  success[0] = 1;
+  return WriteReply(STDOUT_FILENO, Span<const uint8_t>(success),
+                    Span<const uint8_t>(out));
+}
+
 template <const EVP_MD *HashFunc()>
 static bool HMAC(const Span<const uint8_t> args[]) {
   const EVP_MD *const md = HashFunc();
@@ -658,6 +768,8 @@
     {"AES-CBC/decrypt", 3, AES_CBC<AES_set_decrypt_key, AES_DECRYPT>},
     {"AES-CTR/encrypt", 3, AES_CTR},
     {"AES-CTR/decrypt", 3, AES_CTR},
+    {"AES-GCM/seal", 5, AESGCMSeal},
+    {"AES-GCM/open", 5, AESGCMOpen},
     {"HMAC-SHA-1", 2, HMAC<EVP_sha1>},
     {"HMAC-SHA2-224", 2, HMAC<EVP_sha224>},
     {"HMAC-SHA2-256", 2, HMAC<EVP_sha256>},