Skip to content

Commit

Permalink
X-Wing Hybrid KEM
Browse files Browse the repository at this point in the history
Based on draft-connolly-cfrg-xwing-kem-02
  • Loading branch information
FAlbertDev committed Aug 5, 2024
1 parent a6828dc commit be862c1
Show file tree
Hide file tree
Showing 9 changed files with 402 additions and 0 deletions.
19 changes: 19 additions & 0 deletions doc/api_ref/pubkey.rst
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,25 @@ 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.

X-Wing KEM Combiner
~~~~~~~~~~~~~~~~~~~

This combiner is based on
`draft-connolly-cfrg-xwing-kem-02 <https://datatracker.ietf.org/doc/draft-connolly-cfrg-xwing-kem/>`_.
X-Wing combines X25519 and ML-KEM-768 into a single KEM. It provides IND-CCA
security as long as at least one of the combined algorithms is secure. X-Wing
is easy to use since it doesn't require any specific configurations, but it
cannot be used with other algorithm combinations.

.. _creating_new_private_keys:

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

x-wing OBJECT IDENTIFIER ::= { publicKey 19 }

xmss OBJECT IDENTIFIER ::= { publicKey 8 }

-- The current dilithium implementation is compatible with the reference
Expand Down
3 changes: 3 additions & 0 deletions src/build-data/oids.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
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

# draft-connolly-cfrg-xwing-kem-02 (currently in Botan's private arc until published)
1.3.6.1.4.1.25258.1.19 = X-Wing

