Skip to content

Commit

Permalink
Use Valgrind for CMCE
Browse files Browse the repository at this point in the history
Use CT::poison/unpoison for CMCE. Simultaneously, fixes some
constant-time issues in the CMCE implementation that may leak some
information about the pivots (for the semi-systematic matrix form).

Co-Authored-By: René Meusel <[email protected]>
  • Loading branch information
FAlbertDev and reneme committed Jul 23, 2024
1 parent 9a6f4df commit fbcc57e
Show file tree
Hide file tree
Showing 13 changed files with 158 additions and 58 deletions.
10 changes: 9 additions & 1 deletion src/lib/pubkey/classic_mceliece/cmce.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <botan/internal/cmce_field_ordering.h>
#include <botan/internal/cmce_keys_internal.h>
#include <botan/internal/cmce_matrix.h>
#include <botan/internal/ct_utils.h>
#include <botan/internal/pk_ops_impl.h>

#include <algorithm>
Expand Down Expand Up @@ -87,19 +88,26 @@ std::unique_ptr<PK_Ops::KEM_Encryption> Classic_McEliece_PublicKey::create_kem_e
Classic_McEliece_PrivateKey::Classic_McEliece_PrivateKey(RandomNumberGenerator& rng,
Classic_McEliece_Parameter_Set param_set) {
auto params = Classic_McEliece_Parameters::create(param_set);
auto seed = rng.random_vec<CmceInitialSeed>(params.seed_len());
const auto seed = rng.random_vec<CmceInitialSeed>(params.seed_len());
CT::poison(seed);
std::tie(m_private, m_public) = Classic_McEliece_KeyPair_Internal::generate(params, seed).decompose_to_pair();

BOTAN_ASSERT_NONNULL(m_private);
BOTAN_ASSERT_NONNULL(m_public);
CT::unpoison_all(*m_private, *m_public);
}

Classic_McEliece_PrivateKey::Classic_McEliece_PrivateKey(std::span<const uint8_t> sk,
Classic_McEliece_Parameter_Set param_set) {
auto scope = CT::scoped_poison(sk);
auto params = Classic_McEliece_Parameters::create(param_set);
auto sk_internal = Classic_McEliece_PrivateKeyInternal::from_bytes(params, sk);
m_private = std::make_shared<Classic_McEliece_PrivateKeyInternal>(std::move(sk_internal));
// This creates and loads the public key, which is very large. Potentially, we could only load
// it on demand (since one may use the private key only for decapsulation without needing the public key).
// TODO: consider building a load-on-demand mechanism for the public key
m_public = Classic_McEliece_PublicKeyInternal::create_from_private_key(*m_private);
CT::unpoison_all(*m_public, *m_private);
}

