acvp: add AES-CCM support.

Change-Id: Ia8cbfd0b8f0f3932aea20e801e031d8df318f386
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/43286
Commit-Queue: Adam Langley <agl@google.com>
Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/util/fipstools/acvp/acvptool/subprocess/aead.go b/util/fipstools/acvp/acvptool/subprocess/aead.go
index 0c38b85..e6585d1 100644
--- a/util/fipstools/acvp/acvptool/subprocess/aead.go
+++ b/util/fipstools/acvp/acvptool/subprocess/aead.go
@@ -23,7 +23,8 @@
 // aead implements an ACVP algorithm by making requests to the subprocess
 // to encrypt and decrypt with an AEAD.
 type aead struct {
-	algo string
+	algo                    string
+	tagMergedWithCiphertext bool
 }
 
 type aeadVectorSet struct {
@@ -120,18 +121,6 @@
 				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
@@ -148,6 +137,27 @@
 				return nil, fmt.Errorf("failed to decode hex in test case %d/%d: %s", group.ID, test.ID, err)
 			}
 
+			var tag []byte
+			if a.tagMergedWithCiphertext {
+				if len(test.TagHex) != 0 {
+					return nil, fmt.Errorf("test case %d/%d has unexpected tag input (should be merged into ciphertext)", group.ID, test.ID)
+				}
+				if !encrypt && len(input) < tagBytes {
+					return nil, fmt.Errorf("test case %d/%d has ciphertext shorter than the tag, but the tag should be included in it", group.ID, test.ID)
+				}
+			} else {
+				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)
+				}
+			}
+
 			testResp := aeadTestResponse{ID: test.ID}
 
 			if encrypt {
@@ -160,12 +170,16 @@
 					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)
+				if a.tagMergedWithCiphertext {
+					ciphertextHex := hex.EncodeToString(result[0])
+					testResp.CiphertextHex = &ciphertextHex
+				} else {
+					ciphertext := result[0][:len(result[0])-tagBytes]
+					ciphertextHex := hex.EncodeToString(ciphertext)
+					testResp.CiphertextHex = &ciphertextHex
+					tag := result[0][len(result[0])-tagBytes:]
+					testResp.TagHex = hex.EncodeToString(tag)
+				}
 			} else {
 				result, err := m.Transact(op, 2, uint32le(uint32(tagBytes)), key, append(input, tag...), nonce, aad)
 				if err != nil {
diff --git a/util/fipstools/acvp/acvptool/subprocess/subprocess.go b/util/fipstools/acvp/acvptool/subprocess/subprocess.go
index 9c554c2..c713ea8 100644
--- a/util/fipstools/acvp/acvptool/subprocess/subprocess.go
+++ b/util/fipstools/acvp/acvptool/subprocess/subprocess.go
@@ -79,9 +79,10 @@
 		"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"},
-		"ACVP-AES-KW":   &aead{"AES-KW"},
-		"ACVP-AES-KWP":  &aead{"AES-KWP"},
+		"ACVP-AES-GCM":  &aead{"AES-GCM", false},
+		"ACVP-AES-CCM":  &aead{"AES-CCM", true},
+		"ACVP-AES-KW":   &aead{"AES-KW", false},
+		"ACVP-AES-KWP":  &aead{"AES-KWP", false},
 		"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 961eaf7..ff5ef6c 100644
--- a/util/fipstools/acvp/modulewrapper/modulewrapper.cc
+++ b/util/fipstools/acvp/modulewrapper/modulewrapper.cc
@@ -236,6 +236,21 @@
         "payloadLen": [{"min": 8, "max": 1024, "increment": 8}]
       },
       {
+        "algorithm": "ACVP-AES-CCM",
+        "revision": "1.0",
+        "direction": [
+            "encrypt",
+            "decrypt"
+        ],
+        "keyLen": [
+            128
+        ],
+        "payloadLen": [{"min": 0, "max": 256, "increment": 8}],
+        "ivLen": [104],
+        "tagLen": [32],
+        "aadLen": [{"min": 0, "max": 1024, "increment": 8}]
+      },
+      {
         "algorithm": "HMAC-SHA-1",
         "revision": "1.0",
         "keyLen": [{
@@ -495,7 +510,41 @@
   return true;
 }
 
-static bool AESGCMSeal(const Span<const uint8_t> args[]) {
+static bool AESCCMSetup(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));
+  if (tag_len_32 != 4) {
+    fprintf(stderr, "AES-CCM only supports 4-byte tags, but %u was requested\n",
+            static_cast<unsigned>(tag_len_32));
+    return false;
+  }
+
+  if (key.size() != 16) {
+    fprintf(stderr,
+            "AES-CCM only supports 128-bit keys, but %u bits were given\n",
+            static_cast<unsigned>(key.size() * 8));
+    return false;
+  }
+
+  if (!EVP_AEAD_CTX_init(ctx, EVP_aead_aes_128_ccm_bluetooth(), key.data(),
+                         key.size(), tag_len_32, nullptr)) {
+    fprintf(stderr, "Failed to setup AES-CCM with tag length %u\n",
+            static_cast<unsigned>(tag_len_32));
+    return false;
+  }
+
+  return true;
+}
+
+template <bool (*SetupFunc)(EVP_AEAD_CTX *ctx, Span<const uint8_t> tag_len_span,
+                            Span<const uint8_t> key)>
+static bool AEADSeal(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];
@@ -503,7 +552,7 @@
   Span<const uint8_t> ad = args[4];
 
   bssl::ScopedEVP_AEAD_CTX ctx;
-  if (!AESGCMSetup(ctx.get(), tag_len_span, key)) {
+  if (!SetupFunc(ctx.get(), tag_len_span, key)) {
     return false;
   }
 
@@ -523,7 +572,9 @@
   return WriteReply(STDOUT_FILENO, Span<const uint8_t>(out));
 }
 
-static bool AESGCMOpen(const Span<const uint8_t> args[]) {
+template <bool (*SetupFunc)(EVP_AEAD_CTX *ctx, Span<const uint8_t> tag_len_span,
+                            Span<const uint8_t> key)>
+static bool AEADOpen(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];
@@ -531,7 +582,7 @@
   Span<const uint8_t> ad = args[4];
 
   bssl::ScopedEVP_AEAD_CTX ctx;
-  if (!AESGCMSetup(ctx.get(), tag_len_span, key)) {
+  if (!SetupFunc(ctx.get(), tag_len_span, key)) {
     return false;
   }
 
@@ -916,12 +967,14 @@
     {"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},
+    {"AES-GCM/seal", 5, AEADSeal<AESGCMSetup>},
+    {"AES-GCM/open", 5, AEADOpen<AESGCMSetup>},
     {"AES-KW/seal", 5, AESKeyWrapSeal},
     {"AES-KW/open", 5, AESKeyWrapOpen},
     {"AES-KWP/seal", 5, AESPaddedKeyWrapSeal},
     {"AES-KWP/open", 5, AESPaddedKeyWrapOpen},
+    {"AES-CCM/seal", 5, AEADSeal<AESCCMSetup>},
+    {"AES-CCM/open", 5, AEADOpen<AESCCMSetup>},
     {"HMAC-SHA-1", 2, HMAC<EVP_sha1>},
     {"HMAC-SHA2-224", 2, HMAC<EVP_sha224>},
     {"HMAC-SHA2-256", 2, HMAC<EVP_sha256>},