acvptool: ML-DSA external mu, internal sig. interface

This commit extends the acvptool's support for ML-DSA ACVP testing to
support externalMu=true, and signatureInterface=external. In practice
this only requires wiring through more arguments to the sigGen and
sigVer command handlers.

See the ACVP specification[0] for more information on the respective
capabilities and test case schema fields.

[0]: https://pages.nist.gov/ACVP/draft-celi-acvp-ml-dsa.html

Change-Id: I1af5e610289691c9e018965996d8aedb77ea9c52
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/83807
Commit-Queue: Adam Langley <agl@google.com>
Reviewed-by: Lily Chen <chlily@google.com>
diff --git a/crypto/fipsmodule/bcm_interface.h b/crypto/fipsmodule/bcm_interface.h
index 67c8f33..c5fd02a 100644
--- a/crypto/fipsmodule/bcm_interface.h
+++ b/crypto/fipsmodule/bcm_interface.h
@@ -330,6 +330,12 @@
     const uint8_t *context, size_t context_len,
     const uint8_t randomizer[BCM_MLDSA_SIGNATURE_RANDOMIZER_BYTES]);
 
+OPENSSL_EXPORT bcm_status BCM_mldsa65_sign_mu_internal(
+    uint8_t out_encoded_signature[MLDSA65_SIGNATURE_BYTES],
+    const MLDSA65_private_key *private_key,
+    const uint8_t msg_rep[MLDSA_MU_BYTES],
+    const uint8_t randomizer[BCM_MLDSA_SIGNATURE_RANDOMIZER_BYTES]);
+
 // BCM_mldsa5_verify_internal verifies that |encoded_signature| is a valid
 // signature of |msg| by |public_key|. The |context_prefix| and |context| are
 // prefixed to the message before verification, in that order.
@@ -442,6 +448,12 @@
     const uint8_t *context, size_t context_len,
     const uint8_t randomizer[BCM_MLDSA_SIGNATURE_RANDOMIZER_BYTES]);
 
+OPENSSL_EXPORT bcm_status BCM_mldsa87_sign_mu_internal(
+    uint8_t out_encoded_signature[MLDSA87_SIGNATURE_BYTES],
+    const MLDSA87_private_key *private_key,
+    const uint8_t msg_rep[MLDSA_MU_BYTES],
+    const uint8_t randomizer[BCM_MLDSA_SIGNATURE_RANDOMIZER_BYTES]);
+
 // BCM_mldsa87_verify_internal verifies that |encoded_signature| is a valid
 // signature of |msg| by |public_key|. The |context_prefix| and |context| are
 // prefixed to the message before verification, in that order.
@@ -553,6 +565,12 @@
     const uint8_t *context, size_t context_len,
     const uint8_t randomizer[BCM_MLDSA_SIGNATURE_RANDOMIZER_BYTES]);
 
+OPENSSL_EXPORT bcm_status BCM_mldsa44_sign_mu_internal(
+    uint8_t out_encoded_signature[MLDSA44_SIGNATURE_BYTES],
+    const MLDSA44_private_key *private_key,
+    const uint8_t msg_rep[MLDSA_MU_BYTES],
+    const uint8_t randomizer[BCM_MLDSA_SIGNATURE_RANDOMIZER_BYTES]);
+
 // BCM_mldsa44_verify_internal verifies that |encoded_signature| is a valid
 // signature of |msg| by |public_key|. The |context_prefix| and |context| are
 // prefixed to the message before verification, in that order.
