// Copyright 2024 The BoringSSL Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include #include #include #include #include #include "../fipsmodule/bcm_interface.h" #include "../internal.h" #include "../test/file_test.h" #include "../test/test_util.h" namespace { template std::vector Marshal(bcm_status (*marshal_func)(CBB *, const T *), const T *t) { bssl::ScopedCBB cbb; uint8_t *encoded; size_t encoded_len; if (!CBB_init(cbb.get(), 1) || // marshal_func(cbb.get(), t) != bcm_status::approved || // !CBB_finish(cbb.get(), &encoded, &encoded_len)) { abort(); } std::vector ret(encoded, encoded + encoded_len); OPENSSL_free(encoded); return ret; } // This test is very slow, so it is disabled by default. TEST(MLDSATest, DISABLED_BitFlips) { std::vector encoded_public_key(MLDSA65_PUBLIC_KEY_BYTES); auto priv = std::make_unique(); uint8_t seed[MLDSA_SEED_BYTES]; EXPECT_TRUE( MLDSA65_generate_key(encoded_public_key.data(), seed, priv.get())); std::vector encoded_signature(MLDSA65_SIGNATURE_BYTES); static const uint8_t kMessage[] = {'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'}; EXPECT_TRUE(MLDSA65_sign(encoded_signature.data(), priv.get(), kMessage, sizeof(kMessage), nullptr, 0)); auto pub = std::make_unique(); CBS cbs = CBS(encoded_public_key); ASSERT_TRUE(MLDSA65_parse_public_key(pub.get(), &cbs)); EXPECT_EQ(MLDSA65_verify(pub.get(), encoded_signature.data(), encoded_signature.size(), kMessage, sizeof(kMessage), nullptr, 0), 1); for (size_t i = 0; i < MLDSA65_SIGNATURE_BYTES; i++) { for (int j = 0; j < 8; j++) { encoded_signature[i] ^= 1 << j; EXPECT_EQ(MLDSA65_verify(pub.get(), encoded_signature.data(), encoded_signature.size(), kMessage, sizeof(kMessage), nullptr, 0), 0) << "Bit flip in signature at byte " << i << " bit " << j << " didn't cause a verification failure"; encoded_signature[i] ^= 1 << j; } } } template < typename PrivateKey, typename PublicKey, size_t PublicKeyBytes, size_t SignatureBytes, int (*Generate)(uint8_t *, uint8_t *, PrivateKey *), int (*Sign)(uint8_t *, const PrivateKey *, const uint8_t *, size_t, const uint8_t *, size_t), int (*ParsePublicKey)(PublicKey *, CBS *), int (*Verify)(const PublicKey *, const uint8_t *, size_t, const uint8_t *, size_t, const uint8_t *, size_t), int (*PrivateKeyFromSeed)(PrivateKey *, const uint8_t *, size_t), typename BCMPrivateKey, bcm_status (*ParsePrivate)(BCMPrivateKey *, CBS *), bcm_status (*MarshalPrivate)(CBB *, const BCMPrivateKey *)> static void MLDSABasicTest() { std::vector encoded_public_key(PublicKeyBytes); auto priv = std::make_unique(); uint8_t seed[MLDSA_SEED_BYTES]; EXPECT_TRUE(Generate(encoded_public_key.data(), seed, priv.get())); const std::vector encoded_private_key = Marshal(MarshalPrivate, reinterpret_cast(priv.get())); CBS cbs = CBS(encoded_private_key); EXPECT_TRUE(bcm_success( ParsePrivate(reinterpret_cast(priv.get()), &cbs))); std::vector encoded_signature(SignatureBytes); static const uint8_t kMessage[] = {'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'}; static const uint8_t kContext[] = {'c', 't', 'x'}; EXPECT_TRUE(Sign(encoded_signature.data(), priv.get(), kMessage, sizeof(kMessage), kContext, sizeof(kContext))); auto pub = std::make_unique(); cbs = CBS(encoded_public_key); ASSERT_TRUE(ParsePublicKey(pub.get(), &cbs)); EXPECT_EQ( Verify(pub.get(), encoded_signature.data(), encoded_signature.size(), kMessage, sizeof(kMessage), kContext, sizeof(kContext)), 1); auto priv2 = std::make_unique(); EXPECT_TRUE(PrivateKeyFromSeed(priv2.get(), seed, sizeof(seed))); EXPECT_EQ( Bytes(Declassified(Marshal( MarshalPrivate, reinterpret_cast(priv.get())))), Bytes(Declassified(Marshal( MarshalPrivate, reinterpret_cast(priv2.get()))))); } TEST(MLDSATest, Basic65) { MLDSABasicTest(); } // These are the wrapper functions needed for `MLDSABasicTest`. ML-DSA-87 isn't // publicly exposed yet, so they are included here. It's good to exercise the // ML-DSA-65 wrapper functions so that they aren't untested (even if they are // quite trivial) thus `MLDSABasicTest` is done this way around. struct MLDSA87_private_key { BCM_mldsa87_private_key priv; }; struct MLDSA87_public_key { BCM_mldsa87_public_key pub; }; static int MLDSA87_generate_key( uint8_t out_encoded_public_key[BCM_MLDSA87_PUBLIC_KEY_BYTES], uint8_t out_seed[MLDSA_SEED_BYTES], struct MLDSA87_private_key *out_private_key) { return bcm_success(BCM_mldsa87_generate_key( out_encoded_public_key, out_seed, reinterpret_cast(out_private_key))); } static int MLDSA87_private_key_from_seed( struct MLDSA87_private_key *out_private_key, const uint8_t *seed, size_t seed_len) { if (seed_len != BCM_MLDSA_SEED_BYTES) { return 0; } return bcm_success(BCM_mldsa87_private_key_from_seed( reinterpret_cast(out_private_key), seed)); } static int MLDSA87_sign( uint8_t out_encoded_signature[BCM_MLDSA87_SIGNATURE_BYTES], const struct MLDSA87_private_key *private_key, const uint8_t *msg, size_t msg_len, const uint8_t *context, size_t context_len) { return bcm_success(BCM_mldsa87_sign( out_encoded_signature, reinterpret_cast(private_key), msg, msg_len, context, context_len)); } static int MLDSA87_verify(const struct MLDSA87_public_key *public_key, const uint8_t *signature, size_t signature_len, const uint8_t *msg, size_t msg_len, const uint8_t *context, size_t context_len) { if (context_len > 255 || signature_len != BCM_MLDSA87_SIGNATURE_BYTES) { return 0; } return bcm_success(BCM_mldsa87_verify( reinterpret_cast(public_key), signature, msg, msg_len, context, context_len)); } static int MLDSA87_parse_public_key(struct MLDSA87_public_key *public_key, CBS *in) { return bcm_success(BCM_mldsa87_parse_public_key( reinterpret_cast(public_key), in)); } TEST(MLDSATest, Basic87) { MLDSABasicTest(); } TEST(MLDSATest, SignatureIsRandomized) { std::vector encoded_public_key(MLDSA65_PUBLIC_KEY_BYTES); auto priv = std::make_unique(); uint8_t seed[MLDSA_SEED_BYTES]; EXPECT_TRUE( MLDSA65_generate_key(encoded_public_key.data(), seed, priv.get())); auto pub = std::make_unique(); CBS cbs = CBS(encoded_public_key); ASSERT_TRUE(MLDSA65_parse_public_key(pub.get(), &cbs)); std::vector encoded_signature1(MLDSA65_SIGNATURE_BYTES); std::vector encoded_signature2(MLDSA65_SIGNATURE_BYTES); static const uint8_t kMessage[] = {'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'}; EXPECT_TRUE(MLDSA65_sign(encoded_signature1.data(), priv.get(), kMessage, sizeof(kMessage), nullptr, 0)); EXPECT_TRUE(MLDSA65_sign(encoded_signature2.data(), priv.get(), kMessage, sizeof(kMessage), nullptr, 0)); EXPECT_NE(Bytes(encoded_signature1), Bytes(encoded_signature2)); // Even though the signatures are different, they both verify. EXPECT_EQ(MLDSA65_verify(pub.get(), encoded_signature1.data(), encoded_signature1.size(), kMessage, sizeof(kMessage), nullptr, 0), 1); EXPECT_EQ(MLDSA65_verify(pub.get(), encoded_signature2.data(), encoded_signature2.size(), kMessage, sizeof(kMessage), nullptr, 0), 1); } TEST(MLDSATest, PublicFromPrivateIsConsistent) { std::vector encoded_public_key(MLDSA65_PUBLIC_KEY_BYTES); auto priv = std::make_unique(); uint8_t seed[MLDSA_SEED_BYTES]; EXPECT_TRUE( MLDSA65_generate_key(encoded_public_key.data(), seed, priv.get())); auto pub = std::make_unique(); EXPECT_TRUE(MLDSA65_public_from_private(pub.get(), priv.get())); std::vector encoded_public_key2(MLDSA65_PUBLIC_KEY_BYTES); CBB cbb; CBB_init_fixed(&cbb, encoded_public_key2.data(), encoded_public_key2.size()); ASSERT_TRUE(MLDSA65_marshal_public_key(&cbb, pub.get())); EXPECT_EQ(Bytes(encoded_public_key2), Bytes(encoded_public_key)); } TEST(MLDSATest, InvalidPublicKeyEncodingLength) { // Encode a public key with a trailing 0 at the end. std::vector encoded_public_key(MLDSA65_PUBLIC_KEY_BYTES + 1); auto priv = std::make_unique(); uint8_t seed[MLDSA_SEED_BYTES]; EXPECT_TRUE( MLDSA65_generate_key(encoded_public_key.data(), seed, priv.get())); // Public key is 1 byte too short. CBS cbs = CBS(bssl::Span(encoded_public_key).first(MLDSA65_PUBLIC_KEY_BYTES - 1)); auto parsed_pub = std::make_unique(); EXPECT_FALSE(MLDSA65_parse_public_key(parsed_pub.get(), &cbs)); // Public key has the correct length. cbs = CBS(bssl::Span(encoded_public_key).first(MLDSA65_PUBLIC_KEY_BYTES)); EXPECT_TRUE(MLDSA65_parse_public_key(parsed_pub.get(), &cbs)); // Public key is 1 byte too long. cbs = CBS(encoded_public_key); EXPECT_FALSE(MLDSA65_parse_public_key(parsed_pub.get(), &cbs)); } TEST(MLDSATest, InvalidPrivateKeyEncodingLength) { std::vector encoded_public_key(MLDSA65_PUBLIC_KEY_BYTES); auto priv = std::make_unique(); uint8_t seed[MLDSA_SEED_BYTES]; EXPECT_TRUE(bcm_success( BCM_mldsa65_generate_key(encoded_public_key.data(), seed, priv.get()))); CBB cbb; std::vector malformed_private_key(MLDSA65_PRIVATE_KEY_BYTES + 1, 0); CBB_init_fixed(&cbb, malformed_private_key.data(), MLDSA65_PRIVATE_KEY_BYTES); ASSERT_TRUE(bcm_success(BCM_mldsa65_marshal_private_key( &cbb, reinterpret_cast(priv.get())))); CBS cbs; auto parsed_priv = std::make_unique(); // Private key is 1 byte too short. CBS_init(&cbs, malformed_private_key.data(), MLDSA65_PRIVATE_KEY_BYTES - 1); EXPECT_FALSE( bcm_success(BCM_mldsa65_parse_private_key(parsed_priv.get(), &cbs))); // Private key has the correct length. CBS_init(&cbs, malformed_private_key.data(), MLDSA65_PRIVATE_KEY_BYTES); EXPECT_TRUE( bcm_success(BCM_mldsa65_parse_private_key(parsed_priv.get(), &cbs))); // Private key is 1 byte too long. CBS_init(&cbs, malformed_private_key.data(), MLDSA65_PRIVATE_KEY_BYTES + 1); EXPECT_FALSE( bcm_success(BCM_mldsa65_parse_private_key(parsed_priv.get(), &cbs))); } template static void MLDSASigGenTest(FileTest *t) { std::vector private_key_bytes, msg, expected_signature; ASSERT_TRUE(t->GetBytes(&private_key_bytes, "sk")); ASSERT_TRUE(t->GetBytes(&msg, "message")); ASSERT_TRUE(t->GetBytes(&expected_signature, "signature")); auto priv = std::make_unique(); CBS cbs; CBS_init(&cbs, private_key_bytes.data(), private_key_bytes.size()); EXPECT_TRUE(bcm_success(ParsePrivateKey(priv.get(), &cbs))); const uint8_t zero_randomizer[BCM_MLDSA_SIGNATURE_RANDOMIZER_BYTES] = {0}; std::vector signature(SignatureBytes); EXPECT_TRUE(bcm_success(SignInternal(signature.data(), priv.get(), msg.data(), msg.size(), nullptr, 0, nullptr, 0, zero_randomizer))); EXPECT_EQ(Bytes(signature), Bytes(expected_signature)); auto pub = std::make_unique(); ASSERT_TRUE(bcm_success(PublicFromPrivate(pub.get(), priv.get()))); EXPECT_TRUE( bcm_success(VerifyInternal(pub.get(), signature.data(), msg.data(), msg.size(), nullptr, 0, nullptr, 0))); } TEST(MLDSATest, SigGenTests65) { FileTestGTest( "crypto/mldsa/mldsa_nist_siggen_65_tests.txt", MLDSASigGenTest); } TEST(MLDSATest, SigGenTests87) { FileTestGTest( "crypto/mldsa/mldsa_nist_siggen_87_tests.txt", MLDSASigGenTest); } template static void MLDSAKeyGenTest(FileTest *t) { std::vector seed, expected_public_key, expected_private_key; ASSERT_TRUE(t->GetBytes(&seed, "seed")); CONSTTIME_SECRET(seed.data(), seed.size()); ASSERT_TRUE(t->GetBytes(&expected_public_key, "pub")); ASSERT_TRUE(t->GetBytes(&expected_private_key, "priv")); std::vector encoded_public_key(PublicKeyBytes); auto priv = std::make_unique(); ASSERT_TRUE(bcm_success( Generate(encoded_public_key.data(), priv.get(), seed.data()))); const std::vector encoded_private_key = Marshal(MarshalPrivate, priv.get()); EXPECT_EQ(Bytes(encoded_public_key), Bytes(expected_public_key)); EXPECT_EQ(Bytes(Declassified(encoded_private_key)), Bytes(expected_private_key)); } TEST(MLDSATest, KeyGenTests65) { FileTestGTest( "crypto/mldsa/mldsa_nist_keygen_65_tests.txt", MLDSAKeyGenTest); } TEST(MLDSATest, KeyGenTests87) { FileTestGTest( "crypto/mldsa/mldsa_nist_keygen_87_tests.txt", MLDSAKeyGenTest); } template < typename PrivateKey, bcm_status_t (*ParsePrivateKey)(PrivateKey *, CBS *), size_t SignatureBytes, bcm_status_t (*SignInternal)(uint8_t *, const PrivateKey *, const uint8_t *, size_t, const uint8_t *, size_t, const uint8_t *, size_t, const uint8_t *)> static void MLDSAWycheproofSignTest(FileTest *t) { std::vector private_key_bytes, msg, expected_signature, context; ASSERT_TRUE(t->GetInstructionBytes(&private_key_bytes, "privateKey")); ASSERT_TRUE(t->GetBytes(&msg, "msg")); ASSERT_TRUE(t->GetBytes(&expected_signature, "sig")); if (t->HasAttribute("ctx")) { t->GetBytes(&context, "ctx"); } std::string result; ASSERT_TRUE(t->GetAttribute(&result, "result")); t->IgnoreAttribute("flags"); CBS cbs; CBS_init(&cbs, private_key_bytes.data(), private_key_bytes.size()); auto priv = std::make_unique(); const int priv_ok = bcm_success(ParsePrivateKey(priv.get(), &cbs)); if (!priv_ok) { ASSERT_TRUE(result != "valid"); return; } // Unfortunately we need to reimplement the context length check here because // we are using the internal function in order to pass in an all-zero // randomizer. if (context.size() > 255) { ASSERT_TRUE(result != "valid"); return; } const uint8_t zero_randomizer[BCM_MLDSA_SIGNATURE_RANDOMIZER_BYTES] = {0}; std::vector signature(SignatureBytes); const uint8_t context_prefix[2] = {0, static_cast(context.size())}; EXPECT_TRUE(bcm_success(SignInternal(signature.data(), priv.get(), msg.data(), msg.size(), context_prefix, sizeof(context_prefix), context.data(), context.size(), zero_randomizer))); EXPECT_EQ(Bytes(signature), Bytes(expected_signature)); } TEST(MLDSATest, WycheproofSignTests65) { FileTestGTest( "third_party/wycheproof_testvectors/mldsa_65_standard_sign_test.txt", MLDSAWycheproofSignTest< BCM_mldsa65_private_key, BCM_mldsa65_parse_private_key, MLDSA65_SIGNATURE_BYTES, BCM_mldsa65_sign_internal>); } TEST(MLDSATest, WycheproofSignTests87) { FileTestGTest( "third_party/wycheproof_testvectors/mldsa_87_standard_sign_test.txt", MLDSAWycheproofSignTest< BCM_mldsa87_private_key, BCM_mldsa87_parse_private_key, BCM_MLDSA87_SIGNATURE_BYTES, BCM_mldsa87_sign_internal>); } template static void MLDSAWycheproofVerifyTest(FileTest *t) { std::vector public_key_bytes, msg, signature, context; ASSERT_TRUE(t->GetInstructionBytes(&public_key_bytes, "publicKey")); ASSERT_TRUE(t->GetBytes(&msg, "msg")); ASSERT_TRUE(t->GetBytes(&signature, "sig")); if (t->HasAttribute("ctx")) { t->GetBytes(&context, "ctx"); } std::string result, flags; ASSERT_TRUE(t->GetAttribute(&result, "result")); ASSERT_TRUE(t->GetAttribute(&flags, "flags")); CBS cbs; CBS_init(&cbs, public_key_bytes.data(), public_key_bytes.size()); auto pub = std::make_unique(); const int pub_ok = bcm_success(ParsePublicKey(pub.get(), &cbs)); if (!pub_ok) { EXPECT_EQ(flags, "IncorrectPublicKeyLength"); return; } const int sig_ok = signature.size() == SignatureLength && context.size() <= 255 && bcm_success(Verify(pub.get(), signature.data(), msg.data(), msg.size(), context.data(), context.size())); if (!sig_ok) { EXPECT_EQ(result, "invalid"); } else { EXPECT_EQ(result, "valid"); } } TEST(MLDSATest, WycheproofVerifyTests65) { FileTestGTest( "third_party/wycheproof_testvectors/mldsa_65_standard_verify_test.txt", MLDSAWycheproofVerifyTest< BCM_mldsa65_public_key, BCM_MLDSA65_SIGNATURE_BYTES, BCM_mldsa65_parse_public_key, BCM_mldsa65_verify>); } TEST(MLDSATest, WycheproofVerifyTests87) { FileTestGTest( "third_party/wycheproof_testvectors/mldsa_87_standard_verify_test.txt", MLDSAWycheproofVerifyTest< BCM_mldsa87_public_key, BCM_MLDSA87_SIGNATURE_BYTES, BCM_mldsa87_parse_public_key, BCM_mldsa87_verify>); } TEST(MLDSATest, Self) { ASSERT_TRUE(boringssl_self_test_mldsa()); } TEST(MLDSATest, PWCT) { uint8_t seed[BCM_MLDSA_SEED_BYTES]; auto pub65 = std::make_unique(BCM_MLDSA65_PUBLIC_KEY_BYTES); auto priv65 = std::make_unique(); ASSERT_EQ(BCM_mldsa65_generate_key_fips(pub65.get(), seed, priv65.get()), bcm_status::approved); auto pub87 = std::make_unique(BCM_MLDSA87_PUBLIC_KEY_BYTES); auto priv87 = std::make_unique(); ASSERT_EQ(BCM_mldsa87_generate_key_fips(pub87.get(), seed, priv87.get()), bcm_status::approved); } TEST(MLDSATest, NullptrArgumentsToCreate) { // For FIPS reasons, this should fail rather than crash. ASSERT_EQ(BCM_mldsa65_generate_key_fips(nullptr, nullptr, nullptr), bcm_status::failure); ASSERT_EQ(BCM_mldsa87_generate_key_fips(nullptr, nullptr, nullptr), bcm_status::failure); ASSERT_EQ( BCM_mldsa65_generate_key_external_entropy_fips(nullptr, nullptr, nullptr), bcm_status::failure); ASSERT_EQ( BCM_mldsa87_generate_key_external_entropy_fips(nullptr, nullptr, nullptr), bcm_status::failure); } } // namespace