Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Abstraction Layer for Hybrid KEMs #4067

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions src/lib/pubkey/hybrid_kem/hybrid_kem.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* Abstraction for a combined KEM public and private key.
*
* (C) 2024 Jack Lloyd
* 2024 Fabian Albert, René Meusel - Rohde & Schwarz Cybersecurity
*
* Botan is released under the Simplified BSD License (see license.txt)
*/
#include <botan/hybrid_kem.h>

#include <botan/pk_algs.h>
#include <botan/internal/fmt.h>
#include <botan/internal/kex_to_kem_adapter.h>
#include <botan/internal/pk_ops_impl.h>
#include <botan/internal/stl_util.h>

namespace Botan {

Hybrid_PublicKey::Hybrid_PublicKey(std::vector<std::unique_ptr<Public_Key>> pks) :
m_pks(std::move(pks)), m_key_length(0), m_estimated_strength(0) {
BOTAN_ARG_CHECK(m_pks.size() >= 2, "List of public keys must include at least two keys");
for(const auto& pk : m_pks) {
BOTAN_ARG_CHECK(pk != nullptr, "List of public keys contains a nullptr");
BOTAN_ARG_CHECK(pk->supports_operation(PublicKeyOperation::KeyEncapsulation),
fmt("Public key type '{}' does not support key encapsulation", pk->algo_name()).c_str());
m_key_length = std::max(m_key_length, pk->key_length());
m_estimated_strength = std::max(m_estimated_strength, pk->estimated_strength());
}
}

bool Hybrid_PublicKey::check_key(RandomNumberGenerator& rng, bool strong) const {
return reduce(public_keys(), true, [&](bool ckr, const auto& key) { return ckr && key->check_key(rng, strong); });
}

std::vector<uint8_t> Hybrid_PublicKey::raw_public_key_bits() const {
return reduce(public_keys(), std::vector<uint8_t>(), [](auto pkb, const auto& key) {
return concat(pkb, key->raw_public_key_bits());
});
}

bool Hybrid_PublicKey::supports_operation(PublicKeyOperation op) const {
return PublicKeyOperation::KeyEncapsulation == op;
}

std::vector<std::unique_ptr<Private_Key>> Hybrid_PublicKey::generate_other_sks_from_pks(
RandomNumberGenerator& rng) const {
std::vector<std::unique_ptr<Private_Key>> new_private_keys;
new_private_keys.reserve(public_keys().size());
for(const auto& pk : public_keys()) {
new_private_keys.push_back(pk->generate_another(rng));
}
return new_private_keys;
}

Hybrid_PrivateKey::Hybrid_PrivateKey(std::vector<std::unique_ptr<Private_Key>> private_keys) :
m_sks(std::move(private_keys)) {
BOTAN_ARG_CHECK(m_sks.size() >= 2, "List of secret keys must include at least two keys");
for(const auto& sk : m_sks) {
BOTAN_ARG_CHECK(sk != nullptr, "List of secret keys contains a nullptr");
BOTAN_ARG_CHECK(sk->supports_operation(PublicKeyOperation::KeyEncapsulation),
"Some provided secret key is not compatible with this hybrid wrapper");
}
}

secure_vector<uint8_t> Hybrid_PrivateKey::private_key_bits() const {
throw Not_Implemented("Hybrid private keys cannot be serialized");
}

bool Hybrid_PrivateKey::check_key(RandomNumberGenerator& rng, bool strong) const {
return reduce(private_keys(), true, [&](bool ckr, const auto& key) { return ckr && key->check_key(rng, strong); });
}

std::vector<std::unique_ptr<Public_Key>> Hybrid_PrivateKey::extract_public_keys(
const std::vector<std::unique_ptr<Private_Key>>& private_keys) {
std::vector<std::unique_ptr<Public_Key>> public_keys;
public_keys.reserve(private_keys.size());
for(const auto& sk : private_keys) {
BOTAN_ARG_CHECK(sk != nullptr, "List of private keys contains a nullptr");
public_keys.push_back(sk->public_key());
}
return public_keys;
}

} // namespace Botan
135 changes: 135 additions & 0 deletions src/lib/pubkey/hybrid_kem/hybrid_kem.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/**
* Abstraction for a combined KEM public and private key.
*
* (C) 2024 Jack Lloyd
* 2024 Fabian Albert, René Meusel - Rohde & Schwarz Cybersecurity
*
* Botan is released under the Simplified BSD License (see license.txt)
*/

#ifndef BOTAN_HYBRID_KEM_H_
#define BOTAN_HYBRID_KEM_H_

#include <botan/pk_algs.h>
#include <botan/pk_keys.h>
#include <botan/pubkey.h>

#include <memory>
#include <vector>

