acvp: move CMAC verification into the module wrapper.

CMAC is not inside our FIPS module and we have ACVP support for it just
for testing (other modules need to validate CMAC). This change makes the
CMAC verify test an explicit action for the module wrapper so that a
verification function exposed by a FIPS module can be tested.

Change-Id: I3943bde175f2c1d62881002b4e12d7bca68a9018
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/45264
Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/util/fipstools/acvp/ACVP.md b/util/fipstools/acvp/ACVP.md
index fd64984..2e82f15 100644
--- a/util/fipstools/acvp/ACVP.md
+++ b/util/fipstools/acvp/ACVP.md
@@ -64,6 +64,7 @@
 | AES/decrypt          | Key, input block, num iterations¹ | Result, Previous result |
 | AES/encrypt          | Key, input block, num iterations¹ | Result, Previous result |
 | CMAC-AES             | Number output bytes, key, message | MAC |
+| CMAC-AES/verify      | Key, message, claimed MAC | One-byte success flag |
 | ctrDRBG/AES-256      | Output length, entropy, personalisation, ad1, ad2, nonce | Output |
 | ECDH/&lt;CURVE&gt;   | X, Y, private key | X, Y, shared key |
 | ECDSA/keyGen         | Curve name | Private key, X, Y |
diff --git a/util/fipstools/acvp/acvptool/subprocess/keyedMac.go b/util/fipstools/acvp/acvptool/subprocess/keyedMac.go
index c0feac4..ebd2c0d 100644
--- a/util/fipstools/acvp/acvptool/subprocess/keyedMac.go
+++ b/util/fipstools/acvp/acvptool/subprocess/keyedMac.go
@@ -15,7 +15,6 @@
 package subprocess
 
 import (
-	"bytes"
 	"encoding/hex"
 	"encoding/json"
 	"fmt"
@@ -122,24 +121,37 @@
 				return nil, fmt.Errorf("failed to decode MsgHex in test case %d/%d: %v", group.ID, test.ID, err)
 			}
 
-			result, err := m.Transact(k.algo, 1, outputBytes, key, msg)
-			if err != nil {
-				return nil, fmt.Errorf("wrapper %s operation failed: %s", k.algo, err)
-			}
-
-			calculatedMAC := result[0]
-			if len(calculatedMAC) != int(group.MACBits/8) {
-				return nil, fmt.Errorf("%s operation returned incorrect length value", k.algo)
-			}
-
 			if generate {
+				result, err := m.Transact(k.algo, 1, outputBytes, key, msg)
+				if err != nil {
+					return nil, fmt.Errorf("wrapper %s operation failed: %s", k.algo, err)
+				}
+
+				calculatedMAC := result[0]
+				if len(calculatedMAC) != int(group.MACBits/8) {
+					return nil, fmt.Errorf("%s operation returned incorrect length value", k.algo)
+				}
+
 				respTest.MACHex = hex.EncodeToString(calculatedMAC)
 			} else {
 				expectedMAC, err := hex.DecodeString(test.MACHex)
 				if err != nil {
 					return nil, fmt.Errorf("failed to decode MACHex in test case %d/%d: %v", group.ID, test.ID, err)
 				}
-				ok := bytes.Equal(calculatedMAC, expectedMAC)
+				if 8*len(expectedMAC) != int(group.MACBits) {
+					return nil, fmt.Errorf("MACHex in test case %d/%d is %x, but should be %d bits", group.ID, test.ID, expectedMAC, group.MACBits)
+				}
+
+				result, err := m.Transact(k.algo+"/verify", 1, key, msg, expectedMAC)
+				if err != nil {
+					return nil, fmt.Errorf("wrapper %s operation failed: %s", k.algo, err)
+				}
+
+				if len(result[0]) != 1 || (result[0][0]&0xfe) != 0 {
+					return nil, fmt.Errorf("wrapper %s returned invalid success flag: %x", k.algo, result[0])
+				}
+
+				ok := result[0][0] == 1
 				respTest.Passed = &ok
 			}
 
diff --git a/util/fipstools/acvp/modulewrapper/modulewrapper.cc b/util/fipstools/acvp/modulewrapper/modulewrapper.cc
index e3d05c7..06eac8b 100644
--- a/util/fipstools/acvp/modulewrapper/modulewrapper.cc
+++ b/util/fipstools/acvp/modulewrapper/modulewrapper.cc
@@ -678,7 +678,7 @@
         "algorithm": "CMAC-AES",
         "revision": "1.0",
         "capabilities": [{
-          "direction": ["gen", "ver"],
+          "direction": ["gen"],
           "msgLen": [{
             "min": 0,
             "max": 65536,
@@ -1472,6 +1472,20 @@
   return WriteReply(STDOUT_FILENO, Span<const uint8_t>(mac, mac_len));
 }
 
+static bool CMAC_AESVerify(const Span<const uint8_t> args[]) {
+  // This function is just for testing since libcrypto doesn't do the
+  // verification itself. The regcap doesn't advertise "ver" support.
+  uint8_t mac[16];
+  if (!AES_CMAC(mac, args[0].data(), args[0].size(), args[1].data(),
+                args[1].size()) ||
+      args[2].size() > sizeof(mac)) {
+    return false;
+  }
+
+  const uint8_t ok = OPENSSL_memcmp(mac, args[2].data(), args[2].size());
+  return WriteReply(STDOUT_FILENO, Span<const uint8_t>(&ok, sizeof(ok)));
+}
+
 static std::map<unsigned, bssl::UniquePtr<RSA>>& CachedRSAKeys() {
   static std::map<unsigned, bssl::UniquePtr<RSA>> keys;
   return keys;
@@ -1773,6 +1787,7 @@
     {"ECDSA/sigGen", 4, ECDSASigGen},
     {"ECDSA/sigVer", 7, ECDSASigVer},
     {"CMAC-AES", 3, CMAC_AES},
+    {"CMAC-AES/verify", 3, CMAC_AESVerify},
     {"RSA/keyGen", 1, RSAKeyGen},
     {"RSA/sigGen/SHA2-224/pkcs1v1.5", 2, RSASigGen<EVP_sha224, false>},
     {"RSA/sigGen/SHA2-256/pkcs1v1.5", 2, RSASigGen<EVP_sha256, false>},