-
Notifications
You must be signed in to change notification settings - Fork 574
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
a265013
commit b4e0875
Showing
12 changed files
with
596 additions
and
271 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
/** | ||
* 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/pubkey.h> | ||
#include <botan/internal/pk_ops_impl.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: | ||
explicit Hybrid_PublicKey(std::vector<std::unique_ptr<Public_Key>> pks); | ||
|
||
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 auto& 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. | ||
*/ | ||
// template <typename PK_Logic, typename SK_Logic> | ||
class BOTAN_TEST_API Hybrid_PrivateKey : virtual public Private_Key { | ||
public: | ||
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 auto& 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.