diff --git a/src/bogo_shim/bogo_shim.cpp b/src/bogo_shim/bogo_shim.cpp index 5a4e96c336..24add03710 100644 --- a/src/bogo_shim/bogo_shim.cpp +++ b/src/bogo_shim/bogo_shim.cpp @@ -998,12 +998,18 @@ class Shim_Policy final : public Botan::TLS::Policy { groups.push_back(group); } - // Given that this is still a draft-standard, we didn't add the - // hybrid groups to the default policy, yet. - // + // Eventually the pre-standard hybrid exchange using Kyber-R3 will be + // retired and removed. Hence, it will likely never be part of the + // default `TLS::Policy::key_exchange_groups()`. + 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. - if(group == Botan::TLS::Group_Params::HYBRID_X25519_KYBER_768_R3_OQS) { + // + // See: https://github.com/randombit/botan/pull/4305 + if(group == Botan::TLS::Group_Params::HYBRID_X25519_ML_KEM_768) { groups.push_back(group); } } diff --git a/src/bogo_shim/config.json b/src/bogo_shim/config.json index 9becc27126..656b02315f 100644 --- a/src/bogo_shim/config.json +++ b/src/bogo_shim/config.json @@ -179,8 +179,9 @@ "Renegotiate-Client-UnfinishedWrite": "BoringSSL specific API test", "FailEarlyCallback": "BoringSSL specific API test", - "*MLKEM*": "No support for hybrid key exchange with ML-KEM, yet", - + "MLKEMKeyShareIncludedSecond": "BoringSSL specific policy test (we may offer solo PQ/T groups)", + "NotJustMLKEMKeyShare": "BoringSSL specific policy test (we may offer solo PQ/T groups)", + "MLKEMKeyShareIncludedThird": "BoringSSL specific policy test (we may offer solo PQ/T groups)", "NotJustKyberKeyShare": "BoringSSL specific policy test (we may offer solo PQ/T groups)", "KyberKeyShareIncludedSecond": "BoringSSL specific policy test (we may offer solo PQ/T groups)", "KyberKeyShareIncludedThird": "BoringSSL specific policy test (we may offer solo PQ/T groups)", diff --git a/src/examples/tls_13_hybrid_key_exchange_client.cpp b/src/examples/tls_13_hybrid_key_exchange_client.cpp index a61bc33cf5..251c4a827c 100644 --- a/src/examples/tls_13_hybrid_key_exchange_client.cpp +++ b/src/examples/tls_13_hybrid_key_exchange_client.cpp @@ -54,16 +54,17 @@ class Client_Policy : public Botan::TLS::Default_Policy { // additional to the default (classical) key exchange groups std::vector key_exchange_groups() const override { auto groups = Botan::TLS::Default_Policy::key_exchange_groups(); + groups.push_back(Botan::TLS::Group_Params::HYBRID_X25519_ML_KEM_768); + groups.push_back(Botan::TLS::Group_Params::HYBRID_SECP256R1_ML_KEM_768); groups.push_back(Botan::TLS::Group_Params::HYBRID_X25519_KYBER_768_R3_OQS); - groups.push_back(Botan::TLS::Group_Params::HYBRID_X25519_KYBER_512_R3_OQS); + groups.push_back(Botan::TLS::Group_Params::HYBRID_SECP256R1_KYBER_768_R3_OQS); return groups; } // Define that the client should exclusively pre-offer hybrid groups // in its initial Client Hello. std::vector key_exchange_groups_to_offer() const override { - return {Botan::TLS::Group_Params::HYBRID_X25519_KYBER_768_R3_OQS, - Botan::TLS::Group_Params::HYBRID_X25519_KYBER_512_R3_OQS}; + return {Botan::TLS::Group_Params::HYBRID_X25519_ML_KEM_768}; } }; diff --git a/src/lib/tls/tls13_pqc/hybrid_public_key.cpp b/src/lib/tls/tls13_pqc/hybrid_public_key.cpp index cf6b4ec7ed..43efe9301b 100644 --- a/src/lib/tls/tls13_pqc/hybrid_public_key.cpp +++ b/src/lib/tls/tls13_pqc/hybrid_public_key.cpp @@ -25,6 +25,22 @@ std::vector> algorithm_specs_for_group(Group BOTAN_ARG_CHECK(group.is_pqc_hybrid(), "Group is not hybrid"); switch(group.code()) { + // draft-kwiatkowski-tls-ecdhe-mlkem-02 Section 3 + // + // NIST's special publication 800-56Cr2 approves the usage of HKDF with + // two distinct shared secrets, with the condition that the first one + // is computed by a FIPS-approved key-establishment scheme. FIPS also + // requires a certified implementation of the scheme, which will remain + // more ubiqutous for secp256r1 in the coming years. + // + // For this reason we put the ML-KEM-768 shared secret first in + // X25519MLKEM768, and the secp256r1 shared secret first in + // SecP256r1MLKEM768. + case Group_Params::HYBRID_X25519_ML_KEM_768: + return {{"ML-KEM", "ML-KEM-768"}, {"X25519", "X25519"}}; + case Group_Params::HYBRID_SECP256R1_ML_KEM_768: + return {{"ECDH", "secp256r1"}, {"ML-KEM", "ML-KEM-768"}}; + case Group_Params::HYBRID_X25519_KYBER_512_R3_OQS: case Group_Params::HYBRID_X25519_KYBER_512_R3_CLOUDFLARE: return {{"X25519", "X25519"}, {"Kyber", "Kyber-512-r3"}}; @@ -98,6 +114,11 @@ std::vector public_value_lengths_for_group(Group_Params group) { // TODO: Find a way to expose important algorithm constants globally // in the library, to avoid violating the DRY principle. switch(group.code()) { + case Group_Params::HYBRID_X25519_ML_KEM_768: + return {1184, 32}; + case Group_Params::HYBRID_SECP256R1_ML_KEM_768: + return {32, 1184}; + case Group_Params::HYBRID_X25519_KYBER_512_R3_CLOUDFLARE: case Group_Params::HYBRID_X25519_KYBER_512_R3_OQS: return {32, 800}; diff --git a/src/lib/tls/tls_algos.cpp b/src/lib/tls/tls_algos.cpp index eac5d4e9af..77b564c886 100644 --- a/src/lib/tls/tls_algos.cpp +++ b/src/lib/tls/tls_algos.cpp @@ -214,6 +214,14 @@ std::optional Group_Params::from_string(std::string_view group_nam if(group_name == "x25519/Kyber-768-r3") { return Group_Params::HYBRID_X25519_KYBER_768_R3_OQS; } + + if(group_name == "x25519/ML-KEM-768") { + return Group_Params::HYBRID_X25519_ML_KEM_768; + } + if(group_name == "secp256r1/ML-KEM-768") { + return Group_Params::HYBRID_SECP256R1_ML_KEM_768; + } + if(group_name == "x448/Kyber-768-r3") { return Group_Params::HYBRID_X448_KYBER_768_R3_OQS; } @@ -344,6 +352,12 @@ std::optional Group_Params::to_string() const { return "x25519/Kyber-512-r3"; case Group_Params::HYBRID_X25519_KYBER_768_R3_OQS: return "x25519/Kyber-768-r3"; + + case Group_Params::HYBRID_X25519_ML_KEM_768: + return "x25519/ML-KEM-768"; + case Group_Params::HYBRID_SECP256R1_ML_KEM_768: + return "secp256r1/ML-KEM-768"; + case Group_Params::HYBRID_X448_KYBER_768_R3_OQS: return "x448/Kyber-768-r3"; diff --git a/src/lib/tls/tls_algos.h b/src/lib/tls/tls_algos.h index b3ea759336..ab2209f28c 100644 --- a/src/lib/tls/tls_algos.h +++ b/src/lib/tls/tls_algos.h @@ -123,6 +123,10 @@ enum class Group_Params_Code : uint16_t { HYBRID_X25519_KYBER_512_R3_OQS = 0x2F39, HYBRID_X25519_KYBER_768_R3_OQS = 0x6399, + // https://datatracker.ietf.org/doc/draft-kwiatkowski-tls-ecdhe-mlkem/02/ + HYBRID_SECP256R1_ML_KEM_768 = 0x11EB, + HYBRID_X25519_ML_KEM_768 = 0x11EC, + HYBRID_X448_KYBER_768_R3_OQS = 0x2F90, HYBRID_SECP256R1_KYBER_512_R3_OQS = 0x2F3A, @@ -216,7 +220,9 @@ class BOTAN_PUBLIC_API(3, 2) Group_Params final { BOTAN_DIAGNOSTIC_PUSH BOTAN_DIAGNOSTIC_IGNORE_DEPRECATED_DECLARATIONS - return m_code == Group_Params_Code::HYBRID_X25519_KYBER_512_R3_CLOUDFLARE || + 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 || 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 || diff --git a/src/scripts/test_cli.py b/src/scripts/test_cli.py index e3f5bac09f..320a470b79 100755 --- a/src/scripts/test_cli.py +++ b/src/scripts/test_cli.py @@ -1324,7 +1324,9 @@ def get_oqs_rootca(): test_cfg = [ TestConfig("pq.cloudflareresearch.com", "x25519/Kyber-768-r3"), + TestConfig("pq.cloudflareresearch.com", "x25519/ML-KEM-768"), TestConfig("google.com", "x25519/Kyber-768-r3"), + TestConfig("google.com", "x25519/ML-KEM-768"), TestConfig("qsc.eu-de.kms.cloud.ibm.com", "secp256r1/Kyber-512-r3"), TestConfig("qsc.eu-de.kms.cloud.ibm.com", "secp384r1/Kyber-768-r3"), @@ -1339,6 +1341,8 @@ def get_oqs_rootca(): if oqsp and oqs_test_ca: # src/scripts/test_cli.py --run-online-tests ./botan pqc_hybrid_tests test_cfg += [ + TestConfig("test.openquantumsafe.org", "x25519/ML-KEM-768", port=oqsp['X25519MLKEM768'], ca=oqs_test_ca), + TestConfig("test.openquantumsafe.org", "secp256r1/ML-KEM-768", port=oqsp['SecP256r1MLKEM768'], ca=oqs_test_ca), TestConfig("test.openquantumsafe.org", "x25519/Kyber-512-r3", port=oqsp['x25519_kyber512'], ca=oqs_test_ca), TestConfig("test.openquantumsafe.org", "x25519/Kyber-768-r3", port=oqsp['x25519_kyber768'], ca=oqs_test_ca), TestConfig("test.openquantumsafe.org", "x448/Kyber-768-r3", port=oqsp['x448_kyber768'], ca=oqs_test_ca),