Skip to content

Commit

Permalink
Ounsworth KEM Combiner
Browse files Browse the repository at this point in the history
  • Loading branch information
FAlbertDev committed Jun 10, 2024
1 parent f34a73a commit bd7fa71
Show file tree
Hide file tree
Showing 10 changed files with 1,551 additions and 3 deletions.
8 changes: 5 additions & 3 deletions src/lib/mac/kmac/kmac.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,11 @@ Key_Length_Specification KMAC::key_spec() const {
// KMAC supports key lengths from zero up to 2²⁰⁴⁰ (2^(2040)) bits:
// https://nvlpubs.nist.gov/nistpubs/specialpublications/nist.sp.800-185.pdf#page=28
//
// However, we restrict the key length to 64 bytes in order to avoid allocation of overly
// large memory stretches when client code works with the maximal key length.
return Key_Length_Specification(0, 64);
// However, we restrict the key length to 164 bytes in order to avoid allocation of overly
// large memory stretches when client code works with the maximal key length. 164 is the
// length of the default_salt of the one-step KDM with KMAC128.
// (see NIST SP 800-56C Rev. 2, Section 4.1, Implementation-Dependent Parameters 3.).
return Key_Length_Specification(0, 164);
}

bool KMAC::has_keying_material() const {
Expand Down
22 changes: 22 additions & 0 deletions src/lib/pubkey/ounsworth_kem_combiner/info.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<defines>
OUNSWORTH -> 20240528
</defines>

<module_info>
name -> "Ounsworth"
</module_info>

<header:public>
ounsworth.h
ounsworth_mode.h
</header:public>

<header:internal>
sp800_56c_helper.h
</header:internal>

<requires>
kex_to_kem_adapter
hybrid_kem
hkdf
</requires>
308 changes: 308 additions & 0 deletions src/lib/pubkey/ounsworth_kem_combiner/ounsworth.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,308 @@
/**
* Implementation of the Ounsworth KEM combiner (TS 103 744 - V1.1.1)
*
* (C) 2024 Jack Lloyd
* 2024 Fabian Albert - Rohde & Schwarz Cybersecurity
*
* Botan is released under the Simplified BSD License (see license.txt)
*/

#include <botan/ounsworth.h>

#include <botan/kdf.h>
#include <botan/kyber.h>
#include <botan/x25519.h>
#include <botan/internal/bit_ops.h>
#include <botan/internal/hybrid_kem_ops.h>
#include <botan/internal/int_utils.h>
#include <botan/internal/kex_to_kem_adapter.h>
#include <botan/internal/loadstor.h>
#include <botan/internal/sha3.h>
#include <botan/internal/sp800_56c_helper.h>

#include <algorithm>

namespace Botan {

namespace {

template <typename Alloc>
std::vector<uint8_t, Alloc> flatten(const std::vector<std::vector<uint8_t, Alloc>>& vec) {
std::vector<uint8_t, Alloc> flat;
flat.reserve(
reduce(vec, size_t(0), [](size_t acc, const std::vector<uint8_t, Alloc>& v) { return acc + v.size(); }));
for(const auto& v : vec) {
flat.insert(flat.end(), v.begin(), v.end());
}
return flat;
}

// 4.1. Length encoding (or right_encode of SP800-185 Section 2.3.1)
std::vector<uint8_t> rlen(std::span<const uint8_t> s) {
// 1. Let x = len(s)
size_t x = s.size();
// 1. Let n be the smallest positive integer for which 2^{8n} > x
size_t n = std::max(size_t(1), significant_bytes(x));
// 2. Let x_1, x_2, ..., x_n be the base-256 encoding of x satisfying:
// x = sum 28(n-i)x i, for i = 1 to n
// 3. Let O_i = uint8(x_i), for i = 1 to n
auto be_sz_encoding = store_be<std::array<uint8_t, sizeof(size_t)>>(x);
BOTAN_ASSERT_NOMSG(be_sz_encoding.size() >= n);
auto o_i = std::vector<uint8_t>(be_sz_encoding.end() - n, be_sz_encoding.end());
// 4. Let O_{n+1} = uint8(n)
o_i.push_back(checked_cast_to_or_throw<uint8_t, Invalid_Argument>(n, "Too many input bytes"));
// 5. rlen(s) = O_1 || O_2 || ... || O_n || O_{n+1}
return o_i;
}

void ounsworth_secret_combiner(std::span<uint8_t> out_shared_secret,
const Ounsworth_Mode& mode,
const std::vector<secure_vector<uint8_t>>& shared_secrets,
const std::vector<std::vector<uint8_t>>& ciphertexts,
std::span<const uint8_t> fixed_info,
std::span<const uint8_t> big_k) {
BOTAN_UNUSED(out_shared_secret, mode, shared_secrets, ciphertexts, fixed_info);
BOTAN_ASSERT_NOMSG(shared_secrets.size() == ciphertexts.size());
BOTAN_ASSERT_NOMSG(big_k.empty() || mode.is_mac_based_kdf());

std::vector<std::vector<uint8_t>> ss_len_encodings;
std::transform(shared_secrets.begin(),
shared_secrets.end(),
std::back_inserter(ss_len_encodings),
[](const secure_vector<uint8_t>& ss) { return rlen(ss); });

std::vector<std::vector<uint8_t>> ct_len_encodings;
std::transform(
ciphertexts.begin(), ciphertexts.end(), std::back_inserter(ct_len_encodings), [](const std::vector<uint8_t>& ct) {
return rlen(ct);
});

const size_t kdf_input_buffer_size =
reduce(shared_secrets, size_t(0), [](size_t acc, const secure_vector<uint8_t>& ss) { return acc + ss.size(); }) +
reduce(ciphertexts, size_t(0), [](size_t acc, const std::vector<uint8_t>& ct) { return acc + ct.size(); }) +
reduce(ss_len_encodings,
size_t(0),
[](size_t acc, const std::vector<uint8_t>& ss_len) { return acc + ss_len.size(); }) +
reduce(ct_len_encodings, size_t(0), [](size_t acc, const std::vector<uint8_t>& ct_len) {
return acc + ct_len.size();
});

secure_vector<uint8_t> kdf_input_buffer(kdf_input_buffer_size);
BufferStuffer kdf_stuffer(kdf_input_buffer);

// ct_1 || rlen(ct_1) || ss_1 || rlen(ss_1) || ct_2 || ...
for(size_t i = 0; i < shared_secrets.size(); ++i) {
kdf_stuffer.append(ciphertexts[i]);
kdf_stuffer.append(ct_len_encodings[i]);
kdf_stuffer.append(shared_secrets[i]);
kdf_stuffer.append(ss_len_encodings[i]);
}
BOTAN_ASSERT_NOMSG(kdf_stuffer.full());

std::visit(
overloaded{
[&](std::unique_ptr<MAC> mac) { kdm(out_shared_secret, *mac, kdf_input_buffer, fixed_info, big_k); },
[&](std::unique_ptr<HashFunction> hash) { kdm(out_shared_secret, *hash, kdf_input_buffer, fixed_info); }},
mode.kdf_instance(out_shared_secret.size()));
}

std::vector<uint8_t> get_mac_big_k(const Ounsworth_Mode& mode, std::string_view params) {
BOTAN_ARG_CHECK(params.empty() || mode.is_mac_based_kdf(),
"Parameters for Ounsworth KEM combiner with SHA3 must be empty.");

if(params.empty()) {
// Use default_salt of SP800-56C2, Section 4.1, Implementation-Dependent Parameters 3.
switch(mode.kdf_mode()) {
// If H(x) = KMAC128 [...] the default_salt shall be an all-zero string of 164 bytes.
case Ounsworth_Mode::KMAC128:
return std::vector<uint8_t>(164, 0);
// If H(x) = KMAC256 [...] the default_salt shall be an all-zero string of 132 bytes.
case Ounsworth_Mode::KMAC256:
return std::vector<uint8_t>(132, 0);
// Hash based KDFs do not use a K.
case Ounsworth_Mode::SHA3_256:
case Ounsworth_Mode::SHA3_512:
return {};
}
} else {
// By default K is the bytes of the input string.
return {params.begin(), params.end()};
}
}

/**
* The Ounsworth combiner supports two domain separation mechanisms:
*
* 1. The required fixed_info parameter. In Botan it is represented as the salt
* parameter. This is NOT the salt of the underlying SP800-56C2 One-Step KDF.
*
* 2. Optional and only for KMAC based constructions: The context-specific
* string K, which is the key of the MAC. This string is passed via the
* params parameter in the create_kem_en/decryption_op. If it is empty,
* the default value is used as defined in SP800-56C2, Section 4.1,
* Implementation-Dependent Parameters 3. (named "default_salt").
*/
class Ounsworth_Encryptor final : public KEM_Encryption_with_Combiner {
public:
Ounsworth_Encryptor(const Ounsworth_PublicKey& public_key,
std::string_view mac_big_k,
std::string_view provider) :
KEM_Encryption_with_Combiner(public_key.public_keys(), provider),
m_mac_big_k(get_mac_big_k(public_key.mode(), mac_big_k)),
m_mode(public_key.mode()) {}

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> fixed_info) override {
ounsworth_secret_combiner(out_shared_secret, m_mode, shared_secrets, ciphertexts, fixed_info, m_mac_big_k);
}

size_t shared_key_length(size_t desired_shared_key_len) const override { return desired_shared_key_len; }

private:
std::vector<uint8_t> m_mac_big_k;
Ounsworth_Mode m_mode;
};

class Ounsworth_Decryptor final : public KEM_Decryption_with_Combiner {
public:
Ounsworth_Decryptor(const Ounsworth_PrivateKey& private_key,
RandomNumberGenerator& rng,
const std::string_view mac_big_k,
const std::string_view provider) :
KEM_Decryption_with_Combiner(private_key.private_keys(), rng, provider),
m_mac_big_k(get_mac_big_k(private_key.mode(), mac_big_k)),
m_mode(private_key.mode()) {}

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*/, // TODO: already given by out_shared_secret.size()
std::span<const uint8_t> salt) override {
ounsworth_secret_combiner(out_shared_secret, m_mode, shared_secrets, ciphertexts, salt, m_mac_big_k);
}

size_t shared_key_length(size_t desired_shared_key_len) const override { return desired_shared_key_len; }

private:
std::vector<uint8_t> m_mac_big_k;
Ounsworth_Mode m_mode;
};

} // namespace

