diff --git a/src/lib/tls/tls_policy.cpp b/src/lib/tls/tls_policy.cpp index a49843b08a7..2c7b2623d81 100644 --- a/src/lib/tls/tls_policy.cpp +++ b/src/lib/tls/tls_policy.cpp @@ -691,6 +691,17 @@ void print_vec(std::ostream& o, const char* key, const std::vector& schemes) { + o << key << " = "; + for(size_t i = 0; i != schemes.size(); ++i) { + o << schemes[i].to_string(); + if(i != schemes.size() - 1) { + o << ' '; + } + } + o << '\n'; +} + void print_bool(std::ostream& o, const char* key, bool b) { o << key << " = " << (b ? "true" : "false") << '\n'; } @@ -706,6 +717,8 @@ void Policy::print(std::ostream& o) const { print_vec(o, "macs", allowed_macs()); print_vec(o, "signature_hashes", allowed_signature_hashes()); print_vec(o, "signature_methods", allowed_signature_methods()); + print_vec(o, "signature_schemes", allowed_signature_schemes()); + print_vec(o, "acceptable_signature_schemes", acceptable_signature_schemes()); print_vec(o, "key_exchange_methods", allowed_key_exchange_methods()); print_vec(o, "key_exchange_groups", key_exchange_groups()); const auto groups_to_offer = key_exchange_groups_to_offer(); diff --git a/src/lib/tls/tls_policy.h b/src/lib/tls/tls_policy.h index 96388c181ed..c5dd46966de 100644 --- a/src/lib/tls/tls_policy.h +++ b/src/lib/tls/tls_policy.h @@ -69,6 +69,12 @@ class BOTAN_PUBLIC_API(2, 0) Policy /* NOLINT(*-special-member-functions) */ { */ virtual std::vector allowed_signature_methods() const; + /** + * Returns a list of signature schemes we are willing to use, in order of + * preference. By default, this list contains all supported schemes that + * comply with the outputs of allowed_signature_methods() and + * allowed_signature_hashes(). + */ virtual std::vector allowed_signature_schemes() const; /** @@ -808,6 +814,10 @@ class BOTAN_PUBLIC_API(2, 0) Text_Policy : public Policy { std::vector allowed_signature_methods() const override; + std::vector allowed_signature_schemes() const override; + + std::vector acceptable_signature_schemes() const override; + std::vector key_exchange_groups() const override; std::vector key_exchange_groups_to_offer() const override; @@ -889,6 +899,7 @@ class BOTAN_PUBLIC_API(2, 0) Text_Policy : public Policy { std::vector read_group_list(std::string_view group_str) const; std::vector read_cert_type_list(const std::string& cert_type_str) const; + std::vector read_sig_scheme_list(std::string_view sig_scheme_str) const; size_t get_len(const std::string& key, size_t def) const; diff --git a/src/lib/tls/tls_signature_scheme.cpp b/src/lib/tls/tls_signature_scheme.cpp index 40f1cfa14f9..a1c110138c0 100644 --- a/src/lib/tls/tls_signature_scheme.cpp +++ b/src/lib/tls/tls_signature_scheme.cpp @@ -8,9 +8,12 @@ #include #include +#include #include #include #include +#include +#include namespace Botan::TLS { @@ -44,6 +47,57 @@ const std::vector& Signature_Scheme::all_available_schemes() { return all_schemes; } +Signature_Scheme Signature_Scheme::from_string(std::string_view str) { + if(str == "RSA_PKCS1_SHA1") { + return RSA_PKCS1_SHA1; + } + if(str == "RSA_PKCS1_SHA256") { + return RSA_PKCS1_SHA256; + } + if(str == "RSA_PKCS1_SHA384") { + return RSA_PKCS1_SHA384; + } + if(str == "RSA_PKCS1_SHA512") { + return RSA_PKCS1_SHA512; + } + + if(str == "ECDSA_SHA1") { + return ECDSA_SHA1; + } + if(str == "ECDSA_SHA256") { + return ECDSA_SHA256; + } + if(str == "ECDSA_SHA384") { + return ECDSA_SHA384; + } + if(str == "ECDSA_SHA512") { + return ECDSA_SHA512; + } + + if(str == "RSA_PSS_SHA256") { + return RSA_PSS_SHA256; + } + if(str == "RSA_PSS_SHA384") { + return RSA_PSS_SHA384; + } + if(str == "RSA_PSS_SHA512") { + return RSA_PSS_SHA512; + } + + // Parse signature schemes passed as hexadecimal code points (e.g. "0x081A") + if(str.size() == 6 && str.starts_with("0x")) { + try { + std::array wire_code{}; + Botan::hex_decode(wire_code, str.substr(2), false /* no white space */); + return Signature_Scheme(load_be(wire_code)); + } catch(const Invalid_Argument&) { + // pass, will throw below + } + } + + throw Invalid_Argument(fmt("Unknown TLS signature scheme '{}'", str)); +} + Signature_Scheme::Signature_Scheme() : m_code(NONE) {} Signature_Scheme::Signature_Scheme(uint16_t wire_code) : Signature_Scheme(Signature_Scheme::Code(wire_code)) {} diff --git a/src/lib/tls/tls_signature_scheme.h b/src/lib/tls/tls_signature_scheme.h index 94797a23dd5..0e13fb6ec9d 100644 --- a/src/lib/tls/tls_signature_scheme.h +++ b/src/lib/tls/tls_signature_scheme.h @@ -62,6 +62,11 @@ class BOTAN_PUBLIC_API(3, 0) Signature_Scheme final { */ static const std::vector& all_available_schemes(); + /** + * @return the signature scheme corresponding to the given string + */ + static Signature_Scheme from_string(std::string_view str); + /** * Construct an uninitialized / invalid scheme */ diff --git a/src/lib/tls/tls_text_policy.cpp b/src/lib/tls/tls_text_policy.cpp index 7edf02fcd2c..edfa308f6f6 100644 --- a/src/lib/tls/tls_text_policy.cpp +++ b/src/lib/tls/tls_text_policy.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -41,6 +42,16 @@ std::vector Text_Policy::allowed_signature_methods() const { return get_list("signature_methods", Policy::allowed_signature_methods()); } +std::vector Text_Policy::allowed_signature_schemes() const { + const auto sig_schemes_str = get_str("signature_schemes", ""); + return (sig_schemes_str.empty()) ? Policy::allowed_signature_schemes() : read_sig_scheme_list(sig_schemes_str); +} + +std::vector Text_Policy::acceptable_signature_schemes() const { + const auto sig_schemes_str = get_str("acceptable_signature_schemes", ""); + return (sig_schemes_str.empty()) ? Policy::acceptable_signature_schemes() : read_sig_scheme_list(sig_schemes_str); +} + bool Text_Policy::use_ecc_point_compression() const { return get_bool("use_ecc_point_compression", Policy::use_ecc_point_compression()); } @@ -290,6 +301,14 @@ std::vector Text_Policy::read_cert_type_list(const std::string return cert_types; } +std::vector Text_Policy::read_sig_scheme_list(std::string_view sig_scheme_str) const { + std::vector sig_schemes; + for(const auto& sig_scheme_name : split_on(sig_scheme_str, ' ')) { + sig_schemes.push_back(Signature_Scheme::from_string(sig_scheme_name)); + } + return sig_schemes; +} + size_t Text_Policy::get_len(const std::string& key, size_t def) const { const std::string v = get_str(key); diff --git a/src/tests/data/tls-policy/rfc8448_1rtt.txt b/src/tests/data/tls-policy/rfc8448_1rtt.txt index bd0d54bfab8..24b38d99fc1 100644 --- a/src/tests/data/tls-policy/rfc8448_1rtt.txt +++ b/src/tests/data/tls-policy/rfc8448_1rtt.txt @@ -6,8 +6,7 @@ allow_dtls10 = false allow_dtls12 = false ciphers = AES-128/GCM ChaCha20Poly1305 AES-256/GCM macs = AEAD -signature_hashes = SHA-512 SHA-384 SHA-256 -signature_methods = ECDSA RSA +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 key_exchange_groups = x25519 secp256r1 secp384r1 secp521r1 ffdhe/ietf/2048 ffdhe/ietf/3072 ffdhe/ietf/4096 ffdhe/ietf/6144 ffdhe/ietf/8192 key_exchange_groups_to_offer = x25519 allow_insecure_renegotiation = false diff --git a/src/tests/data/tls-policy/rfc8448_client_auth_server.txt b/src/tests/data/tls-policy/rfc8448_client_auth_server.txt index 4213cc2e728..a12d13ff290 100644 --- a/src/tests/data/tls-policy/rfc8448_client_auth_server.txt +++ b/src/tests/data/tls-policy/rfc8448_client_auth_server.txt @@ -6,8 +6,7 @@ allow_dtls10 = false allow_dtls12 = false ciphers = AES-128/GCM ChaCha20Poly1305 AES-256/GCM macs = AEAD -signature_hashes = SHA-512 SHA-384 SHA-256 -signature_methods = ECDSA RSA +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 key_exchange_groups = x25519 secp256r1 secp384r1 secp521r1 ffdhe/ietf/2048 ffdhe/ietf/3072 ffdhe/ietf/4096 ffdhe/ietf/6144 ffdhe/ietf/8192 key_exchange_groups_to_offer = x25519 allow_insecure_renegotiation = false diff --git a/src/tests/data/tls-policy/rfc8448_compat_client.txt b/src/tests/data/tls-policy/rfc8448_compat_client.txt index 901ef0c96f4..8ebe6e8d350 100644 --- a/src/tests/data/tls-policy/rfc8448_compat_client.txt +++ b/src/tests/data/tls-policy/rfc8448_compat_client.txt @@ -6,8 +6,7 @@ allow_dtls10 = false allow_dtls12 = false ciphers = AES-128/GCM ChaCha20Poly1305 AES-256/GCM macs = AEAD -signature_hashes = SHA-512 SHA-384 SHA-256 -signature_methods = ECDSA RSA +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 key_exchange_groups = x25519 secp256r1 secp384r1 secp521r1 ffdhe/ietf/2048 ffdhe/ietf/3072 ffdhe/ietf/4096 ffdhe/ietf/6144 ffdhe/ietf/8192 key_exchange_groups_to_offer = x25519 allow_insecure_renegotiation = false diff --git a/src/tests/data/tls-policy/rfc8448_compat_server.txt b/src/tests/data/tls-policy/rfc8448_compat_server.txt index 0e0d3967545..47a3519636b 100644 --- a/src/tests/data/tls-policy/rfc8448_compat_server.txt +++ b/src/tests/data/tls-policy/rfc8448_compat_server.txt @@ -6,8 +6,7 @@ allow_dtls10 = false allow_dtls12 = false ciphers = AES-128/GCM ChaCha20Poly1305 AES-256/GCM macs = AEAD -signature_hashes = SHA-512 SHA-384 SHA-256 -signature_methods = ECDSA RSA +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 key_exchange_groups = x25519 secp256r1 secp384r1 secp521r1 ffdhe/ietf/2048 ffdhe/ietf/3072 ffdhe/ietf/4096 ffdhe/ietf/6144 ffdhe/ietf/8192 key_exchange_groups_to_offer = x25519 allow_insecure_renegotiation = false diff --git a/src/tests/data/tls-policy/rfc8448_hrr_client.txt b/src/tests/data/tls-policy/rfc8448_hrr_client.txt index b8c8f57c935..e6c9b70617e 100644 --- a/src/tests/data/tls-policy/rfc8448_hrr_client.txt +++ b/src/tests/data/tls-policy/rfc8448_hrr_client.txt @@ -6,8 +6,7 @@ allow_dtls10 = false allow_dtls12 = false ciphers = AES-128/GCM ChaCha20Poly1305 AES-256/GCM macs = AEAD -signature_hashes = SHA-512 SHA-384 SHA-256 -signature_methods = ECDSA RSA +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 key_exchange_groups = x25519 secp256r1 secp384r1 key_exchange_groups_to_offer = x25519 allow_insecure_renegotiation = false diff --git a/src/tests/data/tls-policy/rfc8448_hrr_server.txt b/src/tests/data/tls-policy/rfc8448_hrr_server.txt index fd32a85ba38..6b4026b37d9 100644 --- a/src/tests/data/tls-policy/rfc8448_hrr_server.txt +++ b/src/tests/data/tls-policy/rfc8448_hrr_server.txt @@ -6,8 +6,7 @@ allow_dtls10 = false allow_dtls12 = false ciphers = AES-128/GCM ChaCha20Poly1305 AES-256/GCM macs = AEAD -signature_hashes = SHA-512 SHA-384 SHA-256 -signature_methods = ECDSA RSA +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 key_exchange_groups = secp256r1 secp384r1 x25519 allow_insecure_renegotiation = false include_time_in_hello_random = false diff --git a/src/tests/data/tls-policy/rfc8448_psk_dhe.txt b/src/tests/data/tls-policy/rfc8448_psk_dhe.txt index d51925c0651..7fc03fa1cdf 100644 --- a/src/tests/data/tls-policy/rfc8448_psk_dhe.txt +++ b/src/tests/data/tls-policy/rfc8448_psk_dhe.txt @@ -6,8 +6,7 @@ allow_dtls10 = false allow_dtls12 = false ciphers = AES-128/GCM ChaCha20Poly1305 AES-256/GCM macs = AEAD -signature_hashes = SHA-512 SHA-384 SHA-256 -signature_methods = ECDSA RSA +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 key_exchange_methods = ECDH DH ECDHE_PSK key_exchange_groups = x25519 secp256r1 secp384r1 allow_insecure_renegotiation = false diff --git a/src/tests/data/tls-policy/rfc8448_rawpubkey.txt b/src/tests/data/tls-policy/rfc8448_rawpubkey.txt index 7fc47e8e2f4..fcd20571c3e 100644 --- a/src/tests/data/tls-policy/rfc8448_rawpubkey.txt +++ b/src/tests/data/tls-policy/rfc8448_rawpubkey.txt @@ -9,8 +9,7 @@ accepted_client_certificate_types=RawPublicKey require_client_certificate_authentication=true ciphers = AES-128/GCM ChaCha20Poly1305 AES-256/GCM macs = AEAD -signature_hashes = SHA-512 SHA-384 SHA-256 -signature_methods = ECDSA RSA +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 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 5e1936bd913..43a383439a2 100644 --- a/src/tests/test_tls_rfc8448.cpp +++ b/src/tests/test_tls_rfc8448.cpp @@ -494,36 +494,8 @@ class RFC8448_Text_Policy : public Botan::TLS::Text_Policy { } public: - explicit RFC8448_Text_Policy(const std::string& policy_file, bool rfc8448 = true) : - Botan::TLS::Text_Policy(read_policy(policy_file)), m_rfc8448(rfc8448) {} - - std::vector allowed_signature_schemes() const override { - if(!m_rfc8448) { - return Botan::TLS::Text_Policy::allowed_signature_schemes(); - } - - // We extend the allowed signature schemes with algorithms that we don't - // actually support. The nature of the RFC 8448 test forces us to generate - // bit-compatible TLS messages. Unfortunately, the test data offers all - // those algorithms in its Client Hellos. - return { - Botan::TLS::Signature_Scheme::ECDSA_SHA256, - Botan::TLS::Signature_Scheme::ECDSA_SHA384, - Botan::TLS::Signature_Scheme::ECDSA_SHA512, - Botan::TLS::Signature_Scheme::ECDSA_SHA1, // not actually supported - Botan::TLS::Signature_Scheme::RSA_PSS_SHA256, - Botan::TLS::Signature_Scheme::RSA_PSS_SHA384, - Botan::TLS::Signature_Scheme::RSA_PSS_SHA512, - Botan::TLS::Signature_Scheme::RSA_PKCS1_SHA256, - Botan::TLS::Signature_Scheme::RSA_PKCS1_SHA384, - Botan::TLS::Signature_Scheme::RSA_PKCS1_SHA512, - Botan::TLS::Signature_Scheme::RSA_PKCS1_SHA1, // not actually supported - Botan::TLS::Signature_Scheme(0x0402), // DSA_SHA256, not actually supported - Botan::TLS::Signature_Scheme(0x0502), // DSA_SHA384, not actually supported - Botan::TLS::Signature_Scheme(0x0602), // DSA_SHA512, not actually supported - Botan::TLS::Signature_Scheme(0x0202), // DSA_SHA1, not actually supported - }; - } + explicit RFC8448_Text_Policy(const std::string& policy_file) : + Botan::TLS::Text_Policy(read_policy(policy_file)) {} // Overriding the key exchange group selection to favour the server's key // exchange group preference. This is required to enforce a Hello Retry Request @@ -540,9 +512,6 @@ class RFC8448_Text_Policy : public Botan::TLS::Text_Policy { return selected_group != supported_by_us.end() ? *selected_group : Named_Group::NONE; } - - private: - bool m_rfc8448; }; /** @@ -1566,15 +1535,14 @@ class Test_TLS_RFC8448_Client : public Test_TLS_RFC8448 { return { CHECK("Client Hello", [&](Test::Result& result) { - ctx = std::make_unique( - std::move(rng), - std::make_shared("rfc8448_psk_dhe", false /* no rfc8448 */), - vars.get_req_u64("CurrentTimestamp"), - sort_our_extensions, - std::nullopt, - ExternalPSK(vars.get_req_str("PskIdentity"), - vars.get_req_str("PskPRF"), - lock(vars.get_req_bin("PskSecret")))); + ctx = std::make_unique(std::move(rng), + std::make_shared("rfc8448_psk_dhe"), + vars.get_req_u64("CurrentTimestamp"), + sort_our_extensions, + std::nullopt, + ExternalPSK(vars.get_req_str("PskIdentity"), + vars.get_req_str("PskPRF"), + lock(vars.get_req_bin("PskSecret")))); result.test_is_true("client not closed", !ctx->client.is_closed()); ctx->check_callback_invocations(result, @@ -2495,17 +2463,16 @@ class Test_TLS_RFC8448_Server : public Test_TLS_RFC8448 { } }; - ctx = std::make_unique( - std::move(rng), - std::make_shared("rfc8448_psk_dhe", false /* no rfc8448 */), - vars.get_req_u64("CurrentTimestamp"), - sort_our_extensions, - make_mock_signatures(vars), - false, - std::nullopt, - ExternalPSK(vars.get_req_str("PskIdentity"), - vars.get_req_str("PskPRF"), - lock(vars.get_req_bin("PskSecret")))); + ctx = std::make_unique(std::move(rng), + std::make_shared("rfc8448_psk_dhe"), + vars.get_req_u64("CurrentTimestamp"), + sort_our_extensions, + make_mock_signatures(vars), + false, + std::nullopt, + ExternalPSK(vars.get_req_str("PskIdentity"), + vars.get_req_str("PskPRF"), + lock(vars.get_req_bin("PskSecret")))); result.test_is_true("server not closed", !ctx->server.is_closed()); ctx->server.received_data(vars.get_req_bin("Record_ClientHello_1")); diff --git a/src/tests/test_tls_signature_scheme.cpp b/src/tests/test_tls_signature_scheme.cpp index 57dd82d10b2..9ca42a7b67c 100644 --- a/src/tests/test_tls_signature_scheme.cpp +++ b/src/tests/test_tls_signature_scheme.cpp @@ -28,6 +28,9 @@ std::vector test_signature_scheme() { result.test_is_true("is_available handles all cases", s.is_available()); result.test_is_true("to_string handles all cases", not_unknown(s.to_string())); + result.test_u16_eq("to_string/from_string roundtrip", + Botan::TLS::Signature_Scheme::from_string(s.to_string()).wire_code(), + s.wire_code()); result.test_is_true("hash_function_name handles all cases", not_unknown(s.hash_function_name())); result.test_is_true("padding_string handles all cases", not_unknown(s.padding_string())); result.test_is_true("algorithm_name handles all cases", not_unknown(s.algorithm_name())); @@ -53,6 +56,29 @@ std::vector test_signature_scheme() { Botan::AlgorithmIdentifier() == bogus.key_algorithm_identifier()); })); + results.push_back(CHECK("from_string", [&](auto& result) { + using Sig = Botan::TLS::Signature_Scheme; + + result.test_u16_eq("RSA_PKCS1_SHA1", Sig::from_string("RSA_PKCS1_SHA1").wire_code(), Sig::RSA_PKCS1_SHA1); + result.test_u16_eq("RSA_PKCS1_SHA256", Sig::from_string("RSA_PKCS1_SHA256").wire_code(), Sig::RSA_PKCS1_SHA256); + result.test_u16_eq("RSA_PKCS1_SHA384", Sig::from_string("RSA_PKCS1_SHA384").wire_code(), Sig::RSA_PKCS1_SHA384); + 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_SHA384", Sig::from_string("ECDSA_SHA384").wire_code(), Sig::ECDSA_SHA384); + result.test_u16_eq("ECDSA_SHA512", Sig::from_string("ECDSA_SHA512").wire_code(), Sig::ECDSA_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); + + result.test_u16_eq("custom code point", Sig::from_string("0xFE42").wire_code(), Sig(0xFE42).wire_code()); + + result.test_throws("from_string throws on unknown scheme", [] { Sig::from_string("bogus"); }); + result.test_throws("from_string throws on invalid hex 1", [] { Sig::from_string("0xZZZZ"); }); + result.test_throws("from_string throws on invalid hex 2", [] { Sig::from_string("0x03g0"); }); + result.test_throws("from_string throws on invalid hex 3", [] { Sig::from_string("0xabc"); }); + })); + return results; }