namespace Botan {

/**
* @brief Abstraction for a combined KEM public key.
*
* Two or more KEM public keys are combined into a single KEM public key. Derived classes
* must implement the abstract methods to provide the encryption operation, e.g. by
* specifying how encryption results are combined to the ciphertext and how a KEM combiner
* is applied to derive the shared secret using the individual shared secrets, ciphertexts,
* and other context information.
*/
class BOTAN_TEST_API Hybrid_PublicKey : public virtual Public_Key {
reneme marked this conversation as resolved.
Show resolved Hide resolved
public:
/**
* @brief Constructor for a list of multiple KEM public keys.
*
* To use KEX algorithms use the KEX_to_KEM_Adapter_PublicKey.
* @param public_keys List of public keys to combine
*/
explicit Hybrid_PublicKey(std::vector<std::unique_ptr<Public_Key>> public_keys);

Hybrid_PublicKey(Hybrid_PublicKey&&) = default;
Hybrid_PublicKey(const Hybrid_PublicKey&) = delete;
Hybrid_PublicKey& operator=(Hybrid_PublicKey&&) = default;
Hybrid_PublicKey& operator=(const Hybrid_PublicKey&) = delete;
~Hybrid_PublicKey() override = default;

size_t estimated_strength() const override { return m_estimated_strength; }

size_t key_length() const override { return m_key_length; }

bool check_key(RandomNumberGenerator& rng, bool strong) const override;

std::vector<uint8_t> raw_public_key_bits() const override;

/**
* @brief Return the public key bits of this hybrid key as the concatenated
* bytes of the individual public keys (without encoding).
*
* @return the public key bytes
*/
std::vector<uint8_t> public_key_bits() const override { return raw_public_key_bits(); }

bool supports_operation(PublicKeyOperation op) const override;

/// @returns the public keys combined in this hybrid key
const std::vector<std::unique_ptr<Public_Key>>& public_keys() const { return m_pks; }

protected:
// Default constructor used for virtual inheritance to prevent, that the derived class
// calls the constructor twice.
Hybrid_PublicKey() = default;

/**
* @brief Helper function for generate_another. Generate a new private key for each
* public key in this hybrid key.
*/
std::vector<std::unique_ptr<Private_Key>> generate_other_sks_from_pks(RandomNumberGenerator& rng) const;

private:
std::vector<std::unique_ptr<Public_Key>> m_pks;

size_t m_key_length;
size_t m_estimated_strength;
};

BOTAN_DIAGNOSTIC_PUSH
BOTAN_DIAGNOSTIC_IGNORE_INHERITED_VIA_DOMINANCE

/**
* @brief Abstraction for a combined KEM private key.
*
* Two or more KEM private keys are combined into a single KEM private key. Derived classes
* must implement the abstract methods to provide the decryption operation, e.g. by
* specifying how a KEM combiner is applied to derive the shared secret using the
* individual shared secrets, ciphertexts, and other context information.
*/
class BOTAN_TEST_API Hybrid_PrivateKey : virtual public Private_Key {
public:
Hybrid_PrivateKey(const Hybrid_PrivateKey&) = delete;
Hybrid_PrivateKey& operator=(const Hybrid_PrivateKey&) = delete;

Hybrid_PrivateKey(Hybrid_PrivateKey&&) = default;
Hybrid_PrivateKey& operator=(Hybrid_PrivateKey&&) = default;

~Hybrid_PrivateKey() override = default;

/**
* @brief Constructor for a list of multiple KEM private keys.
*
* To use KEX algorithms use the KEX_to_KEM_Adapter_PrivateKey.
* @param private_keys List of private keys to combine
*/
Hybrid_PrivateKey(std::vector<std::unique_ptr<Private_Key>> private_keys);

/// Disabled by default
secure_vector<uint8_t> private_key_bits() const override;

/// @returns the private keys combined in this hybrid key
const std::vector<std::unique_ptr<Private_Key>>& private_keys() const { return m_sks; }

bool check_key(RandomNumberGenerator& rng, bool strong) const override;

protected:
static std::vector<std::unique_ptr<Public_Key>> extract_public_keys(
const std::vector<std::unique_ptr<Private_Key>>& private_keys);

private:
std::vector<std::unique_ptr<Private_Key>> m_sks;
};

BOTAN_DIAGNOSTIC_POP

} // namespace Botan

#endif
109 changes: 109 additions & 0 deletions src/lib/pubkey/hybrid_kem/hybrid_kem_ops.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/**
* Abstraction for a combined KEM encryptors and decryptors.
*
* (C) 2024 Jack Lloyd
* 2024 Fabian Albert, René Meusel - Rohde & Schwarz Cybersecurity
*
* Botan is released under the Simplified BSD License (see license.txt)
*/
#include <botan/internal/hybrid_kem_ops.h>
FAlbertDev marked this conversation as resolved.
Show resolved Hide resolved

#include <botan/internal/stl_util.h>