Ounsworth_PublicKey::Ounsworth_PublicKey(std::vector<std::unique_ptr<Public_Key>> pks, const Ounsworth_Mode& mode) :
Hybrid_PublicKey(std::move(pks)), m_mode(mode) {}

Ounsworth_PublicKey::Ounsworth_PublicKey(std::span<const uint8_t> pk_bytes, const Ounsworth_Mode& mode) :
Ounsworth_PublicKey(
[&]() {
BOTAN_ARG_CHECK(pk_bytes.size() == mode.pk_length(), "Invalid Ounsworth KEM combiner public key size");
BufferSlicer slicer(pk_bytes);
std::vector<std::unique_ptr<Public_Key>> pks;
std::transform(mode.sub_algos().begin(),
mode.sub_algos().end(),
std::back_inserter(pks),
[&](const Ounsworth_Mode::Sub_Algo& sub_algo) {
return sub_algo.load_public_key(slicer.take(sub_algo.raw_pk_length()));
});
BOTAN_ASSERT_NOMSG(slicer.empty());
return pks;
}(),
mode) {}

Ounsworth_PublicKey::Ounsworth_PublicKey(const AlgorithmIdentifier& alg_id, std::span<const uint8_t> pk_bytes) :
Ounsworth_PublicKey(pk_bytes, Ounsworth_Mode(alg_id)) {}