diff --git a/crypto/fipsmodule/mldsa/mldsa.cc.inc b/crypto/fipsmodule/mldsa/mldsa.cc.inc
index 0871618..a2b7f31 100644
--- a/crypto/fipsmodule/mldsa/mldsa.cc.inc
+++ b/crypto/fipsmodule/mldsa/mldsa.cc.inc
@@ -1662,8 +1662,7 @@
 template <int K, int L>
 int mldsa_generate_key_external_entropy_no_self_test(
     uint8_t out_encoded_public_key[public_key_bytes<K>()],
-    private_key<K, L> *priv,
-    const uint8_t entropy[MLDSA_SEED_BYTES]) {
+    private_key<K, L> *priv, const uint8_t entropy[MLDSA_SEED_BYTES]) {
   // Step 1-2.
   uint8_t augmented_entropy[MLDSA_SEED_BYTES + 2];
   OPENSSL_memcpy(augmented_entropy, entropy, MLDSA_SEED_BYTES);
@@ -1982,10 +1981,9 @@
 // FIPS 204, Algorithm 8 (`ML-DSA.Verify_internal`), using a pre-computed mu.
 // Returns 1 on success and 0 on failure.
 template <int K, int L>
-int mldsa_verify_mu(
-    const public_key<K> *pub,
-    const uint8_t encoded_signature[signature_bytes<K>()],
-    const uint8_t mu[kMuBytes]) {
+int mldsa_verify_mu(const public_key<K> *pub,
+                    const uint8_t encoded_signature[signature_bytes<K>()],
+                    const uint8_t mu[kMuBytes]) {
   fips::ensure_verify_self_test();
   return mldsa_verify_mu_no_self_test<K, L>(pub, encoded_signature, mu);
 }
@@ -2386,7 +2384,7 @@
 }
 
 const MLDSA65_public_key *BCM_mldsa65_public_of_private(
-    const MLDSA65_private_key *private_key){
+    const MLDSA65_private_key *private_key) {
   return reinterpret_cast<const MLDSA65_public_key *>(
       &mldsa::private_key_from_external_65(private_key)->pub);
 }
@@ -2403,6 +2401,16 @@
       randomizer));
 }
 
+bcm_status BCM_mldsa65_sign_mu_internal(
+    uint8_t out_encoded_signature[MLDSA65_SIGNATURE_BYTES],
+    const MLDSA65_private_key *private_key,
+    const uint8_t msg_rep[MLDSA_MU_BYTES],
+    const uint8_t randomizer[BCM_MLDSA_SIGNATURE_RANDOMIZER_BYTES]) {
+  return bcm_as_approved_status(mldsa_sign_mu(
+      out_encoded_signature, mldsa::private_key_from_external_65(private_key),
+      msg_rep, randomizer));
+}
+
 // ML-DSA signature in randomized mode, filling the random bytes with
 // |BCM_rand_bytes|.
 bcm_status BCM_mldsa65_sign(
@@ -2614,7 +2622,7 @@
 }
 
 const MLDSA87_public_key *BCM_mldsa87_public_of_private(
-    const MLDSA87_private_key *private_key){
+    const MLDSA87_private_key *private_key) {
   return reinterpret_cast<const MLDSA87_public_key *>(
       &mldsa::private_key_from_external_87(private_key)->pub);
 }
@@ -2631,6 +2639,16 @@
       randomizer));
 }
 
+bcm_status BCM_mldsa87_sign_mu_internal(
+    uint8_t out_encoded_signature[MLDSA87_SIGNATURE_BYTES],
+    const MLDSA87_private_key *private_key,
+    const uint8_t msg_rep[MLDSA_MU_BYTES],
+    const uint8_t randomizer[BCM_MLDSA_SIGNATURE_RANDOMIZER_BYTES]) {
+  return bcm_as_approved_status(mldsa_sign_mu(
+      out_encoded_signature, mldsa::private_key_from_external_87(private_key),
+      msg_rep, randomizer));
+}
+
 // ML-DSA signature in randomized mode, filling the random bytes with
 // |BCM_rand_bytes|.
 bcm_status BCM_mldsa87_sign(
@@ -2841,7 +2859,7 @@
 }
 
 const MLDSA44_public_key *BCM_mldsa44_public_of_private(
-    const MLDSA44_private_key *private_key){
+    const MLDSA44_private_key *private_key) {
   return reinterpret_cast<const MLDSA44_public_key *>(
       &mldsa::private_key_from_external_44(private_key)->pub);
 }
@@ -2858,6 +2876,16 @@
       randomizer));
 }
 