namespace Botan {

KEM_Encryption_with_Combiner::KEM_Encryption_with_Combiner(const std::vector<std::unique_ptr<Public_Key>>& public_keys,
std::string_view provider) :
m_encapsulated_key_length(0) {
m_encryptors.reserve(public_keys.size());
for(const auto& pk : public_keys) {
const auto& newenc = m_encryptors.emplace_back(*pk, "Raw", provider);
m_encapsulated_key_length += newenc.encapsulated_key_length();
}
}

void KEM_Encryption_with_Combiner::kem_encrypt(std::span<uint8_t> out_encapsulated_key,
std::span<uint8_t> out_shared_key,
RandomNumberGenerator& rng,
size_t desired_shared_key_len,
std::span<const uint8_t> salt) {
BOTAN_ARG_CHECK(out_encapsulated_key.size() == encapsulated_key_length(),
"Encapsulated key output buffer has wrong size");
BOTAN_ARG_CHECK(out_shared_key.size() == shared_key_length(desired_shared_key_len),
"Shared key output buffer has wrong size");

std::vector<secure_vector<uint8_t>> shared_secrets;
shared_secrets.reserve(m_encryptors.size());

std::vector<std::vector<uint8_t>> ciphertexts;
ciphertexts.reserve(m_encryptors.size());

for(auto& encryptor : m_encryptors) {
auto [ct, ss] = KEM_Encapsulation::destructure(encryptor.encrypt(rng, 0 /* no KDF */));
shared_secrets.push_back(std::move(ss));
ciphertexts.push_back(std::move(ct));
}
combine_ciphertexts(out_encapsulated_key, ciphertexts, salt);
combine_shared_secrets(out_shared_key, shared_secrets, ciphertexts, desired_shared_key_len, salt);
}

void KEM_Encryption_with_Combiner::combine_ciphertexts(std::span<uint8_t> out_ciphertext,
const std::vector<std::vector<uint8_t>>& ciphertexts,
std::span<const uint8_t> salt) {
BOTAN_ARG_CHECK(salt.empty(), "Salt not supported by this KEM");
BOTAN_ARG_CHECK(ciphertexts.size() == m_encryptors.size(), "Invalid number of ciphertexts");
BOTAN_ARG_CHECK(out_ciphertext.size() == encapsulated_key_length(), "Invalid output buffer size");
BufferStuffer ct_stuffer(out_ciphertext);
for(size_t idx = 0; idx < ciphertexts.size(); idx++) {
BOTAN_ARG_CHECK(ciphertexts.at(idx).size() == m_encryptors.at(idx).encapsulated_key_length(),
"Invalid ciphertext length");
ct_stuffer.append(ciphertexts.at(idx));
}
BOTAN_ASSERT_NOMSG(ct_stuffer.full());
}

KEM_Decryption_with_Combiner::KEM_Decryption_with_Combiner(
const std::vector<std::unique_ptr<Private_Key>>& private_keys,
RandomNumberGenerator& rng,
std::string_view provider) :
m_encapsulated_key_length(0) {
m_decryptors.reserve(private_keys.size());
for(const auto& sk : private_keys) {
const auto& newenc = m_decryptors.emplace_back(*sk, rng, "Raw", provider);
m_encapsulated_key_length += newenc.encapsulated_key_length();
}
}

void KEM_Decryption_with_Combiner::kem_decrypt(std::span<uint8_t> out_shared_key,
std::span<const uint8_t> encapsulated_key,
size_t desired_shared_key_len,
std::span<const uint8_t> salt) {
BOTAN_ARG_CHECK(encapsulated_key.size() == encapsulated_key_length(), "Invalid encapsulated key length");
BOTAN_ARG_CHECK(out_shared_key.size() == shared_key_length(desired_shared_key_len), "Invalid output buffer size");

std::vector<secure_vector<uint8_t>> shared_secrets;
shared_secrets.reserve(m_decryptors.size());
auto ciphertexts = split_ciphertexts(encapsulated_key);
BOTAN_ASSERT(ciphertexts.size() == m_decryptors.size(), "Correct number of ciphertexts");

for(size_t idx = 0; idx < m_decryptors.size(); idx++) {
shared_secrets.push_back(m_decryptors.at(idx).decrypt(ciphertexts.at(idx), 0 /* no KDF */));
}

combine_shared_secrets(out_shared_key, shared_secrets, ciphertexts, desired_shared_key_len, salt);
}

std::vector<std::vector<uint8_t>> KEM_Decryption_with_Combiner::split_ciphertexts(
std::span<const uint8_t> concat_ciphertext) {
BOTAN_ARG_CHECK(concat_ciphertext.size() == encapsulated_key_length(), "Wrong ciphertext length");
std::vector<std::vector<uint8_t>> ciphertexts;
ciphertexts.reserve(m_decryptors.size());
BufferSlicer ct_slicer(concat_ciphertext);
for(const auto& decryptor : m_decryptors) {
ciphertexts.push_back(ct_slicer.copy_as_vector(decryptor.encapsulated_key_length()));
}
BOTAN_ASSERT_NOMSG(ct_slicer.empty());
return ciphertexts;
}

} // namespace Botan
Loading
Loading