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

Ounsworth KEM Combiner #4119

Open
wants to merge 2 commits 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
21 changes: 21 additions & 0 deletions doc/api_ref/pubkey.rst
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,27 @@ A set of signature schemes based on elliptic curves. All are national standards
in their respective countries (Germany, South Korea, China, and Russia, resp),
and are completely obscure and unused outside of that context.

KEM Combiner
------------

A KEM Combiner is a key encapsulation mechanism (KEM) that combines multiple
KEMs into a single KEM. The resulting KEM is secure if at least one combined
KEM is secure. Usually, the KEM Combiner combines a classical KEM with a
post-quantum secure KEM. Note that every key exchange algorithm can also be
described as a KEM.

Ounsworth KEM Combiner
~~~~~~~~~~~~~~~~~~~~~~

This combiner is based on
`draft-ounsworth-cfrg-kem-combiners-05 <https://github.com/EntrustCorporation/draft-ounsworth-cfrg-kem-combiners/blob/475ff53eb8fb7213f6e5ab26dd23e5dc3203f7fa/draft-ounsworth-cfrg-kem-combiners.txt>`_
(Feb 2024). It is a generic combiner that achieves IND-CCA security if at least
one combined KEM is. Every KEM that implements Botan's private and public key
interfaces can be an ingredient of this combiner. However, some KEMs are
predefined for easier usage. The predefined KEMs are Kyber (Round 3) and
FrodoKEM for post-quantum security and X25519, X448, and ECDH (with various
named curves) as classical key exchange algorithms formulated as KEMs.

.. _creating_new_private_keys:

Creating New Private Keys
Expand Down
13 changes: 13 additions & 0 deletions doc/dev_ref/oids.rst
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,19 @@ Values currently assigned are::
kyber-768-90s OBJECT IDENTIFIER ::= { kyber-90s 2 }
kyber-1024-90s OBJECT IDENTIFIER ::= { kyber-90s 3 }

ounsworth-kem-combiner OBJECT IDENTIFIER ::= { publicKey 20 }

ounsworth-kem-combiner-kyber-768-r3-x25519-kmac-256 OBJECT IDENTIFIER ::= { ounsworth-kem-combiner 1 }
ounsworth-kem-combiner-kyber-1024-r3-x448-kmac-256 OBJECT IDENTIFIER ::= { ounsworth-kem-combiner 2 }

ounsworth-kem-combiner-kyber-512-r3-ecdh-secp256r1-kmac-128 OBJECT IDENTIFIER ::= { ounsworth-kem-combiner 3 }
ounsworth-kem-combiner-kyber-768-r3-ecdh-secp384r1-kmac-256 OBJECT IDENTIFIER ::= { ounsworth-kem-combiner 4 }
ounsworth-kem-combiner-kyber-1024-r3-ecdh-secp512r1-kmac-256 OBJECT IDENTIFIER ::= { ounsworth-kem-combiner 5 }

ounsworth-kem-combiner-frodokem-640-shake-ecdh-brainpool256r1-kmac-128 OBJECT IDENTIFIER ::= { ounsworth-kem-combiner 6 }
ounsworth-kem-combiner-frodokem-976-shake-ecdh-brainpool384r1-kmac-256 OBJECT IDENTIFIER ::= { ounsworth-kem-combiner 7 }
ounsworth-kem-combiner-frodokem-1344-shake-ecdh-brainpool512r1-kmac-256 OBJECT IDENTIFIER ::= { ounsworth-kem-combiner 8 }

xmss OBJECT IDENTIFIER ::= { publicKey 8 }

-- The current dilithium implementation is compatible with the reference
Expand Down
13 changes: 13 additions & 0 deletions src/build-data/oids.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,19 @@
1.3.6.1.4.1.25258.1.11.2 = Kyber-768-90s-r3
1.3.6.1.4.1.25258.1.11.3 = Kyber-1024-90s-r3

# Ounsworth KEM Combiner Draft (Feb 2024)
# https://github.com/EntrustCorporation/draft-ounsworth-cfrg-kem-combiners/blob/475ff53eb8fb7213f6e5ab26dd23e5dc3203f7fa/draft-ounsworth-cfrg-kem-combiners.txt
1.3.6.1.4.1.25258.1.20.1 = OunsworthKEMCombiner/Kyber-768-r3/X25519/KMAC-256
1.3.6.1.4.1.25258.1.20.2 = OunsworthKEMCombiner/Kyber-1024-r3/X448/KMAC-256

1.3.6.1.4.1.25258.1.20.3 = OunsworthKEMCombiner/Kyber-512-r3/ECDH-secp256r1/KMAC-128
1.3.6.1.4.1.25258.1.20.4 = OunsworthKEMCombiner/Kyber-768-r3/ECDH-secp384r1/KMAC-256
1.3.6.1.4.1.25258.1.20.5 = OunsworthKEMCombiner/Kyber-1024-r3/ECDH-secp521r1/KMAC-256

1.3.6.1.4.1.25258.1.20.6 = OunsworthKEMCombiner/FrodoKEM-640-SHAKE/ECDH-brainpool256r1/KMAC-128
1.3.6.1.4.1.25258.1.20.7 = OunsworthKEMCombiner/FrodoKEM-976-SHAKE/ECDH-brainpool384r1/KMAC-256
1.3.6.1.4.1.25258.1.20.8 = OunsworthKEMCombiner/FrodoKEM-1344-SHAKE/ECDH-brainpool512r1/KMAC-256