std::string Ounsworth_PublicKey::algo_name() const {
return "OunsworthKEMCombiner";
}

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

std::unique_ptr<Private_Key> Ounsworth_PublicKey::generate_another(RandomNumberGenerator& rng) const {
return std::make_unique<Ounsworth_PrivateKey>(rng, m_mode);
}

std::unique_ptr<PK_Ops::KEM_Encryption> Ounsworth_PublicKey::create_kem_encryption_op(std::string_view mac_big_k,
std::string_view provider) const {
return std::make_unique<Ounsworth_Encryptor>(*this, mac_big_k, provider);
}

std::unique_ptr<Ounsworth_PublicKey> Ounsworth_PublicKey::from_public_keys(std::vector<std::unique_ptr<Public_Key>> pks,
const Ounsworth_Mode& mode) {
return std::unique_ptr<Ounsworth_PublicKey>(new Ounsworth_PublicKey(std::move(pks), mode));
}

Ounsworth_PrivateKey::Ounsworth_PrivateKey(RandomNumberGenerator& rng, const Ounsworth_Mode& mode) :
Ounsworth_PrivateKey(
[&]() {
std::vector<std::unique_ptr<Private_Key>> sks;
std::transform(mode.sub_algos().begin(),
mode.sub_algos().end(),
std::back_inserter(sks),
[&](const Ounsworth_Mode::Sub_Algo& sub_algo) { return sub_algo.create_private_key(rng); });

std::vector<std::unique_ptr<Public_Key>> pks = extract_public_keys(sks);
return std::make_pair(std::move(pks), std::move(sks));
}(),
mode) {}