+bcm_status BCM_mldsa44_sign_mu_internal(
+    uint8_t out_encoded_signature[MLDSA44_SIGNATURE_BYTES],
+    const MLDSA44_private_key *private_key,
+    const uint8_t msg_rep[MLDSA_MU_BYTES],
+    const uint8_t randomizer[BCM_MLDSA_SIGNATURE_RANDOMIZER_BYTES]) {
+  return bcm_as_approved_status(mldsa_sign_mu(
+      out_encoded_signature, mldsa::private_key_from_external_44(private_key),
+      msg_rep, randomizer));
+}
+
 // ML-DSA signature in randomized mode, filling the random bytes with
 // |BCM_rand_bytes|.
 bcm_status BCM_mldsa44_sign(
diff --git a/util/fipstools/acvp/ACVP.md b/util/fipstools/acvp/ACVP.md
index c93f79c..49be6a7 100644
--- a/util/fipstools/acvp/ACVP.md
+++ b/util/fipstools/acvp/ACVP.md
@@ -136,8 +136,8 @@
 | TLSKDF/1.2/&lt;HASH&gt; | Number output bytes, secret, label, seed1, seed2 | Output |
 | PBKDF                | HMAC name, key length (bits), salt, password, iteration count | Derived key |
 | ML-DSA-XX/keyGen     | Seed | Public key, private key |
-| ML-DSA-XX/sigGen     | Private key, message, randomizer | Signature |
-| ML-DSA-XX/sigVer     | Public key, message, signature | Single-byte validity flag |
+| ML-DSA-XX/sigGen     | Private key, message, randomizer, context, mu | Signature |
+| ML-DSA-XX/sigVer     | Public key, message, signature, context, mu | Single-byte validity flag |
 | ML-KEM-XX/keyGen     | Seed | Public key, private key |
 | ML-KEM-XX/encap      | Public key, entropy | Ciphertext, shared secret |
 | ML-KEM-XX/decap      | Private key, ciphertext | Shared secret |
diff --git a/util/fipstools/acvp/acvptool/subprocess/mldsa.go b/util/fipstools/acvp/acvptool/subprocess/mldsa.go
index b7cde7d..73ebe3e 100644
--- a/util/fipstools/acvp/acvptool/subprocess/mldsa.go
+++ b/util/fipstools/acvp/acvptool/subprocess/mldsa.go
@@ -56,11 +56,12 @@
 }
 
 type mldsaSigGenTestGroup struct {
-	ID            uint64            `json:"tgId"`
-	TestType      string            `json:"testType"`
-	ParameterSet  string            `json:"parameterSet"`
-	Deterministic bool              `json:"deterministic"`
-	Tests         []mldsaSigGenTest `json:"tests"`
+	ID                 uint64            `json:"tgId"`
+	TestType           string            `json:"testType"`
+	ParameterSet       string            `json:"parameterSet"`
+	Deterministic      bool              `json:"deterministic"`
+	SignatureInterface string            `json:"signatureInterface"`
+	Tests              []mldsaSigGenTest `json:"tests"`
 }
 
 type mldsaSigGenTest struct {
@@ -68,6 +69,8 @@
 	Message    string `json:"message"`
 	PrivateKey string `json:"sk"`
 	Randomizer string `json:"rnd"`
+	Context    string `json:"context"`
+	Mu         string `json:"mu"`
 }
 
 type mldsaSigGenTestGroupResponse struct {
@@ -89,10 +92,11 @@
 }
 
 type mldsaSigVerTestGroup struct {
-	ID           uint64            `json:"tgId"`
-	TestType     string            `json:"testType"`
-	ParameterSet string            `json:"parameterSet"`
-	Tests        []mldsaSigVerTest `json:"tests"`
+	ID                 uint64            `json:"tgId"`
+	TestType           string            `json:"testType"`
+	ParameterSet       string            `json:"parameterSet"`
+	SignatureInterface string            `json:"signatureInterface"`
+	Tests              []mldsaSigVerTest `json:"tests"`
 }
 
 type mldsaSigVerTest struct {
@@ -100,6 +104,8 @@
 	PublicKey string `json:"pk"`
 	Message   string `json:"message"`
 	Signature string `json:"signature"`
+	Context   string `json:"context"`
+	Mu        string `json:"mu"`
 }
 
 type mldsaSigVerTestGroupResponse struct {
@@ -218,7 +224,32 @@
 				}
 			}
 
