diff --git a/doc/migration_guide.rst b/doc/migration_guide.rst index ce742a738da..c2a4c75dfda 100644 --- a/doc/migration_guide.rst +++ b/doc/migration_guide.rst @@ -26,6 +26,14 @@ algorithm headers (such as ``aes.h``) have been removed. Instead you should create objects via the factory methods (in the case of AES, ``BlockCipher::create``) which works in both 2.x and 3.0 +Errata: ``pk_ops.h`` +^^^^^^^^^^^^^^^^^^^^ + +Between Botan 3.0 and 3.2 the public header ``pk_ops.h`` was removed +accidentally. This header is typically required for specialized applications +that interface with dedicated crypto hardware. If you are migrating such an +application, please make sure to use Botan 3.3 or newer. + Build Artifacts --------------- diff --git a/src/examples/hybrid_key_encapsulation.cpp b/src/examples/hybrid_key_encapsulation.cpp new file mode 100644 index 00000000000..09fecae5ac5 --- /dev/null +++ b/src/examples/hybrid_key_encapsulation.cpp @@ -0,0 +1,377 @@ +#include +#include +#include +#include +#include +#include + +#include +#include + +/** + * This class is an example of a custom public-key algorithm in Botan. + * + * It combines a classic key exchange algorithm like Diffie-Hellman and a key + * encapsulation mechanism (KEM) to provide a "hybrid" key encapsulation + * mechanism (KEM). + * + * This approach is useful as an intermediate step towards post-quantum secure + * cryptography as it combines the historical confidence in a classic algorithm + * with the future-proofness of a post-quantum algorithm. + * + * Other use cases for such a custom public-key algorithm class include: + * - adding support for a new public-key algorithm that Botan doesn't support + * - writing a wrapper to offload public-key operations to a hardware device + */ +class Hybrid_PublicKey : public virtual Botan::Public_Key { + public: + explicit Hybrid_PublicKey(std::unique_ptr kex, std::unique_ptr kem) : + m_kex_pk(std::move(kex)), m_kem_pk(std::move(kem)) { + BOTAN_ASSERT_NONNULL(m_kex_pk); + BOTAN_ASSERT_NONNULL(m_kem_pk); + BOTAN_ASSERT_NOMSG(m_kex_pk->supports_operation(Botan::PublicKeyOperation::KeyAgreement)); + BOTAN_ASSERT_NOMSG(m_kem_pk->supports_operation(Botan::PublicKeyOperation::KeyEncapsulation)); + } + + std::string algo_name() const override { + return "Hybrid-KEM(" + m_kex_pk->algo_name() + "," + m_kem_pk->algo_name() + ")"; + } + + /** + * This returns an object of a custom sub-class of + * Botan::PK_Ops::KEM_Encryption. See below for the implementation of that + * class, where the actual hybrid operation is performed. + * + * Note, that applications typically don't call this directly, but they + * use the Botan::PK_KEM_Encryptor class, which in turn calls this method. + * See the main() function below for an example. + */ + std::unique_ptr create_kem_encryption_op(std::string_view params, + std::string_view provider) const override; + + /** + * In an actual implementation, when you want to use this key in a + * protocol like X.509, this may return an algorithm identifier that fits + * your needs. For instance, using a custom OID. + */ + Botan::AlgorithmIdentifier algorithm_identifier() const override { + throw Botan::Not_Implemented("Hybrid-KEM does not have an algorithm identifier"); + } + + /** + * In an actual implementation, this may return a serialized + * representation of the public keys. For instance, using some ASN.1 + * encoding to combine the two public keys. + */ + std::vector public_key_bits() const override { + throw Botan::Not_Implemented("Key serialization is not supported"); + } + + std::unique_ptr generate_another(Botan::RandomNumberGenerator& rng) const override; + + bool supports_operation(Botan::PublicKeyOperation op) const override { + return op == Botan::PublicKeyOperation::KeyEncapsulation; + } + + size_t estimated_strength() const override { + return std::max(m_kex_pk->estimated_strength(), m_kem_pk->estimated_strength()); + } + + size_t key_length() const override { return m_kex_pk->key_length() + m_kem_pk->key_length(); } + + bool check_key(Botan::RandomNumberGenerator& rng, bool strong) const override { + return m_kex_pk->check_key(rng, strong) && m_kem_pk->check_key(rng, strong); + } + + const Botan::Public_Key& kex_public_key() const { return *m_kex_pk; } + + const Botan::Public_Key& kem_public_key() const { return *m_kem_pk; } + + private: + std::unique_ptr m_kex_pk; + std::unique_ptr m_kem_pk; +}; + +/** + * This is the private key class for the custom public-key algorithm. + */ +class Hybrid_PrivateKey : public virtual Botan::Private_Key, + public virtual Hybrid_PublicKey { + public: + explicit Hybrid_PrivateKey(std::unique_ptr kex, std::unique_ptr kem) : + Hybrid_PublicKey(kex->public_key(), kem->public_key()), + m_kex_sk(std::move(kex)), + m_kem_sk(std::move(kem)) {} + + /** + * This returns an object of a custom sub-class of Botan::PK_Ops::KEM_Decryption. + * See below for the implementation of that class, where the actual hybrid operation + * is performed. + * + * Note, that applications typically don't call this directly, but they + * use the Botan::PK_KEM_Decryptor class, which in turn calls this method. + * See the main() function below for an example. + */ + std::unique_ptr create_kem_decryption_op(Botan::RandomNumberGenerator& rng, + std::string_view params, + std::string_view provider) const override; + + /** + * In an actual implementation, this should return a serialized + * representation of the private keys. For instance, using some ASN.1 + * encoding to combine the two private keys. + */ + Botan::secure_vector private_key_bits() const override { + throw Botan::Not_Implemented("Key serialization is not supported"); + } + + std::unique_ptr public_key() const override { + return std::make_unique(m_kex_sk->public_key(), m_kem_sk->public_key()); + } + + const Botan::Private_Key& kex_private_key() const { return *m_kex_sk; } + + const Botan::Private_Key& kem_private_key() const { return *m_kem_sk; } + + private: + std::unique_ptr m_kex_sk; + std::unique_ptr m_kem_sk; +}; + +namespace { + +/** + * This implements the actual hybrid key encapsulation operation. It derives + * shared secrets from the key exchange algorithm (KEX) and the key + * encapsulation mechanism (KEM), and combines them using a Key Derivation + * Function (KDF). + */ +class Hybrid_Encryption_Operation : public Botan::PK_Ops::KEM_Encryption { + public: + Hybrid_Encryption_Operation(const Hybrid_PublicKey& hybrid_pk, std::string_view kdf) : + m_hybrid_pk(hybrid_pk), + m_kem_encryptor(hybrid_pk.kem_public_key(), "Raw"), + m_kdf(Botan::KDF::create_or_throw(kdf)) { + BOTAN_ASSERT_NONNULL(m_kdf); + } + + /** + * This returns the length of the encapsulated key in bytes. For such a + * hybrid key encapsulation, this comprises the length of the KEX's public + * key (ephemeral key pair) and the length of the KEM's encapsulated key. + */ + size_t encapsulated_key_length() const override { + return m_hybrid_pk.kex_public_key().public_key_bits().size() + m_kem_encryptor.encapsulated_key_length(); + } + + /** + * This returns the length of the output shared secret in bytes. It is + * the output length of the KDF, which acts as the "combiner" of the + * shared secrets of both algorithms. + */ + size_t shared_key_length(size_t desired_shared_key_length) const override { return desired_shared_key_length; } + + /** + * This method performs the actual hybrid key encapsulation operation. + */ + void kem_encrypt(std::span out_encapsed_key, + std::span out_shared_key, + Botan::RandomNumberGenerator& rng, + size_t desired_shared_key_length, + std::span salt) override { + // The basic idea of the hybrid operation: + // 1. Generate an ephemeral key pair for the key exchange algorithm, + // 2. and agree on a shared secret using the KEX's public key of the + // other party and the ephemeral private key, + // 3. Encapsulate a shared secret using the KEM's public key of the + // other party, resulting in a shared secret and its encapsulation, + // 4. Concatenate the ephemeral public key and the encapsulation to + // form a "hybrid encapsulation" (to be sent to the other party), + // 5. Concatenate the shared secrets of both algorithms and pass the + // result through a user-defined key derivation function to form a + // "hybrid shared secret" (to be used by the application). + + // 1. KEX: Generate an ephemeral key pair with the same parameters as + // the provided key exchange public key. + auto ephemeral_keypair = m_hybrid_pk.kex_public_key().generate_another(rng); + + // Note: Currently, we cannot pre-create the PK_Key_Agreement object in + // the constructor, because it requires an RNG object. + // + // TODO: fix this upstream by harmonizing the constructors of the + // PK_Key_Agreement and PK_KEM_Encryptor classes. + Botan::PK_Key_Agreement kex(*ephemeral_keypair, rng, "Raw"); + + // 2. KEX: Agree on a shared secret using the public key of the other + // party and our ephemeral private key. The ephemeral public + // key acts as the "encapsulation" of the key agreement. + // + // Note: kex.derive_key() does not have a std::span<> based overload to + // write straight into the output buffer. + // + // TODO: kex.derive_key() should allow a std::span<>-based out param, + // which would save a copy in this case. (See GH #3318) + const auto kex_shared_key = + kex.derive_key(0 /* no KDF */, m_hybrid_pk.kex_public_key().public_key_bits()).bits_of(); + const auto kex_encapsed_key = ephemeral_keypair->public_key_bits(); + + // 3. KEX: Encapsulate a shared secret using the KEM's public key, + // yielding a shared secret and its encapsulation. + const auto [kem_encapsed_key, kem_shared_key] = + Botan::KEM_Encapsulation::destructure(m_kem_encryptor.encrypt(rng)); + + // 4. Hybrid: Concatenate the ephemeral public key and the KEM's + // encapsulation to form a combined "hybrid encapsulation". + BOTAN_ASSERT_NOMSG(out_encapsed_key.size() == kex_encapsed_key.size() + kem_encapsed_key.size()); + std::copy(kex_encapsed_key.begin(), kex_encapsed_key.end(), out_encapsed_key.begin()); + std::copy( + kem_encapsed_key.begin(), kem_encapsed_key.end(), out_encapsed_key.begin() + kex_encapsed_key.size()); + + // 5. Hybrid: Combine the shared secrets of both algorithms. + Botan::secure_vector concat_shared_key; + concat_shared_key.insert(concat_shared_key.end(), kex_shared_key.begin(), kex_shared_key.end()); + concat_shared_key.insert(concat_shared_key.end(), kem_shared_key.begin(), kem_shared_key.end()); + + BOTAN_ASSERT_NOMSG(out_shared_key.size() >= desired_shared_key_length); + m_kdf->derive_key(out_shared_key.first(desired_shared_key_length), concat_shared_key, salt, {}); + } + + private: + const Hybrid_PublicKey& m_hybrid_pk; + Botan::PK_KEM_Encryptor m_kem_encryptor; + const std::unique_ptr m_kdf; +}; + +/** + * This implements the actual hybrid key decapsulation operation. It derives + * the shared secrets from the key exchange algorithm (KEX) and the key + * encapsulation mechanism (KEM), and combines them using a Key Derivation + * Function (KDF). + */ +class Hybrid_Decryption_Operation : public Botan::PK_Ops::KEM_Decryption { + public: + Hybrid_Decryption_Operation(const Hybrid_PrivateKey& hybrid_sk, + Botan::RandomNumberGenerator& rng, + std::string_view kdf) : + m_hybrid_sk(hybrid_sk), + m_key_agreement(hybrid_sk.kex_private_key(), rng, "Raw"), + m_kem_decryptor(hybrid_sk.kem_private_key(), rng, "Raw"), + m_kdf(Botan::KDF::create_or_throw(kdf)) { + BOTAN_ASSERT_NONNULL(m_kdf); + } + + /** + * This returns the length of the encapsulated key in bytes. For such a + * hybrid key encapsulation, this comprises the length of the KEX's public + * key (ephemeral key pair) and the length of the KEM's encapsulated key. + */ + size_t encapsulated_key_length() const override { + return m_hybrid_sk.kex_public_key().public_key_bits().size() + m_kem_decryptor.encapsulated_key_length(); + } + + /** + * This returns the length of the output shared secret in bytes. It is + * the output length of the KDF, which acts as the "combiner" of the + * shared secrets of both algorithms. + */ + size_t shared_key_length(size_t desired_shared_key_length) const override { return desired_shared_key_length; } + + /** + * This method performs the actual hybrid key decapsulation operation. + */ + void kem_decrypt(std::span out_shared_key, + std::span encapsulated_key, + size_t desired_shared_key_length, + std::span salt) override { + BOTAN_ASSERT_NOMSG(encapsulated_key.size() == encapsulated_key_length()); + + // The basic idea of the hybrid operation: + // 1. Extract the ephemeral public key and the KEM's encapsulation + // from the hybrid encapsulation, + // 2. Agree on a shared secret using the KEX's private key and the + // ephemeral public key (from the other party), + // 3. Decapsulate a shared secret using the KEM's private key and + // the KEM's encapsulation (from the other party), + // 4. Concatenate the shared secrets of both algorithms and pass the + // result through a user-defined key derivation function to form a + // "hybrid shared secret" (to be used by the application). + + // 1. Hybrid: Extract the ephemeral public key and the encapsulation. + const auto kex_encapsed_key = + encapsulated_key.subspan(0, m_hybrid_sk.kex_public_key().public_key_bits().size()); + const auto kem_encapsed_key = encapsulated_key.subspan(kex_encapsed_key.size()); + + // 2. KEX: Agree on a shared secret using the KEX's private key and the + // ephemeral public key of the other party. + const auto kex_shared_key = m_key_agreement.derive_key(0 /* no KDF */, kex_encapsed_key).bits_of(); + + // 3. KEM: Decapsulate a shared secret using the KEM's private key and + // the encapsulation of the other party. + const auto kem_shared_key = m_kem_decryptor.decrypt(kem_encapsed_key); + + // 4. Hybrid: Combine the shared secrets of both algorithms. + Botan::secure_vector concat_shared_key; + concat_shared_key.insert(concat_shared_key.end(), kex_shared_key.begin(), kex_shared_key.end()); + concat_shared_key.insert(concat_shared_key.end(), kem_shared_key.begin(), kem_shared_key.end()); + + BOTAN_ASSERT_NOMSG(out_shared_key.size() >= desired_shared_key_length); + m_kdf->derive_key(out_shared_key.first(desired_shared_key_length), concat_shared_key, salt, {}); + } + + private: + const Hybrid_PrivateKey& m_hybrid_sk; + Botan::PK_Key_Agreement m_key_agreement; + Botan::PK_KEM_Decryptor m_kem_decryptor; + std::unique_ptr m_kdf; +}; + +} // namespace + +std::unique_ptr Hybrid_PublicKey::create_kem_encryption_op(std::string_view params, + std::string_view) const { + return std::make_unique(*this, params); +} + +std::unique_ptr Hybrid_PrivateKey::create_kem_decryption_op( + Botan::RandomNumberGenerator& rng, std::string_view params, std::string_view) const { + return std::make_unique(*this, rng, params); +} + +std::unique_ptr Hybrid_PublicKey::generate_another(Botan::RandomNumberGenerator& rng) const { + return std::make_unique(m_kex_pk->generate_another(rng), m_kem_pk->generate_another(rng)); +} + +int main() { + Botan::AutoSeeded_RNG rng; + + // Alice generates two key pairs suitable for: + // 1) key exchange (X25519), and + // 2) key encapsulation (Kyber). + // + // She then combines them into a custom "hybrid" key pair that acts + // like a key encapsulation mechanism (KEM). + const auto private_key_of_alice = std::make_unique( + Botan::create_private_key("Curve25519", rng), Botan::create_private_key("Kyber", rng, "Kyber-768-r3")); + const auto public_key_of_alice = private_key_of_alice->public_key(); + + // Bob uses Alice's public key to encapsulate a shared secret, and + // derives a shared key from it using HKDF. + Botan::PK_KEM_Encryptor kem_enc(*public_key_of_alice, "HKDF(SHA-256)"); + const auto encapsulation_by_bob = kem_enc.encrypt(rng); + + // Alice decapsulates the shared secret from Bob's encapsulation using her + // private key, and derives a matching shared key using HKDF. + Botan::PK_KEM_Decryptor kem_dec(*private_key_of_alice, rng, "HKDF(SHA-256)"); + const auto shared_key = kem_dec.decrypt(encapsulation_by_bob.encapsulated_shared_key()); + + // Check that Alice and Bob now share the same secret + std::cout << "Alice's shared key: " << Botan::hex_encode(shared_key) << "\n" + << "Bob's shared key: " << Botan::hex_encode(encapsulation_by_bob.shared_key()) << "\n"; + + if(shared_key == encapsulation_by_bob.shared_key()) { + std::cout << '\n' << "Alice and Bob share the same secret!\n"; + return 0; + } else { + return 1; + } +} diff --git a/src/lib/prov/pkcs11/p11_ecdh.cpp b/src/lib/prov/pkcs11/p11_ecdh.cpp index f7881d6400d..a9849c2a03c 100644 --- a/src/lib/prov/pkcs11/p11_ecdh.cpp +++ b/src/lib/prov/pkcs11/p11_ecdh.cpp @@ -11,9 +11,9 @@ #if defined(BOTAN_HAS_ECDH) #include + #include #include #include - #include namespace Botan::PKCS11 { diff --git a/src/lib/prov/pkcs11/p11_ecdsa.cpp b/src/lib/prov/pkcs11/p11_ecdsa.cpp index 3fc4589c087..e374463b644 100644 --- a/src/lib/prov/pkcs11/p11_ecdsa.cpp +++ b/src/lib/prov/pkcs11/p11_ecdsa.cpp @@ -10,10 +10,10 @@ #if defined(BOTAN_HAS_ECDSA) + #include #include #include #include - #include namespace Botan::PKCS11 { diff --git a/src/lib/prov/pkcs11/p11_rsa.cpp b/src/lib/prov/pkcs11/p11_rsa.cpp index c891574ee0e..e54680d38cd 100644 --- a/src/lib/prov/pkcs11/p11_rsa.cpp +++ b/src/lib/prov/pkcs11/p11_rsa.cpp @@ -16,7 +16,6 @@ #include #include #include - #include #include namespace Botan::PKCS11 { diff --git a/src/lib/prov/tpm/tpm.cpp b/src/lib/prov/tpm/tpm.cpp index b8d9d537eab..3eeff4311b7 100644 --- a/src/lib/prov/tpm/tpm.cpp +++ b/src/lib/prov/tpm/tpm.cpp @@ -9,10 +9,10 @@ #include #include +#include #include #include #include -#include #include #include diff --git a/src/lib/pubkey/info.txt b/src/lib/pubkey/info.txt index f91d90faf11..bad0debd1d2 100644 --- a/src/lib/pubkey/info.txt +++ b/src/lib/pubkey/info.txt @@ -11,6 +11,7 @@ brief -> "Implementations of public key schemes" pk_algs.h pk_keys.h pk_ops_fwd.h +pk_ops.h pkcs8.h pubkey.h x509_key.h @@ -18,7 +19,6 @@ x509_key.h blinding.h -pk_ops.h pk_ops_impl.h workfactor.h diff --git a/src/lib/pubkey/mce/mce_internal.h b/src/lib/pubkey/mce/mce_internal.h index e34de9e438a..4e14ff75dcd 100644 --- a/src/lib/pubkey/mce/mce_internal.h +++ b/src/lib/pubkey/mce/mce_internal.h @@ -13,7 +13,7 @@ #define BOTAN_MCELIECE_INTERNAL_H_ #include -#include +#include #include namespace Botan { diff --git a/src/lib/pubkey/pk_keys.cpp b/src/lib/pubkey/pk_keys.cpp index 300fe05b2e3..336be14255c 100644 --- a/src/lib/pubkey/pk_keys.cpp +++ b/src/lib/pubkey/pk_keys.cpp @@ -10,8 +10,8 @@ #include #include #include +#include #include -#include namespace Botan { diff --git a/src/lib/pubkey/pk_ops.h b/src/lib/pubkey/pk_ops.h index 846ceb94bc7..d6a9f046d5e 100644 --- a/src/lib/pubkey/pk_ops.h +++ b/src/lib/pubkey/pk_ops.h @@ -16,6 +16,11 @@ * Unless you're doing something like that, you don't need anything * here. Instead use pubkey.h which wraps these types safely and * provides a stable application-oriented API. +* +* Note: This header was accidentally pulled from the public API between +* Botan 3.0.0 and 3.2.0, and then restored in 3.3.0. If you are +* maintaining an application which used this header in Botan 2.x, +* you should make sure to use Botan 3.3.0 or later when migrating. */ #include @@ -33,7 +38,7 @@ namespace PK_Ops { /** * Public key encryption interface */ -class Encryption { +class BOTAN_UNSTABLE_API Encryption { public: virtual secure_vector encrypt(const uint8_t msg[], size_t msg_len, RandomNumberGenerator& rng) = 0; @@ -47,7 +52,7 @@ class Encryption { /** * Public key decryption interface */ -class Decryption { +class BOTAN_UNSTABLE_API Decryption { public: virtual secure_vector decrypt(uint8_t& valid_mask, const uint8_t ciphertext[], @@ -61,7 +66,7 @@ class Decryption { /** * Public key signature verification interface */ -class Verification { +class BOTAN_UNSTABLE_API Verification { public: /** * Add more data to the message currently being signed @@ -86,7 +91,7 @@ class Verification { /** * Public key signature creation interface */ -class Signature { +class BOTAN_UNSTABLE_API Signature { public: /** * Add more data to the message currently being signed @@ -124,7 +129,7 @@ class Signature { /** * A generic key agreement operation (eg DH or ECDH) */ -class Key_Agreement { +class BOTAN_UNSTABLE_API Key_Agreement { public: virtual secure_vector agree( size_t key_len, const uint8_t other_key[], size_t other_key_len, const uint8_t salt[], size_t salt_len) = 0; @@ -137,7 +142,7 @@ class Key_Agreement { /** * KEM (key encapsulation) */ -class KEM_Encryption { +class BOTAN_UNSTABLE_API KEM_Encryption { public: virtual void kem_encrypt(std::span out_encapsulated_key, std::span out_shared_key, @@ -152,7 +157,7 @@ class KEM_Encryption { virtual ~KEM_Encryption() = default; }; -class KEM_Decryption { +class BOTAN_UNSTABLE_API KEM_Decryption { public: virtual void kem_decrypt(std::span out_shared_key, std::span encapsulated_key, diff --git a/src/lib/pubkey/pk_ops_impl.h b/src/lib/pubkey/pk_ops_impl.h index d95f960158b..990ec3e6a7f 100644 --- a/src/lib/pubkey/pk_ops_impl.h +++ b/src/lib/pubkey/pk_ops_impl.h @@ -10,8 +10,8 @@ #include #include +#include #include -#include namespace Botan::PK_Ops { diff --git a/src/lib/pubkey/pubkey.cpp b/src/lib/pubkey/pubkey.cpp index 35c970b0e53..903195b9003 100644 --- a/src/lib/pubkey/pubkey.cpp +++ b/src/lib/pubkey/pubkey.cpp @@ -10,11 +10,11 @@ #include #include #include +#include #include #include #include #include -#include #include namespace Botan { diff --git a/src/lib/pubkey/sm2/sm2_enc.cpp b/src/lib/pubkey/sm2/sm2_enc.cpp index 7985b2b7d0c..cb852e0d65d 100644 --- a/src/lib/pubkey/sm2/sm2_enc.cpp +++ b/src/lib/pubkey/sm2/sm2_enc.cpp @@ -11,9 +11,9 @@ #include #include #include +#include #include #include -#include #include namespace Botan { diff --git a/src/lib/pubkey/sphincsplus/sphincsplus_common/sphincsplus.cpp b/src/lib/pubkey/sphincsplus/sphincsplus_common/sphincsplus.cpp index c7e3fad5878..92a54840aac 100644 --- a/src/lib/pubkey/sphincsplus/sphincsplus_common/sphincsplus.cpp +++ b/src/lib/pubkey/sphincsplus/sphincsplus_common/sphincsplus.cpp @@ -6,9 +6,9 @@ * Botan is released under the Simplified BSD License (see license.txt) **/ -#include #include +#include #include #include #include diff --git a/src/lib/pubkey/xmss/xmss_signature_operation.h b/src/lib/pubkey/xmss/xmss_signature_operation.h index f500a0a1580..a3fa001902b 100644 --- a/src/lib/pubkey/xmss/xmss_signature_operation.h +++ b/src/lib/pubkey/xmss/xmss_signature_operation.h @@ -8,8 +8,8 @@ #ifndef BOTAN_XMSS_SIGNATURE_OPERATION_H_ #define BOTAN_XMSS_SIGNATURE_OPERATION_H_ +#include #include -#include #include #include #include diff --git a/src/lib/pubkey/xmss/xmss_verification_operation.h b/src/lib/pubkey/xmss/xmss_verification_operation.h index 4ff02510c97..db5e7d266dc 100644 --- a/src/lib/pubkey/xmss/xmss_verification_operation.h +++ b/src/lib/pubkey/xmss/xmss_verification_operation.h @@ -8,8 +8,8 @@ #ifndef BOTAN_XMSS_VERIFICATION_OPERATION_H_ #define BOTAN_XMSS_VERIFICATION_OPERATION_H_ +#include #include -#include #include namespace Botan {