diff --git a/src/tests/test_frodokem.cpp b/src/tests/test_frodokem.cpp index f4a023e029d..1323d0b5ed0 100644 --- a/src/tests/test_frodokem.cpp +++ b/src/tests/test_frodokem.cpp @@ -31,43 +31,35 @@ namespace Botan_Tests { #if defined(BOTAN_HAS_FRODOKEM) namespace { -class Frodo_KAT_Tests_Impl { +class Frodo_KAT_Tests final : public Botan_Tests::PK_PQC_KEM_KAT_Test { public: - using public_key_t = Botan::FrodoKEM_PublicKey; - using private_key_t = Botan::FrodoKEM_PrivateKey; + Frodo_KAT_Tests() : PK_PQC_KEM_KAT_Test("FrodoKEM", "pubkey/frodokem_kat.vec") {} - static constexpr const char* algo_name = "FrodoKEM"; - static constexpr const char* input_file = "pubkey/frodokem_kat.vec"; - - public: - Frodo_KAT_Tests_Impl(std::string_view algo_spec) : m_mode(algo_spec) {} - - decltype(auto) mode() const { return m_mode; } + private: + Botan::FrodoKEMMode get_mode(const std::string& mode) const { return Botan::FrodoKEMMode(mode); } - bool available() const { return m_mode.is_available(); } + bool is_available(const std::string& mode) const final { return get_mode(mode).is_available(); } - auto map_value(std::span<const uint8_t> value) const { + std::vector<uint8_t> map_value(const std::string&, std::span<const uint8_t> value, VarType var_type) const final { + if(var_type == VarType::SharedSecret) { + return {value.begin(), value.end()}; + } auto xof = Botan::XOF::create_or_throw("SHAKE-256"); xof->update(value); return xof->output<std::vector<uint8_t>>(16); } - auto rng_for_keygen(Botan::RandomNumberGenerator& rng) const { - Botan::FrodoKEMConstants consts(m_mode); + Fixed_Output_RNG rng_for_keygen(const std::string& mode, Botan::RandomNumberGenerator& rng) const final { + Botan::FrodoKEMConstants consts(get_mode(mode)); return Fixed_Output_RNG(rng, consts.len_sec_bytes() + consts.len_se_bytes() + consts.len_a_bytes()); } - auto rng_for_encapsulation(Botan::RandomNumberGenerator& rng) const { - Botan::FrodoKEMConstants consts(m_mode); + Fixed_Output_RNG rng_for_encapsulation(const std::string& mode, Botan::RandomNumberGenerator& rng) const final { + Botan::FrodoKEMConstants consts(get_mode(mode)); return Fixed_Output_RNG(rng, consts.len_sec_bytes() + consts.len_salt_bytes()); } - - private: - Botan::FrodoKEMMode m_mode; }; -class Frodo_KAT_Tests : public Botan_Tests::PK_PQC_KEM_KAT_Test<Frodo_KAT_Tests_Impl> {}; - std::vector<Test::Result> test_frodo_roundtrips() { auto& rng = Test::rng(); diff --git a/src/tests/test_kyber.cpp b/src/tests/test_kyber.cpp index 9ad15890a3d..a451b3d504d 100644 --- a/src/tests/test_kyber.cpp +++ b/src/tests/test_kyber.cpp @@ -110,47 +110,41 @@ BOTAN_REGISTER_TEST("kyber", "kyber_pairwise", KYBER_Tests); namespace { -class Kyber_KAT_Tests_Impl { +class Kyber_KAT_Tests final : public PK_PQC_KEM_KAT_Test { public: - using public_key_t = Botan::Kyber_PublicKey; - using private_key_t = Botan::Kyber_PrivateKey; + Kyber_KAT_Tests() : PK_PQC_KEM_KAT_Test("Kyber", "pubkey/kyber_kat.vec") {} - static constexpr const char* algo_name = "Kyber"; - static constexpr const char* input_file = "pubkey/kyber_kat.vec"; - - public: - Kyber_KAT_Tests_Impl(std::string_view algo_spec) : m_mode(algo_spec) {} - - decltype(auto) mode() const { return m_mode; } + private: + Botan::KyberMode get_mode(const std::string& mode) const { return Botan::KyberMode(mode); } - bool available() const { return m_mode.is_available(); } + bool is_available(const std::string& mode) const final { return get_mode(mode).is_available(); } - std::vector<uint8_t> map_value(std::span<const uint8_t> value) const { + std::vector<uint8_t> map_value(const std::string& mode, + std::span<const uint8_t> value, + VarType var_type) const final { + if(var_type == VarType::SharedSecret) { + return {value.begin(), value.end()}; + } // We use different hash functions for Kyber 90s and Kyber "modern", as // those are consistent with the requirements of the implementations. - std::string_view hash_name = m_mode.is_modern() ? "SHAKE-256(128)" : "SHA-256"; + std::string_view hash_name = get_mode(mode).is_modern() ? "SHAKE-256(128)" : "SHA-256"; auto hash = Botan::HashFunction::create_or_throw(hash_name); const auto digest = hash->process(value); return {digest.begin(), digest.begin() + 16}; } - auto rng_for_keygen(Botan::RandomNumberGenerator& rng) const { + Fixed_Output_RNG rng_for_keygen(const std::string&, Botan::RandomNumberGenerator& rng) const final { const auto seed = rng.random_vec(32); const auto z = rng.random_vec(32); return Fixed_Output_RNG(Botan::concat(seed, z)); } - auto rng_for_encapsulation(Botan::RandomNumberGenerator& rng) const { + Fixed_Output_RNG rng_for_encapsulation(const std::string&, Botan::RandomNumberGenerator& rng) const final { return Fixed_Output_RNG(rng.random_vec(32)); } - - private: - Botan::KyberMode m_mode; }; -class Kyber_KAT_Tests : public Botan_Tests::PK_PQC_KEM_KAT_Test<Kyber_KAT_Tests_Impl> {}; - } // namespace BOTAN_REGISTER_TEST("kyber", "kyber_kat", Kyber_KAT_Tests); diff --git a/src/tests/test_pubkey_pqc.h b/src/tests/test_pubkey_pqc.h index dff2c94c9cd..0ee026d3e47 100644 --- a/src/tests/test_pubkey_pqc.h +++ b/src/tests/test_pubkey_pqc.h @@ -15,30 +15,11 @@ #include "test_rng.h" #include <botan/hash.h> + #include <botan/pk_algs.h> #include <botan/internal/fmt.h> namespace Botan_Tests { -namespace detail { - -template <typename T> -concept PQC_KEM_KAT_Test_Implementation = - std::derived_from<typename T::private_key_t, Botan::Private_Key> && - std::derived_from<typename T::public_key_t, Botan::Public_Key> && - std::convertible_to<decltype(T::input_file), std::string> && - std::convertible_to<decltype(T::algo_name), std::string> && - requires( - T impl, Botan::RandomNumberGenerator& rng, std::span<const uint8_t> crypto_artefact, std::string_view algo_spec) { - { T(algo_spec) } -> std::same_as<T>; - { impl.rng_for_keygen(rng) } -> std::same_as<Botan_Tests::Fixed_Output_RNG>; - { impl.rng_for_encapsulation(rng) } -> std::same_as<Botan_Tests::Fixed_Output_RNG>; - { impl.map_value(crypto_artefact) } -> std::same_as<std::vector<uint8_t>>; - { impl.available() } -> std::convertible_to<bool>; - { std::is_constructible_v<typename T::private_key_t, Botan::RandomNumberGenerator&, decltype(impl.mode())> }; - }; - -} - /** * This is an abstraction over the Known Answer Tests used by the KEM candidates * in the NIST PQC competition. @@ -49,22 +30,54 @@ concept PQC_KEM_KAT_Test_Implementation = * * See also: https://csrc.nist.gov/projects/post-quantum-cryptography/post-quantum-cryptography-standardization/example-files */ -template <detail::PQC_KEM_KAT_Test_Implementation Delegate> class PK_PQC_KEM_KAT_Test : public PK_Test { - public: - PK_PQC_KEM_KAT_Test() : PK_Test(Delegate::algo_name, Delegate::input_file, "Seed,SS,PK,SK,CT") {} + protected: + /// Type of a KAT vector entry that can be recomputed using the seed + enum class VarType { SharedSecret, PublicKey, PrivateKey, Ciphertext }; - private: - using Private_Key = typename Delegate::private_key_t; - using Public_Key = typename Delegate::public_key_t; + PK_PQC_KEM_KAT_Test(const std::string& algo_name, const std::string& input_file) : + PK_Test(algo_name, input_file, "Seed,SS,PK,SK,CT") {} + + // --- Callbacks --- + + /// Map a recomputed value to the expected value from the KAT vector (e.g. apply a hash function if Botan's KAT entry is hashed) + virtual std::vector<uint8_t> map_value(const std::string& params, + std::span<const uint8_t> value, + VarType value_type) const = 0; + + /// Create an RNG that can be used to generate the keypair. @p rng is the DRBG that is used to expand the seed. + virtual Fixed_Output_RNG rng_for_keygen(const std::string& params, Botan::RandomNumberGenerator& rng) const = 0; + + /// Create an RNG that can be used to generate the keypair. @p rng is the DRBG that is used to expand the seed. + virtual Fixed_Output_RNG rng_for_encapsulation(const std::string& params, + Botan::RandomNumberGenerator& rng) const = 0; + + /// Return true if the algorithm with the specified params should be tested + virtual bool is_available(const std::string& params) const = 0; + + /// Callback to test the RNG's state after key generation. If not overridden checks that the RNG is empty. + virtual void inspect_rng_after_keygen(const std::string& params, + const Fixed_Output_RNG& rng_keygen, + Test::Result& result) const { + BOTAN_UNUSED(params); + result.confirm("All prepared random bits used for key generation", rng_keygen.empty()); + } + + /// Callback to test the RNG's state after encapsulation. If not overridden checks that the RNG is empty. + virtual void inspect_rng_after_encaps(const std::string& params, + const Fixed_Output_RNG& rng_encaps, + Test::Result& result) const { + BOTAN_UNUSED(params); + result.confirm("All prepared random bits used for encapsulation", rng_encaps.empty()); + } private: - bool skip_this_test(const std::string& header, const VarMap&) override { + bool skip_this_test(const std::string& params, const VarMap&) final { #if !defined(BOTAN_HAS_AES) - BOTAN_UNUSED(header); + BOTAN_UNUSED(params); return true; #else - return !Delegate(header).available(); + return !is_available(params); #endif } @@ -77,47 +90,54 @@ class PK_PQC_KEM_KAT_Test : public PK_Test { #endif } - Test::Result run_one_test(const std::string& header, const VarMap& vars) final { - Test::Result result(Botan::fmt("PQC KAT for {} with parameters {}", algo_name(), header)); - auto d = Delegate(header); + Test::Result run_one_test(const std::string& params, const VarMap& vars) final { + Test::Result result(Botan::fmt("PQC KAT for {} with parameters {}", algo_name(), params)); // All PQC algorithms use this DRBG in their KAT tests to generate // their private keys. The amount of data that needs to be pulled from // the RNG for keygen and encapsulation is dependent on the algorithm // and the implementation. auto ctr_drbg = create_drbg(vars.get_req_bin("Seed")); - auto rng_keygen = d.rng_for_keygen(*ctr_drbg); - auto rng_encaps = d.rng_for_encapsulation(*ctr_drbg); + auto rng_keygen = rng_for_keygen(params, *ctr_drbg); + auto rng_encaps = rng_for_encapsulation(params, *ctr_drbg); // Key Generation - auto sk = Private_Key(rng_keygen, d.mode()); - result.test_is_eq("Generated private key", d.map_value(sk.raw_private_key_bits()), vars.get_req_bin("SK")); - result.confirm("All prepared random bits used for key generation", rng_keygen.empty()); + auto sk = Botan::create_private_key(algo_name(), rng_keygen, params); + result.test_is_eq("Generated private key", + map_value(params, sk->raw_private_key_bits(), VarType::PrivateKey), + vars.get_req_bin("SK")); + inspect_rng_after_keygen(params, rng_keygen, result); // Algorithm properties - result.test_eq("algorithm name", sk.algo_name(), algo_name()); - result.confirm("supported operation", sk.supports_operation(Botan::PublicKeyOperation::KeyEncapsulation)); - result.test_gte("Key has reasonable estimated strength (lower)", sk.estimated_strength(), 64); - result.test_lt("Key has reasonable estimated strength (upper)", sk.estimated_strength(), 512); + result.test_eq("Algorithm name", sk->algo_name(), algo_name()); + result.confirm("Supported operation KeyEncapsulation", + sk->supports_operation(Botan::PublicKeyOperation::KeyEncapsulation)); + result.test_gte("Key has reasonable estimated strength (lower)", sk->estimated_strength(), 64); + result.test_lt("Key has reasonable estimated strength (upper)", sk->estimated_strength(), 512); // Extract Public Key - auto pk = sk.public_key(); - result.test_is_eq("Generated public key", d.map_value(pk->public_key_bits()), vars.get_req_bin("PK")); + auto pk = sk->public_key(); + result.test_is_eq("Generated public key", + map_value(params, pk->public_key_bits(), VarType::PublicKey), + vars.get_req_bin("PK")); // Serialize/Deserialize the Public Key - auto pk2 = Public_Key(pk->public_key_bits(), d.mode()); + auto pk2 = Botan::load_public_key(pk->algorithm_identifier(), pk->public_key_bits()); // Encapsulation - auto enc = Botan::PK_KEM_Encryptor(pk2, "Raw"); + auto enc = Botan::PK_KEM_Encryptor(*pk2, "Raw"); const auto encaped = enc.encrypt(rng_encaps, 0 /* no KDF */); - result.test_is_eq("Shared Secret", encaped.shared_key(), Botan::lock(vars.get_req_bin("SS"))); - result.test_is_eq("Ciphertext", d.map_value(encaped.encapsulated_shared_key()), vars.get_req_bin("CT")); - result.confirm("All prepared random bits used for encapsulation", rng_encaps.empty()); + result.test_is_eq( + "Shared Secret", map_value(params, encaped.shared_key(), VarType::SharedSecret), vars.get_req_bin("SS")); + result.test_is_eq("Ciphertext", + map_value(params, encaped.encapsulated_shared_key(), VarType::Ciphertext), + vars.get_req_bin("CT")); + inspect_rng_after_encaps(params, rng_keygen, result); // Decapsulation - Private_Key sk2(sk.private_key_bits(), d.mode()); + auto sk2 = Botan::load_private_key(sk->algorithm_identifier(), sk->private_key_bits()); Botan::Null_RNG null_rng; - auto dec = Botan::PK_KEM_Decryptor(sk2, null_rng, "Raw"); + auto dec = Botan::PK_KEM_Decryptor(*sk2, null_rng, "Raw"); const auto shared_key = dec.decrypt(encaped.encapsulated_shared_key(), 0 /* no KDF */); result.test_is_eq("Decaps. Shared Secret", shared_key, Botan::lock(vars.get_req_bin("SS")));