# Dilithium OIDs are currently in Botan's private arc
1.3.6.1.4.1.25258.1.9.1 = Dilithium-4x4-r3
1.3.6.1.4.1.25258.1.9.2 = Dilithium-6x5-r3
Expand Down
19 changes: 19 additions & 0 deletions src/lib/asn1/oid_maps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,14 @@ std::unordered_map<std::string, std::string> OID_Map::load_oid2str_map() {
{"1.3.6.1.4.1.25258.1.17.1", "eFrodoKEM-640-AES"},
{"1.3.6.1.4.1.25258.1.17.2", "eFrodoKEM-976-AES"},
{"1.3.6.1.4.1.25258.1.17.3", "eFrodoKEM-1344-AES"},
{"1.3.6.1.4.1.25258.1.20.1", "OunsworthKEMCombiner/Kyber-768-r3/X25519/KMAC-256"},
{"1.3.6.1.4.1.25258.1.20.2", "OunsworthKEMCombiner/Kyber-1024-r3/X448/KMAC-256"},
{"1.3.6.1.4.1.25258.1.20.3", "OunsworthKEMCombiner/Kyber-512-r3/ECDH-secp256r1/KMAC-128"},
{"1.3.6.1.4.1.25258.1.20.4", "OunsworthKEMCombiner/Kyber-768-r3/ECDH-secp384r1/KMAC-256"},
{"1.3.6.1.4.1.25258.1.20.5", "OunsworthKEMCombiner/Kyber-1024-r3/ECDH-secp521r1/KMAC-256"},
{"1.3.6.1.4.1.25258.1.20.6", "OunsworthKEMCombiner/FrodoKEM-640-SHAKE/ECDH-brainpool256r1/KMAC-128"},
{"1.3.6.1.4.1.25258.1.20.7", "OunsworthKEMCombiner/FrodoKEM-976-SHAKE/ECDH-brainpool384r1/KMAC-256"},
{"1.3.6.1.4.1.25258.1.20.8", "OunsworthKEMCombiner/FrodoKEM-1344-SHAKE/ECDH-brainpool512r1/KMAC-256"},
{"1.3.6.1.4.1.25258.1.3", "McEliece"},
{"1.3.6.1.4.1.25258.1.5", "XMSS-draft6"},
{"1.3.6.1.4.1.25258.1.6.1", "GOST-34.10-2012-256/SHA-256"},
Expand Down Expand Up @@ -432,6 +440,17 @@ std::unordered_map<std::string, OID> OID_Map::load_str2oid_map() {
{"Microsoft UPN", OID({1, 3, 6, 1, 4, 1, 311, 20, 2, 3})},
{"OpenPGP.Curve25519", OID({1, 3, 6, 1, 4, 1, 3029, 1, 5, 1})},
{"OpenPGP.Ed25519", OID({1, 3, 6, 1, 4, 1, 11591, 15, 1})},
{"OunsworthKEMCombiner/FrodoKEM-1344-SHAKE/ECDH-brainpool512r1/KMAC-256",
OID({1, 3, 6, 1, 4, 1, 25258, 1, 20, 8})},
{"OunsworthKEMCombiner/FrodoKEM-640-SHAKE/ECDH-brainpool256r1/KMAC-128",
OID({1, 3, 6, 1, 4, 1, 25258, 1, 20, 6})},
{"OunsworthKEMCombiner/FrodoKEM-976-SHAKE/ECDH-brainpool384r1/KMAC-256",
OID({1, 3, 6, 1, 4, 1, 25258, 1, 20, 7})},
{"OunsworthKEMCombiner/Kyber-1024-r3/ECDH-secp521r1/KMAC-256", OID({1, 3, 6, 1, 4, 1, 25258, 1, 20, 5})},
{"OunsworthKEMCombiner/Kyber-1024-r3/X448/KMAC-256", OID({1, 3, 6, 1, 4, 1, 25258, 1, 20, 2})},
{"OunsworthKEMCombiner/Kyber-512-r3/ECDH-secp256r1/KMAC-128", OID({1, 3, 6, 1, 4, 1, 25258, 1, 20, 3})},
{"OunsworthKEMCombiner/Kyber-768-r3/ECDH-secp384r1/KMAC-256", OID({1, 3, 6, 1, 4, 1, 25258, 1, 20, 4})},
{"OunsworthKEMCombiner/Kyber-768-r3/X25519/KMAC-256", OID({1, 3, 6, 1, 4, 1, 25258, 1, 20, 1})},
{"PBE-PKCS5v20", OID({1, 2, 840, 113549, 1, 5, 13})},
{"PBES2", OID({1, 2, 840, 113549, 1, 5, 13})},
{"PKCS5.PBKDF2", OID({1, 2, 840, 113549, 1, 5, 12})},
Expand Down
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
137 changes: 137 additions & 0 deletions src/lib/pubkey/hybrid_kem/hybrid_kem.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/**
* 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> 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;

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
Loading
Loading