# 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
2 changes: 2 additions & 0 deletions src/lib/asn1/oid_maps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ 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.19", "X-Wing"},
{"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 @@ -529,6 +530,7 @@ std::unordered_map<std::string, OID> OID_Map::load_str2oid_map() {
{"Twofish/GCM", OID({1, 3, 6, 1, 4, 1, 25258, 3, 102})},
{"Twofish/OCB", OID({1, 3, 6, 1, 4, 1, 25258, 3, 2, 5})},
{"Twofish/SIV", OID({1, 3, 6, 1, 4, 1, 25258, 3, 4, 5})},
{"X-Wing", OID({1, 3, 6, 1, 4, 1, 25258, 1, 19})},
{"X25519", OID({1, 3, 101, 110})},
{"X448", OID({1, 3, 101, 111})},
{"X509v3.AnyPolicy", OID({2, 5, 29, 32, 0})},
Expand Down
22 changes: 22 additions & 0 deletions src/lib/pubkey/pk_algs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@
#include <botan/hss_lms.h>
#endif

#if defined(BOTAN_HAS_X_WING)
#include <botan/x_wing.h>
#endif

#if defined(BOTAN_HAS_XMSS_RFC8391)
#include <botan/xmss.h>
#endif
Expand Down Expand Up @@ -146,6 +150,12 @@ std::unique_ptr<Public_Key> load_public_key(const AlgorithmIdentifier& alg_id,
}
#endif

#if defined(BOTAN_HAS_X_WING)
if(alg_name == "X-Wing") {
return std::make_unique<X_Wing_PublicKey>(key_bits);
}
#endif

#if defined(BOTAN_HAS_ECDSA)
if(alg_name == "ECDSA") {
return std::make_unique<ECDSA_PublicKey>(alg_id, key_bits);
Expand Down Expand Up @@ -299,6 +309,12 @@ std::unique_ptr<Private_Key> load_private_key(const AlgorithmIdentifier& alg_id,
}
#endif

#if defined(BOTAN_HAS_X_WING)
if(alg_name == "X-Wing") {
return std::make_unique<X_Wing_PrivateKey>(key_bits);
}
#endif

#if defined(BOTAN_HAS_MCELIECE)
if(alg_name == "McEliece") {
return std::make_unique<McEliece_PrivateKey>(key_bits);
Expand Down Expand Up @@ -488,6 +504,12 @@ std::unique_ptr<Private_Key> create_private_key(std::string_view alg_name,
}
#endif

#if defined(BOTAN_HAS_X_WING)
if(alg_name == "X-Wing") {
return std::make_unique<X_Wing_PrivateKey>(rng);
}
#endif

#if defined(BOTAN_HAS_DILITHIUM) || defined(BOTAN_HAS_DILITHIUM_AES)
if(alg_name == "Dilithium" || alg_name == "Dilithium-") {
const auto mode = [&]() -> DilithiumMode {
Expand Down
20 changes: 20 additions & 0 deletions src/lib/pubkey/x_wing/info.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<defines>
X_WING -> 20240503
</defines>

<module_info>
name -> "X-Wing"
lifecycle -> "Experimental"
</module_info>

<header:public>
x_wing.h
</header:public>

<requires>
kex_to_kem_adapter
hybrid_kem
sha3
x25519
kyber
</requires>
186 changes: 186 additions & 0 deletions src/lib/pubkey/x_wing/x_wing.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
/**
* Implementation of
* X-Wing: general-purpose hybrid post-quantum KEM
*
* (C) 2024 Jack Lloyd
* 2024 Fabian Albert - Rohde & Schwarz Cybersecurity
*
* Botan is released under the Simplified BSD License (see license.txt)
*/
#include <botan/x_wing.h>

#include <botan/kyber.h>
#include <botan/x25519.h>
#include <botan/internal/hybrid_kem_ops.h>
#include <botan/internal/kex_to_kem_adapter.h>
#include <botan/internal/sha3.h>

namespace Botan {

namespace {

// TODO: We need ML_KEM IPD. Change the mode after ML_KEM is available.
const KyberMode X_WING_KYBER_MODE = KyberMode::Kyber768;
const size_t KYBER_PK_LEN = 1184;
const size_t KYBER_SK_LEN = 2400;

const size_t X25519_LEN = 32;
const size_t PK_LEN = KYBER_PK_LEN + X25519_LEN;
const size_t SK_LEN = KYBER_SK_LEN + 2 * X25519_LEN;

const size_t X_WING_SHARED_SECRET_LENGTH = 32;

// X-Wing draft Section 5.3: Combiner
void x_wing_secret_combiner(std::span<uint8_t> out_shared_secret,
const std::vector<secure_vector<uint8_t>>& shared_secrets,
const std::vector<std::vector<uint8_t>>& ciphertexts) {
BOTAN_ARG_CHECK(out_shared_secret.size() == X_WING_SHARED_SECRET_LENGTH, "Invalid output buffer size");
BOTAN_ARG_CHECK(shared_secrets.size() == 2 && ciphertexts.size() == 2,
"Mismatched number of shared secrets and ciphertexts");

// 5.3 XWingLabel: \./
// /^\ (as concatenated bytes)
const std::array<uint8_t, 6> x_wing_label = {'\\', '.', '/', '/', '^', '\\'};
SHA_3 hash(8 * X_WING_SHARED_SECRET_LENGTH);
hash.update(x_wing_label);
hash.update(shared_secrets[0]);
hash.update(shared_secrets[1]);
hash.update(ciphertexts[0]);
hash.update(ciphertexts[1]);
return hash.final(out_shared_secret);
}

class X_Wing_Encryptor final : public KEM_Encryption_with_Combiner {
public:
X_Wing_Encryptor(const std::vector<std::unique_ptr<Public_Key>>& public_keys, std::string_view provider) :
KEM_Encryption_with_Combiner(public_keys, provider) {}

void combine_shared_secrets(std::span<uint8_t> out_shared_secret,
const std::vector<secure_vector<uint8_t>>& shared_secrets,
const std::vector<std::vector<uint8_t>>& ciphertexts,
size_t /*desired_shared_key_len*/,
std::span<const uint8_t> /*salt*/) override {
x_wing_secret_combiner(out_shared_secret, shared_secrets, ciphertexts);
}

size_t shared_key_length(size_t /*desired_shared_key_len*/) const override { return X_WING_SHARED_SECRET_LENGTH; }
};

class X_Wing_Decryptor final : public KEM_Decryption_with_Combiner {
public:
X_Wing_Decryptor(const std::vector<std::unique_ptr<Private_Key>>& private_keys,
RandomNumberGenerator& rng,
const std::string_view provider) :
KEM_Decryption_with_Combiner(private_keys, rng, provider) {}

void combine_shared_secrets(std::span<uint8_t> out_shared_secret,
const std::vector<secure_vector<uint8_t>>& shared_secrets,
const std::vector<std::vector<uint8_t>>& ciphertexts,
size_t /*desired_shared_key_len*/,
std::span<const uint8_t> /*salt*/) override {
x_wing_secret_combiner(out_shared_secret, shared_secrets, ciphertexts);
}

size_t shared_key_length(size_t /*desired_shared_key_len*/) const override { return X_WING_SHARED_SECRET_LENGTH; }
};

} // namespace

X_Wing_PublicKey::X_Wing_PublicKey(std::vector<std::unique_ptr<Public_Key>> pks) : Hybrid_PublicKey(std::move(pks)) {}

X_Wing_PublicKey::X_Wing_PublicKey(std::span<const uint8_t> pk_bytes) :
X_Wing_PublicKey([&pk_bytes]() {
BOTAN_ARG_CHECK(pk_bytes.size() == PK_LEN, "Invalid X-Wing public key size");
BufferSlicer slicer(pk_bytes);
std::vector<std::unique_ptr<Public_Key>> pks;
pks.push_back(std::make_unique<Kyber_PublicKey>(slicer.take(KYBER_PK_LEN), X_WING_KYBER_MODE));
pks.push_back(std::make_unique<KEX_to_KEM_Adapter_PublicKey>(
std::make_unique<X25519_PublicKey>(slicer.take(X25519_LEN))));
BOTAN_ASSERT_NOMSG(slicer.empty());
return pks;
}()) {}

std::string X_Wing_PublicKey::algo_name() const {
return "X-Wing";
}

AlgorithmIdentifier X_Wing_PublicKey::algorithm_identifier() const {
return AlgorithmIdentifier(OID::from_string(algo_name()), AlgorithmIdentifier::USE_EMPTY_PARAM);
}

std::unique_ptr<Private_Key> X_Wing_PublicKey::generate_another(RandomNumberGenerator& rng) const {
return std::make_unique<X_Wing_PrivateKey>(rng);
}

std::unique_ptr<PK_Ops::KEM_Encryption> X_Wing_PublicKey::create_kem_encryption_op(std::string_view params,
std::string_view provider) const {
if(params != "Raw" && !params.empty()) {
throw Botan::Invalid_Argument("X-Wing encryption does not support KDFs");
}
return std::make_unique<X_Wing_Encryptor>(public_keys(), provider);
}

std::unique_ptr<X_Wing_PublicKey> X_Wing_PublicKey::from_public_keys(std::vector<std::unique_ptr<Public_Key>> pks) {
return std::unique_ptr<X_Wing_PublicKey>(new X_Wing_PublicKey(std::move(pks)));
}

X_Wing_PrivateKey::X_Wing_PrivateKey(RandomNumberGenerator& rng) :
X_Wing_PrivateKey([&rng]() {
std::vector<std::unique_ptr<Private_Key>> sks;
sks.push_back(std::make_unique<Kyber_PrivateKey>(rng, X_WING_KYBER_MODE));
sks.push_back(std::make_unique<KEX_to_KEM_Adapter_PrivateKey>(std::make_unique<X25519_PrivateKey>(rng)));

return std::make_pair(extract_public_keys(sks), std::move(sks));
}()) {}

X_Wing_PrivateKey::X_Wing_PrivateKey(std::span<const uint8_t> key_bytes) :
X_Wing_PrivateKey([&key_bytes] {
BOTAN_ARG_CHECK(key_bytes.size() == SK_LEN, "Invalid X-Wing private key size");
std::vector<std::unique_ptr<Private_Key>> sks;
BufferSlicer slicer(key_bytes);
sks.push_back(std::make_unique<Kyber_PrivateKey>(slicer.take(KYBER_SK_LEN), X_WING_KYBER_MODE));
sks.push_back(std::make_unique<KEX_to_KEM_Adapter_PrivateKey>(
std::make_unique<X25519_PrivateKey>(slicer.copy_as_secure_vector(X25519_LEN))));
auto pk_x_bytes = slicer.take(X25519_LEN);
BOTAN_ASSERT_NOMSG(slicer.empty());

auto pks = extract_public_keys(sks);
auto pk_x_bytes_from_sk = pks.at(1)->raw_public_key_bits();
BOTAN_ARG_CHECK(std::equal(pk_x_bytes.begin(), pk_x_bytes.end(), pk_x_bytes_from_sk.begin()),
"X25519 public key in secret key does not match with secret value");

return std::make_pair(std::move(pks), std::move(sks));
}()) {}

std::unique_ptr<Public_Key> X_Wing_PrivateKey::public_key() const {
return from_public_keys(extract_public_keys(private_keys()));
}

secure_vector<uint8_t> X_Wing_PrivateKey::raw_private_key_bits() const {
// X-Wing RFC Draft section 5.2:
// sk, pk = concat(sk_M, sk_X, pk_X), concat(pk_M, pk_X)
secure_vector<uint8_t> bits(SK_LEN);
return concat(private_keys().at(0)->raw_private_key_bits(),
private_keys().at(1)->raw_private_key_bits(),
public_keys().at(1)->raw_public_key_bits());
return bits;
}

bool X_Wing_PrivateKey::check_key(RandomNumberGenerator& rng, bool strong) const {
return Hybrid_PrivateKey::check_key(rng, strong);
}

std::unique_ptr<PK_Ops::KEM_Decryption> X_Wing_PrivateKey::create_kem_decryption_op(RandomNumberGenerator& rng,
std::string_view params,
std::string_view provider) const {
if(params != "Raw" && !params.empty()) {
throw Botan::Invalid_Argument("X-Wing decryption does not support KDFs");
}
return std::make_unique<X_Wing_Decryptor>(private_keys(), rng, provider);
}

X_Wing_PrivateKey::X_Wing_PrivateKey(
std::pair<std::vector<std::unique_ptr<Public_Key>>, std::vector<std::unique_ptr<Private_Key>>> key_pairs) :
Hybrid_PublicKey(std::move(key_pairs.first)), Hybrid_PrivateKey(std::move(key_pairs.second)) {}

} // namespace Botan
Loading

0 comments on commit be862c1

Please sign in to comment.