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

Enable the post-quantum x25519+ML-KEM-768 TLS 1.3 ciphersuite by default #4305

Open
wants to merge 1 commit 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
8 changes: 0 additions & 8 deletions src/bogo_shim/bogo_shim.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1004,14 +1004,6 @@ class Shim_Policy final : public Botan::TLS::Policy {
if(group == Botan::TLS::Group_Params::HYBRID_X25519_KYBER_768_R3_OQS) {
groups.push_back(group);
}

// TODO: once `TLS::Policy::key_exchange_groups()` contains it by
// default, remove this explicit check.
//
// See: https://github.com/randombit/botan/pull/4305
if(group == Botan::TLS::Group_Params::HYBRID_X25519_ML_KEM_768) {
groups.push_back(group);
}
}

return groups;
Expand Down
11 changes: 7 additions & 4 deletions src/cli/tls_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,14 +99,17 @@ class Callbacks : public Botan::TLS::Callbacks {
void tls_session_activated() override { output() << "Handshake complete\n"; }

void tls_session_established(const Botan::TLS::Session_Summary& session) override {
output() << "Handshake complete, " << session.version().to_string() << " using "
<< session.ciphersuite().to_string();
output() << "Handshake complete, " << session.version().to_string() << "\n";

if(const auto& psk = session.external_psk_identity()) {
output() << " (utilized PSK identity: " << maybe_hex_encode(psk.value()) << ")";
output() << "Utilized PSK identity: " << maybe_hex_encode(psk.value()) << "\n";
}

output() << std::endl;
output() << "Negotiated ciphersuite " << session.ciphersuite().to_string() << "\n";

if(auto kex_params = session.kex_parameters()) {
output() << "Key exchange using " << *kex_params << "\n";
}

if(const auto& session_id = session.session_id(); !session_id.empty()) {
output() << "Session ID " << Botan::hex_encode(session_id.get()) << "\n";
Expand Down
83 changes: 83 additions & 0 deletions src/lib/tls/tls_algos.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,89 @@ Auth_Method auth_method_from_string(std::string_view str) {
throw Invalid_Argument(fmt("Unknown TLS signature method '{}'", str));
}

bool Group_Params::is_available() const {
#if !defined(BOTAN_HAS_X25519)
if(is_x25519()) {
return false;
}
if(is_pqc_hybrid() && pqc_hybrid_ecc() == Group_Params_Code::X25519) {
return false;
}
#endif

#if !defined(BOTAN_HAS_X448)
if(is_x448()) {
return false;
}
if(is_pqc_hybrid() && pqc_hybrid_ecc() == Group_Params_Code::X448) {
return false;
}
#endif

#if !defined(BOTAN_HAS_DIFFIE_HELLMAN)
if(is_in_ffdhe_range()) {
return false;
}
#endif

#if !defined(BOTAN_HAS_KYBER_ROUND3)
if(is_pure_kyber_r3() || is_pqc_hybrid_kyber_r3()) {
return false;
}
#endif

#if !defined(BOTAN_HAS_ML_KEM)
if(is_pqc_hybrid_ml_kem()) {
return false;
}
#endif

#if !defined(BOTAN_HAS_FRODOKEM)
if(is_pure_frodokem() || is_pqc_hybrid_frodokem()) {
return false;
}
#endif

return true;
}

std::optional<Group_Params_Code> Group_Params::pqc_hybrid_ecc() const {
switch(m_code) {
case Group_Params_Code::HYBRID_X25519_ML_KEM_768:
case Group_Params_Code::HYBRID_X25519_KYBER_512_R3_CLOUDFLARE:
case Group_Params_Code::HYBRID_X25519_KYBER_512_R3_OQS:
case Group_Params_Code::HYBRID_X25519_KYBER_768_R3_OQS:
case Group_Params_Code::HYBRID_X25519_eFRODOKEM_640_SHAKE_OQS:
case Group_Params_Code::HYBRID_X25519_eFRODOKEM_640_AES_OQS:
return Group_Params_Code::X25519;

case Group_Params_Code::HYBRID_X448_KYBER_768_R3_OQS:
case Group_Params_Code::HYBRID_X448_eFRODOKEM_976_SHAKE_OQS:
case Group_Params_Code::HYBRID_X448_eFRODOKEM_976_AES_OQS:
return Group_Params_Code::X448;

case Group_Params_Code::HYBRID_SECP256R1_ML_KEM_768:
case Group_Params_Code::HYBRID_SECP256R1_KYBER_512_R3_OQS:
case Group_Params_Code::HYBRID_SECP256R1_KYBER_768_R3_OQS:
case Group_Params_Code::HYBRID_SECP256R1_eFRODOKEM_640_SHAKE_OQS:
case Group_Params_Code::HYBRID_SECP256R1_eFRODOKEM_640_AES_OQS:
return Group_Params_Code::SECP256R1;

case Group_Params_Code::HYBRID_SECP384R1_KYBER_768_R3_OQS:
case Group_Params_Code::HYBRID_SECP384R1_eFRODOKEM_976_SHAKE_OQS:
case Group_Params_Code::HYBRID_SECP384R1_eFRODOKEM_976_AES_OQS:
return Group_Params_Code::SECP384R1;

case Group_Params_Code::HYBRID_SECP521R1_KYBER_1024_R3_OQS:
case Group_Params_Code::HYBRID_SECP521R1_eFRODOKEM_1344_SHAKE_OQS:
case Group_Params_Code::HYBRID_SECP521R1_eFRODOKEM_1344_AES_OQS:
return Group_Params_Code::SECP521R1;

default:
return {};
}
}

std::optional<Group_Params> Group_Params::from_string(std::string_view group_name) {
if(group_name == "secp256r1") {
return Group_Params::SECP256R1;
Expand Down
44 changes: 31 additions & 13 deletions src/lib/tls/tls_algos.h
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,11 @@ class BOTAN_PUBLIC_API(3, 2) Group_Params final {

constexpr uint16_t wire_code() const { return static_cast<uint16_t>(m_code); }

/**
* Returns false if this group/KEX is not available in the build configuration
*/
bool is_available() const;

constexpr bool is_x25519() const { return m_code == Group_Params_Code::X25519; }

constexpr bool is_x448() const { return m_code == Group_Params_Code::X448; }
Expand All @@ -198,7 +203,7 @@ class BOTAN_PUBLIC_API(3, 2) Group_Params final {
m_code == Group_Params_Code::FFDHE_8192;
}

constexpr bool is_pure_kyber() const {
constexpr bool is_pure_kyber_r3() const {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a public API. I think we'll have to go with deprecation of is_pure_kyber() here.

return m_code == Group_Params_Code::KYBER_512_R3_OQS || m_code == Group_Params_Code::KYBER_768_R3_OQS ||
m_code == Group_Params_Code::KYBER_1024_R3_OQS;
}
Expand All @@ -214,37 +219,50 @@ class BOTAN_PUBLIC_API(3, 2) Group_Params final {

constexpr bool is_pure_ecc_group() const { return is_x25519() || is_x448() || is_ecdh_named_curve(); }

constexpr bool is_post_quantum() const { return is_pure_kyber() || is_pure_frodokem() || is_pqc_hybrid(); }
constexpr bool is_post_quantum() const { return is_pure_kyber_r3() || is_pure_frodokem() || is_pqc_hybrid(); }

constexpr bool is_pqc_hybrid() const {
constexpr bool is_pqc_hybrid_ml_kem() const {
return m_code == Group_Params_Code::HYBRID_SECP256R1_ML_KEM_768 ||
m_code == Group_Params_Code::HYBRID_X25519_ML_KEM_768;
}

constexpr bool is_pqc_hybrid_kyber_r3() const {
BOTAN_DIAGNOSTIC_PUSH
BOTAN_DIAGNOSTIC_IGNORE_DEPRECATED_DECLARATIONS

return m_code == Group_Params_Code::HYBRID_SECP256R1_ML_KEM_768 ||
m_code == Group_Params_Code::HYBRID_X25519_ML_KEM_768 ||
m_code == Group_Params_Code::HYBRID_X25519_KYBER_512_R3_CLOUDFLARE ||
return m_code == Group_Params_Code::HYBRID_X25519_KYBER_512_R3_CLOUDFLARE ||
m_code == Group_Params_Code::HYBRID_X25519_KYBER_512_R3_OQS ||
m_code == Group_Params_Code::HYBRID_X25519_KYBER_768_R3_OQS ||
m_code == Group_Params_Code::HYBRID_X448_KYBER_768_R3_OQS ||
m_code == Group_Params_Code::HYBRID_X25519_eFRODOKEM_640_SHAKE_OQS ||
m_code == Group_Params_Code::HYBRID_SECP256R1_KYBER_512_R3_OQS ||
m_code == Group_Params_Code::HYBRID_SECP256R1_KYBER_768_R3_OQS ||
m_code == Group_Params_Code::HYBRID_SECP384R1_KYBER_768_R3_OQS ||
m_code == Group_Params_Code::HYBRID_SECP521R1_KYBER_1024_R3_OQS;

BOTAN_DIAGNOSTIC_POP
}

constexpr bool is_pqc_hybrid_frodokem() const {
return m_code == Group_Params_Code::HYBRID_X25519_eFRODOKEM_640_SHAKE_OQS ||
m_code == Group_Params_Code::HYBRID_X25519_eFRODOKEM_640_AES_OQS ||
m_code == Group_Params_Code::HYBRID_X448_eFRODOKEM_976_SHAKE_OQS ||
m_code == Group_Params_Code::HYBRID_X448_eFRODOKEM_976_AES_OQS ||
m_code == Group_Params_Code::HYBRID_SECP256R1_KYBER_512_R3_OQS ||
m_code == Group_Params_Code::HYBRID_SECP256R1_KYBER_768_R3_OQS ||
m_code == Group_Params_Code::HYBRID_SECP256R1_eFRODOKEM_640_SHAKE_OQS ||
m_code == Group_Params_Code::HYBRID_SECP256R1_eFRODOKEM_640_AES_OQS ||
m_code == Group_Params_Code::HYBRID_SECP384R1_KYBER_768_R3_OQS ||
m_code == Group_Params_Code::HYBRID_SECP384R1_eFRODOKEM_976_SHAKE_OQS ||
m_code == Group_Params_Code::HYBRID_SECP384R1_eFRODOKEM_976_AES_OQS ||
m_code == Group_Params_Code::HYBRID_SECP521R1_KYBER_1024_R3_OQS ||
m_code == Group_Params_Code::HYBRID_SECP521R1_eFRODOKEM_1344_SHAKE_OQS ||
m_code == Group_Params_Code::HYBRID_SECP521R1_eFRODOKEM_1344_AES_OQS;
}

BOTAN_DIAGNOSTIC_POP
// If this is a pqc hybrid group, returns the ECC ID
std::optional<Group_Params_Code> pqc_hybrid_ecc() const;

constexpr bool is_pqc_hybrid() const {
return is_pqc_hybrid_ml_kem() || is_pqc_hybrid_kyber_r3() || is_pqc_hybrid_frodokem();
}

constexpr bool is_kem() const { return is_pure_kyber() || is_pure_frodokem() || is_pqc_hybrid(); }
constexpr bool is_kem() const { return is_pure_kyber_r3() || is_pure_frodokem() || is_pqc_hybrid(); }

// Returns std::nullopt if the param has no known name
std::optional<std::string> to_string() const;
Expand Down
4 changes: 2 additions & 2 deletions src/lib/tls/tls_callbacks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ std::unique_ptr<Public_Key> TLS::Callbacks::tls_deserialize_peer_public_key(
#endif

#if defined(BOTAN_HAS_KYBER)
if(group_params.is_pure_kyber()) {
if(group_params.is_pure_kyber_r3()) {
return std::make_unique<Kyber_PublicKey>(key_bits, KyberMode(group_params.to_string().value()));
}
#endif
Expand All @@ -245,7 +245,7 @@ std::unique_ptr<Public_Key> TLS::Callbacks::tls_deserialize_peer_public_key(

std::unique_ptr<Private_Key> TLS::Callbacks::tls_kem_generate_key(TLS::Group_Params group, RandomNumberGenerator& rng) {
#if defined(BOTAN_HAS_KYBER)
if(group.is_pure_kyber()) {
if(group.is_pure_kyber_r3()) {
return std::make_unique<Kyber_PrivateKey>(rng, KyberMode(group.to_string().value()));
}
#endif
Expand Down
84 changes: 69 additions & 15 deletions src/lib/tls/tls_policy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,30 @@ Group_Params Policy::choose_key_exchange_group(const std::vector<Group_Params>&

const std::vector<Group_Params> our_groups = key_exchange_groups();

// Prefer groups that were offered by the peer for the sake of saving
// an additional round trip. For TLS 1.2, this won't be used.
const bool client_supports_pqc = std::any_of(
supported_by_peer.begin(), supported_by_peer.end(), [](const Group_Params& g) { return g.is_post_quantum(); });

if(client_supports_pqc) {
// If the client supports PQ and sent us a PQ key share we can use, take it
for(auto g : offered_by_peer) {
if(g.is_post_quantum() && value_exists(our_groups, g)) {
return g;
}
}

// If the client supports PQ but not a PQ key share, still prefer PQ
for(auto g : supported_by_peer) {
if(g.is_post_quantum() && value_exists(our_groups, g)) {
return g;
}
}
}

// If we are here, the client did not offer any (mutually supported)
// post quantum algorithms

// Prefer groups that were offered by the peer, for the sake of saving an
// additional round trip. For TLS 1.2, this won't be used.
for(auto g : offered_by_peer) {
if(value_exists(our_groups, g)) {
return g;
Expand Down Expand Up @@ -161,31 +183,63 @@ Group_Params Policy::default_dh_group() const {
}

std::vector<Group_Params> Policy::key_exchange_groups() const {
// Default list is ordered by performance
return {
// clang-format off
#if defined(BOTAN_HAS_TLS_13_PQC) && defined(BOTAN_HAS_ML_KEM) && defined(BOTAN_HAS_X25519)
Group_Params::HYBRID_X25519_ML_KEM_768,
#endif

#if defined(BOTAN_HAS_X25519)
Group_Params::X25519,
#endif

Group_Params::SECP256R1,

#if defined(BOTAN_HAS_X448)
Group_Params::X448,
Group_Params::X448,
#endif

Group_Params::SECP256R1, Group_Params::BRAINPOOL256R1, Group_Params::SECP384R1, Group_Params::BRAINPOOL384R1,
Group_Params::SECP521R1, Group_Params::BRAINPOOL512R1,
Group_Params::SECP384R1,
Group_Params::SECP521R1,

Group_Params::FFDHE_2048, Group_Params::FFDHE_3072, Group_Params::FFDHE_4096, Group_Params::FFDHE_6144,
Group_Params::FFDHE_8192,
Group_Params::BRAINPOOL256R1,
Group_Params::BRAINPOOL384R1,
Group_Params::BRAINPOOL512R1,

Group_Params::FFDHE_2048,
Group_Params::FFDHE_3072,

// clang-format on
};
}

std::vector<Group_Params> Policy::key_exchange_groups_to_offer() const {
// by default, we offer a key share for the most-preferred group, only
std::vector<Group_Params> groups_to_offer;
const auto supported_groups = key_exchange_groups();
if(!supported_groups.empty()) {
groups_to_offer.push_back(supported_groups.front());
/*
By default, we offer a key share for the most-preferred pure ECC group
by default, if any pure ECC group is enabled in the policy.
Comment on lines +218 to +219
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
By default, we offer a key share for the most-preferred pure ECC group
by default, if any pure ECC group is enabled in the policy.
By default, we offer a key share for the most-preferred pure ECC group
if any pure ECC group is enabled in the policy.


We skip PQC (or hybrids) since the keys are much larger and they are not
yet widely supported; the most common case is we waste a lot of packet
space sending a key share that the peer will ignore.

Likewise we skip DH since the keys are large

However if no pure ECC is enabled then we offer the first enabled
key exchange group, no matter what kind it is.
*/
const auto kex_groups = key_exchange_groups();

for(auto group : kex_groups) {
if(group.is_pure_ecc_group()) {
return {group};
}
}

if(kex_groups.empty()) {
return {};
} else {
return {kex_groups[0]};
}
return groups_to_offer;
}

size_t Policy::minimum_dh_group_size() const {
Expand Down Expand Up @@ -651,7 +705,7 @@ void Policy::print(std::ostream& o) const {
}
o << "maximum_session_tickets_per_client_hello = " << maximum_session_tickets_per_client_hello() << '\n';
o << "session_ticket_lifetime = " << session_ticket_lifetime().count() << '\n';
o << "reuse_session_tickets = " << reuse_session_tickets() << '\n';
print_bool(o, "reuse_session_tickets", reuse_session_tickets());
o << "new_session_tickets_upon_handshake_success = " << new_session_tickets_upon_handshake_success() << '\n';
o << "minimum_dh_group_size = " << minimum_dh_group_size() << '\n';
o << "minimum_ecdh_group_size = " << minimum_ecdh_group_size() << '\n';
Expand Down
Loading
Loading