From ff7620ae68a10f403b688e34fc8267b34fce25e6 Mon Sep 17 00:00:00 2001 From: Rene Meusel Date: Wed, 29 Jul 2026 08:34:25 +0200 Subject: [PATCH 1/2] Explicitly state Signature_Scheme::ECDSA_SECP***R1_TLS13_SHA*** TLS 1.3 explicitly means ECDSA on the NIST curves when using these code points. Other code points exist for the Brainpool curves (GH #5771). In contrast, for TLS 1.2 these code points meant the usage of ECDSA on any curve paired with the specified hash function. To reflect this distinction in the code base, this introduces aliases in the Signature_Scheme enum accordingly. --- src/bogo_shim/bogo_shim.cpp | 2 +- src/lib/tls/tls_signature_scheme.cpp | 66 ++++++++++++------------- src/lib/tls/tls_signature_scheme.h | 26 ++++++++-- src/tests/test_tls_rfc8448.cpp | 6 +-- src/tests/test_tls_signature_scheme.cpp | 9 ++++ 5 files changed, 69 insertions(+), 40 deletions(-) diff --git a/src/bogo_shim/bogo_shim.cpp b/src/bogo_shim/bogo_shim.cpp index 5eefc18f70d..1e24b952ab3 100644 --- a/src/bogo_shim/bogo_shim.cpp +++ b/src/bogo_shim/bogo_shim.cpp @@ -1167,7 +1167,7 @@ class Shim_Policy final : public Botan::TLS::Policy { if(!m_args.flag_set("server")) { schemes.emplace_back(Botan::TLS::Signature_Scheme::RSA_PKCS1_SHA256); schemes.emplace_back(Botan::TLS::Signature_Scheme::RSA_PSS_SHA256); - schemes.emplace_back(Botan::TLS::Signature_Scheme::ECDSA_SHA256); + schemes.emplace_back(Botan::TLS::Signature_Scheme::ECDSA_SECP256R1_TLS13_SHA256); } return schemes; diff --git a/src/lib/tls/tls_signature_scheme.cpp b/src/lib/tls/tls_signature_scheme.cpp index 7cc882701de..c3014841f8e 100644 --- a/src/lib/tls/tls_signature_scheme.cpp +++ b/src/lib/tls/tls_signature_scheme.cpp @@ -31,9 +31,9 @@ const std::vector& Signature_Scheme::all_available_schemes() { // EDDSA_25519, // #endif - ECDSA_SHA384, - ECDSA_SHA512, - ECDSA_SHA256, + ECDSA_SECP384R1_TLS13_SHA384, + ECDSA_SECP521R1_TLS13_SHA512, + ECDSA_SECP256R1_TLS13_SHA256, RSA_PSS_SHA384, RSA_PSS_SHA256, @@ -68,14 +68,14 @@ Signature_Scheme Signature_Scheme::from_string(std::string_view str) { if(str == "ECDSA_SHA1") { return ECDSA_SHA1; } - if(str == "ECDSA_SHA256") { - return ECDSA_SHA256; + if(str == "ECDSA_SHA256" || str == "ECDSA_SECP256R1_TLS13_SHA256") { + return ECDSA_SECP256R1_TLS13_SHA256; } - if(str == "ECDSA_SHA384") { - return ECDSA_SHA384; + if(str == "ECDSA_SHA384" || str == "ECDSA_SECP384R1_TLS13_SHA384") { + return ECDSA_SECP384R1_TLS13_SHA384; } - if(str == "ECDSA_SHA512") { - return ECDSA_SHA512; + if(str == "ECDSA_SHA512" || str == "ECDSA_SECP521R1_TLS13_SHA512") { + return ECDSA_SECP521R1_TLS13_SHA512; } if(str == "RSA_PSS_SHA256") { @@ -129,9 +129,9 @@ bool Signature_Scheme::is_available() const noexcept { case ECDSA_BRAINPOOL384R1_TLS13_SHA384: case ECDSA_BRAINPOOL256R1_TLS13_SHA256: case ECDSA_BRAINPOOL512R1_TLS13_SHA512: - case ECDSA_SHA384: - case ECDSA_SHA512: - case ECDSA_SHA256: + case ECDSA_SECP384R1_TLS13_SHA384: + case ECDSA_SECP521R1_TLS13_SHA512: + case ECDSA_SECP256R1_TLS13_SHA256: return true; default: return false; @@ -155,11 +155,11 @@ std::string Signature_Scheme::to_string() const { case ECDSA_SHA1: return "ECDSA_SHA1"; - case ECDSA_SHA256: + case ECDSA_SECP256R1_TLS13_SHA256: return "ECDSA_SHA256"; - case ECDSA_SHA384: + case ECDSA_SECP384R1_TLS13_SHA384: return "ECDSA_SHA384"; - case ECDSA_SHA512: + case ECDSA_SECP521R1_TLS13_SHA512: return "ECDSA_SHA512"; case ECDSA_BRAINPOOL256R1_TLS13_SHA256: @@ -192,19 +192,19 @@ std::string Signature_Scheme::hash_function_name() const { case ECDSA_SHA1: return "SHA-1"; - case ECDSA_SHA256: + case ECDSA_SECP256R1_TLS13_SHA256: case RSA_PKCS1_SHA256: case RSA_PSS_SHA256: case ECDSA_BRAINPOOL256R1_TLS13_SHA256: return "SHA-256"; - case ECDSA_SHA384: + case ECDSA_SECP384R1_TLS13_SHA384: case RSA_PKCS1_SHA384: case RSA_PSS_SHA384: case ECDSA_BRAINPOOL384R1_TLS13_SHA384: return "SHA-384"; - case ECDSA_SHA512: + case ECDSA_SECP521R1_TLS13_SHA512: case RSA_PKCS1_SHA512: case RSA_PSS_SHA512: case ECDSA_BRAINPOOL512R1_TLS13_SHA512: @@ -232,13 +232,13 @@ std::string Signature_Scheme::padding_string() const { case ECDSA_SHA1: return "SHA-1"; - case ECDSA_SHA256: + case ECDSA_SECP256R1_TLS13_SHA256: case ECDSA_BRAINPOOL256R1_TLS13_SHA256: return "SHA-256"; - case ECDSA_SHA384: + case ECDSA_SECP384R1_TLS13_SHA384: case ECDSA_BRAINPOOL384R1_TLS13_SHA384: return "SHA-384"; - case ECDSA_SHA512: + case ECDSA_SECP521R1_TLS13_SHA512: case ECDSA_BRAINPOOL512R1_TLS13_SHA512: return "SHA-512"; @@ -270,9 +270,9 @@ std::string Signature_Scheme::algorithm_name() const { return "RSA"; case ECDSA_SHA1: - case ECDSA_SHA256: - case ECDSA_SHA384: - case ECDSA_SHA512: + case ECDSA_SECP256R1_TLS13_SHA256: + case ECDSA_SECP384R1_TLS13_SHA384: + case ECDSA_SECP521R1_TLS13_SHA512: case ECDSA_BRAINPOOL256R1_TLS13_SHA256: case ECDSA_BRAINPOOL384R1_TLS13_SHA384: case ECDSA_BRAINPOOL512R1_TLS13_SHA512: @@ -301,11 +301,11 @@ AlgorithmIdentifier Signature_Scheme::key_algorithm_identifier() const { switch(m_code) { // case ECDSA_SHA1: not defined - case ECDSA_SHA256: + case ECDSA_SECP256R1_TLS13_SHA256: return {"ECDSA", der_encode_oid("secp256r1")}; - case ECDSA_SHA384: + case ECDSA_SECP384R1_TLS13_SHA384: return {"ECDSA", der_encode_oid("secp384r1")}; - case ECDSA_SHA512: + case ECDSA_SECP521R1_TLS13_SHA512: return {"ECDSA", der_encode_oid("secp521r1")}; case ECDSA_BRAINPOOL256R1_TLS13_SHA256: @@ -347,13 +347,13 @@ AlgorithmIdentifier Signature_Scheme::algorithm_identifier() const { case ECDSA_SHA1: return AlgorithmIdentifier(OID::from_string("ECDSA/SHA-1"), AlgorithmIdentifier::USE_EMPTY_PARAM); - case ECDSA_SHA256: + case ECDSA_SECP256R1_TLS13_SHA256: case ECDSA_BRAINPOOL256R1_TLS13_SHA256: return AlgorithmIdentifier(OID::from_string("ECDSA/SHA-256"), AlgorithmIdentifier::USE_EMPTY_PARAM); - case ECDSA_SHA384: + case ECDSA_SECP384R1_TLS13_SHA384: case ECDSA_BRAINPOOL384R1_TLS13_SHA384: return AlgorithmIdentifier(OID::from_string("ECDSA/SHA-384"), AlgorithmIdentifier::USE_EMPTY_PARAM); - case ECDSA_SHA512: + case ECDSA_SECP521R1_TLS13_SHA512: case ECDSA_BRAINPOOL512R1_TLS13_SHA512: return AlgorithmIdentifier(OID::from_string("ECDSA/SHA-512"), AlgorithmIdentifier::USE_EMPTY_PARAM); @@ -384,9 +384,9 @@ std::optional Signature_Scheme::format() const noexcept { return Signature_Format::Standard; case ECDSA_SHA1: - case ECDSA_SHA256: - case ECDSA_SHA384: - case ECDSA_SHA512: + case ECDSA_SECP256R1_TLS13_SHA256: + case ECDSA_SECP384R1_TLS13_SHA384: + case ECDSA_SECP521R1_TLS13_SHA512: case ECDSA_BRAINPOOL256R1_TLS13_SHA256: case ECDSA_BRAINPOOL384R1_TLS13_SHA384: case ECDSA_BRAINPOOL512R1_TLS13_SHA512: diff --git a/src/lib/tls/tls_signature_scheme.h b/src/lib/tls/tls_signature_scheme.h index eaf8a4078ec..0f360f1f03a 100644 --- a/src/lib/tls/tls_signature_scheme.h +++ b/src/lib/tls/tls_signature_scheme.h @@ -43,10 +43,30 @@ class BOTAN_PUBLIC_API(3, 0) Signature_Scheme final { RSA_PKCS1_SHA384 = 0x0501, RSA_PKCS1_SHA512 = 0x0601, + // RFC 9846 4.3.3 + // ECDSA algorithms: Indicates a signature algorithm using ECDSA, + // the corresponding curve as defined in NIST SP 800-186. + // + // In TLS 1.3 these code points specifically refer to the NIST curves + // P-256, P-384, and P-521. In contrast TLS 1.2 uses these code points + // for ECDSA on any curve paired with the specified hash function. + ECDSA_SECP256R1_TLS13_SHA256 = 0x0403, + ECDSA_SECP384R1_TLS13_SHA384 = 0x0503, + ECDSA_SECP521R1_TLS13_SHA512 = 0x0603, + + // RFC 5246 7.4.1.4.1 + // The client uses the "signature_algorithms" extension to indicate + // to the server which signature/hash algorithm pairs may be used in + // digital signatures. + // + // In TLS 1.2 the signature_algorithms extension contains pairs of hash + // and signature algorithms. For ECDSA these code points are not bound + // to a specific curve in contrast to TLS 1.3, where these code points + // imply the usage of NIST's P-256, P-384, and P-521 curves. ECDSA_SHA1 = 0x0203, // not implemented - ECDSA_SHA256 = 0x0403, - ECDSA_SHA384 = 0x0503, - ECDSA_SHA512 = 0x0603, + ECDSA_SHA256 = ECDSA_SECP256R1_TLS13_SHA256, + ECDSA_SHA384 = ECDSA_SECP384R1_TLS13_SHA384, + ECDSA_SHA512 = ECDSA_SECP521R1_TLS13_SHA512, RSA_PSS_SHA256 = 0x0804, RSA_PSS_SHA384 = 0x0805, diff --git a/src/tests/test_tls_rfc8448.cpp b/src/tests/test_tls_rfc8448.cpp index 43a383439a2..164cb25d010 100644 --- a/src/tests/test_tls_rfc8448.cpp +++ b/src/tests/test_tls_rfc8448.cpp @@ -1509,9 +1509,9 @@ class Test_TLS_RFC8448_Client : public Test_TLS_RFC8448 { Signature_Scheme::RSA_PKCS1_SHA384, Signature_Scheme::RSA_PKCS1_SHA512, Signature_Scheme::RSA_PKCS1_SHA256, - Signature_Scheme::ECDSA_SHA384, - Signature_Scheme::ECDSA_SHA512, - Signature_Scheme::ECDSA_SHA256, + Signature_Scheme::ECDSA_SECP384R1_TLS13_SHA384, + Signature_Scheme::ECDSA_SECP521R1_TLS13_SHA512, + Signature_Scheme::ECDSA_SECP256R1_TLS13_SHA256, })); } diff --git a/src/tests/test_tls_signature_scheme.cpp b/src/tests/test_tls_signature_scheme.cpp index 7e5dd8d67fa..b91fc081b12 100644 --- a/src/tests/test_tls_signature_scheme.cpp +++ b/src/tests/test_tls_signature_scheme.cpp @@ -65,8 +65,17 @@ std::vector test_signature_scheme() { result.test_u16_eq("RSA_PKCS1_SHA512", Sig::from_string("RSA_PKCS1_SHA512").wire_code(), Sig::RSA_PKCS1_SHA512); result.test_u16_eq("ECDSA_SHA1", Sig::from_string("ECDSA_SHA1").wire_code(), Sig::ECDSA_SHA1); result.test_u16_eq("ECDSA_SHA256", Sig::from_string("ECDSA_SHA256").wire_code(), Sig::ECDSA_SHA256); + result.test_u16_eq("ECDSA_SECP256R1_TLS13_SHA256", + Sig::from_string("ECDSA_SECP256R1_TLS13_SHA256").wire_code(), + Sig::ECDSA_SECP256R1_TLS13_SHA256); result.test_u16_eq("ECDSA_SHA384", Sig::from_string("ECDSA_SHA384").wire_code(), Sig::ECDSA_SHA384); + result.test_u16_eq("ECDSA_SECP384R1_TLS13_SHA384", + Sig::from_string("ECDSA_SECP384R1_TLS13_SHA384").wire_code(), + Sig::ECDSA_SECP384R1_TLS13_SHA384); result.test_u16_eq("ECDSA_SHA512", Sig::from_string("ECDSA_SHA512").wire_code(), Sig::ECDSA_SHA512); + result.test_u16_eq("ECDSA_SECP521R1_TLS13_SHA512", + Sig::from_string("ECDSA_SECP521R1_TLS13_SHA512").wire_code(), + Sig::ECDSA_SECP521R1_TLS13_SHA512); result.test_u16_eq("RSA_PSS_SHA256", Sig::from_string("RSA_PSS_SHA256").wire_code(), Sig::RSA_PSS_SHA256); result.test_u16_eq("RSA_PSS_SHA384", Sig::from_string("RSA_PSS_SHA384").wire_code(), Sig::RSA_PSS_SHA384); result.test_u16_eq("RSA_PSS_SHA512", Sig::from_string("RSA_PSS_SHA512").wire_code(), Sig::RSA_PSS_SHA512); From 585419e2bbddbf52ffccd3ff4d01e7d7bcb123ff Mon Sep 17 00:00:00 2001 From: Rene Meusel Date: Wed, 29 Jul 2026 16:21:50 +0200 Subject: [PATCH 2/2] Use Text_Policy for scheme selection in RFC8448 cont'd --- src/tests/data/tls-policy/rfc8448_psk_dhe.txt | 2 +- src/tests/test_tls_rfc8448.cpp | 23 +------------------ 2 files changed, 2 insertions(+), 23 deletions(-) diff --git a/src/tests/data/tls-policy/rfc8448_psk_dhe.txt b/src/tests/data/tls-policy/rfc8448_psk_dhe.txt index 7fc03fa1cdf..7d85040e7be 100644 --- a/src/tests/data/tls-policy/rfc8448_psk_dhe.txt +++ b/src/tests/data/tls-policy/rfc8448_psk_dhe.txt @@ -6,7 +6,7 @@ allow_dtls10 = false allow_dtls12 = false ciphers = AES-128/GCM ChaCha20Poly1305 AES-256/GCM macs = AEAD -signature_schemes = ECDSA_SHA256 ECDSA_SHA384 ECDSA_SHA512 ECDSA_SHA1 RSA_PSS_SHA256 RSA_PSS_SHA384 RSA_PSS_SHA512 RSA_PKCS1_SHA256 RSA_PKCS1_SHA384 RSA_PKCS1_SHA512 RSA_PKCS1_SHA1 0x0402 0x0502 0x0602 0x0202 +signature_schemes = RSA_PSS_SHA384 RSA_PSS_SHA256 RSA_PSS_SHA512 RSA_PKCS1_SHA384 RSA_PKCS1_SHA512 RSA_PKCS1_SHA256 ECDSA_SECP384R1_TLS13_SHA384 ECDSA_SECP521R1_TLS13_SHA512 ECDSA_SECP256R1_TLS13_SHA256 key_exchange_methods = ECDH DH ECDHE_PSK key_exchange_groups = x25519 secp256r1 secp384r1 allow_insecure_renegotiation = false diff --git a/src/tests/test_tls_rfc8448.cpp b/src/tests/test_tls_rfc8448.cpp index 164cb25d010..df09a3b7f13 100644 --- a/src/tests/test_tls_rfc8448.cpp +++ b/src/tests/test_tls_rfc8448.cpp @@ -1493,28 +1493,7 @@ class Test_TLS_RFC8448_Client : public Test_TLS_RFC8448 { auto sort_our_extensions = [](Botan::TLS::Extensions& exts, Botan::TLS::Connection_Side /* side */, - Botan::TLS::Handshake_Type msg_type) { - if(msg_type == Handshake_Type::ClientHello) { - exts.remove_extension(Signature_Algorithms::static_type()); - // This is the preference order of signature algorithms that we - // used when we first implemented this test case. To stay - // compatible with the now hard-coded transcript, we pin the - // algorithm order of preference. - // - // NOLINTNEXTLINE(*-owning-memory) - exts.add(new Signature_Algorithms({ - Signature_Scheme::RSA_PSS_SHA384, - Signature_Scheme::RSA_PSS_SHA256, - Signature_Scheme::RSA_PSS_SHA512, - Signature_Scheme::RSA_PKCS1_SHA384, - Signature_Scheme::RSA_PKCS1_SHA512, - Signature_Scheme::RSA_PKCS1_SHA256, - Signature_Scheme::ECDSA_SECP384R1_TLS13_SHA384, - Signature_Scheme::ECDSA_SECP521R1_TLS13_SHA512, - Signature_Scheme::ECDSA_SECP256R1_TLS13_SHA256, - })); - } - + Botan::TLS::Handshake_Type /* msg_type */) { // This is the order of extensions when we first introduced the PSK // implementation and generated the transcript. To stay compatible // with the now hard-coded transcript, we pin the extension order.