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
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
76 changes: 76 additions & 0 deletions src/lib/tls/tls_algos.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,82 @@ 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

#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_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
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_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
29 changes: 21 additions & 8 deletions src/lib/tls/tls_algos.h
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,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 @@ -232,7 +237,7 @@ class BOTAN_PUBLIC_API(3, 2) Group_Params final {
BOTAN_DIAGNOSTIC_POP
}

constexpr bool is_pqc_hybrid() const {
constexpr bool is_pqc_hybrid_kyber() const {
BOTAN_DIAGNOSTIC_PUSH
BOTAN_DIAGNOSTIC_IGNORE_DEPRECATED_DECLARATIONS

Expand All @@ -242,22 +247,25 @@ class BOTAN_PUBLIC_API(3, 2) Group_Params final {
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_kem() const {
Expand All @@ -269,6 +277,11 @@ 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;

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

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

Expand Down
27 changes: 20 additions & 7 deletions src/lib/tls/tls_policy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -161,20 +161,33 @@ 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_KYBER_ROUND3) && defined(BOTAN_HAS_TLS_13_PQC)
Group_Params::HYBRID_X25519_KYBER_512_R3_CLOUDFLARE,
randombit marked this conversation as resolved.
Show resolved Hide resolved
#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
};
}

Expand Down Expand Up @@ -651,7 +664,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
72 changes: 48 additions & 24 deletions src/lib/tls/tls_session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,43 @@ Session_Summary::Session_Summary(const Session_Base& base,

#if defined(BOTAN_HAS_TLS_13)

namespace {

std::string tls13_kex_to_string(bool psk, std::optional<Named_Group> group) {
if(psk && group) {
if(group->is_dh_named_group()) {
return kex_method_to_string(Kex_Algo::DHE_PSK);
} else if(group->is_ecdh_named_curve() || group->is_x25519() || group->is_x448()) {
reneme marked this conversation as resolved.
Show resolved Hide resolved
return kex_method_to_string(Kex_Algo::ECDHE_PSK);
} else if(group->is_pure_kyber()) {
return kex_method_to_string(Kex_Algo::KEM_PSK);
} else if(group->is_pqc_hybrid()) {
return kex_method_to_string(Kex_Algo::HYBRID_PSK);
randombit marked this conversation as resolved.
Show resolved Hide resolved
} else if(auto s = group->to_string()) {
return *s;
}
} else if(psk) {
return kex_method_to_string(Kex_Algo::PSK);
} else {
BOTAN_ASSERT_NOMSG(group.has_value());
if(group->is_dh_named_group()) {
return kex_method_to_string(Kex_Algo::DH);
} else if(group->is_ecdh_named_curve() || group->is_x25519() || group->is_x448()) {
return kex_method_to_string(Kex_Algo::ECDH);
} else if(group->is_pure_kyber()) {
return kex_method_to_string(Kex_Algo::KEM);
} else if(group->is_pqc_hybrid()) {
return kex_method_to_string(Kex_Algo::HYBRID);
randombit marked this conversation as resolved.
Show resolved Hide resolved
} else if(auto s = group->to_string()) {
return *s;
}
}

return kex_method_to_string(Kex_Algo::UNDEFINED);
}

} // namespace

Session_Summary::Session_Summary(const Server_Hello_13& server_hello,
Connection_Side side,
std::vector<X509_Certificate> peer_certs,
Expand Down Expand Up @@ -138,39 +175,26 @@ Session_Summary::Session_Summary(const Server_Hello_13& server_hello,

// In TLS 1.3 the key exchange algorithm is not negotiated in the ciphersuite
// anymore. This provides a compatible identifier for applications to use.
m_kex_algo = kex_method_to_string([&] {

std::optional<Named_Group> group = [&]() -> std::optional<Named_Group> {
randombit marked this conversation as resolved.
Show resolved Hide resolved
if(psk_used() || was_resumption()) {
if(const auto keyshare = server_hello.extensions().get<Key_Share>()) {
const auto group = keyshare->selected_group();
if(group.is_dh_named_group()) {
return Kex_Algo::DHE_PSK;
} else if(group.is_ecdh_named_curve() || group.is_x25519() || group.is_x448()) {
return Kex_Algo::ECDHE_PSK;
} else if(group.is_pure_kyber()) {
return Kex_Algo::KEM_PSK;
} else if(group.is_pqc_hybrid()) {
return Kex_Algo::HYBRID_PSK;
}
return keyshare->selected_group();
} else {
return Kex_Algo::PSK;
return {};
}
} else {
const auto keyshare = server_hello.extensions().get<Key_Share>();
BOTAN_ASSERT_NONNULL(keyshare);
const auto group = keyshare->selected_group();
if(group.is_dh_named_group()) {
return Kex_Algo::DH;
} else if(group.is_ecdh_named_curve() || group.is_x25519() || group.is_x448()) {
return Kex_Algo::ECDH;
} else if(group.is_pure_kyber()) {
return Kex_Algo::KEM;
} else if(group.is_pqc_hybrid()) {
return Kex_Algo::HYBRID;
}
return keyshare->selected_group();
}
}();

if(group.has_value()) {
m_kex_parameters = group->to_string();
}
reneme marked this conversation as resolved.
Show resolved Hide resolved

return Kex_Algo::UNDEFINED;
}());
m_kex_algo = tls13_kex_to_string(psk_used() || was_resumption(), group);
}

#endif
Expand Down
3 changes: 3 additions & 0 deletions src/lib/tls/tls_session.h
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,8 @@ class BOTAN_PUBLIC_API(3, 0) Session_Summary : public Session_Base {

std::string kex_algo() const { return m_kex_algo; }

std::optional<std::string> kex_parameters() const { return m_kex_parameters; }
randombit marked this conversation as resolved.
Show resolved Hide resolved

std::string cipher_algo() const { return ciphersuite().cipher_algo(); }

std::string mac_algo() const { return ciphersuite().mac_algo(); }
Expand Down Expand Up @@ -324,6 +326,7 @@ class BOTAN_PUBLIC_API(3, 0) Session_Summary : public Session_Base {

bool m_was_resumption;
std::string m_kex_algo;
std::optional<std::string> m_kex_parameters;
};

/**
Expand Down
9 changes: 2 additions & 7 deletions src/lib/tls/tls_text_policy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -233,14 +233,9 @@ std::vector<Group_Params> Text_Policy::read_group_list(std::string_view group_st
for(const auto& group_name : split_on(group_str, ' ')) {
Group_Params group_id = Group_Params::from_string(group_name).value_or(Group_Params::NONE);

#if !defined(BOTAN_HAS_X25519)
if(group_id == Group_Params::X25519)
if(!group_id.is_available()) {
continue;
#endif
#if !defined(BOTAN_HAS_X448)
if(group_id == Group_Params::X448)
continue;
#endif
}

if(group_id == Group_Params::NONE) {
try {
Expand Down
4 changes: 2 additions & 2 deletions src/scripts/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -1150,10 +1150,10 @@ def __init__(self, name, protocol_version, policy, **kwargs):

TestConfig("PSK TLS 1.2", "1.2", "allow_tls12=true\nallow_tls13=false\nkey_exchange_methods=ECDHE_PSK\n",
psk=psk, psk_identity=psk_identity,
stdout_regex=f'Handshake complete, TLS v1\\.2.*utilized PSK identity: {psk_identity}.*'),
stdout_regex=f'Handshake complete, TLS v1\\.2.*\nUtilized PSK identity: {psk_identity}.*'),
TestConfig("PSK TLS 1.3", "1.3", "allow_tls12=false\nallow_tls13=true\nkey_exchange_methods=ECDHE_PSK\n",
psk=psk, psk_identity=psk_identity,
stdout_regex=f'Handshake complete, TLS v1\\.3.*utilized PSK identity: {psk_identity}.*'),
stdout_regex=f'Handshake complete, TLS v1\\.3.*\nUtilized PSK identity: {psk_identity}.*'),

TestConfig("Kyber KEM", "1.3", "allow_tls12=false\nallow_tls13=true\nkey_exchange_groups=Kyber-512-r3"),
TestConfig("Hybrid PQ/T", "1.3", "allow_tls12=false\nallow_tls13=true\nkey_exchange_groups=x25519/Kyber-512-r3"),
Expand Down
15 changes: 10 additions & 5 deletions src/tests/data/tls-policy/datagram.txt
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
allow_tls10 = false
allow_tls11 = false
allow_tls12 = false
allow_tls13 = false
allow_dtls10 = false
allow_dtls12 = true
allow_ssl_key_log_file = false
ciphers = ChaCha20Poly1305 AES-256/GCM AES-128/GCM
macs = AEAD
signature_hashes = SHA-512 SHA-384 SHA-256
signature_methods = ECDSA RSA
key_exchange_methods = ECDH DH
key_exchange_groups = x25519 x448 secp256r1 brainpool256r1 secp384r1 brainpool384r1 secp521r1 brainpool512r1 ffdhe/ietf/2048 ffdhe/ietf/3072 ffdhe/ietf/4096 ffdhe/ietf/6144 ffdhe/ietf/8192
key_exchange_groups = x25519 secp256r1 x25519/Kyber-512-r3/cloudflare x448 secp384r1 secp521r1 brainpool256r1 brainpool384r1 brainpool512r1 ffdhe/ietf/2048 ffdhe/ietf/3072
allow_insecure_renegotiation = false
reneme marked this conversation as resolved.
Show resolved Hide resolved
include_time_in_hello_random = true
allow_server_initiated_renegotiation = false
hide_unknown_users = false
server_uses_own_ciphersuite_preferences = true
negotiate_encrypt_then_mac = true
support_cert_status_message = true
tls_13_middlebox_compatibility_mode = true
accepted_client_certificate_types = X509
accepted_server_certificate_types = X509
hash_hello_random = true
maximum_session_tickets_per_client_hello = 1
session_ticket_lifetime = 86400
dh_group = modp/ietf/2048
reuse_session_tickets = false
new_session_tickets_upon_handshake_success = 1
minimum_dh_group_size = 2048
Comment on lines +17 to +25
Copy link
Collaborator

Choose a reason for hiding this comment

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

Informational: Most of those are TLS 1.3 specific policies (except hash_hello_random). None of these will currently have an effect on DTLS 1.2. No objection to add them, though.

minimum_ecdh_group_size = 255
minimum_rsa_bits = 2048
Expand Down
Loading
Loading