Skip to content

Commit

Permalink
X448 Implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
FAlbertDev committed Mar 25, 2024
1 parent cd66c3c commit 4c54efa
Show file tree
Hide file tree
Showing 10 changed files with 3,028 additions and 1 deletion.
1 change: 1 addition & 0 deletions src/build-data/oids.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
1.3.6.1.4.1.3029.1.2.1 = ElGamal
1.3.6.1.4.1.25258.1.3 = McEliece
1.3.101.110 = Curve25519
1.3.101.111 = Curve448
1.3.101.112 = Ed25519

# FrodoKEM OIDs are currently in Botan's private arc
Expand Down
4 changes: 3 additions & 1 deletion src/lib/asn1/oid_maps.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* OID maps
*
* This file was automatically generated by ./src/scripts/dev_tools/gen_oids.py on 2023-11-02
* This file was automatically generated by ./src/scripts/dev_tools/gen_oids.py on 2024-02-29
*
* All manual edits to this file will be lost. Edit the script
* then regenerate this source file.
Expand Down Expand Up @@ -107,6 +107,7 @@ std::unordered_map<std::string, std::string> OID_Map::load_oid2str_map() {
{"1.2.840.113549.2.9", "HMAC(SHA-256)"},
{"1.2.840.113549.3.7", "TripleDES/CBC"},
{"1.3.101.110", "Curve25519"},
{"1.3.101.111", "X448"},
{"1.3.101.112", "Ed25519"},
{"1.3.132.0.10", "secp256k1"},
{"1.3.132.0.30", "secp160r2"},
Expand Down Expand Up @@ -337,6 +338,7 @@ std::unordered_map<std::string, OID> OID_Map::load_str2oid_map() {
{"ChaCha20Poly1305", OID({1, 2, 840, 113549, 1, 9, 16, 3, 18})},
{"Compression.Zlib", OID({1, 2, 840, 113549, 1, 9, 16, 3, 8})},
{"Curve25519", OID({1, 3, 101, 110})},
{"X448", OID({1, 3, 101, 111})},
{"DES/CBC", OID({1, 3, 14, 3, 2, 7})},
{"DH", OID({1, 2, 840, 10046, 2, 1})},
{"DSA", OID({1, 2, 840, 10040, 4, 1})},
Expand Down
16 changes: 16 additions & 0 deletions src/lib/pubkey/curve448/x448/info.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<defines>
X448 -> 20240219
</defines>

<module_info>
name -> "X448"
brief -> "X448 key agreement algorithm"
</module_info>

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

<header:internal>
x448_internal.h
</header:internal>
129 changes: 129 additions & 0 deletions src/lib/pubkey/curve448/x448/x448.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* X448
* (C) 2024 Jack Lloyd
* 2024 Fabian Albert - Rohde & Schwarz Cybersecurity
*
* Botan is released under the Simplified BSD License (see license.txt)
*/

#include <botan/x448.h>

#include <botan/ber_dec.h>
#include <botan/der_enc.h>
#include <botan/rng.h>
#include <botan/internal/ct_utils.h>
#include <botan/internal/pk_ops_impl.h>
#include <botan/internal/x448_internal.h>

namespace Botan {

namespace {
void x448_basepoint_from_data(std::span<uint8_t, X448_LEN> mypublic, std::span<const uint8_t, X448_LEN> secret) {
auto bp = x448_basepoint(decode_scalar(secret));
auto bp_bytes = encode_point(bp);
copy_mem(mypublic, bp_bytes);
}

secure_vector<uint8_t> ber_decode_sk(std::span<const uint8_t> key_bits) {
secure_vector<uint8_t> decoded_bits;
BER_Decoder(key_bits).decode(decoded_bits, ASN1_Type::OctetString).verify_end();
BOTAN_ASSERT_NOMSG(decoded_bits.size() == X448_LEN);
return decoded_bits;
}

} // namespace

AlgorithmIdentifier X448_PublicKey::algorithm_identifier() const {
return AlgorithmIdentifier(object_identifier(), AlgorithmIdentifier::USE_EMPTY_PARAM);
}

bool X448_PublicKey::check_key(RandomNumberGenerator& /*rng*/, bool /*strong*/) const {
return true; // no tests possible?
}

std::vector<uint8_t> X448_PublicKey::public_key_bits() const {
return public_value();
}

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

X448_PublicKey::X448_PublicKey(const AlgorithmIdentifier& /*alg_id*/, std::span<const uint8_t> key_bits) :
X448_PublicKey(key_bits) {}

X448_PublicKey::X448_PublicKey(std::span<const uint8_t> pub) {
BOTAN_ARG_CHECK(pub.size() == X448_LEN, "Invalid size for X448 public key");
copy_mem(m_public, pub);
}

X448_PrivateKey::X448_PrivateKey(const AlgorithmIdentifier& /*alg_id*/, std::span<const uint8_t> key_bits) :
X448_PrivateKey(ber_decode_sk(key_bits)) {}

X448_PrivateKey::X448_PrivateKey(std::span<const uint8_t> secret_key) {
BOTAN_ARG_CHECK(secret_key.size() == X448_LEN, "Invalid size for X448 private key");
m_private = {secret_key.begin(), secret_key.end()};
x448_basepoint_from_data(m_public, std::span(m_private).first<X448_LEN>());
}

X448_PrivateKey::X448_PrivateKey(RandomNumberGenerator& rng) {
m_private.resize(X448_LEN);
rng.randomize(m_private);
x448_basepoint_from_data(m_public, std::span(m_private).first<X448_LEN>());
}

std::unique_ptr<Public_Key> X448_PrivateKey::public_key() const {
return std::make_unique<X448_PublicKey>(public_value());
}

secure_vector<uint8_t> X448_PrivateKey::private_key_bits() const {
return DER_Encoder().encode(m_private, ASN1_Type::OctetString).get_contents();
}

bool X448_PrivateKey::check_key(RandomNumberGenerator& /*rng*/, bool /*strong*/) const {
std::array<uint8_t, X448_LEN> public_point;
BOTAN_ASSERT_NOMSG(m_private.size() == X448_LEN);
x448_basepoint_from_data(public_point, std::span(m_private).first<X448_LEN>());
return CT::is_equal(public_point.data(), m_public.data(), m_public.size()).as_bool();
}

namespace {

/**
* X448 operation
*/
class X448_KA_Operation final : public PK_Ops::Key_Agreement_with_KDF {
public:
X448_KA_Operation(std::span<const uint8_t> sk, std::string_view kdf) :
PK_Ops::Key_Agreement_with_KDF(kdf), m_sk(sk.begin(), sk.end()) {
BOTAN_ARG_CHECK(sk.size() == X448_LEN, "Invalid size for X448 private key");
}

size_t agreed_value_size() const override { return X448_LEN; }

secure_vector<uint8_t> raw_agree(const uint8_t w_data[], size_t w_len) override {
std::span<const uint8_t> w(w_data, w_len);
BOTAN_ARG_CHECK(w.size() == X448_LEN, "Invalid size for X448 private key");
BOTAN_ASSERT_NOMSG(m_sk.size() == X448_LEN);
const auto k = decode_scalar(m_sk);
const auto u = decode_point(w);

return encode_point(x448(k, u));
}

private:
secure_vector<uint8_t> m_sk;
};

} // namespace

std::unique_ptr<PK_Ops::Key_Agreement> X448_PrivateKey::create_key_agreement_op(RandomNumberGenerator& /*rng*/,
std::string_view params,
std::string_view provider) const {
if(provider == "base" || provider.empty()) {
return std::make_unique<X448_KA_Operation>(m_private, params);
}
throw Provider_Not_Found(algo_name(), provider);
}

} // namespace Botan
108 changes: 108 additions & 0 deletions src/lib/pubkey/curve448/x448/x448.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* X448
* (C) 2024 Jack Lloyd
* 2024 Fabian Albert - Rohde & Schwarz Cybersecurity
*
* Botan is released under the Simplified BSD License (see license.txt)
*/
#ifndef BOTAN_X448_H_
#define BOTAN_X448_H_

#include <botan/pk_keys.h>

#include <array>

namespace Botan {
/**
* @brief A public key for the X448 key agreement scheme according to RFC 7748.
*/
class BOTAN_PUBLIC_API(3, 4) X448_PublicKey : public virtual Public_Key {
public:
/**
* Create a Curve25519 Public Key.
* @param alg_id the X.509 algorithm identifier
* @param key_bits DER encoded public key bits
*/
X448_PublicKey(const AlgorithmIdentifier& alg_id, std::span<const uint8_t> key_bits);

/**
* Create a X448 Public Key.
* @param pub 56-byte raw public key
*/
explicit X448_PublicKey(std::span<const uint8_t> pub);

std::string algo_name() const override { return "X448"; }

size_t estimated_strength() const override { return 224; }

size_t key_length() const override { return 448; }

bool check_key(RandomNumberGenerator& rng, bool strong) const override;

AlgorithmIdentifier algorithm_identifier() const override;

std::vector<uint8_t> public_value() const { return {m_public.begin(), m_public.end()}; }

std::vector<uint8_t> public_key_bits() const override;

bool supports_operation(PublicKeyOperation op) const override { return (op == PublicKeyOperation::KeyAgreement); }

std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator& rng) const final;

protected:
X448_PublicKey() = default;
std::array<uint8_t, 56> m_public;
};

BOTAN_DIAGNOSTIC_PUSH
BOTAN_DIAGNOSTIC_IGNORE_INHERITED_VIA_DOMINANCE

/**
* @brief A private key for the X448 key agreement scheme according to RFC 7748.
*/
class BOTAN_PUBLIC_API(3, 4) X448_PrivateKey final : public X448_PublicKey,
public virtual Private_Key,
public virtual PK_Key_Agreement_Key {
public:
/**
* Construct a private key from the specified parameters.
* @param alg_id the X.509 algorithm identifier
* @param key_bits PKCS #8 structure
*/
X448_PrivateKey(const AlgorithmIdentifier& alg_id, std::span<const uint8_t> key_bits);

/**
* Generate a private key.
* @param rng the RNG to use
*/
explicit X448_PrivateKey(RandomNumberGenerator& rng);

/**
* Construct a private key from the specified parameters.
* @param secret_key the private key
*/
explicit X448_PrivateKey(std::span<const uint8_t> secret_key);

std::vector<uint8_t> public_value() const override { return X448_PublicKey::public_key_bits(); }

secure_vector<uint8_t> raw_private_key_bits() const override { return {m_private.begin(), m_private.end()}; }

secure_vector<uint8_t> private_key_bits() const override;

std::unique_ptr<Public_Key> public_key() const override;

bool check_key(RandomNumberGenerator& rng, bool strong) const override;

std::unique_ptr<PK_Ops::Key_Agreement> create_key_agreement_op(RandomNumberGenerator& rng,
std::string_view params,
std::string_view provider) const override;

private:
secure_vector<uint8_t> m_private;
};

BOTAN_DIAGNOSTIC_POP

} // namespace Botan

#endif
89 changes: 89 additions & 0 deletions src/lib/pubkey/curve448/x448/x448_internal.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* X448 Internal
* (C) 2024 Jack Lloyd
* 2024 Fabian Albert - Rohde & Schwarz Cybersecurity
*
* Botan is released under the Simplified BSD License (see license.txt)
*/
#include <botan/internal/x448_internal.h>

#include <botan/internal/ct_utils.h>
#include <botan/internal/curve448_gf.h>

namespace Botan {

namespace {
uint64_t get_bit(const ScalarX448& scalar, size_t bit) {
return (scalar[bit / 8] >> (bit % 8)) & 1;
}
} // namespace

secure_vector<uint8_t> encode_point(const Point448& p) {
return {p.begin(), p.end()};
}

Point448 decode_point(std::span<const uint8_t> p_bytes) {
BOTAN_ARG_CHECK(p_bytes.size() == X448_LEN, "Invalid size for X448 point");
return typecast_copy<Point448>(p_bytes);
}

ScalarX448 decode_scalar(std::span<const uint8_t> scalar_bytes) {
BOTAN_ARG_CHECK(scalar_bytes.size() == X448_LEN, "Invalid size for X448 scalar");
auto buf = typecast_copy<ScalarX448>(scalar_bytes);

buf[0] &= 0xfc;
buf[55] |= 0x80;

return buf;
}

/// Multiply a scalar with the base group element (5)
Point448 x448_basepoint(const ScalarX448& k) {
const Point448 u({5});
return x448(k, u);
}

// Algorithm see RFC 7748, Section 5:
// https://datatracker.ietf.org/doc/html/rfc7748#section-5
Point448 x448(const ScalarX448& k, const Point448& u) {
const Gf448Elem a24 = 39081;

Gf448Elem x_1 = Gf448Elem(u.get());
Gf448Elem x_2 = 1;
Gf448Elem z_2 = 0;
Gf448Elem x_3 = Gf448Elem(u.get());
Gf448Elem z_3 = 1;
auto swap = CT::Mask<uint64_t>::cleared();

for(int16_t t = 448 - 1; t >= 0; --t) {
auto k_t = CT::Mask<uint64_t>::expand(get_bit(k, t));
swap ^= k_t;

x_2.ct_cond_swap(swap.as_bool(), x_3);
z_2.ct_cond_swap(swap.as_bool(), z_3);
swap = k_t;

const auto A = x_2 + z_2;
const auto AA = square(A);
const auto B = x_2 - z_2;
const auto BB = square(B);
const auto E = AA - BB;
const auto C = x_3 + z_3;
const auto D = x_3 - z_3;
const auto DA = D * A;
const auto CB = C * B;
x_3 = square(DA + CB);
z_3 = x_1 * square(DA - CB);
x_2 = AA * BB;
z_2 = E * (AA + a24 * E);
}

x_2.ct_cond_swap(swap.as_bool(), x_3);
z_2.ct_cond_swap(swap.as_bool(), z_3);

const auto res = x_2 / z_2;

return Point448(res.to_bytes());
}

} // namespace Botan
Loading

0 comments on commit 4c54efa

Please sign in to comment.