-			result, err := t.Transact(cmdName, 1, sk, msg, randomizer)
+			var context []byte
+			context, err = hex.DecodeString(test.Context)
+			if err != nil {
+				return nil, fmt.Errorf("failed to decode context in test case %d/%d: %s",
+					group.ID, test.ID, err)
+			}
+			if group.SignatureInterface != "external" && len(context) > 0 {
+				return nil, fmt.Errorf("unexpected context for internal interface test case %d/%d: %s",
+					group.ID, test.ID, err)
+			}
+
+			var mu []byte
+			mu, err = hex.DecodeString(test.Mu)
+			if err != nil {
+				return nil, fmt.Errorf("failed to decode mu in test case %d/%d: %s",
+					group.ID, test.ID, err)
+			}
+			if group.SignatureInterface != "internal" && len(mu) > 0 {
+				return nil, fmt.Errorf("unexpected mu for external interface test case %d/%d: %s",
+					group.ID, test.ID, err)
+			} else if len(mu) > 0 && len(msg) > 0 {
+				return nil, fmt.Errorf("unexpected message for internal interface test case with mu %d/%d",
+					group.ID, test.ID)
+			}
+
+			result, err := t.Transact(cmdName, 1, sk, msg, randomizer, context, mu)
 			if err != nil {
 				return nil, fmt.Errorf("signature generation failed for test case %d/%d: %s",
 					group.ID, test.ID, err)
@@ -273,7 +304,32 @@
 					group.ID, test.ID, err)
 			}
 
