From 8a46ea62a5e2368f80b3ef759bd22bf367c70ce1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Meusel?= Date: Fri, 20 Sep 2024 13:46:25 +0200 Subject: [PATCH] implement ::check_key() methods --- src/lib/pubkey/kyber/kyber_common/kyber.cpp | 33 ++++++++++++++++++- src/lib/pubkey/kyber/kyber_common/kyber.h | 4 ++- .../pubkey/kyber/kyber_common/kyber_algos.cpp | 1 + src/lib/pubkey/kyber/ml_kem/ml_kem_impl.cpp | 3 +- src/tests/test_kyber.cpp | 13 +++++++- src/tests/test_pubkey.cpp | 2 +- 6 files changed, 51 insertions(+), 5 deletions(-) diff --git a/src/lib/pubkey/kyber/kyber_common/kyber.cpp b/src/lib/pubkey/kyber/kyber_common/kyber.cpp index 4681b38f41..cd51b9be15 100644 --- a/src/lib/pubkey/kyber/kyber_common/kyber.cpp +++ b/src/lib/pubkey/kyber/kyber_common/kyber.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include @@ -209,7 +210,16 @@ size_t Kyber_PublicKey::key_length() const { } bool Kyber_PublicKey::check_key(RandomNumberGenerator&, bool) const { - return true; // ?? + // The length checks described in FIPS 203, Section 7.2 are already performed + // while decoding the public key. See constructor of Kyber_PublicKeyInternal. + // The decoding function KyberAlgos::byte_decode() also checks the range of + // the decoded values. The check below is added for completeness. + + std::vector test(m_public->mode().polynomial_vector_bytes()); + Kyber_Algos::encode_polynomial_vector(test, m_public->t()); + + const auto& serialized_pubkey = m_public->public_key_bits_raw(); + return test.size() < serialized_pubkey.size() && std::equal(test.begin(), test.end(), serialized_pubkey.begin()); } std::unique_ptr Kyber_PublicKey::generate_another(RandomNumberGenerator& rng) const { @@ -252,6 +262,27 @@ secure_vector Kyber_PrivateKey::private_key_bits() const { return m_private->mode().keypair_codec().encode_keypair({m_public, m_private}); } +bool Kyber_PrivateKey::check_key(RandomNumberGenerator& rng, bool strong) const { + // As we do not support loading a private key in extended format but rather + // always extract it from a 64-byte seed, these checks (as described in + // FIPS 203, Section 7.1) should never fail. Particularly, the length checks + // and the hash consistency check described in Section 7.2 and 7.3 are + // trivial when the private key is always extracted from a seed. The encaps/ + // decaps roundtrip test is added for completeness. + + if(!Kyber_PublicKey::check_key(rng, strong)) { + return false; + } + + PK_KEM_Encryptor enc(*this, "Raw"); + PK_KEM_Decryptor dec(*this, rng, "Raw"); + + const auto [c, K] = KEM_Encapsulation::destructure(enc.encrypt(rng)); + const auto K_prime = dec.decrypt(c); + + return K == K_prime; +} + std::unique_ptr Kyber_PublicKey::create_kem_encryption_op(std::string_view params, std::string_view provider) const { if(provider.empty() || provider == "base") { diff --git a/src/lib/pubkey/kyber/kyber_common/kyber.h b/src/lib/pubkey/kyber/kyber_common/kyber.h index 03dca3f5a5..e14fb4da22 100644 --- a/src/lib/pubkey/kyber/kyber_common/kyber.h +++ b/src/lib/pubkey/kyber/kyber_common/kyber.h @@ -108,7 +108,7 @@ class BOTAN_PUBLIC_API(3, 0) Kyber_PublicKey : public virtual Public_Key { std::vector public_key_bits() const override; - bool check_key(RandomNumberGenerator&, bool) const override; + bool check_key(RandomNumberGenerator& rng, bool strong) const override; std::unique_ptr generate_another(RandomNumberGenerator& rng) const final; @@ -152,6 +152,8 @@ class BOTAN_PUBLIC_API(3, 0) Kyber_PrivateKey final : public virtual Kyber_Publi secure_vector raw_private_key_bits() const override; + bool check_key(RandomNumberGenerator& rng, bool strong) const override; + std::unique_ptr create_kem_decryption_op(RandomNumberGenerator& rng, std::string_view params, std::string_view provider) const override; diff --git a/src/lib/pubkey/kyber/kyber_common/kyber_algos.cpp b/src/lib/pubkey/kyber/kyber_common/kyber_algos.cpp index ef80d7e0d8..4621d5788e 100644 --- a/src/lib/pubkey/kyber/kyber_common/kyber_algos.cpp +++ b/src/lib/pubkey/kyber/kyber_common/kyber_algos.cpp @@ -364,6 +364,7 @@ std::pair decompress_ciphertext(StrongSpan out_enc * NIST FIPS 203, Algorithm 18 (ML-KEM.Decaps_internal) and 21 (ML-KEM.Decaps) * * The public and private keys are readily available as member variables and - * don't need to be decoded. + * don't need to be decoded. The checks stated in FIPS 203, Section 7.3 are + * performed before decoding the keys and the ciphertext. */ void ML_KEM_Decryptor::decapsulate(StrongSpan out_shared_key, StrongSpan c) { diff --git a/src/tests/test_kyber.cpp b/src/tests/test_kyber.cpp index f264e5faa7..a286e0ef01 100644 --- a/src/tests/test_kyber.cpp +++ b/src/tests/test_kyber.cpp @@ -286,11 +286,22 @@ class Kyber_Keygen_Tests final : public PK_Key_Generation_Test { #endif #if defined(BOTAN_HAS_KYBER) "Kyber-512-r3", "Kyber-768-r3", "Kyber-1024-r3", + #endif + #if defined(BOTAN_HAS_ML_KEM) + "ML-KEM-512", "ML-KEM-768", "ML-KEM-1024", #endif }; } - std::string algo_name() const override { return "Kyber"; } + std::string algo_name(std::string_view param) const override { + if(param.starts_with("Kyber-")) { + return "Kyber"; + } else { + return "ML-KEM"; + } + } + + std::string algo_name() const override { throw Test_Error("No default algo name set for Kyber"); } std::unique_ptr public_key_from_raw(std::string_view keygen_params, std::string_view /* provider */, diff --git a/src/tests/test_pubkey.cpp b/src/tests/test_pubkey.cpp index ae3724ed5f..26b9c58c4c 100644 --- a/src/tests/test_pubkey.cpp +++ b/src/tests/test_pubkey.cpp @@ -612,7 +612,7 @@ std::vector PK_Key_Generation_Test::run() { oid.value().to_string(), key.object_identifier().to_string()); } else { - const bool exception = name == "Kyber" || name == "FrodoKEM" || name == "SPHINCS+"; + const bool exception = name == "Kyber" || name == "ML-KEM" || name == "FrodoKEM" || name == "SPHINCS+"; if(!exception) { result.test_failure("Keys name " + name + " does not map to an OID");