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/<HASH> | 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,