-			result, err := t.Transact(cmdName, 1, pk, msg, sig)
+			var context []byte
+			context, err = hex.DecodeString(test.Context)
+			if err != nil {
+				return nil, fmt.Errorf("failed to decode context in test case %d/%d: %s",
+					group.ID, test.ID, err)
+			}
+			if group.SignatureInterface != "external" && len(context) > 0 {
+				return nil, fmt.Errorf("unexpected context for internal interface test case %d/%d: %s",
+					group.ID, test.ID, err)
+			}
+
+			var mu []byte
+			mu, err = hex.DecodeString(test.Mu)
+			if err != nil {
+				return nil, fmt.Errorf("failed to decode mu in test case %d/%d: %s",
+					group.ID, test.ID, err)
+			}
+			if group.SignatureInterface != "internal" && len(mu) > 0 {
+				return nil, fmt.Errorf("unexpected mu for external interface test case %d/%d: %s",
+					group.ID, test.ID, err)
+			} else if len(mu) > 0 && len(msg) > 0 {
+				return nil, fmt.Errorf("unexpected message for internal interface test case with mu %d/%d",
+					group.ID, test.ID)
+			}
+
+			result, err := t.Transact(cmdName, 1, pk, msg, sig, context, mu)
 			if err != nil {
 				return nil, fmt.Errorf("signature verification failed for test case %d/%d: %s",
 					group.ID, test.ID, err)
diff --git a/util/fipstools/acvp/acvptool/test/expected/ML-DSA.bz2 b/util/fipstools/acvp/acvptool/test/expected/ML-DSA.bz2
index e15bf78..c7df171 100644
--- a/util/fipstools/acvp/acvptool/test/expected/ML-DSA.bz2
+++ b/util/fipstools/acvp/acvptool/test/expected/ML-DSA.bz2
Binary files differ
diff --git a/util/fipstools/acvp/acvptool/test/vectors/ML-DSA.bz2 b/util/fipstools/acvp/acvptool/test/vectors/ML-DSA.bz2
index 870db65..a060a84 100644
--- a/util/fipstools/acvp/acvptool/test/vectors/ML-DSA.bz2
+++ b/util/fipstools/acvp/acvptool/test/vectors/ML-DSA.bz2
Binary files differ
diff --git a/util/fipstools/acvp/modulewrapper/modulewrapper.cc b/util/fipstools/acvp/modulewrapper/modulewrapper.cc
index 762f3e9..577111c 100644
--- a/util/fipstools/acvp/modulewrapper/modulewrapper.cc
+++ b/util/fipstools/acvp/modulewrapper/modulewrapper.cc
@@ -752,7 +752,8 @@
           false
         ],
         "externalMu": [
-          false
+          false,
+          true
         ],
         "capabilities": [{
           "parameterSets": [
@@ -772,6 +773,10 @@
         "mode": "sigVer",
         "revision": "FIPS204",
         "signatureInterfaces": ["internal"],
+        "externalMu": [
+          false,
+          true
+        ],
         "capabilities": [{
           "messageLength": [{
             "min": 8,
@@ -2075,7 +2080,9 @@
           bcm_status (*SignInternal)(uint8_t *, const PrivateKey *,
                                      const uint8_t *, size_t, const uint8_t *,
                                      size_t, const uint8_t *, size_t,
-                                     const uint8_t *)>
+                                     const uint8_t *),
+          bcm_status (*SignMuInternal)(uint8_t *, const PrivateKey *,
+                                       const uint8_t *, const uint8_t *)>
 static bool MLDSASigGen(const Span<const uint8_t> args[],
                         ReplyCallback write_reply) {
   CBS cbs = args[0];
@@ -2087,18 +2094,36 @@
 
   const Span<const uint8_t> msg = args[1];
   const Span<const uint8_t> randomizer = args[2];
+  const Span<const uint8_t> context = args[3];
+  const Span<const uint8_t> mu = args[4];
 
   if (randomizer.size() != BCM_MLDSA_SIGNATURE_RANDOMIZER_BYTES) {
     LOG_ERROR("Bad randomizer size.\n");
     return false;
   }
 
+  if (!context.empty()) {
+    LOG_ERROR("ML-DSA context should be empty.\n");
+    return false;
+  }
+
+  if (mu.size() != 0 && mu.size() != MLDSA_MU_BYTES) {
+    LOG_ERROR("Bad ML-DSA mu length.\n");
+    return false;
+  }
+
   uint8_t signature[SignatureBytes];
-  if (SignInternal(signature, priv.get(), msg.data(), msg.size(),
-                   // It's not just an empty context, the context prefix
-                   // is omitted too.
-                   nullptr, 0, nullptr, 0,
-                   randomizer.data()) != bcm_status::approved) {
+  if (mu.size() != 0) {
+    if (SignMuInternal(signature, priv.get(), mu.data(), randomizer.data()) !=
+        bcm_status::approved) {
+      LOG_ERROR("ML-DSA mu-signing failed.\n");
+      return false;
+    }
+  } else if (SignInternal(signature, priv.get(), msg.data(), msg.size(),
+                          // It's not just an empty context, the context
+                          // prefix is omitted too.
+                          nullptr, 0, nullptr, 0,
+                          randomizer.data()) != bcm_status::approved) {
     LOG_ERROR("ML-DSA signing failed.\n");
     return false;
   }
@@ -2110,12 +2135,16 @@
           bcm_status (*ParsePublicKey)(PublicKey *, CBS *),
           bcm_status (*VerifyInternal)(const PublicKey *, const uint8_t *,
                                        const uint8_t *, size_t, const uint8_t *,
-                                       size_t, const uint8_t *, size_t)>
+                                       size_t, const uint8_t *, size_t),
+          bcm_status (*VerifyMu)(const PublicKey *, const uint8_t *,
+                                 const uint8_t *)>
 static bool MLDSASigVer(const Span<const uint8_t> args[],
                         ReplyCallback write_reply) {
   const Span<const uint8_t> pub_key_bytes = args[0];
   const Span<const uint8_t> msg = args[1];
   const Span<const uint8_t> signature = args[2];
+  const Span<const uint8_t> context = args[3];
+  const Span<const uint8_t> mu = args[4];
 
   CBS cbs = pub_key_bytes;
   auto pub = std::make_unique<PublicKey>();
@@ -2129,11 +2158,26 @@
     return false;
   }
 
-  const uint8_t ok = bcm_success(
-      VerifyInternal(pub.get(), signature.data(), msg.data(), msg.size(),
-                     // It's not just an empty context, the context
-                     // prefix is omitted too.
-                     nullptr, 0, nullptr, 0));
+  if (!context.empty()) {
+    LOG_ERROR("ML-DSA context should be empty.\n");
+    return false;
+  }
+
+  if (mu.size() != 0 && mu.size() != MLDSA_MU_BYTES) {
+    LOG_ERROR("Bad ML-DSA mu length.\n");
+    return false;
+  }
+
+  uint8_t ok;
+  if (mu.size() != 0) {
+    ok = bcm_success(VerifyMu(pub.get(), signature.data(), mu.data()));
+  } else {
+    ok = bcm_success(VerifyInternal(pub.get(), signature.data(), msg.data(),
+                                    msg.size(),
+                                    // It's not just an empty context, the
+                                    // context prefix is omitted too.
+                                    nullptr, 0, nullptr, 0));
+  }
 
   return write_reply({Span<const uint8_t>(&ok, sizeof(ok))});
 }
@@ -2388,24 +2432,30 @@
      MLDSAKeyGen<MLDSA87_private_key, MLDSA87_PUBLIC_KEY_BYTES,
                  BCM_mldsa87_generate_key_external_entropy_fips,
                  BCM_mldsa87_marshal_private_key>},
-    {"ML-DSA-44/sigGen", 3,
+    {"ML-DSA-44/sigGen", 5,
      MLDSASigGen<MLDSA44_private_key, MLDSA44_SIGNATURE_BYTES,
-                 BCM_mldsa44_parse_private_key, BCM_mldsa44_sign_internal>},
-    {"ML-DSA-65/sigGen", 3,
+                 BCM_mldsa44_parse_private_key, BCM_mldsa44_sign_internal,
+                 BCM_mldsa44_sign_mu_internal>},
+    {"ML-DSA-65/sigGen", 5,
      MLDSASigGen<MLDSA65_private_key, MLDSA65_SIGNATURE_BYTES,
-                 BCM_mldsa65_parse_private_key, BCM_mldsa65_sign_internal>},
-    {"ML-DSA-87/sigGen", 3,
+                 BCM_mldsa65_parse_private_key, BCM_mldsa65_sign_internal,
+                 BCM_mldsa65_sign_mu_internal>},
+    {"ML-DSA-87/sigGen", 5,
      MLDSASigGen<MLDSA87_private_key, MLDSA87_SIGNATURE_BYTES,
-                 BCM_mldsa87_parse_private_key, BCM_mldsa87_sign_internal>},
-    {"ML-DSA-44/sigVer", 3,
+                 BCM_mldsa87_parse_private_key, BCM_mldsa87_sign_internal,
+                 BCM_mldsa87_sign_mu_internal>},
+    {"ML-DSA-44/sigVer", 5,
      MLDSASigVer<MLDSA44_public_key, MLDSA44_SIGNATURE_BYTES,
-                 BCM_mldsa44_parse_public_key, BCM_mldsa44_verify_internal>},
-    {"ML-DSA-65/sigVer", 3,
+                 BCM_mldsa44_parse_public_key, BCM_mldsa44_verify_internal,
+                 BCM_mldsa44_verify_message_representative>},
+    {"ML-DSA-65/sigVer", 5,
      MLDSASigVer<MLDSA65_public_key, MLDSA65_SIGNATURE_BYTES,
-                 BCM_mldsa65_parse_public_key, BCM_mldsa65_verify_internal>},
-    {"ML-DSA-87/sigVer", 3,
+                 BCM_mldsa65_parse_public_key, BCM_mldsa65_verify_internal,
+                 BCM_mldsa65_verify_message_representative>},
+    {"ML-DSA-87/sigVer", 5,
      MLDSASigVer<MLDSA87_public_key, MLDSA87_SIGNATURE_BYTES,
-                 BCM_mldsa87_parse_public_key, BCM_mldsa87_verify_internal>},
+                 BCM_mldsa87_parse_public_key, BCM_mldsa87_verify_internal,
+                 BCM_mldsa87_verify_message_representative>},
     {"ML-KEM-768/keyGen", 1,
      MLKEMKeyGen<MLKEM768_private_key, MLKEM768_PUBLIC_KEY_BYTES,
                  BCM_mlkem768_generate_key_external_seed,