Ounsworth_PrivateKey::Ounsworth_PrivateKey(std::span<const uint8_t> key_bytes, const Ounsworth_Mode& mode) :
Ounsworth_PrivateKey(
[&] {
BOTAN_ARG_CHECK(key_bytes.size() == mode.sk_length(), "Invalid Ounsworth KEM combiner private key size");
std::vector<std::unique_ptr<Private_Key>> sks;
BufferSlicer slicer(key_bytes);
std::transform(mode.sub_algos().begin(),
mode.sub_algos().end(),
std::back_inserter(sks),
[&](const Ounsworth_Mode::Sub_Algo& sub_algo) {
return sub_algo.load_private_key(slicer.take(sub_algo.raw_sk_length()));
});
BOTAN_ASSERT_NOMSG(slicer.empty());

std::vector<std::unique_ptr<Public_Key>> pks = extract_public_keys(sks);
return std::make_pair(std::move(pks), std::move(sks));
}(),
mode) {}

Ounsworth_PrivateKey::Ounsworth_PrivateKey(const AlgorithmIdentifier& alg_id, std::span<const uint8_t> key_bytes) :
Ounsworth_PrivateKey(key_bytes, Ounsworth_Mode(alg_id)) {}

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

secure_vector<uint8_t> Ounsworth_PrivateKey::raw_private_key_bits() const {
secure_vector<uint8_t> sk_bytes;
sk_bytes.reserve(mode().sk_length());
for(const auto& sk : private_keys()) {
auto sk_bytes_part = sk->raw_private_key_bits();
sk_bytes.insert(sk_bytes.end(), sk_bytes_part.begin(), sk_bytes_part.end());
}
BOTAN_ASSERT(sk_bytes.size() == mode().sk_length(), "Some private key length does not match the expected length.");
return sk_bytes;
}

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

std::unique_ptr<PK_Ops::KEM_Decryption> Ounsworth_PrivateKey::create_kem_decryption_op(
RandomNumberGenerator& rng, std::string_view mac_big_k, std::string_view provider) const {
return std::make_unique<Ounsworth_Decryptor>(*this, rng, mac_big_k, provider);
}

Ounsworth_PrivateKey::Ounsworth_PrivateKey(
std::pair<std::vector<std::unique_ptr<Public_Key>>, std::vector<std::unique_ptr<Private_Key>>> key_pairs,
const Ounsworth_Mode& mode) :
Hybrid_PublicKey(std::move(key_pairs.first)),
Ounsworth_PublicKey(mode),
Hybrid_PrivateKey(std::move(key_pairs.second)) {}

} // namespace Botan
Loading

0 comments on commit bd7fa71

Please sign in to comment.