Skip to content

Commit

Permalink
Abstraction layer for hybrid KEMs
Browse files Browse the repository at this point in the history
  • Loading branch information
FAlbertDev committed May 23, 2024
1 parent a265013 commit f9ade5e
Show file tree
Hide file tree
Showing 12 changed files with 653 additions and 273 deletions.
93 changes: 93 additions & 0 deletions src/lib/pubkey/hybrid_kem/hybrid_kem.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/**
* 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)) {
BOTAN_ARG_CHECK(m_pks.size() >= 2, "List of public keys must include at least two keys");
BOTAN_ARG_CHECK(std::all_of(m_pks.begin(), m_pks.end(), [](const auto& pk) { return pk != nullptr; }),
"List of public keys contains a nullptr");
BOTAN_ARG_CHECK(
std::all_of(m_pks.begin(),
m_pks.end(),
[](const auto& pk) { return pk->supports_operation(PublicKeyOperation::KeyEncapsulation); }),
"Some provided public key is not compatible with this hybrid wrapper");
m_key_length = reduce(m_pks, size_t(0), [](size_t kl, const auto& key) { return std::max(kl, key->key_length()); });
m_estimated_strength =
reduce(m_pks, size_t(0), [](size_t es, const auto& key) { return std::max(es, key->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::public_key_bits() const {
return reduce(public_keys(), std::vector<uint8_t>(), [](auto pkb, const auto& key) {
// Technically, this is not correct! `public_key_bits()` is meant to
// return a BER-encoded public key.
//
// TODO: Provide something like Public_Key::raw_public_key_bits() to
// reflect that difference.
return concat(pkb, key->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;
std::transform(
public_keys().begin(), public_keys().end(), std::back_inserter(new_private_keys), [&](const auto& public_key) {
return public_key->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");
BOTAN_ARG_CHECK(std::all_of(m_sks.begin(), m_sks.end(), [](const auto& sk) { return sk != nullptr; }),
"List of secret keys contains a nullptr");
BOTAN_ARG_CHECK(
std::all_of(m_sks.begin(),
m_sks.end(),
[](const auto& sk) { return 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& private_key : private_keys) {
BOTAN_ARG_CHECK(private_key != nullptr, "List of private keys contains a nullptr");
public_keys.push_back(private_key->public_key());
}
return public_keys;
}

} // namespace Botan
129 changes: 129 additions & 0 deletions src/lib/pubkey/hybrid_kem/hybrid_kem.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/**
* 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 {
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> public_key_bits() const override;

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;

std::vector<std::unique_ptr<Public_Key>> copy_public_keys() const;

/**
* @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
101 changes: 101 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,101 @@
#include <botan/internal/hybrid_kem_ops.h>

#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

0 comments on commit f9ade5e

Please sign in to comment.