From 5d0e324aa22f57a473f102b28bc79fc53b78e77d Mon Sep 17 00:00:00 2001 From: Fabian Albert Date: Wed, 6 Mar 2024 16:04:05 +0100 Subject: [PATCH] x448 and Ed448 Integration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: René Meusel --- doc/api_ref/pubkey.rst | 32 ++-- doc/api_ref/python.rst | 14 +- readme.rst | 2 +- src/build-data/oids.txt | 2 +- src/build-data/policy/bsi.txt | 1 + src/build-data/policy/modern.txt | 3 +- src/build-data/policy/nist.txt | 2 + src/cli/speed.cpp | 24 +++ src/lib/ffi/ffi.h | 24 +++ src/lib/ffi/ffi_pkey_algs.cpp | 142 +++++++++++++++++- src/lib/tls/tls12/msg_client_kex.cpp | 11 +- src/lib/tls/tls12/msg_server_kex.cpp | 7 +- .../tls/tls13/tls_extensions_key_share.cpp | 9 +- src/lib/tls/tls13_pqc/hybrid_public_key.cpp | 12 ++ src/lib/tls/tls13_pqc/kex_to_kem_adapter.cpp | 17 +++ src/lib/tls/tls_algos.cpp | 20 +++ src/lib/tls/tls_algos.h | 13 +- src/lib/tls/tls_callbacks.cpp | 23 +++ src/lib/tls/tls_policy.cpp | 5 +- src/lib/tls/tls_session.cpp | 4 +- src/lib/tls/tls_signature_scheme.cpp | 2 + src/lib/tls/tls_signature_scheme.h | 2 +- src/lib/tls/tls_text_policy.cpp | 4 + src/lib/x509/x509_obj.cpp | 2 +- src/lib/x509/x509path.cpp | 3 +- src/python/botan3.py | 11 ++ src/scripts/run_tls_fuzzer.py | 1 - src/scripts/test_cli.py | 13 +- src/scripts/test_python.py | 43 ++++++ src/scripts/tls_scanner/policy.txt | 2 +- src/tests/data/tls-policy/compat.txt | 2 +- src/tests/data/tls-policy/datagram.txt | 2 +- src/tests/data/tls-policy/default.txt | 2 +- src/tests/data/tls-policy/default_tls13.txt | 2 +- src/tests/data/tls-policy/strict.txt | 2 +- src/tests/data/tls-policy/strict_tls13.txt | 2 +- src/tests/test_ffi.cpp | 122 +++++++++++++++ src/tests/unit_x509.cpp | 7 +- 38 files changed, 536 insertions(+), 55 deletions(-) diff --git a/doc/api_ref/pubkey.rst b/doc/api_ref/pubkey.rst index 88f31c4c9d5..913a1023147 100644 --- a/doc/api_ref/pubkey.rst +++ b/doc/api_ref/pubkey.rst @@ -83,11 +83,11 @@ ECDSA Fast signature scheme based on elliptic curves. -ECDH, DH, and X25519 -~~~~~~~~~~~~~~~~~~~~~~~ +ECDH, DH, X25519 and X448 +~~~~~~~~~~~~~~~~~~~~~~~~~ Key agreement schemes. DH uses arithmetic over finite fields and is slower and -with larger keys. ECDH and X25519 use elliptic curves instead. +with larger keys. ECDH, X25519 and X448 use elliptic curves instead. Dilithium ~~~~~~~~~~ @@ -108,10 +108,10 @@ Post-quantum key encapsulation scheme based on (structured) lattices. The final NIST specification version of Kyber is not yet implemented. -Ed25519 -~~~~~~~~~~ +Ed25519 and Ed448 +~~~~~~~~~~~~~~~~~ -Signature scheme based on a specific elliptic curve. +Signature schemes based on a specific elliptic curve. XMSS ~~~~~~~~~ @@ -770,7 +770,7 @@ Botan implements the following signature algorithms: not supporting ``Raw``. #. GOST 34.10-2001. Requires a :ref:`hash function ` as parameter. -#. Ed25519. See :ref:`Ed25519_variants` for parameters. +#. Ed25519 and Ed448. See :ref:`Ed25519_Ed448_variants` for parameters. #. SM2. Takes one of the following as parameter: @@ -928,28 +928,28 @@ Parameters specification: - ``Raw`` - ``Raw()`` -.. _Ed25519_variants: +.. _Ed25519_Ed448_variants: -Ed25519 Variants -~~~~~~~~~~~~~~~~~~ +Ed25519 and Ed448 Variants +~~~~~~~~~~~~~~~~~~~~~~~~~~ Most signature schemes in Botan follow a hash-then-sign paradigm. That is, the entire message is digested to a fixed length representative using a collision -resistant hash function, and then the digest is signed. Ed25519 instead signs -the message directly. This is beneficial, in that the Ed25519 design should +resistant hash function, and then the digest is signed. Ed25519 and Ed448 instead sign +the message directly. This is beneficial, in that the design should remain secure even in the (extremely unlikely) event that a collision attack on SHA-512 is found. However it means the entire message must be buffered in memory, which can be a problem for many applications which might need to sign -large inputs. To use this variety of Ed25519, use a padding name of "Pure". +large inputs. To use this variety of Ed25519/Ed448, use a padding name of "Pure". This is the default mode if no padding name is given. Parameter specification: ``Pure`` / ``Identity`` -Ed25519ph (pre-hashed) instead hashes the message with SHA-512 and then signs -the digest plus a special prefix specified in RFC 8032. To use it, specify -padding name "Ed25519ph". +Ed25519ph (or Ed448) (pre-hashed) instead hashes the message with SHA-512 (or SHAKE256(512)) +and then signs the digest plus a special prefix specified in RFC 8032. To use it, specify +padding name "Ed25519ph" (or "Ed448ph"). Parameter specification: ``Ed25519ph`` diff --git a/doc/api_ref/python.rst b/doc/api_ref/python.rst index 0fd40bd08a8..2e9ed56a671 100644 --- a/doc/api_ref/python.rst +++ b/doc/api_ref/python.rst @@ -344,8 +344,8 @@ Private Key Creates a new private key. The parameter type/value depends on the algorithm. For "rsa" is is the size of the key in bits. For "ecdsa" and "ecdh" it is a group name (for instance - "secp256r1"). For "ecdh" there is also a special case for group - "curve25519" (which is actually a completely distinct key type + "secp256r1"). For "ecdh" there is also a special case for groups + "curve25519" and "x448" (which are actually completely distinct key types with a non-standard encoding). .. py:classmethod:: load(val, passphrase="") @@ -538,7 +538,7 @@ HOTP X509Cert ----------------------------------------- -.. py:class:: X509Cert(filename=None, buf=None) +.. py:class:: X509Cert(filename=None, buf=None) .. py:method:: time_starts() @@ -550,7 +550,7 @@ X509Cert Return the time the certificate expires, as a string in form "YYYYMMDDHHMMSSZ" where Z is a literal character reflecting that this time is - relative to UTC. + relative to UTC. .. py:method:: to_string() @@ -586,7 +586,7 @@ X509Cert Get a value from the subject DN field. - ``key`` specifies a value to get, for instance ``"Name"`` or `"Country"`. + ``key`` specifies a value to get, for instance ``"Name"`` or `"Country"`. .. py:method:: issuer_dn(key, index) @@ -600,7 +600,7 @@ X509Cert .. py:method:: not_before() - Return the time the certificate becomes valid, as seconds since epoch. + Return the time the certificate becomes valid, as seconds since epoch. .. py:method:: not_after() @@ -620,7 +620,7 @@ X509Cert reference_time=0 \ crls=None) - Verify a certificate. Returns 0 if validation was successful, returns a positive error code + Verify a certificate. Returns 0 if validation was successful, returns a positive error code if the validation was unsuccesful. ``intermediates`` is a list of untrusted subauthorities. diff --git a/readme.rst b/readme.rst index 32fafd43abc..b4cfaee0cba 100644 --- a/readme.rst +++ b/readme.rst @@ -99,7 +99,7 @@ Public Key Cryptography * RSA signatures and encryption * DH and ECDH key agreement -* Signature schemes ECDSA, DSA, Ed25519, ECGDSA, ECKCDSA, SM2, GOST 34.10 +* Signature schemes ECDSA, DSA, Ed25519, Ed448, ECGDSA, ECKCDSA, SM2, GOST 34.10 * Post-quantum signature schemes Dilithium, SPHINCS+, and XMSS * Post-quantum key agreement schemes McEliece, Kyber and FrodoKEM * ElGamal encryption diff --git a/src/build-data/oids.txt b/src/build-data/oids.txt index 4c1c15380f0..071c520ae3e 100644 --- a/src/build-data/oids.txt +++ b/src/build-data/oids.txt @@ -11,7 +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.111 = X448 1.3.101.112 = Ed25519 1.3.101.113 = Ed448 diff --git a/src/build-data/policy/bsi.txt b/src/build-data/policy/bsi.txt index 7a6a30179cd..9ba2c7f99b0 100644 --- a/src/build-data/policy/bsi.txt +++ b/src/build-data/policy/bsi.txt @@ -147,6 +147,7 @@ sp800_56a # pubkey curve25519 +x448 ec_h2c ed25519 elgamal diff --git a/src/build-data/policy/modern.txt b/src/build-data/policy/modern.txt index 166d7af2dc5..f03dee0adcd 100644 --- a/src/build-data/policy/modern.txt +++ b/src/build-data/policy/modern.txt @@ -29,7 +29,9 @@ bcrypt pbes2 ed25519 +ed448 curve25519 +x448 ecdh ecdsa rsa @@ -47,7 +49,6 @@ ffi tls prf_tls -ed25519 ghash_cpu ghash_vperm diff --git a/src/build-data/policy/nist.txt b/src/build-data/policy/nist.txt index 53f0cdde5fd..c5a784aed58 100644 --- a/src/build-data/policy/nist.txt +++ b/src/build-data/policy/nist.txt @@ -128,7 +128,9 @@ prf_x942 # pubkey curve25519 +x448 ed25519 +ed448 ecgdsa eckcdsa elgamal diff --git a/src/cli/speed.cpp b/src/cli/speed.cpp index 746adfada0c..1f3fd8823fa 100644 --- a/src/cli/speed.cpp +++ b/src/cli/speed.cpp @@ -405,7 +405,9 @@ class Speed final : public Command { "ECDH", "ECDSA", "Ed25519", + "Ed448", "Curve25519", + "X448", "McEliece", "Kyber", "SPHINCS+", @@ -569,6 +571,11 @@ class Speed final : public Command { bench_ed25519(provider, msec); } #endif +#if defined(BOTAN_HAS_ED448) + else if(algo == "Ed448") { + bench_ed448(provider, msec); + } +#endif #if defined(BOTAN_HAS_DIFFIE_HELLMAN) else if(algo == "DH") { bench_dh(provider, msec); @@ -594,6 +601,11 @@ class Speed final : public Command { bench_curve25519(provider, msec); } #endif +#if defined(BOTAN_HAS_X448) + else if(algo == "X448") { + bench_x448(provider, msec); + } +#endif #if defined(BOTAN_HAS_MCELIECE) else if(algo == "McEliece") { bench_mceliece(provider, msec); @@ -1851,6 +1863,12 @@ class Speed final : public Command { } #endif +#if defined(BOTAN_HAS_ED448) + void bench_ed448(const std::string& provider, std::chrono::milliseconds msec) { + return bench_pk_sig_ecc("Ed448", "Pure", provider, std::vector{""}, msec); + } +#endif + #if defined(BOTAN_HAS_DIFFIE_HELLMAN) void bench_dh(const std::string& provider, std::chrono::milliseconds msec) { for(size_t bits : {2048, 3072, 4096, 6144, 8192}) { @@ -1913,6 +1931,12 @@ class Speed final : public Command { } #endif +#if defined(BOTAN_HAS_X448) + void bench_x448(const std::string& provider, std::chrono::milliseconds msec) { + bench_pk_ka("X448", "X448", "", provider, msec); + } +#endif + #if defined(BOTAN_HAS_MCELIECE) void bench_mceliece(const std::string& provider, std::chrono::milliseconds msec) { /* diff --git a/src/lib/ffi/ffi.h b/src/lib/ffi/ffi.h index 44f4b467819..56551dcde9c 100644 --- a/src/lib/ffi/ffi.h +++ b/src/lib/ffi/ffi.h @@ -1421,6 +1421,18 @@ BOTAN_FFI_EXPORT(2, 2) int botan_privkey_ed25519_get_privkey(botan_privkey_t key BOTAN_FFI_EXPORT(2, 2) int botan_pubkey_ed25519_get_pubkey(botan_pubkey_t key, uint8_t pubkey[32]); +/* +* Algorithm specific key operations: Ed448 +*/ + +BOTAN_FFI_EXPORT(3, 4) int botan_privkey_load_ed448(botan_privkey_t* key, const uint8_t privkey[57]); + +BOTAN_FFI_EXPORT(3, 4) int botan_pubkey_load_ed448(botan_pubkey_t* key, const uint8_t pubkey[57]); + +BOTAN_FFI_EXPORT(3, 4) int botan_privkey_ed448_get_privkey(botan_privkey_t key, uint8_t output[57]); + +BOTAN_FFI_EXPORT(3, 4) int botan_pubkey_ed448_get_pubkey(botan_pubkey_t key, uint8_t pubkey[57]); + /* * Algorithm specific key operations: X25519 */ @@ -1433,6 +1445,18 @@ BOTAN_FFI_EXPORT(2, 8) int botan_privkey_x25519_get_privkey(botan_privkey_t key, BOTAN_FFI_EXPORT(2, 8) int botan_pubkey_x25519_get_pubkey(botan_pubkey_t key, uint8_t pubkey[32]); +/* +* Algorithm specific key operations: X448 +*/ + +BOTAN_FFI_EXPORT(3, 4) int botan_privkey_load_x448(botan_privkey_t* key, const uint8_t privkey[56]); + +BOTAN_FFI_EXPORT(3, 4) int botan_pubkey_load_x448(botan_pubkey_t* key, const uint8_t pubkey[56]); + +BOTAN_FFI_EXPORT(3, 4) int botan_privkey_x448_get_privkey(botan_privkey_t key, uint8_t output[56]); + +BOTAN_FFI_EXPORT(3, 4) int botan_pubkey_x448_get_pubkey(botan_pubkey_t key, uint8_t pubkey[56]); + /* * Algorithm specific key operations: Kyber */ diff --git a/src/lib/ffi/ffi_pkey_algs.cpp b/src/lib/ffi/ffi_pkey_algs.cpp index ef85f58e90f..4c2effb239f 100644 --- a/src/lib/ffi/ffi_pkey_algs.cpp +++ b/src/lib/ffi/ffi_pkey_algs.cpp @@ -51,10 +51,18 @@ #include #endif +#if defined(BOTAN_HAS_X448) + #include +#endif + #if defined(BOTAN_HAS_ED25519) #include #endif +#if defined(BOTAN_HAS_ED448) + #include +#endif + #if defined(BOTAN_HAS_MCELIECE) #include #endif @@ -504,7 +512,7 @@ int botan_pubkey_load_dh(botan_pubkey_t* key, botan_mp_t p, botan_mp_t g, botan_ #endif } -/* ECDH + x25519 specific operations */ +/* ECDH + x25519/x448 specific operations */ int botan_privkey_create_ecdh(botan_privkey_t* key_obj, botan_rng_t rng_obj, const char* param_str) { if(param_str == nullptr) { @@ -517,6 +525,10 @@ int botan_privkey_create_ecdh(botan_privkey_t* key_obj, botan_rng_t rng_obj, con return botan_privkey_create(key_obj, "Curve25519", "", rng_obj); } + if(params == "x448") { + return botan_privkey_create(key_obj, "X448", "", rng_obj); + } + return botan_privkey_create(key_obj, "ECDH", param_str, rng_obj); } @@ -709,6 +721,70 @@ int botan_pubkey_ed25519_get_pubkey(botan_pubkey_t key, uint8_t output[32]) { #endif } +/* Ed448 specific operations */ + +int botan_privkey_load_ed448(botan_privkey_t* key, const uint8_t privkey[57]) { +#if defined(BOTAN_HAS_ED448) + *key = nullptr; + return ffi_guard_thunk(__func__, [=]() -> int { + auto ed448 = std::make_unique(std::span(privkey, 57)); + *key = new botan_privkey_struct(std::move(ed448)); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(key, privkey); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_pubkey_load_ed448(botan_pubkey_t* key, const uint8_t pubkey[57]) { +#if defined(BOTAN_HAS_ED448) + *key = nullptr; + return ffi_guard_thunk(__func__, [=]() -> int { + auto ed448 = std::make_unique(std::span(pubkey, 57)); + *key = new botan_pubkey_struct(std::move(ed448)); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(key, pubkey); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_privkey_ed448_get_privkey(botan_privkey_t key, uint8_t output[57]) { +#if defined(BOTAN_HAS_ED448) + return BOTAN_FFI_VISIT(key, [=](const auto& k) { + if(auto ed = dynamic_cast(&k)) { + const auto ed_key = ed->raw_private_key_bits(); + Botan::copy_mem(std::span(output, 57), ed_key); + return BOTAN_FFI_SUCCESS; + } else { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + }); +#else + BOTAN_UNUSED(key, output); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_pubkey_ed448_get_pubkey(botan_pubkey_t key, uint8_t output[57]) { +#if defined(BOTAN_HAS_ED448) + return BOTAN_FFI_VISIT(key, [=](const auto& k) { + if(auto ed = dynamic_cast(&k)) { + const auto ed_key = ed->public_key_bits(); + Botan::copy_mem(std::span(output, 57), ed_key); + return BOTAN_FFI_SUCCESS; + } else { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + }); +#else + BOTAN_UNUSED(key, output); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + /* X25519 specific operations */ int botan_privkey_load_x25519(botan_privkey_t* key, const uint8_t privkey[32]) { @@ -779,6 +855,70 @@ int botan_pubkey_x25519_get_pubkey(botan_pubkey_t key, uint8_t output[32]) { #endif } +/* X448 specific operations */ + +int botan_privkey_load_x448(botan_privkey_t* key, const uint8_t privkey[56]) { +#if defined(BOTAN_HAS_X448) + *key = nullptr; + return ffi_guard_thunk(__func__, [=]() -> int { + auto x448 = std::make_unique(std::span(privkey, 56)); + *key = new botan_privkey_struct(std::move(x448)); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(key, privkey); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_pubkey_load_x448(botan_pubkey_t* key, const uint8_t pubkey[56]) { +#if defined(BOTAN_HAS_X448) + *key = nullptr; + return ffi_guard_thunk(__func__, [=]() -> int { + auto x448 = std::make_unique(std::span(pubkey, 56)); + *key = new botan_pubkey_struct(std::move(x448)); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(key, pubkey); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_privkey_x448_get_privkey(botan_privkey_t key, uint8_t output[56]) { +#if defined(BOTAN_HAS_X448) + return BOTAN_FFI_VISIT(key, [=](const auto& k) { + if(auto x448 = dynamic_cast(&k)) { + const auto x448_key = x448->raw_private_key_bits(); + Botan::copy_mem(std::span(output, 56), x448_key); + return BOTAN_FFI_SUCCESS; + } else { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + }); +#else + BOTAN_UNUSED(key, output); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_pubkey_x448_get_pubkey(botan_pubkey_t key, uint8_t output[56]) { +#if defined(BOTAN_HAS_X448) + return BOTAN_FFI_VISIT(key, [=](const auto& k) { + if(auto x448 = dynamic_cast(&k)) { + const std::vector& x448_key = x448->public_value(); + Botan::copy_mem(std::span(output, 56), x448_key); + return BOTAN_FFI_SUCCESS; + } else { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + }); +#else + BOTAN_UNUSED(key, output); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + /* * Algorithm specific key operations: Kyber */ diff --git a/src/lib/tls/tls12/msg_client_kex.cpp b/src/lib/tls/tls12/msg_client_kex.cpp index 663ba9e409d..1b8a624c497 100644 --- a/src/lib/tls/tls12/msg_client_kex.cpp +++ b/src/lib/tls/tls12/msg_client_kex.cpp @@ -105,7 +105,7 @@ Client_Key_Exchange::Client_Key_Exchange(Handshake_IO& io, const Group_Params curve_id = static_cast(reader.get_uint16_t()); const std::vector peer_public_value = reader.get_range(1, 1, 255); - if(!curve_id.is_ecdh_named_curve() && !curve_id.is_x25519()) { + if(!curve_id.is_ecdh_named_curve() && !curve_id.is_x25519() && !curve_id.is_x448()) { throw TLS_Exception(Alert::HandshakeFailure, "Server selected a group that is not compatible with the negotiated ciphersuite"); } @@ -122,8 +122,9 @@ Client_Key_Exchange::Client_Key_Exchange(Handshake_IO& io, // With X25519 and X448, a receiving party MUST check whether the // computed premaster secret is the all-zero value and abort the // handshake if so, as described in Section 6 of [RFC7748]. - if(curve_id == Group_Params::X25519 && CT::all_zeros(shared_secret.data(), shared_secret.size()).as_bool()) { - throw TLS_Exception(Alert::DecryptError, "Bad X25519 key exchange"); + if((curve_id == Group_Params::X25519 || curve_id == Group_Params::X448) && + CT::all_zeros(shared_secret.data(), shared_secret.size()).as_bool()) { + throw TLS_Exception(Alert::DecryptError, "Bad X25519 or X448 key exchange"); } if(kex_algo == Kex_Algo::ECDH) { @@ -281,9 +282,9 @@ Client_Key_Exchange::Client_Key_Exchange(const std::vector& contents, // handshake if so, as described in Section 6 of [RFC7748]. BOTAN_ASSERT_NOMSG(state.server_kex()->params().size() >= 3); Group_Params group = static_cast(state.server_kex()->params().at(2)); - if(group == Group_Params::X25519 && + if((group == Group_Params::X25519 || group == Group_Params::X448) && CT::all_zeros(shared_secret.data(), shared_secret.size()).as_bool()) { - throw TLS_Exception(Alert::DecryptError, "Bad X25519 key exchange"); + throw TLS_Exception(Alert::DecryptError, "Bad X25519 or X448 key exchange"); } } diff --git a/src/lib/tls/tls12/msg_server_kex.cpp b/src/lib/tls/tls12/msg_server_kex.cpp index 72f6e454d0a..9479bce969a 100644 --- a/src/lib/tls/tls12/msg_server_kex.cpp +++ b/src/lib/tls/tls12/msg_server_kex.cpp @@ -23,6 +23,9 @@ #if defined(BOTAN_HAS_CURVE_25519) #include #endif +#if defined(BOTAN_HAS_X448) + #include +#endif namespace Botan::TLS { @@ -103,10 +106,10 @@ Server_Key_Exchange::Server_Key_Exchange(Handshake_IO& io, std::vector ecdh_public_val; - if(m_shared_group.value() == Group_Params::X25519) { + if(m_shared_group.value() == Group_Params::X25519 || m_shared_group.value() == Group_Params::X448) { m_kex_key = state.callbacks().tls_generate_ephemeral_key(m_shared_group.value(), rng); if(!m_kex_key) { - throw TLS_Exception(Alert::InternalError, "Application did not provide a X25519 key"); + throw TLS_Exception(Alert::InternalError, "Application did not provide an EC key"); } ecdh_public_val = m_kex_key->public_value(); } else { diff --git a/src/lib/tls/tls13/tls_extensions_key_share.cpp b/src/lib/tls/tls13/tls_extensions_key_share.cpp index c7a0da1a3d2..ab5857630f1 100644 --- a/src/lib/tls/tls13/tls_extensions_key_share.cpp +++ b/src/lib/tls/tls13/tls_extensions_key_share.cpp @@ -26,6 +26,10 @@ #include #endif +#if defined(BOTAN_HAS_X448) + #include +#endif + #include #include #include @@ -125,8 +129,9 @@ class Key_Share_Entry { // With X25519 and X448, a receiving party MUST check whether the // computed premaster secret is the all-zero value and abort the // handshake if so, as described in Section 6 of [RFC7748]. - if(m_group == Named_Group::X25519 && CT::all_zeros(shared_secret.data(), shared_secret.size()).as_bool()) { - throw TLS_Exception(Alert::DecryptError, "Bad X25519 key exchange"); + if((m_group == Named_Group::X25519 || m_group == Named_Group::X448) && + CT::all_zeros(shared_secret.data(), shared_secret.size()).as_bool()) { + throw TLS_Exception(Alert::DecryptError, "Bad X25519 or X448 key exchange"); } return shared_secret; diff --git a/src/lib/tls/tls13_pqc/hybrid_public_key.cpp b/src/lib/tls/tls13_pqc/hybrid_public_key.cpp index dc503c0b25a..75a95d77cf5 100644 --- a/src/lib/tls/tls13_pqc/hybrid_public_key.cpp +++ b/src/lib/tls/tls13_pqc/hybrid_public_key.cpp @@ -30,10 +30,16 @@ std::vector> algorithm_specs_for_group(Group return {{"Curve25519", "Curve25519"}, {"Kyber", "Kyber-512-r3"}}; case Group_Params::HYBRID_X25519_KYBER_768_R3_OQS: return {{"Curve25519", "Curve25519"}, {"Kyber", "Kyber-768-r3"}}; + case Group_Params::HYBRID_X448_KYBER_768_R3_OQS: + return {{"X448", "X448"}, {"Kyber", "Kyber-768-r3"}}; case Group_Params::HYBRID_X25519_eFRODOKEM_640_SHAKE_OQS: return {{"Curve25519", "Curve25519"}, {"FrodoKEM", "eFrodoKEM-640-SHAKE"}}; case Group_Params::HYBRID_X25519_eFRODOKEM_640_AES_OQS: return {{"Curve25519", "Curve25519"}, {"FrodoKEM", "eFrodoKEM-640-AES"}}; + case Group_Params::HYBRID_X448_eFRODOKEM_976_SHAKE_OQS: + return {{"X448", "X448"}, {"FrodoKEM", "eFrodoKEM-976-SHAKE"}}; + case Group_Params::HYBRID_X448_eFRODOKEM_976_AES_OQS: + return {{"X448", "X448"}, {"FrodoKEM", "eFrodoKEM-976-AES"}}; case Group_Params::HYBRID_SECP256R1_KYBER_512_R3_OQS: return {{"ECDH", "secp256r1"}, {"Kyber", "Kyber-512-r3"}}; @@ -97,10 +103,16 @@ std::vector public_value_lengths_for_group(Group_Params group) { return {32, 800}; case Group_Params::HYBRID_X25519_KYBER_768_R3_OQS: return {32, 1184}; + case Group_Params::HYBRID_X448_KYBER_768_R3_OQS: + return {56, 1184}; case Group_Params::HYBRID_X25519_eFRODOKEM_640_SHAKE_OQS: return {32, 9616}; case Group_Params::HYBRID_X25519_eFRODOKEM_640_AES_OQS: return {32, 9616}; + case Group_Params::HYBRID_X448_eFRODOKEM_976_SHAKE_OQS: + return {56, 15632}; + case Group_Params::HYBRID_X448_eFRODOKEM_976_AES_OQS: + return {56, 15632}; case Group_Params::HYBRID_SECP256R1_KYBER_512_R3_OQS: return {32, 800}; diff --git a/src/lib/tls/tls13_pqc/kex_to_kem_adapter.cpp b/src/lib/tls/tls13_pqc/kex_to_kem_adapter.cpp index b43b581b6af..b544663de94 100644 --- a/src/lib/tls/tls13_pqc/kex_to_kem_adapter.cpp +++ b/src/lib/tls/tls13_pqc/kex_to_kem_adapter.cpp @@ -27,6 +27,10 @@ #include #endif +#if defined(BOTAN_HAS_X448) + #include +#endif + namespace Botan::TLS { namespace { @@ -60,6 +64,12 @@ std::vector kex_public_value(const Public_Key& kex_public_key) { } #endif +#if defined(BOTAN_HAS_X448) + if(const auto* curve = dynamic_cast(&kex_public_key)) { + return curve->public_value(); + } +#endif + throw Not_Implemented( fmt("Cannot get public value from unknown key agreement public key of type '{}' in the hybrid KEM key", kex_public_key.algo_name())); @@ -97,6 +107,13 @@ size_t kex_shared_key_length(const Public_Key& kex_public_key) { } #endif +#if defined(BOTAN_HAS_X448) + if(const auto* curve = dynamic_cast(&kex_public_key)) { + BOTAN_UNUSED(curve); + return 56; /* TODO: magic number */ + } +#endif + throw Not_Implemented( fmt("Cannot get shared kex key length from unknown key agreement public key of type '{}' in the hybrid KEM key", kex_public_key.algo_name())); diff --git a/src/lib/tls/tls_algos.cpp b/src/lib/tls/tls_algos.cpp index 8623fe35777..eac5d4e9afd 100644 --- a/src/lib/tls/tls_algos.cpp +++ b/src/lib/tls/tls_algos.cpp @@ -156,6 +156,9 @@ std::optional Group_Params::from_string(std::string_view group_nam if(group_name == "x25519") { return Group_Params::X25519; } + if(group_name == "x448") { + return Group_Params::X448; + } if(group_name == "ffdhe/ietf/2048") { return Group_Params::FFDHE_2048; @@ -211,12 +214,21 @@ 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 == "x448/Kyber-768-r3") { + return Group_Params::HYBRID_X448_KYBER_768_R3_OQS; + } if(group_name == "x25519/eFrodoKEM-640-SHAKE") { return Group_Params::HYBRID_X25519_eFRODOKEM_640_SHAKE_OQS; } if(group_name == "x25519/eFrodoKEM-640-AES") { return Group_Params::HYBRID_X25519_eFRODOKEM_640_AES_OQS; } + if(group_name == "x448/eFrodoKEM-976-SHAKE") { + return Group_Params::HYBRID_X448_eFRODOKEM_976_SHAKE_OQS; + } + if(group_name == "x448/eFrodoKEM-976-AES") { + return Group_Params::HYBRID_X448_eFRODOKEM_976_AES_OQS; + } if(group_name == "secp256r1/Kyber-512-r3") { return Group_Params::HYBRID_SECP256R1_KYBER_512_R3_OQS; @@ -270,6 +282,8 @@ std::optional Group_Params::to_string() const { return "brainpool512r1"; case Group_Params::X25519: return "x25519"; + case Group_Params::X448: + return "x448"; case Group_Params::FFDHE_2048: return "ffdhe/ietf/2048"; @@ -306,6 +320,10 @@ std::optional Group_Params::to_string() const { return "x25519/eFrodoKEM-640-SHAKE"; case Group_Params::HYBRID_X25519_eFRODOKEM_640_AES_OQS: return "x25519/eFrodoKEM-640-AES"; + case Group_Params::HYBRID_X448_eFRODOKEM_976_SHAKE_OQS: + return "x448/eFrodoKEM-976-SHAKE"; + case Group_Params::HYBRID_X448_eFRODOKEM_976_AES_OQS: + return "x448/eFrodoKEM-976-AES"; case Group_Params::HYBRID_SECP256R1_eFRODOKEM_640_SHAKE_OQS: return "secp256r1/eFrodoKEM-640-SHAKE"; case Group_Params::HYBRID_SECP256R1_eFRODOKEM_640_AES_OQS: @@ -326,6 +344,8 @@ 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_X448_KYBER_768_R3_OQS: + return "x448/Kyber-768-r3"; case Group_Params::HYBRID_SECP256R1_KYBER_512_R3_OQS: return "secp256r1/Kyber-512-r3"; diff --git a/src/lib/tls/tls_algos.h b/src/lib/tls/tls_algos.h index 3d52f420f66..6927e8df08d 100644 --- a/src/lib/tls/tls_algos.h +++ b/src/lib/tls/tls_algos.h @@ -90,6 +90,7 @@ enum class Group_Params_Code : uint16_t { BRAINPOOL512R1 = 28, X25519 = 29, + X448 = 30, FFDHE_2048 = 256, FFDHE_3072 = 257, @@ -122,6 +123,8 @@ enum class Group_Params_Code : uint16_t { HYBRID_X25519_KYBER_512_R3_OQS = 0x2F39, HYBRID_X25519_KYBER_768_R3_OQS = 0x6399, + HYBRID_X448_KYBER_768_R3_OQS = 0x2F90, + HYBRID_SECP256R1_KYBER_512_R3_OQS = 0x2F3A, HYBRID_SECP256R1_KYBER_768_R3_OQS = 0x639A, @@ -132,6 +135,9 @@ enum class Group_Params_Code : uint16_t { HYBRID_X25519_eFRODOKEM_640_SHAKE_OQS = 0x2F81, HYBRID_X25519_eFRODOKEM_640_AES_OQS = 0x2F80, + HYBRID_X448_eFRODOKEM_976_SHAKE_OQS = 0x2F83, + HYBRID_X448_eFRODOKEM_976_AES_OQS = 0x2F82, + HYBRID_SECP256R1_eFRODOKEM_640_SHAKE_OQS = 0x2F01, HYBRID_SECP256R1_eFRODOKEM_640_AES_OQS = 0x2F00, @@ -169,6 +175,8 @@ class BOTAN_PUBLIC_API(3, 2) Group_Params final { constexpr bool is_x25519() const { return m_code == Group_Params_Code::X25519; } + constexpr bool is_x448() const { return m_code == Group_Params_Code::X448; } + constexpr bool is_ecdh_named_curve() const { return m_code == Group_Params_Code::SECP256R1 || m_code == Group_Params_Code::SECP384R1 || m_code == Group_Params_Code::SECP521R1 || m_code == Group_Params_Code::BRAINPOOL256R1 || @@ -200,7 +208,7 @@ class BOTAN_PUBLIC_API(3, 2) Group_Params final { m_code == Group_Params_Code::eFRODOKEM_1344_AES_OQS; } - constexpr bool is_pure_ecc_group() const { return is_x25519() || is_ecdh_named_curve(); } + 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(); } @@ -208,8 +216,11 @@ class BOTAN_PUBLIC_API(3, 2) Group_Params final { 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_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 || diff --git a/src/lib/tls/tls_callbacks.cpp b/src/lib/tls/tls_callbacks.cpp index 0f89e70894d..57a81c71d9b 100644 --- a/src/lib/tls/tls_callbacks.cpp +++ b/src/lib/tls/tls_callbacks.cpp @@ -26,6 +26,10 @@ #include #endif +#if defined(BOTAN_HAS_X448) + #include +#endif + #if defined(BOTAN_HAS_KYBER) #include #endif @@ -274,6 +278,12 @@ std::unique_ptr TLS::Callbacks::tls_generate_ephemeral_key } #endif +#if defined(BOTAN_HAS_X448) + if(group_params.is_x448()) { + return std::make_unique(rng); + } +#endif + if(group_params.is_kem()) { throw TLS_Exception(Alert::IllegalParameter, "cannot generate an ephemeral KEX key for a KEM"); } @@ -339,6 +349,19 @@ secure_vector TLS::Callbacks::tls_ephemeral_key_agreement( } #endif +#if defined(BOTAN_HAS_X448) + if(group_params.is_x448()) { + if(public_value.size() != 56) { + throw TLS_Exception(Alert::HandshakeFailure, "Invalid X448 key size"); + } + + X448_PublicKey peer_key(public_value); + policy.check_peer_key_acceptable(peer_key); + + return agree(private_key, peer_key); + } +#endif + throw TLS_Exception(Alert::IllegalParameter, "Did not recognize the key exchange group"); } diff --git a/src/lib/tls/tls_policy.cpp b/src/lib/tls/tls_policy.cpp index 57591667d7c..dc09ee47635 100644 --- a/src/lib/tls/tls_policy.cpp +++ b/src/lib/tls/tls_policy.cpp @@ -161,6 +161,9 @@ std::vector Policy::key_exchange_groups() const { #if defined(BOTAN_HAS_CURVE_25519) Group_Params::X25519, #endif +#if defined(BOTAN_HAS_X448) + Group_Params::X448, +#endif Group_Params::SECP256R1, Group_Params::BRAINPOOL256R1, Group_Params::SECP384R1, Group_Params::BRAINPOOL384R1, Group_Params::SECP521R1, Group_Params::BRAINPOOL512R1, @@ -223,7 +226,7 @@ void Policy::check_peer_key_acceptable(const Public_Key& public_key) const { expected_keylength = minimum_rsa_bits(); } else if(algo_name == "DH") { expected_keylength = minimum_dh_group_size(); - } else if(algo_name == "ECDH" || algo_name == "Curve25519") { + } else if(algo_name == "ECDH" || algo_name == "Curve25519" || algo_name == "X448") { expected_keylength = minimum_ecdh_group_size(); } else if(algo_name == "ECDSA") { expected_keylength = minimum_ecdsa_group_size(); diff --git a/src/lib/tls/tls_session.cpp b/src/lib/tls/tls_session.cpp index 5de8bcdb91a..d4e366b77dc 100644 --- a/src/lib/tls/tls_session.cpp +++ b/src/lib/tls/tls_session.cpp @@ -144,7 +144,7 @@ Session_Summary::Session_Summary(const Server_Hello_13& server_hello, 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()) { + } 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; @@ -160,7 +160,7 @@ Session_Summary::Session_Summary(const Server_Hello_13& server_hello, 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()) { + } 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; diff --git a/src/lib/tls/tls_signature_scheme.cpp b/src/lib/tls/tls_signature_scheme.cpp index d14c6943548..dde09492ca7 100644 --- a/src/lib/tls/tls_signature_scheme.cpp +++ b/src/lib/tls/tls_signature_scheme.cpp @@ -206,6 +206,8 @@ AlgorithmIdentifier Signature_Scheme::key_algorithm_identifier() const noexcept case EDDSA_25519: return {"Ed25519", AlgorithmIdentifier::USE_EMPTY_PARAM}; + case EDDSA_448: + return {"Ed448", AlgorithmIdentifier::USE_EMPTY_PARAM}; case RSA_PKCS1_SHA1: case RSA_PKCS1_SHA256: diff --git a/src/lib/tls/tls_signature_scheme.h b/src/lib/tls/tls_signature_scheme.h index 3506192fc43..84ceb8873ba 100644 --- a/src/lib/tls/tls_signature_scheme.h +++ b/src/lib/tls/tls_signature_scheme.h @@ -47,7 +47,7 @@ class BOTAN_PUBLIC_API(3, 0) Signature_Scheme { RSA_PSS_SHA512 = 0x0806, EDDSA_25519 = 0x0807, - EDDSA_448 = 0x0808, // not implemented + EDDSA_448 = 0x0808, }; public: diff --git a/src/lib/tls/tls_text_policy.cpp b/src/lib/tls/tls_text_policy.cpp index 19bbd1fb817..7ec142a5e8d 100644 --- a/src/lib/tls/tls_text_policy.cpp +++ b/src/lib/tls/tls_text_policy.cpp @@ -233,6 +233,10 @@ std::vector Text_Policy::read_group_list(std::string_view group_st if(group_id == Group_Params::X25519) continue; #endif +#if !defined(BOTAN_HAS_X448) + if(group_id == Group_Params::X448) + continue; +#endif if(group_id == Group_Params::NONE) { try { diff --git a/src/lib/x509/x509_obj.cpp b/src/lib/x509/x509_obj.cpp index fcd4604325b..ae9be85f640 100644 --- a/src/lib/x509/x509_obj.cpp +++ b/src/lib/x509/x509_obj.cpp @@ -166,7 +166,7 @@ std::string x509_signature_padding_for(const std::string& algo_name, return fmt("{}({})", user_specified_padding, hash_fn); } } - } else if(algo_name == "Ed25519") { + } else if(algo_name == "Ed25519" || algo_name == "Ed448") { return user_specified_padding.empty() ? "Pure" : std::string(user_specified_padding); } else if(algo_name.starts_with("Dilithium-")) { return user_specified_padding.empty() ? "Randomized" : std::string(user_specified_padding); diff --git a/src/lib/x509/x509path.cpp b/src/lib/x509/x509path.cpp index a57aa069baf..597bd5f8b38 100644 --- a/src/lib/x509/x509path.cpp +++ b/src/lib/x509/x509path.cpp @@ -959,7 +959,8 @@ Path_Validation_Restrictions::Path_Validation_Restrictions(bool require_rev, m_trusted_hashes.insert("SHA-256"); m_trusted_hashes.insert("SHA-384"); m_trusted_hashes.insert("SHA-512"); - m_trusted_hashes.insert("SHAKE-256(512)"); + m_trusted_hashes.insert("SHAKE-256(512)"); // Dilithium/ML-DSA + m_trusted_hashes.insert("SHAKE-256(912)"); // Ed448 } namespace { diff --git a/src/python/botan3.py b/src/python/botan3.py index c7ac481ff4f..71897cea2a5 100755 --- a/src/python/botan3.py +++ b/src/python/botan3.py @@ -339,10 +339,18 @@ def ffi_api(fn, args, allowed_errors=None): ffi_api(dll.botan_pubkey_load_ed25519, [c_void_p, c_char_p]) ffi_api(dll.botan_privkey_ed25519_get_privkey, [c_void_p, c_char_p]) ffi_api(dll.botan_pubkey_ed25519_get_pubkey, [c_void_p, c_char_p]) + ffi_api(dll.botan_privkey_load_ed448, [c_void_p, c_char_p]) + ffi_api(dll.botan_pubkey_load_ed448, [c_void_p, c_char_p]) + ffi_api(dll.botan_privkey_ed448_get_privkey, [c_void_p, c_char_p]) + ffi_api(dll.botan_pubkey_ed448_get_pubkey, [c_void_p, c_char_p]) ffi_api(dll.botan_privkey_load_x25519, [c_void_p, c_char_p]) ffi_api(dll.botan_pubkey_load_x25519, [c_void_p, c_char_p]) ffi_api(dll.botan_privkey_x25519_get_privkey, [c_void_p, c_char_p]) ffi_api(dll.botan_pubkey_x25519_get_pubkey, [c_void_p, c_char_p]) + ffi_api(dll.botan_privkey_load_x448, [c_void_p, c_char_p]) + ffi_api(dll.botan_pubkey_load_x448, [c_void_p, c_char_p]) + ffi_api(dll.botan_privkey_x448_get_privkey, [c_void_p, c_char_p]) + ffi_api(dll.botan_pubkey_x448_get_pubkey, [c_void_p, c_char_p]) ffi_api(dll.botan_privkey_load_kyber, [c_void_p, c_char_p]) ffi_api(dll.botan_pubkey_load_kyber, [c_void_p, c_char_p]) ffi_api(dll.botan_privkey_view_kyber_raw_key, [c_void_p, c_void_p, VIEW_BIN_CALLBACK]) @@ -1176,6 +1184,9 @@ def create(cls, algo, params, rng_obj): if params == 'curve25519': algo = 'Curve25519' params = '' + elif params == 'x448': + algo = 'X448' + params = '' else: algo = 'ECDH' elif algo in ['mce', 'mceliece']: diff --git a/src/scripts/run_tls_fuzzer.py b/src/scripts/run_tls_fuzzer.py index fd0a4e53e22..d5221e09178 100755 --- a/src/scripts/run_tls_fuzzer.py +++ b/src/scripts/run_tls_fuzzer.py @@ -19,7 +19,6 @@ def script_is_disabled(script_name): disabled = { 'test-SSLv3-padding.py', 'test-serverhello-random.py', # assumes support for SSLv2 hello - 'test-x25519.py', # assumes support for X448 (!) } if script_name in disabled: diff --git a/src/scripts/test_cli.py b/src/scripts/test_cli.py index ac67fb33f18..9accc6df869 100755 --- a/src/scripts/test_cli.py +++ b/src/scripts/test_cli.py @@ -1024,6 +1024,11 @@ def __init__(self, name, protocol_version, policy, **kwargs): self.psk_identity = kwargs.get("psk_identity") configs = [ + # Explicitly testing x448-based key exchange against ourselves, as Bogo test + # don't cover that. Better than nothing... + TestConfig("x448", "1.2", "allow_tls12=true\nallow_tls13=false\nkey_exchange_groups=x448"), + TestConfig("x448", "1.3", "allow_tls12=false\nallow_tls13=true\nkey_exchange_groups=x448"), + # Regression test: TLS 1.3 server hit an assertion when no certificate # chain was found. Here, we provoke this by requiring # an RSA-based certificate (server uses ECDSA). @@ -1055,7 +1060,7 @@ def __init__(self, name, protocol_version, policy, **kwargs): with open(tls_server_policy, 'w', encoding='utf8') as f: f.write('key_exchange_methods = ECDH DH ECDHE_PSK\n') - f.write("key_exchange_groups = x25519 secp256r1 ffdhe/ietf/2048 Kyber-512-r3 x25519/Kyber-512-r3") + f.write("key_exchange_groups = x25519 x448 secp256r1 ffdhe/ietf/2048 Kyber-512-r3 x25519/Kyber-512-r3") tls_server = subprocess.Popen([CLI_PATH, 'tls_server', '--max-clients=%d' % (len(configs)), '--port=%d' % (server_port), '--policy=%s' % (tls_server_policy), @@ -1213,9 +1218,11 @@ def get_oqs_ports(): oqsp = get_oqs_ports() if oqsp: + # src/scripts/test_cli.py --run-online-tests ./botan pqc_hybrid_tests test_cfg += [ 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), TestConfig("test.openquantumsafe.org", "secp256r1/Kyber-512-r3", port=oqsp['p256_kyber512'], ca=oqs_test_ca), TestConfig("test.openquantumsafe.org", "secp256r1/Kyber-768-r3", port=oqsp['p256_kyber768'], ca=oqs_test_ca), TestConfig("test.openquantumsafe.org", "secp384r1/Kyber-768-r3", port=oqsp['p384_kyber768'], ca=oqs_test_ca), @@ -1231,6 +1238,8 @@ def get_oqs_ports(): TestConfig("test.openquantumsafe.org", "eFrodoKEM-1344-AES", port=oqsp['frodo1344aes'], ca=oqs_test_ca), TestConfig("test.openquantumsafe.org", "x25519/eFrodoKEM-640-SHAKE", port=oqsp['x25519_frodo640shake'], ca=oqs_test_ca), TestConfig("test.openquantumsafe.org", "x25519/eFrodoKEM-640-AES", port=oqsp['x25519_frodo640aes'], ca=oqs_test_ca), + TestConfig("test.openquantumsafe.org", "x448/eFrodoKEM-976-SHAKE", port=oqsp['x448_frodo976shake'], ca=oqs_test_ca), + TestConfig("test.openquantumsafe.org", "x448/eFrodoKEM-976-AES", port=oqsp['x448_frodo976aes'], ca=oqs_test_ca), TestConfig("test.openquantumsafe.org", "secp256r1/eFrodoKEM-640-SHAKE", port=oqsp['p256_frodo640shake'], ca=oqs_test_ca), TestConfig("test.openquantumsafe.org", "secp256r1/eFrodoKEM-640-AES", port=oqsp['p256_frodo640aes'], ca=oqs_test_ca), TestConfig("test.openquantumsafe.org", "secp384r1/eFrodoKEM-976-SHAKE", port=oqsp['p384_frodo976shake'], ca=oqs_test_ca), @@ -1501,7 +1510,7 @@ def cli_speed_pk_tests(_tmp_dir): msec = 1 pk_algos = ["ECDSA", "ECDH", "SM2", "ECKCDSA", "ECGDSA", "GOST-34.10", - "DH", "DSA", "ElGamal", "Ed25519", "Curve25519", "McEliece", + "DH", "DSA", "ElGamal", "Ed25519", "Ed448", "Curve25519", "X448", "McEliece", "RSA", "RSA_keygen", "XMSS", "ec_h2c", "Kyber", "Dilithium", "SPHINCS+"] diff --git a/src/scripts/test_python.py b/src/scripts/test_python.py index 9dd12cb2ac7..3fd54090598 100644 --- a/src/scripts/test_python.py +++ b/src/scripts/test_python.py @@ -523,6 +523,49 @@ def test_ecdh(self): self.assertEqual(a_pem, new_a.to_pem()) + def test_rfc7748_kex(self): + rng = botan.RandomNumberGenerator() + + for alg in ['Curve25519', 'X448']: + a_priv = botan.PrivateKey.create(alg, '', rng) + b_priv = botan.PrivateKey.create(alg, '', rng) + + a_op = botan.PKKeyAgreement(a_priv, "Raw") + b_op = botan.PKKeyAgreement(b_priv, "Raw") + + a_pubv = a_op.public_value() + b_pubv = b_op.public_value() + + salt = bytes() + + a_key = a_op.agree(b_pubv, 0, salt) + b_key = b_op.agree(a_pubv, 0, salt) + + self.assertEqual(a_key, b_key) + self.assertEqual(len(a_key), a_op.underlying_output_length()) + + def test_eddsa(self): + rng = botan.RandomNumberGenerator() + msg = 'test message' + + for alg in ['Ed25519', 'Ed448']: + priv = botan.PrivateKey.create(alg, '', rng) + pub = priv.get_public_key() + + # Sign message + signer = botan.PKSign(priv, '') + signer.update(msg) + signature = signer.finish(rng) + + # Verify signature + verifier = botan.PKVerify(pub, '') + verifier.update(msg) + self.assertTrue(verifier.check_signature(signature)) + + # Verify invalid signature + verifier.update('not test message') + self.assertFalse(verifier.check_signature(signature)) + def test_certs(self): cert = botan.X509Cert(filename=test_data("src/tests/data/x509/ecc/CSCA.CSCA.csca-germany.1.crt")) pubkey = cert.subject_public_key() diff --git a/src/scripts/tls_scanner/policy.txt b/src/scripts/tls_scanner/policy.txt index c83e3f8070e..36346a33115 100644 --- a/src/scripts/tls_scanner/policy.txt +++ b/src/scripts/tls_scanner/policy.txt @@ -10,7 +10,7 @@ signature_hashes=SHA-384 SHA-256 SHA-1 macs=AEAD SHA-384 SHA-256 SHA-1 key_exchange_methods=ECDH DH RSA signature_methods=ECDSA RSA DSA IMPLICIT -ecc_curves=x25519 secp256r1 secp384r1 +ecc_curves=x25519 x448 secp256r1 secp384r1 minimum_dh_group_size=1024 minimum_ecdh_group_size=255 minimum_rsa_bits=2048 diff --git a/src/tests/data/tls-policy/compat.txt b/src/tests/data/tls-policy/compat.txt index b79d69720f3..ad03ac3c4ee 100644 --- a/src/tests/data/tls-policy/compat.txt +++ b/src/tests/data/tls-policy/compat.txt @@ -16,7 +16,7 @@ macs = AEAD SHA-256 SHA-384 SHA-1 signature_hashes = SHA-512 SHA-384 SHA-256 SHA-1 signature_methods = ECDSA RSA IMPLICIT key_exchange_methods = ECDH DH RSA -key_exchange_groups = x25519 secp256r1 secp521r1 secp384r1 brainpool256r1 brainpool384r1 brainpool512r1 ffdhe/ietf/2048 ffdhe/ietf/3072 ffdhe/ietf/4096 ffdhe/ietf/6144 ffdhe/ietf/8192 +key_exchange_groups = x25519 x448 secp256r1 secp521r1 secp384r1 brainpool256r1 brainpool384r1 brainpool512r1 ffdhe/ietf/2048 ffdhe/ietf/3072 ffdhe/ietf/4096 ffdhe/ietf/6144 ffdhe/ietf/8192 allow_insecure_renegotiation = false include_time_in_hello_random = true allow_client_initiated_renegotiation = true diff --git a/src/tests/data/tls-policy/datagram.txt b/src/tests/data/tls-policy/datagram.txt index 57a897856b1..d583c51faef 100644 --- a/src/tests/data/tls-policy/datagram.txt +++ b/src/tests/data/tls-policy/datagram.txt @@ -9,7 +9,7 @@ macs = AEAD signature_hashes = SHA-512 SHA-384 SHA-256 signature_methods = ECDSA RSA key_exchange_methods = ECDH DH -key_exchange_groups = x25519 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 x448 secp256r1 brainpool256r1 secp384r1 brainpool384r1 secp521r1 brainpool512r1 ffdhe/ietf/2048 ffdhe/ietf/3072 ffdhe/ietf/4096 ffdhe/ietf/6144 ffdhe/ietf/8192 allow_insecure_renegotiation = false include_time_in_hello_random = true allow_server_initiated_renegotiation = false diff --git a/src/tests/data/tls-policy/default.txt b/src/tests/data/tls-policy/default.txt index 3a5dd16dd1a..b5ec58c28f2 100644 --- a/src/tests/data/tls-policy/default.txt +++ b/src/tests/data/tls-policy/default.txt @@ -9,7 +9,7 @@ macs = AEAD SHA-256 SHA-384 SHA-1 signature_hashes = SHA-512 SHA-384 SHA-256 signature_methods = ECDSA RSA key_exchange_methods = ECDH DH -key_exchange_groups = x25519 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 x448 secp256r1 brainpool256r1 secp384r1 brainpool384r1 secp521r1 brainpool512r1 ffdhe/ietf/2048 ffdhe/ietf/3072 ffdhe/ietf/4096 ffdhe/ietf/6144 ffdhe/ietf/8192 allow_insecure_renegotiation = false include_time_in_hello_random = true allow_server_initiated_renegotiation = false diff --git a/src/tests/data/tls-policy/default_tls13.txt b/src/tests/data/tls-policy/default_tls13.txt index 09970c70f70..dfdc84dd5a0 100644 --- a/src/tests/data/tls-policy/default_tls13.txt +++ b/src/tests/data/tls-policy/default_tls13.txt @@ -9,7 +9,7 @@ macs = AEAD SHA-256 SHA-384 SHA-1 signature_hashes = SHA-512 SHA-384 SHA-256 signature_methods = ECDSA RSA key_exchange_methods = ECDH DH -key_exchange_groups = x25519 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 x448 secp256r1 brainpool256r1 secp384r1 brainpool384r1 secp521r1 brainpool512r1 ffdhe/ietf/2048 ffdhe/ietf/3072 ffdhe/ietf/4096 ffdhe/ietf/6144 ffdhe/ietf/8192 allow_insecure_renegotiation = false include_time_in_hello_random = true allow_server_initiated_renegotiation = false diff --git a/src/tests/data/tls-policy/strict.txt b/src/tests/data/tls-policy/strict.txt index ed324be1c61..e21c0000c77 100644 --- a/src/tests/data/tls-policy/strict.txt +++ b/src/tests/data/tls-policy/strict.txt @@ -9,7 +9,7 @@ macs = AEAD signature_hashes = SHA-512 SHA-384 signature_methods = ECDSA RSA key_exchange_methods = ECDH -key_exchange_groups = x25519 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 x448 secp256r1 brainpool256r1 secp384r1 brainpool384r1 secp521r1 brainpool512r1 ffdhe/ietf/2048 ffdhe/ietf/3072 ffdhe/ietf/4096 ffdhe/ietf/6144 ffdhe/ietf/8192 allow_insecure_renegotiation = false include_time_in_hello_random = true allow_server_initiated_renegotiation = false diff --git a/src/tests/data/tls-policy/strict_tls13.txt b/src/tests/data/tls-policy/strict_tls13.txt index 30c5de059c0..28fab2edb6a 100644 --- a/src/tests/data/tls-policy/strict_tls13.txt +++ b/src/tests/data/tls-policy/strict_tls13.txt @@ -9,7 +9,7 @@ macs = AEAD signature_hashes = SHA-512 SHA-384 signature_methods = ECDSA RSA key_exchange_methods = ECDH -key_exchange_groups = x25519 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 x448 secp256r1 brainpool256r1 secp384r1 brainpool384r1 secp521r1 brainpool512r1 ffdhe/ietf/2048 ffdhe/ietf/3072 ffdhe/ietf/4096 ffdhe/ietf/6144 ffdhe/ietf/8192 allow_insecure_renegotiation = false include_time_in_hello_random = true allow_server_initiated_renegotiation = false diff --git a/src/tests/test_ffi.cpp b/src/tests/test_ffi.cpp index c73075a60ce..3d862953f0c 100644 --- a/src/tests/test_ffi.cpp +++ b/src/tests/test_ffi.cpp @@ -2620,6 +2620,73 @@ class FFI_Ed25519_Test final : public FFI_Test { } }; +class FFI_Ed448_Test final : public FFI_Test { + public: + std::string name() const override { return "FFI Ed448"; } + + void ffi_test(Test::Result& result, botan_rng_t rng) override { + botan_pubkey_t pub; + botan_privkey_t priv; + + // RFC 8032: Testvector Ed448, 1 octet + const auto sk = Botan::hex_decode( + "c4eab05d357007c632f3dbb48489924d552b08fe0c353a0d4a1f00acda2c463afbea67c5e8d2877c5e3bc397a659949ef8021e954e0a12274e"); + const auto pk_ref = Botan::hex_decode( + "43ba28f430cdff456ae531545f7ecd0ac834a55d9358c0372bfa0c6c6798c0866aea01eb00742802b8438ea4cb82169c235160627b4c3a9480"); + const auto msg = Botan::hex_decode("03"); + const auto sig_ref = Botan::hex_decode( + "26b8f91727bd62897af15e41eb43c377efb9c610d48f2335cb0bd0087810f4352541b143c4b981b7e18f62de8ccdf633fc1bf037ab7cd779805e0dbcc0aae1cbcee1afb2e027df36bc04dcecbf154336c19f0af7e0a6472905e799f1953d2a0ff3348ab21aa4adafd1d234441cf807c03a00"); + + if(!TEST_FFI_INIT(botan_privkey_load_ed448, (&priv, sk.data()))) { + return; + } + + std::vector retr_privkey(57); + TEST_FFI_OK(botan_privkey_ed448_get_privkey, (priv, retr_privkey.data())); + result.test_is_eq("Private key matches", retr_privkey, sk); + + TEST_FFI_OK(botan_privkey_export_pubkey, (&pub, priv)); + + std::vector retr_pubkey(57); + TEST_FFI_OK(botan_pubkey_ed448_get_pubkey, (pub, retr_pubkey.data())); + result.test_is_eq("Public key matches", retr_pubkey, pk_ref); + + TEST_FFI_OK(botan_pubkey_destroy, (pub)); + TEST_FFI_OK(botan_pubkey_load_ed448, (&pub, pk_ref.data())); + + botan_pk_op_sign_t signer; + std::vector signature; + + if(TEST_FFI_OK(botan_pk_op_sign_create, (&signer, priv, "Pure", 0))) { + TEST_FFI_OK(botan_pk_op_sign_update, (signer, msg.data(), msg.size())); + + size_t sig_len; + TEST_FFI_OK(botan_pk_op_sign_output_length, (signer, &sig_len)); + + signature.resize(sig_len); + + TEST_FFI_OK(botan_pk_op_sign_finish, (signer, rng, signature.data(), &sig_len)); + signature.resize(sig_len); + + TEST_FFI_OK(botan_pk_op_sign_destroy, (signer)); + } + + result.test_eq("Expected signature", signature, sig_ref); + + botan_pk_op_verify_t verifier; + + if(TEST_FFI_OK(botan_pk_op_verify_create, (&verifier, pub, "Pure", 0))) { + TEST_FFI_OK(botan_pk_op_verify_update, (verifier, msg.data(), msg.size())); + TEST_FFI_OK(botan_pk_op_verify_finish, (verifier, signature.data(), signature.size())); + + TEST_FFI_OK(botan_pk_op_verify_destroy, (verifier)); + } + + TEST_FFI_OK(botan_pubkey_destroy, (pub)); + TEST_FFI_OK(botan_privkey_destroy, (priv)); + } +}; + class FFI_X25519_Test final : public FFI_Test { public: std::string name() const override { return "FFI X25519"; } @@ -2674,6 +2741,59 @@ class FFI_X25519_Test final : public FFI_Test { } }; +class FFI_X448_Test final : public FFI_Test { + public: + std::string name() const override { return "FFI X448"; } + + void ffi_test(Test::Result& result, botan_rng_t /*unused*/) override { + // From RFC 7748 Section 6.2 + const auto a_pub_ref = Botan::hex_decode( + "9b08f7cc31b7e3e67d22d5aea121074a273bd2b83de09c63faa73d2c22c5d9bbc836647241d953d40c5b12da88120d53177f80e532c41fa0"); + const auto b_priv_ref = Botan::hex_decode( + "1c306a7ac2a0e2e0990b294470cba339e6453772b075811d8fad0d1d6927c120bb5ee8972b0d3e21374c9c921b09d1b0366f10b65173992d"); + const auto b_pub_ref = Botan::hex_decode( + "3eb7a829b0cd20f5bcfc0b599b6feccf6da4627107bdb0d4f345b43027d8b972fc3e34fb4232a13ca706dcb57aec3dae07bdc1c67bf33609"); + const auto shared_secret_ref = Botan::hex_decode( + "07fff4181ac6cc95ec1c16a94a0f74d12da232ce40a77552281d282bb60c0b56fd2464c335543936521c24403085d59a449a5037514a879d"); + + botan_privkey_t b_priv; + if(!TEST_FFI_INIT(botan_privkey_load_x448, (&b_priv, b_priv_ref.data()))) { + return; + } + + std::vector privkey_read(56); + TEST_FFI_OK(botan_privkey_x448_get_privkey, (b_priv, privkey_read.data())); + result.test_eq("X448 private key", privkey_read, b_priv_ref); + + std::vector pubkey_read(56); + + botan_pubkey_t b_pub; + TEST_FFI_OK(botan_privkey_export_pubkey, (&b_pub, b_priv)); + TEST_FFI_OK(botan_pubkey_x448_get_pubkey, (b_pub, pubkey_read.data())); + result.test_eq("X448 public key b", pubkey_read, b_pub_ref); + + botan_pubkey_t a_pub; + TEST_FFI_OK(botan_pubkey_load_x448, (&a_pub, a_pub_ref.data())); + TEST_FFI_OK(botan_pubkey_x448_get_pubkey, (a_pub, pubkey_read.data())); + result.test_eq("X448 public key a", pubkey_read, a_pub_ref); + + botan_pk_op_ka_t ka; + REQUIRE_FFI_OK(botan_pk_op_key_agreement_create, (&ka, b_priv, "Raw", 0)); + + std::vector shared_output(56); + size_t shared_len = shared_output.size(); + TEST_FFI_OK(botan_pk_op_key_agreement, + (ka, shared_output.data(), &shared_len, a_pub_ref.data(), a_pub_ref.size(), nullptr, 0)); + + result.test_eq("Shared secret matches expected", shared_secret_ref, shared_output); + + TEST_FFI_OK(botan_pubkey_destroy, (a_pub)); + TEST_FFI_OK(botan_pubkey_destroy, (b_pub)); + TEST_FFI_OK(botan_privkey_destroy, (b_priv)); + TEST_FFI_OK(botan_pk_op_key_agreement_destroy, (ka)); + } +}; + int botan_ffi_view_u8_fn(void* ctx, const uint8_t buf[], size_t len) { if(!ctx || !buf) { return BOTAN_FFI_ERROR_NULL_POINTER; @@ -3035,7 +3155,9 @@ BOTAN_REGISTER_TEST("ffi", "ffi_sm2_enc", FFI_SM2_Enc_Test); BOTAN_REGISTER_TEST("ffi", "ffi_ecdh", FFI_ECDH_Test); BOTAN_REGISTER_TEST("ffi", "ffi_mceliece", FFI_McEliece_Test); BOTAN_REGISTER_TEST("ffi", "ffi_ed25519", FFI_Ed25519_Test); +BOTAN_REGISTER_TEST("ffi", "ffi_ed448", FFI_Ed448_Test); BOTAN_REGISTER_TEST("ffi", "ffi_x25519", FFI_X25519_Test); +BOTAN_REGISTER_TEST("ffi", "ffi_x448", FFI_X448_Test); BOTAN_REGISTER_TEST("ffi", "ffi_kyber512", FFI_Kyber512_Test); BOTAN_REGISTER_TEST("ffi", "ffi_kyber768", FFI_Kyber768_Test); BOTAN_REGISTER_TEST("ffi", "ffi_kyber1024", FFI_Kyber1024_Test); diff --git a/src/tests/unit_x509.cpp b/src/tests/unit_x509.cpp index 6a4ace5307d..0f166c29673 100644 --- a/src/tests/unit_x509.cpp +++ b/src/tests/unit_x509.cpp @@ -1506,7 +1506,7 @@ std::vector get_sig_paddings(const std::string& sig_algo, const std } else if(sig_algo == "DSA" || sig_algo == "ECDSA" || sig_algo == "ECGDSA" || sig_algo == "ECKCDSA" || sig_algo == "GOST-34.10") { return {hash}; - } else if(sig_algo == "Ed25519") { + } else if(sig_algo == "Ed25519" || sig_algo == "Ed448") { return {"Pure"}; } else if(sig_algo == "Dilithium") { return {"Randomized"}; @@ -1523,7 +1523,7 @@ class X509_Cert_Unit_Tests final : public Test { auto& rng = this->rng(); const std::string sig_algos[]{ - "RSA", "DSA", "ECDSA", "ECGDSA", "ECKCDSA", "GOST-34.10", "Ed25519", "Dilithium"}; + "RSA", "DSA", "ECDSA", "ECGDSA", "ECKCDSA", "GOST-34.10", "Ed25519", "Ed448", "Dilithium"}; for(const std::string& algo : sig_algos) { #if !defined(BOTAN_HAS_EMSA_PKCS1) @@ -1536,6 +1536,9 @@ class X509_Cert_Unit_Tests final : public Test { if(algo == "Ed25519") { hash = "SHA-512"; } + if(algo == "Ed448") { + hash = "SHAKE-256(912)"; + } if(algo == "Dilithium") { hash = "SHAKE-256(512)"; }