Classic_McEliece_PrivateKey::Classic_McEliece_PrivateKey(const AlgorithmIdentifier& alg_id,
Expand Down
3 changes: 3 additions & 0 deletions src/lib/pubkey/classic_mceliece/cmce_decaps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ void Classic_McEliece_Decryptor::raw_kem_decrypt(std::span<uint8_t> out_shared_k
BOTAN_ARG_CHECK(out_shared_key.size() == m_key->params().hash_out_bytes(), "Invalid shared key output size");
BOTAN_ARG_CHECK(encapsulated_key.size() == m_key->params().ciphertext_size(), "Invalid ciphertext size");

auto scope = CT::scoped_poison(*m_key);

auto [ct, c1] = [&]() -> std::pair<CmceCodeWord, std::span<const uint8_t>> {
if(m_key->params().is_pc()) {
BufferSlicer encaps_key_slicer(encapsulated_key);
Expand Down Expand Up @@ -160,6 +162,7 @@ void Classic_McEliece_Decryptor::raw_kem_decrypt(std::span<uint8_t> out_shared_k
hash_func->update(e_bytes);
hash_func->update(encapsulated_key);
hash_func->final(out_shared_key);
CT::unpoison(out_shared_key);
}

} // namespace Botan
10 changes: 8 additions & 2 deletions src/lib/pubkey/classic_mceliece/cmce_encaps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ CmceCodeWord Classic_McEliece_Encryptor::encode(const Classic_McEliece_Parameter
std::optional<CmceErrorVector> Classic_McEliece_Encryptor::fixed_weight_vector_gen(
const Classic_McEliece_Parameters& params, RandomNumberGenerator& rng) const {
const auto rand = rng.random_vec((params.sigma1() / 8) * params.tau());
CT::poison(rand);
uint16_t mask_m = (uint32_t(1) << params.m()) - 1; // Only take m least significant bits
secure_vector<uint16_t> a_values;
a_values.reserve(params.tau());
Expand All @@ -36,7 +37,9 @@ std::optional<CmceErrorVector> Classic_McEliece_Encryptor::fixed_weight_vector_g
// This side channel only leaks which random elements are selected and which are dropped,
// but no information about their content is leaked.
d &= mask_m;
if(d < params.n() && a_values.size() < params.t()) {
bool d_in_range = d < params.n();
CT::unpoison(d_in_range);
if(d_in_range && a_values.size() < params.t()) {
a_values.push_back(d);
}
}
Expand All @@ -48,7 +51,9 @@ std::optional<CmceErrorVector> Classic_McEliece_Encryptor::fixed_weight_vector_g
// Step 4: Restart if not all a_i are distinct
for(size_t i = 1; i < params.t(); ++i) {
for(size_t j = 0; j < i; ++j) {
if(a_values.at(i) == a_values.at(j)) {
bool a_i_j_equal = a_values.at(i) == a_values.at(j);
CT::unpoison(a_i_j_equal);
if(a_i_j_equal) {
return std::nullopt;
}
}
Expand Down Expand Up @@ -120,6 +125,7 @@ void Classic_McEliece_Encryptor::raw_kem_encrypt(std::span<uint8_t> out_encapsul
hash_func->update(e_bytes);
hash_func->update(out_encapsulated_key);
hash_func->final(out_shared_key);
CT::unpoison_all(out_encapsulated_key, out_shared_key);
}

} // namespace Botan
38 changes: 22 additions & 16 deletions src/lib/pubkey/classic_mceliece/cmce_field_ordering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
#include <botan/mem_ops.h>
#include <botan/internal/loadstor.h>

#include <bitset>
#include <numeric>
#include <utility>
#include <vector>
Expand Down Expand Up @@ -97,6 +96,8 @@ std::pair<secure_vector<T1>, secure_vector<T2>> unzip(const std::span<std::pair<

/// @returns (vec[0],0), ..., (vec[n-1],n-1)
std::vector<std::pair<uint32_t, uint16_t>> enumerate(std::span<const uint32_t> vec) {
BOTAN_DEBUG_ASSERT(vec.size() < std::numeric_limits<uint16_t>::max());

std::vector<std::pair<uint32_t, uint16_t>> enumerated;

std::transform(vec.begin(), vec.end(), std::back_inserter(enumerated), [ctr = uint16_t(0)](uint32_t elem) mutable {
Expand All @@ -107,33 +108,30 @@ std::vector<std::pair<uint32_t, uint16_t>> enumerate(std::span<const uint32_t> v
}

/**
* @brief Create permutation pi as in (Section 8.2, Step 3).
*/
CmcePermutation create_pi(std::span<const uint32_t> a) {
* @brief Create permutation pi as in (Section 8.2, Step 3).
*
* @param a The vector that is sorted
*
* @return (pi sorted after a, a sorted after pi)
*/
std::pair<secure_vector<uint32_t>, CmcePermutation> create_pi(secure_vector<uint32_t> a) {
auto a_pi_zipped = enumerate(a);
CMCE_CT::bitonic_sort_pair(std::span(a_pi_zipped));

CmcePermutation pi_sorted;
std::tie(a, pi_sorted.get()) = unzip(std::span(a_pi_zipped));

return pi_sorted;
return std::make_pair(a, pi_sorted);
}

/**
* @brief Create a GF element from pi as in (Section 8.2, Step 4).
* Corresponds to the reverse bits of pi.
*/
Classic_McEliece_GF from_pi(CmcePermutationElement pi_elem, CmceGfMod modulus, size_t m) {
std::bitset<16> bits(pi_elem.get());
std::bitset<16> reversed_bits;

for(int i = 0; i < 16; ++i) {
reversed_bits[i] = bits[15 - i];
}

auto reversed_bits = ct_reverse_bits(pi_elem.get());
reversed_bits >>= (sizeof(uint16_t) * 8 - m);

return Classic_McEliece_GF(CmceGfElem(static_cast<uint16_t>(reversed_bits.to_ulong())), modulus);
return Classic_McEliece_GF(CmceGfElem(reversed_bits), modulus);
}

/**
Expand Down Expand Up @@ -244,15 +242,23 @@ secure_vector<uint16_t> generate_control_bits_internal(const secure_vector<uint1
return concat(f, z, l);
}

CT::Choice ct_has_adjacent_duplicates(std::span<const uint32_t> vec) {
CT::Mask<uint32_t> mask = CT::Mask<uint32_t>::cleared();
for(size_t i = 0; i < vec.size() - 1; ++i) {
mask |= CT::Mask<uint32_t>::is_equal(vec[i], vec[i + 1]);
}
return mask.as_choice();
}

} // anonymous namespace

std::optional<Classic_McEliece_Field_Ordering> Classic_McEliece_Field_Ordering::create_field_ordering(
const Classic_McEliece_Parameters& params, StrongSpan<const CmceOrderingBits> random_bits) {
BOTAN_ARG_CHECK(random_bits.size() == (params.sigma2() * params.q()) / 8, "Wrong random bits size");

auto a = load_le<secure_vector<uint32_t>>(random_bits); // contains a_0, a_1, ...
auto pi = create_pi(a);
if(std::adjacent_find(a.begin(), a.end()) != a.end()) {
auto [sorted_a, pi] = create_pi(std::move(a));
if(ct_has_adjacent_duplicates(sorted_a).as_bool()) {
return std::nullopt;
}

Expand Down
4 changes: 4 additions & 0 deletions src/lib/pubkey/classic_mceliece/cmce_field_ordering.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ class BOTAN_TEST_API Classic_McEliece_Field_Ordering {
*/
void permute_with_pivots(const Classic_McEliece_Parameters& params, const CmceColumnSelection& pivots);

void _const_time_poison() const { CT::poison(m_pi); }

void _const_time_unpoison() const { CT::unpoison(m_pi); }

private:
Classic_McEliece_Field_Ordering(CmcePermutation pi, CmceGfMod poly_f) : m_pi(std::move(pi)), m_poly_f(poly_f) {}

Expand Down
8 changes: 4 additions & 4 deletions src/lib/pubkey/classic_mceliece/cmce_gf.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,23 +86,23 @@ class BOTAN_TEST_API Classic_McEliece_GF {
* @brief Divide the element by @p other in GF(q). Constant time.
*/
Classic_McEliece_GF operator/(Classic_McEliece_GF other) const {
BOTAN_ASSERT_NOMSG(m_modulus == other.m_modulus);
BOTAN_DEBUG_ASSERT(m_modulus == other.m_modulus);
return *this * other.inv();
}

/**
* @brief Add @p other to the element. Constant time.
*/
Classic_McEliece_GF operator+(Classic_McEliece_GF other) const {
BOTAN_ASSERT_NOMSG(m_modulus == other.m_modulus);
BOTAN_DEBUG_ASSERT(m_modulus == other.m_modulus);
return Classic_McEliece_GF(m_elem ^ other.m_elem, m_modulus);
}

/**
* @brief Add @p other to the element. Constant time.
*/
Classic_McEliece_GF& operator+=(Classic_McEliece_GF other) {
BOTAN_ASSERT_NOMSG(m_modulus == other.m_modulus);
BOTAN_DEBUG_ASSERT(m_modulus == other.m_modulus);
m_elem ^= other.m_elem;
return *this;
}
Expand All @@ -119,7 +119,7 @@ class BOTAN_TEST_API Classic_McEliece_GF {
* @brief Multiply the element by @p other in GF(q). Constant time.
*/
Classic_McEliece_GF& operator*=(Classic_McEliece_GF other) {
BOTAN_ASSERT_NOMSG(m_modulus == other.m_modulus);
BOTAN_DEBUG_ASSERT(m_modulus == other.m_modulus);
*this = *this * other;
return *this;
}
Expand Down
10 changes: 7 additions & 3 deletions src/lib/pubkey/classic_mceliece/cmce_keys_internal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,13 @@ std::shared_ptr<Classic_McEliece_PublicKeyInternal> Classic_McEliece_PublicKeyIn
throw Decoding_Error("Cannot create public key from private key. Private key is invalid.");
}
auto& [pk_matrix, pivot] = pk_matrix_and_pivot.value();
if(!pivot.subvector(0, pivot.size() / 2).all() || !pivot.subvector(pivot.size() / 2).none()) {
// There should not be a pivot other than 0xff ff ff ff 00 00 00 00. Otherwise
// the gauss algorithm failed effectively.

// There should not be a pivot other than 0xff ff ff ff 00 00 00 00.
// Otherwise the gauss algorithm failed effectively.
const auto pivot_is_valid = (CT::Mask<uint8_t>::expand(pivot.subvector(0, pivot.size() / 2).all()) &
CT::Mask<uint8_t>::expand(pivot.subvector(pivot.size() / 2).none()))
.as_choice();
if(!pivot_is_valid.as_bool()) {
throw Decoding_Error("Cannot create public key from private key. Private key is invalid.");
}

Expand Down
6 changes: 6 additions & 0 deletions src/lib/pubkey/classic_mceliece/cmce_keys_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ class BOTAN_TEST_API Classic_McEliece_PublicKeyInternal {
*/
const Classic_McEliece_Parameters& params() const { return m_params; }

constexpr void _const_time_unpoison() const { CT::unpoison(m_matrix); }

private:
Classic_McEliece_Parameters m_params;
Classic_McEliece_Matrix m_matrix;
Expand Down Expand Up @@ -168,6 +170,10 @@ class BOTAN_TEST_API Classic_McEliece_PrivateKeyInternal {
*/
bool check_key() const;

constexpr void _const_time_poison() const { CT::poison_all(m_delta, m_c, m_g, m_field_ordering, m_s); }

constexpr void _const_time_unpoison() const { CT::unpoison_all(m_delta, m_c, m_g, m_field_ordering, m_s); }

private:
Classic_McEliece_Parameters m_params;
CmceKeyGenSeed m_delta;
Expand Down
Loading

0 comments on commit fbcc57e

Please sign in to comment.