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

Merged
merged 1 commit into from
Dec 27, 2024
Merged
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
2 changes: 2 additions & 0 deletions src/bogo_shim/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@
"TooManyChangeCipherSpec-Server-TLS13": "Limits on the number of CCS are not implemented",
"TooManyKeyUpdates": "Limits on the number of KeyUpdates are not implemented",

"PostQuantumNotEnabledByDefaultInClients": "Oh yes it is",

"TLS12SessionID-TLS13": "We don't offer TLS 1.3 when a TLS 1.2 session was found",
"Ticket-Forbidden-TLS13": "We don't offer TLS 1.3 when a TLS 1.2 session was found",
"Resume-Client-NoResume-TLS12-TLS13-TLS": "We don't offer TLS 1.3 when a TLS 1.2 session was found",
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
2 changes: 1 addition & 1 deletion src/lib/tls/tls13/info.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<defines>
TLS_13 -> 20210721
TLS_13 -> 20241208
</defines>

<module_info>
Expand Down
2 changes: 1 addition & 1 deletion src/lib/tls/tls13_pqc/info.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<defines>
TLS_13_PQC -> 20230919
TLS_13_PQC -> 20241208
</defines>

<module_info>
Expand Down
84 changes: 84 additions & 0 deletions src/lib/tls/tls_algos.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,90 @@ 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() || is_pqc_hybrid_kyber()) {
return false;
}
#endif
randombit marked this conversation as resolved.
Show resolved Hide resolved

#if !defined(BOTAN_HAS_ML_KEM)
if(is_pure_ml_kem() || 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:
randombit marked this conversation as resolved.
Show resolved Hide resolved

randombit marked this conversation as resolved.
Show resolved Hide resolved
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;
randombit marked this conversation as resolved.
Show resolved Hide resolved

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
38 changes: 28 additions & 10 deletions src/lib/tls/tls_algos.h
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,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 Down Expand Up @@ -242,32 +247,42 @@ class BOTAN_PUBLIC_API(3, 2) Group_Params final {
BOTAN_DIAGNOSTIC_POP
}

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() const {
randombit marked this conversation as resolved.
Show resolved Hide resolved
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
constexpr bool is_pqc_hybrid() const {
return is_pqc_hybrid_ml_kem() || is_pqc_hybrid_kyber() || is_pqc_hybrid_frodokem();
}

constexpr bool is_kem() const {
Expand All @@ -279,6 +294,9 @@ class BOTAN_PUBLIC_API(3, 2) Group_Params final {
BOTAN_DIAGNOSTIC_POP
}

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

// Returns std::nullopt if the param has no known name
std::optional<std::string> to_string() const;

Expand Down
66 changes: 56 additions & 10 deletions src/lib/tls/tls_policy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,21 @@ Group_Params Policy::choose_key_exchange_group(const std::vector<Group_Params>&
return Group_Params::NONE;
}

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

// First check if the peer sent a PQ share of a group we also support
for(auto share : offered_by_peer) {
if(share.is_post_quantum() && value_exists(our_groups, share)) {
return share;
}
}

// Then check if the peer offered a PQ algo we also support
for(auto share : supported_by_peer) {
if(share.is_post_quantum() && value_exists(our_groups, share)) {
return share;
}
}

// 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.
Expand Down Expand Up @@ -161,30 +175,62 @@ 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_X25519)
Group_Params::X25519,
#endif

Group_Params::SECP256R1,

#if defined(BOTAN_HAS_X25519) && defined(BOTAN_HAS_ML_KEM) && defined(BOTAN_HAS_TLS_13_PQC)
Group_Params_Code::HYBRID_X25519_ML_KEM_768,
#endif

#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::BRAINPOOL256R1,
Group_Params::BRAINPOOL384R1,
Group_Params::BRAINPOOL512R1,

Group_Params::FFDHE_2048,
Group_Params::FFDHE_3072,

Group_Params::FFDHE_2048, Group_Params::FFDHE_3072, Group_Params::FFDHE_4096, Group_Params::FFDHE_6144,
Group_Params::FFDHE_8192,
// 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()) {
BOTAN_ASSERT(!supported_groups.empty(), "Policy allows at least one key exchange group");

/*
* Initially prefer sending a key share only of the first pure-ECC
* group, since these shares are small and PQ support is still not
* that widespread.
*/
for(auto group : key_exchange_groups()) {
if(group.is_pure_ecc_group()) {
groups_to_offer.push_back(group);
break;
}
}

/*
* If for some reason no pure ECC groups are enabled then simply
* send a share of whatever the policys top preference is.
*/
if(groups_to_offer.empty()) {
groups_to_offer.push_back(supported_groups.front());
}

return groups_to_offer;
}

Expand Down Expand Up @@ -651,7 +697,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