From e293f25f5e466103bc6a2552a867d18150f4a73e Mon Sep 17 00:00:00 2001 From: Rene Meusel Date: Wed, 1 Nov 2023 12:40:21 +0100 Subject: [PATCH 1/8] Add user-facing APIs to handle raw public keys --- src/lib/tls/credentials_manager.cpp | 14 ++++++++++- src/lib/tls/credentials_manager.h | 29 ++++++++++++++++++++++ src/lib/tls/tls12/tls_channel_impl_12.h | 8 ++++++ src/lib/tls/tls13/tls_client_impl_13.cpp | 4 +++ src/lib/tls/tls13/tls_client_impl_13.h | 5 ++++ src/lib/tls/tls13/tls_server_impl_13.cpp | 4 +++ src/lib/tls/tls13/tls_server_impl_13.h | 1 + src/lib/tls/tls_callbacks.cpp | 10 ++++++++ src/lib/tls/tls_callbacks.h | 31 ++++++++++++++++++++++++ src/lib/tls/tls_channel.h | 5 ++++ src/lib/tls/tls_channel_impl.h | 5 ++++ src/lib/tls/tls_client.cpp | 4 +++ src/lib/tls/tls_client.h | 2 +- src/lib/tls/tls_extensions.cpp | 21 ++++++++++++++++ src/lib/tls/tls_extensions.h | 6 +++++ src/lib/tls/tls_policy.cpp | 21 ++++++++++++++++ src/lib/tls/tls_policy.h | 27 +++++++++++++++++++++ src/lib/tls/tls_server.cpp | 4 +++ src/lib/tls/tls_server.h | 2 +- src/lib/tls/tls_session.h | 5 ++++ src/lib/tls/tls_text_policy.cpp | 19 +++++++++++++++ 21 files changed, 224 insertions(+), 3 deletions(-) diff --git a/src/lib/tls/credentials_manager.cpp b/src/lib/tls/credentials_manager.cpp index 2b3a91f1157..692e6b955d5 100644 --- a/src/lib/tls/credentials_manager.cpp +++ b/src/lib/tls/credentials_manager.cpp @@ -87,6 +87,12 @@ std::vector Credentials_Manager::find_cert_chain( return cert_chain(key_types, cert_signature_schemes, type, context); } +std::shared_ptr Credentials_Manager::find_raw_public_key(const std::vector& /* key_types */, + const std::string& /* type */, + const std::string& /* context */) { + return nullptr; +} + std::vector Credentials_Manager::cert_chain(const std::vector& /*unused*/, const std::vector& /*unused*/, const std::string& /*unused*/, @@ -105,7 +111,13 @@ std::vector Credentials_Manager::cert_chain_single_type( std::shared_ptr Credentials_Manager::private_key_for(const X509_Certificate& /*unused*/, const std::string& /*unused*/, const std::string& /*unused*/) { - return std::shared_ptr(); + return nullptr; +} + +std::shared_ptr Credentials_Manager::private_key_for(const Public_Key& /* raw_public_key */, + const std::string& /* type */, + const std::string& /* context */) { + return nullptr; } secure_vector Credentials_Manager::session_ticket_key() { diff --git a/src/lib/tls/credentials_manager.h b/src/lib/tls/credentials_manager.h index 75d772f2551..1f388e2f187 100644 --- a/src/lib/tls/credentials_manager.h +++ b/src/lib/tls/credentials_manager.h @@ -75,6 +75,23 @@ class BOTAN_PUBLIC_API(2, 0) Credentials_Manager { const std::string& type, const std::string& context); + /** + * Return a raw public key to be used for authentication or nullptr if no + * public key was found. + * + * It is assumed that the caller can get the private key of the leaf with + * private_key_for(). + * + * @param key_types specifies the key types desired ("RSA", "DSA", + * "ECDSA", etc), or empty if there is no preference by + * the caller. + * @param type specifies the type of operation occurring + * @param context specifies a context relative to type. + */ + virtual std::shared_ptr find_raw_public_key(const std::vector& key_types, + const std::string& type, + const std::string& context); + /** * Return a cert chain we can use, ordered from leaf to root, * or else an empty vector. @@ -134,6 +151,18 @@ class BOTAN_PUBLIC_API(2, 0) Credentials_Manager { const std::string& type, const std::string& context); + /** + * This function should either return nullptr or throw an exception if + * the key is unavailable. + * + * @return private key associated with this raw public key if we should + * use it with this context. @p raw_public_key was returned by + * find_raw_public_key() + */ + virtual std::shared_ptr private_key_for(const Public_Key& raw_public_key, + const std::string& type, + const std::string& context); + /** * Provides a secret value to encrypt session tickets for stateless * session resumptions. The default implementation returns an empty diff --git a/src/lib/tls/tls12/tls_channel_impl_12.h b/src/lib/tls/tls12/tls_channel_impl_12.h index ccfe0780ce7..09fabdb08a2 100644 --- a/src/lib/tls/tls12/tls_channel_impl_12.h +++ b/src/lib/tls/tls12/tls_channel_impl_12.h @@ -107,6 +107,14 @@ class Channel_Impl_12 : public Channel_Impl { */ std::vector peer_cert_chain() const override; + /** + * Note: Raw public key for authentication (RFC7250) is currently not + * implemented for TLS 1.2. + * + * @return raw public key of the peer (will be nullptr) + */ + std::shared_ptr peer_raw_public_key() const override { return nullptr; } + std::optional external_psk_identity() const override; /** diff --git a/src/lib/tls/tls13/tls_client_impl_13.cpp b/src/lib/tls/tls13/tls_client_impl_13.cpp index 3d097fddb0b..99b6bbd34df 100644 --- a/src/lib/tls/tls13/tls_client_impl_13.cpp +++ b/src/lib/tls/tls13/tls_client_impl_13.cpp @@ -586,6 +586,10 @@ std::vector Client_Impl_13::peer_cert_chain() const { return {}; } +std::shared_ptr Client_Impl_13::peer_raw_public_key() const { + return nullptr; +} + std::optional Client_Impl_13::external_psk_identity() const { return m_psk_identity; } diff --git a/src/lib/tls/tls13/tls_client_impl_13.h b/src/lib/tls/tls13/tls_client_impl_13.h index 33c7a9037cc..f8f376fcaef 100644 --- a/src/lib/tls/tls13/tls_client_impl_13.h +++ b/src/lib/tls/tls13/tls_client_impl_13.h @@ -62,6 +62,11 @@ class Client_Impl_13 : public Channel_Impl_13 { */ std::vector peer_cert_chain() const override; + /** + * @return raw public key of the peer (may be nullptr) + */ + std::shared_ptr peer_raw_public_key() const override; + /** * @return identity of the PSK used for this connection * or std::nullopt if no PSK was used. diff --git a/src/lib/tls/tls13/tls_server_impl_13.cpp b/src/lib/tls/tls13/tls_server_impl_13.cpp index f9d0c5ed146..90f06d0d6df 100644 --- a/src/lib/tls/tls13/tls_server_impl_13.cpp +++ b/src/lib/tls/tls13/tls_server_impl_13.cpp @@ -52,6 +52,10 @@ std::vector Server_Impl_13::peer_cert_chain() const { } } +std::shared_ptr Server_Impl_13::peer_raw_public_key() const { + return nullptr; +} + std::optional Server_Impl_13::external_psk_identity() const { return m_psk_identity; } diff --git a/src/lib/tls/tls13/tls_server_impl_13.h b/src/lib/tls/tls13/tls_server_impl_13.h index dd848e3407c..0b543cad957 100644 --- a/src/lib/tls/tls13/tls_server_impl_13.h +++ b/src/lib/tls/tls13/tls_server_impl_13.h @@ -28,6 +28,7 @@ class Server_Impl_13 : public Channel_Impl_13 { std::string application_protocol() const override; std::vector peer_cert_chain() const override; + std::shared_ptr peer_raw_public_key() const override; std::optional external_psk_identity() const override; bool new_session_ticket_supported() const override; diff --git a/src/lib/tls/tls_callbacks.cpp b/src/lib/tls/tls_callbacks.cpp index faf0e82fb1a..b6c4769e82d 100644 --- a/src/lib/tls/tls_callbacks.cpp +++ b/src/lib/tls/tls_callbacks.cpp @@ -102,6 +102,16 @@ void TLS::Callbacks::tls_verify_cert_chain(const std::vector& } } +void TLS::Callbacks::tls_verify_raw_public_key(const Public_Key& raw_public_key, + Usage_Type usage, + std::string_view hostname, + const TLS::Policy& policy) { + BOTAN_UNUSED(raw_public_key, usage, hostname, policy); + // There is no good default implementation for authenticating raw public key. + // Applications that wish to use them for authentication, must override this. + throw TLS_Exception(Alert::CertificateUnknown, "Application did not provide a means to validate the raw public key"); +} + std::optional TLS::Callbacks::tls_parse_ocsp_response(const std::vector& raw_response) { try { return OCSP::Response(raw_response); diff --git a/src/lib/tls/tls_callbacks.h b/src/lib/tls/tls_callbacks.h index 51f78875792..4a889c5ccdb 100644 --- a/src/lib/tls/tls_callbacks.h +++ b/src/lib/tls/tls_callbacks.h @@ -171,6 +171,37 @@ class BOTAN_PUBLIC_API(2, 0) Callbacks { std::string_view hostname, const TLS::Policy& policy); + /** + * Optional callback. Default impl always rejects. + * + * This allows using raw public keys for authentication of peers as + * described in RFC 7250 and RFC 8446 4.2.2. Applications that wish + * to use raw public keys MUST override this callback to verify the + * authenticity of the received public keys. + * + * Default implementation always rejects the raw public key. + * + * This function should throw an exception derived from + * std::exception with an informative what() result if the + * raw public key cannot be verified. + * + * @param raw_public_key specifies the raw public key to be used + * for peer authentication + * @param usage what this cert chain is being used for + * Usage_Type::TLS_SERVER_AUTH for server chains, + * Usage_Type::TLS_CLIENT_AUTH for client chains, + * @param hostname when authenticating a server, this is the hostname + * the client requested (eg via SNI). When authenticating a client, + * this is the server name the client is authenticating *to*. + * Empty in other cases or if no hostname was used. + * @param policy the TLS policy associated with the session being authenticated + * using the raw public key + */ + virtual void tls_verify_raw_public_key(const Public_Key& raw_public_key, + Usage_Type usage, + std::string_view hostname, + const TLS::Policy& policy); + /** * Called by default `tls_verify_cert_chain` to get the timeout to use for OCSP * requests. Return 0 to disable online OCSP checks. diff --git a/src/lib/tls/tls_channel.h b/src/lib/tls/tls_channel.h index dc74ba46851..6276a3989d8 100644 --- a/src/lib/tls/tls_channel.h +++ b/src/lib/tls/tls_channel.h @@ -137,6 +137,11 @@ class BOTAN_PUBLIC_API(2, 0) Channel { */ virtual std::vector peer_cert_chain() const = 0; + /** + * @return raw public key of the peer (may be nullptr) + */ + virtual std::shared_ptr peer_raw_public_key() const = 0; + /** * @return identity of the PSK used for this connection * or std::nullopt if no PSK was used. diff --git a/src/lib/tls/tls_channel_impl.h b/src/lib/tls/tls_channel_impl.h index e39b88c10e2..82fd31874c9 100644 --- a/src/lib/tls/tls_channel_impl.h +++ b/src/lib/tls/tls_channel_impl.h @@ -109,6 +109,11 @@ class Channel_Impl { */ virtual std::vector peer_cert_chain() const = 0; + /** + * @return raw public key of the peer (may be nullptr) + */ + virtual std::shared_ptr peer_raw_public_key() const = 0; + /** * @return identity of the PSK used for this connection * or std::nullopt if no PSK was used. diff --git a/src/lib/tls/tls_client.cpp b/src/lib/tls/tls_client.cpp index c28cc2a6579..bcd8fa20a8e 100644 --- a/src/lib/tls/tls_client.cpp +++ b/src/lib/tls/tls_client.cpp @@ -121,6 +121,10 @@ std::vector Client::peer_cert_chain() const { return m_impl->peer_cert_chain(); } +std::shared_ptr Client::peer_raw_public_key() const { + return m_impl->peer_raw_public_key(); +} + std::optional Client::external_psk_identity() const { return m_impl->external_psk_identity(); } diff --git a/src/lib/tls/tls_client.h b/src/lib/tls/tls_client.h index fce64d16ed2..63aec226d59 100644 --- a/src/lib/tls/tls_client.h +++ b/src/lib/tls/tls_client.h @@ -81,7 +81,7 @@ class BOTAN_PUBLIC_API(2, 0) Client final : public Channel { bool is_closed_for_writing() const override; std::vector peer_cert_chain() const override; - + std::shared_ptr peer_raw_public_key() const override; std::optional external_psk_identity() const override; SymmetricKey key_material_export(std::string_view label, std::string_view context, size_t length) const override; diff --git a/src/lib/tls/tls_extensions.cpp b/src/lib/tls/tls_extensions.cpp index fefcf1e2ae4..037f619a99e 100644 --- a/src/lib/tls/tls_extensions.cpp +++ b/src/lib/tls/tls_extensions.cpp @@ -356,6 +356,27 @@ std::vector Application_Layer_Protocol_Notification::serialize(Connecti return buf; } +std::string certificate_type_to_string(Certificate_Type type) { + switch(type) { + case Certificate_Type::X509: + return "X509"; + case Certificate_Type::RawPublicKey: + return "RawPublicKey"; + } + + return "Unknown"; +} + +Certificate_Type certificate_type_from_string(const std::string& type_str) { + if(type_str == "X509") { + return Certificate_Type::X509; + } else if(type_str == "RawPublicKey") { + return Certificate_Type::RawPublicKey; + } else { + throw Decoding_Error("Unknown certificate type: " + type_str); + } +} + Supported_Groups::Supported_Groups(const std::vector& groups) : m_groups(groups) {} const std::vector& Supported_Groups::groups() const { diff --git a/src/lib/tls/tls_extensions.h b/src/lib/tls/tls_extensions.h index 5ee1a28252c..a1716e8fa4e 100644 --- a/src/lib/tls/tls_extensions.h +++ b/src/lib/tls/tls_extensions.h @@ -196,6 +196,12 @@ class BOTAN_UNSTABLE_API Application_Layer_Protocol_Notification final : public std::vector m_protocols; }; +// As defined in RFC 8446 4.4.2 +enum class Certificate_Type : uint8_t { X509 = 0, RawPublicKey = 2 }; + +std::string certificate_type_to_string(Certificate_Type type); +Certificate_Type certificate_type_from_string(const std::string& type_str); + /** * Session Ticket Extension (RFC 5077) */ diff --git a/src/lib/tls/tls_policy.cpp b/src/lib/tls/tls_policy.cpp index deb8bbcde88..57591667d7c 100644 --- a/src/lib/tls/tls_policy.cpp +++ b/src/lib/tls/tls_policy.cpp @@ -387,6 +387,14 @@ bool Policy::abort_connection_on_undesired_renegotiation() const { return false; } +std::vector Policy::accepted_client_certificate_types() const { + return {Certificate_Type::X509}; +} + +std::vector Policy::accepted_server_certificate_types() const { + return {Certificate_Type::X509}; +} + bool Policy::allow_dtls_epoch0_restart() const { return false; } @@ -585,6 +593,17 @@ void print_vec(std::ostream& o, const char* key, const std::vector o << "\n"; } +void print_vec(std::ostream& o, const char* key, const std::vector& types) { + o << key << " = "; + for(size_t i = 0; i != types.size(); ++i) { + o << certificate_type_to_string(types[i]); + if(i != types.size() - 1) { + o << ' '; + } + } + o << '\n'; +} + void print_bool(std::ostream& o, const char* key, bool b) { o << key << " = " << (b ? "true" : "false") << '\n'; } @@ -615,6 +634,8 @@ void Policy::print(std::ostream& o) const { print_bool(o, "negotiate_encrypt_then_mac", negotiate_encrypt_then_mac()); print_bool(o, "support_cert_status_message", support_cert_status_message()); print_bool(o, "tls_13_middlebox_compatibility_mode", tls_13_middlebox_compatibility_mode()); + print_vec(o, "accepted_client_certificate_types", accepted_client_certificate_types()); + print_vec(o, "accepted_server_certificate_types", accepted_server_certificate_types()); print_bool(o, "hash_hello_random", hash_hello_random()); if(record_size_limit().has_value()) { o << "record_size_limit = " << record_size_limit().value() << '\n'; diff --git a/src/lib/tls/tls_policy.h b/src/lib/tls/tls_policy.h index 75d4a4b3adf..e96873f9a0d 100644 --- a/src/lib/tls/tls_policy.h +++ b/src/lib/tls/tls_policy.h @@ -11,6 +11,7 @@ #define BOTAN_TLS_POLICY_H_ #include +#include #include #include #include @@ -364,6 +365,28 @@ class BOTAN_PUBLIC_API(2, 0) Policy { */ virtual bool request_client_certificate_authentication() const; + /** + * Returns a list of accepted certificate types for client authentication + * in order of preference. See RFC 7250 and RFC 8446 4.4.2 for details. + * Defaults to X509 only. + * + * Note that it is the application's responsibility to provide public keys + * and/or certificates according to the specification in this list via the + * Credentials_Manager. + */ + virtual std::vector accepted_client_certificate_types() const; + + /** + * Returns a list of accepted certificate types for server authentication + * in order of preference. See RFC 7250 and RFC 8446 4.4.2 for details. + * Defaults to X509 only. + * + * Note that it is the application's responsibility to provide public keys + * and/or certificates according to the specification in this list via the + * Credentials_Manager. + */ + virtual std::vector accepted_server_certificate_types() const; + /** * If true, then allow a DTLS client to restart a connection to the * same server association as described in section 4.2.8 of the DTLS RFC @@ -643,6 +666,9 @@ class BOTAN_PUBLIC_API(2, 0) Text_Policy : public Policy { bool require_client_certificate_authentication() const override; + std::vector accepted_client_certificate_types() const override; + std::vector accepted_server_certificate_types() const override; + size_t minimum_ecdh_group_size() const override; size_t minimum_ecdsa_group_size() const override; @@ -687,6 +713,7 @@ class BOTAN_PUBLIC_API(2, 0) Text_Policy : public Policy { std::vector get_list(const std::string& key, const std::vector& def) const; std::vector read_group_list(std::string_view group_str) const; + std::vector read_cert_type_list(const std::string& cert_type_str) const; size_t get_len(const std::string& key, size_t def) const; diff --git a/src/lib/tls/tls_server.cpp b/src/lib/tls/tls_server.cpp index 428d0b55a37..ddf9197e873 100644 --- a/src/lib/tls/tls_server.cpp +++ b/src/lib/tls/tls_server.cpp @@ -89,6 +89,10 @@ std::vector Server::peer_cert_chain() const { return m_impl->peer_cert_chain(); } +std::shared_ptr Server::peer_raw_public_key() const { + return m_impl->peer_raw_public_key(); +} + std::optional Server::external_psk_identity() const { return m_impl->external_psk_identity(); } diff --git a/src/lib/tls/tls_server.h b/src/lib/tls/tls_server.h index e7dbf35f59e..e45d6f54e99 100644 --- a/src/lib/tls/tls_server.h +++ b/src/lib/tls/tls_server.h @@ -76,7 +76,7 @@ class BOTAN_PUBLIC_API(2, 0) Server final : public Channel { bool is_closed_for_writing() const override; std::vector peer_cert_chain() const override; - + std::shared_ptr peer_raw_public_key() const override; std::optional external_psk_identity() const override; SymmetricKey key_material_export(std::string_view label, std::string_view context, size_t length) const override; diff --git a/src/lib/tls/tls_session.h b/src/lib/tls/tls_session.h index fde753855ff..6a03a5c1d4c 100644 --- a/src/lib/tls/tls_session.h +++ b/src/lib/tls/tls_session.h @@ -206,6 +206,11 @@ class BOTAN_PUBLIC_API(3, 0) Session_Base { */ const std::vector& peer_certs() const { return m_peer_certs; } + /** + * Return the raw public key of the peer (possibly empty) + */ + std::shared_ptr peer_raw_public_key() const { return nullptr; } + /** * Get information about the TLS server */ diff --git a/src/lib/tls/tls_text_policy.cpp b/src/lib/tls/tls_text_policy.cpp index cd5ac16eef1..19bbd1fb817 100644 --- a/src/lib/tls/tls_text_policy.cpp +++ b/src/lib/tls/tls_text_policy.cpp @@ -67,6 +67,16 @@ bool Text_Policy::allow_client_initiated_renegotiation() const { return get_bool("allow_client_initiated_renegotiation", Policy::allow_client_initiated_renegotiation()); } +std::vector Text_Policy::accepted_client_certificate_types() const { + const auto cert_types = get_str("accepted_client_certificate_types"); + return (cert_types.empty()) ? Policy::accepted_client_certificate_types() : read_cert_type_list(cert_types); +} + +std::vector Text_Policy::accepted_server_certificate_types() const { + const auto cert_types = get_str("accepted_server_certificate_types"); + return (cert_types.empty()) ? Policy::accepted_server_certificate_types() : read_cert_type_list(cert_types); +} + bool Text_Policy::allow_server_initiated_renegotiation() const { return get_bool("allow_server_initiated_renegotiation", Policy::allow_server_initiated_renegotiation()); } @@ -252,6 +262,15 @@ std::vector Text_Policy::read_group_list(std::string_view group_st return groups; } +std::vector Text_Policy::read_cert_type_list(const std::string& cert_type_names) const { + std::vector cert_types; + for(const std::string& cert_type_name : split_on(cert_type_names, ' ')) { + cert_types.push_back(certificate_type_from_string(cert_type_name)); + } + + return cert_types; +} + size_t Text_Policy::get_len(const std::string& key, size_t def) const { const std::string v = get_str(key); From 243f20ff9b5a1a1ef6983a03226ff2b344a8ef61 Mon Sep 17 00:00:00 2001 From: Rene Meusel Date: Wed, 1 Nov 2023 13:35:47 +0100 Subject: [PATCH 2/8] New TLS Extension: Client/Server_Certificate_Type Co-Authored-By: Fabian Albert --- src/lib/tls/tls_extensions.cpp | 106 +++++++++++++++++++++++++++++++++ src/lib/tls/tls_extensions.h | 75 +++++++++++++++++++++++ 2 files changed, 181 insertions(+) diff --git a/src/lib/tls/tls_extensions.cpp b/src/lib/tls/tls_extensions.cpp index 037f619a99e..5460f02f614 100644 --- a/src/lib/tls/tls_extensions.cpp +++ b/src/lib/tls/tls_extensions.cpp @@ -5,6 +5,7 @@ * 2021 Elektrobit Automotive GmbH * 2022 René Meusel, Hannes Rantzsch - neXenio GmbH * 2023 Mateusz Berezecki +* 2023 Fabian Albert, René Meusel - Rohde & Schwarz Cybersecurity * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -59,6 +60,12 @@ std::unique_ptr make_extension(TLS_Data_Reader& reader, case Extension_Code::ApplicationLayerProtocolNegotiation: return std::make_unique(reader, size, from); + case Extension_Code::ClientCertificateType: + return std::make_unique(reader, size, from); + + case Extension_Code::ServerCertificateType: + return std::make_unique(reader, size, from); + case Extension_Code::ExtendedMasterSecret: return std::make_unique(reader, size); @@ -377,6 +384,105 @@ Certificate_Type certificate_type_from_string(const std::string& type_str) { } } +Certificate_Type_Base::Certificate_Type_Base(std::vector supported_cert_types) : + m_certificate_types(std::move(supported_cert_types)), m_from(Connection_Side::Client) { + BOTAN_ARG_CHECK(!m_certificate_types.empty(), "at least one certificate type must be supported"); +} + +Client_Certificate_Type::Client_Certificate_Type(const Client_Certificate_Type& cct, const Policy& policy) : + Certificate_Type_Base(cct, policy.accepted_client_certificate_types()) {} + +Server_Certificate_Type::Server_Certificate_Type(const Server_Certificate_Type& sct, const Policy& policy) : + Certificate_Type_Base(sct, policy.accepted_server_certificate_types()) {} + +Certificate_Type_Base::Certificate_Type_Base(const Certificate_Type_Base& certificate_type_from_client, + const std::vector& server_preference) : + m_from(Connection_Side::Server) { + // RFC 7250 4.2 + // The server_certificate_type extension in the client hello indicates the + // types of certificates the client is able to process when provided by + // the server in a subsequent certificate payload. [...] With the + // server_certificate_type extension in the server hello, the TLS server + // indicates the certificate type carried in the Certificate payload. + for(const auto server_supported_cert_type : server_preference) { + if(value_exists(certificate_type_from_client.m_certificate_types, server_supported_cert_type)) { + m_certificate_types.push_back(server_supported_cert_type); + return; + } + } + + // RFC 7250 4.2 (2.) + // The server supports the extension defined in this document, but + // it does not have any certificate type in common with the client. + // Then, the server terminates the session with a fatal alert of + // type "unsupported_certificate". + throw TLS_Exception(Alert::UnsupportedCertificate, "Failed to agree on certificate_type"); +} + +Certificate_Type_Base::Certificate_Type_Base(TLS_Data_Reader& reader, uint16_t extension_size, Connection_Side from) : + m_from(from) { + if(extension_size == 0) { + throw Decoding_Error("Certificate type extension cannot be empty"); + } + + if(from == Connection_Side::Client) { + const auto type_bytes = reader.get_tls_length_value(1); + if(static_cast(extension_size) != type_bytes.size() + 1) { + throw Decoding_Error("certificate type extension had inconsistent length"); + } + std::transform( + type_bytes.begin(), type_bytes.end(), std::back_inserter(m_certificate_types), [](const auto type_byte) { + return static_cast(type_byte); + }); + } else { + // RFC 7250 4.2 + // Note that only a single value is permitted in the + // server_certificate_type extension when carried in the server hello. + if(extension_size != 1) { + throw Decoding_Error("Server's certificate type extension must be of length 1"); + } + const auto type_byte = reader.get_byte(); + m_certificate_types.push_back(static_cast(type_byte)); + } +} + +std::vector Certificate_Type_Base::serialize(Connection_Side whoami) const { + std::vector result; + if(whoami == Connection_Side::Client) { + std::vector type_bytes; + std::transform( + m_certificate_types.begin(), m_certificate_types.end(), std::back_inserter(type_bytes), [](const auto type) { + return static_cast(type); + }); + append_tls_length_value(result, type_bytes, 1); + } else { + BOTAN_ASSERT_NOMSG(m_certificate_types.size() == 1); + result.push_back(static_cast(m_certificate_types.front())); + } + return result; +} + +void Certificate_Type_Base::validate_selection(const Certificate_Type_Base& from_server) const { + BOTAN_ASSERT_NOMSG(m_from == Connection_Side::Client); + BOTAN_ASSERT_NOMSG(from_server.m_from == Connection_Side::Server); + + // RFC 7250 4.2 + // The value conveyed in the [client_]certificate_type extension MUST be + // selected from one of the values provided in the [client_]certificate_type + // extension sent in the client hello. + if(!value_exists(m_certificate_types, from_server.selected_certificate_type())) { + throw TLS_Exception(Alert::IllegalParameter, + Botan::fmt("Selected certificate type was not offered: {}", + certificate_type_to_string(from_server.selected_certificate_type()))); + } +} + +Certificate_Type Certificate_Type_Base::selected_certificate_type() const { + BOTAN_ASSERT_NOMSG(m_from == Connection_Side::Server); + BOTAN_ASSERT_NOMSG(m_certificate_types.size() == 1); + return m_certificate_types.front(); +} + Supported_Groups::Supported_Groups(const std::vector& groups) : m_groups(groups) {} const std::vector& Supported_Groups::groups() const { diff --git a/src/lib/tls/tls_extensions.h b/src/lib/tls/tls_extensions.h index a1716e8fa4e..7d8f0f5d5b4 100644 --- a/src/lib/tls/tls_extensions.h +++ b/src/lib/tls/tls_extensions.h @@ -5,6 +5,7 @@ * (C) 2016 Matthias Gierlings * (C) 2021 Elektrobit Automotive GmbH * (C) 2022 René Meusel, Hannes Rantzsch - neXenio GmbH +* (C) 2023 Fabian Albert, René Meusel - Rohde & Schwarz Cybersecurity * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -62,6 +63,10 @@ enum class Extension_Code : uint16_t { // SignedCertificateTimestamp = 18, // NYI + // RFC 7250 (Raw Public Keys in TLS) + ClientCertificateType = 19, + ServerCertificateType = 20, + EncryptThenMac = 22, ExtendedMasterSecret = 23, @@ -202,6 +207,74 @@ enum class Certificate_Type : uint8_t { X509 = 0, RawPublicKey = 2 }; std::string certificate_type_to_string(Certificate_Type type); Certificate_Type certificate_type_from_string(const std::string& type_str); +/** + * RFC 7250 + * Base class for 'client_certificate_type' and 'server_certificate_type' extensions. + */ +class BOTAN_UNSTABLE_API Certificate_Type_Base : public Extension { + public: + /** + * Called by the client to advertise support for a number of cert types. + */ + Certificate_Type_Base(std::vector supported_cert_types); + + protected: + /** + * Called by the server to select a cert type to be used in the handshake. + */ + Certificate_Type_Base(const Certificate_Type_Base& certificate_type_from_client, + const std::vector& server_preference); + + public: + Certificate_Type_Base(TLS_Data_Reader& reader, uint16_t extension_size, Connection_Side from); + + std::vector serialize(Connection_Side whoami) const override; + + void validate_selection(const Certificate_Type_Base& from_server) const; + Certificate_Type selected_certificate_type() const; + + bool empty() const override { + // RFC 7250 4.1 + // If the client has no remaining certificate types to send in the + // client hello, other than the default X.509 type, it MUST omit the + // entire client[/server]_certificate_type extension [...]. + return m_from == Connection_Side::Client && m_certificate_types.size() == 1 && + m_certificate_types.front() == Certificate_Type::X509; + } + + private: + std::vector m_certificate_types; + Connection_Side m_from; +}; + +class BOTAN_UNSTABLE_API Client_Certificate_Type final : public Certificate_Type_Base { + public: + using Certificate_Type_Base::Certificate_Type_Base; + + /** + * Creates the Server Hello extension from the received client preferences. + */ + Client_Certificate_Type(const Client_Certificate_Type& cct, const Policy& policy); + + static Extension_Code static_type() { return Extension_Code::ClientCertificateType; } + + Extension_Code type() const override { return static_type(); } +}; + +class BOTAN_UNSTABLE_API Server_Certificate_Type final : public Certificate_Type_Base { + public: + using Certificate_Type_Base::Certificate_Type_Base; + + /** + * Creates the Server Hello extension from the received client preferences. + */ + Server_Certificate_Type(const Server_Certificate_Type& sct, const Policy& policy); + + static Extension_Code static_type() { return Extension_Code::ServerCertificateType; } + + Extension_Code type() const override { return static_type(); } +}; + /** * Session Ticket Extension (RFC 5077) */ @@ -852,6 +925,8 @@ class BOTAN_UNSTABLE_API Extensions final { size_t size() const { return m_extensions.size(); } + bool empty() const { return m_extensions.empty(); } + void add(std::unique_ptr extn); void add(Extension* extn) { add(std::unique_ptr(extn)); } From ddbcf89d1e8460f3183765f9ae0233c982fb60e8 Mon Sep 17 00:00:00 2001 From: Rene Meusel Date: Wed, 1 Nov 2023 13:40:56 +0100 Subject: [PATCH 3/8] Certificate(_Verify) messages can handle raw public keys Co-Authored-By: Fabian Albert --- src/lib/tls/msg_cert_verify.cpp | 16 +- src/lib/tls/tls13/msg_certificate_13.cpp | 307 +++++++++++++------ src/lib/tls/tls13/tls_client_impl_13.cpp | 9 +- src/lib/tls/tls13/tls_handshake_layer_13.cpp | 2 +- src/lib/tls/tls13/tls_handshake_state_13.h | 4 +- src/lib/tls/tls13/tls_server_impl_13.cpp | 9 +- src/lib/tls/tls_messages.h | 53 +++- 7 files changed, 285 insertions(+), 115 deletions(-) diff --git a/src/lib/tls/msg_cert_verify.cpp b/src/lib/tls/msg_cert_verify.cpp index 8105e211d48..6ad8d388ea9 100644 --- a/src/lib/tls/msg_cert_verify.cpp +++ b/src/lib/tls/msg_cert_verify.cpp @@ -148,11 +148,14 @@ Certificate_Verify_13::Certificate_Verify_13(const Certificate_13& certificate_m m_side(whoami) { BOTAN_ASSERT_NOMSG(!certificate_msg.empty()); - const auto private_key = creds_mgr.private_key_for( - certificate_msg.leaf(), m_side == Connection_Side::Client ? "tls-client" : "tls-server", std::string(hostname)); + const auto op_type = (m_side == Connection_Side::Client) ? "tls-client" : "tls-server"; + const auto context = std::string(hostname); + const auto private_key = (certificate_msg.has_certificate_chain()) + ? creds_mgr.private_key_for(certificate_msg.leaf(), op_type, context) + : creds_mgr.private_key_for(*certificate_msg.public_key(), op_type, context); if(!private_key) { - throw TLS_Exception(Alert::InternalError, "Application did not provide a private key for its certificate"); + throw TLS_Exception(Alert::InternalError, "Application did not provide a private key for its credential"); } m_scheme = choose_signature_scheme(*private_key, policy.allowed_signature_schemes(), peer_allowed_schemes); @@ -177,7 +180,7 @@ Certificate_Verify_13::Certificate_Verify_13(const std::vector& buf, co /* * Verify a Certificate Verify message */ -bool Certificate_Verify_13::verify(const X509_Certificate& cert, +bool Certificate_Verify_13::verify(const Public_Key& public_key, Callbacks& callbacks, const Transcript_Hash& transcript_hash) const { BOTAN_ASSERT_NOMSG(m_scheme.is_available()); @@ -185,13 +188,12 @@ bool Certificate_Verify_13::verify(const X509_Certificate& cert, // RFC 8446 4.2.3 // The keys found in certificates MUST [...] be of appropriate type for // the signature algorithms they are used with. - if(m_scheme.key_algorithm_identifier() != cert.subject_public_key_algo()) { + if(m_scheme.key_algorithm_identifier() != public_key.algorithm_identifier()) { throw TLS_Exception(Alert::IllegalParameter, "Signature algorithm does not match certificate's public key"); } - const auto key = cert.subject_public_key(); const bool signature_valid = callbacks.tls_verify_message( - *key, m_scheme.padding_string(), m_scheme.format().value(), message(m_side, transcript_hash), m_signature); + public_key, m_scheme.padding_string(), m_scheme.format().value(), message(m_side, transcript_hash), m_signature); #if defined(BOTAN_UNSAFE_FUZZER_MODE) BOTAN_UNUSED(signature_valid); diff --git a/src/lib/tls/tls13/msg_certificate_13.cpp b/src/lib/tls/tls13/msg_certificate_13.cpp index b17af141a0e..8394af2f952 100644 --- a/src/lib/tls/tls13/msg_certificate_13.cpp +++ b/src/lib/tls/tls13/msg_certificate_13.cpp @@ -2,6 +2,7 @@ * Certificate Message * (C) 2022 Jack Lloyd * 2022 Hannes Rantzsch, René Meusel - neXenio GmbH +* 2023 René Meusel, Fabian Albert - Rohde & Schwarz Cybersecurity * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -15,12 +16,15 @@ #include #include #include +#include #include +#include #include #include #include #include +#include namespace Botan::TLS { @@ -52,10 +56,19 @@ std::vector filter_signature_schemes(const std::vector Certificate_13::cert_chain() const { + BOTAN_STATE_CHECK(has_certificate_chain()); std::vector result; std::transform(m_entries.cbegin(), m_entries.cend(), std::back_inserter(result), [](const auto& cert_entry) { - return cert_entry.certificate; + return cert_entry.certificate(); }); return result; } @@ -67,17 +80,22 @@ void Certificate_13::validate_extensions(const std::set& request // the Certificate message from the client MUST correspond to // extensions in the CertificateRequest message from the server. for(const auto& entry : m_entries) { - if(entry.extensions.contains_other_than(requested_extensions)) { + if(entry.extensions().contains_other_than(requested_extensions)) { throw TLS_Exception(Alert::IllegalParameter, "Certificate Entry contained an extension that was not offered"); } - cb.tls_examine_extensions(entry.extensions, m_side, type()); + cb.tls_examine_extensions(entry.extensions(), m_side, type()); } } +std::shared_ptr Certificate_13::public_key() const { + BOTAN_STATE_CHECK(!empty()); + return m_entries.front().public_key(); +} + const X509_Certificate& Certificate_13::leaf() const { BOTAN_STATE_CHECK(!empty()); - return m_entries.front().certificate; + return m_entries.front().certificate(); } void Certificate_13::verify(Callbacks& callbacks, @@ -85,21 +103,36 @@ void Certificate_13::verify(Callbacks& callbacks, Credentials_Manager& creds, std::string_view hostname, bool use_ocsp) const { + const auto usage = (m_side == Connection_Side::Client) ? Usage_Type::TLS_CLIENT_AUTH : Usage_Type::TLS_SERVER_AUTH; + + if(is_raw_public_key()) { + callbacks.tls_verify_raw_public_key(*public_key(), usage, hostname, policy); + } else { + verify_certificate_chain(callbacks, policy, creds, hostname, use_ocsp, usage); + } +} + +void Certificate_13::verify_certificate_chain(Callbacks& callbacks, + const Policy& policy, + Credentials_Manager& creds, + std::string_view hostname, + bool use_ocsp, + Usage_Type usage_type) const { std::vector certs; std::vector> ocsp_responses; for(const auto& entry : m_entries) { - certs.push_back(entry.certificate); + certs.push_back(entry.certificate()); if(use_ocsp) { - if(entry.extensions.has()) { + if(entry.extensions().has()) { ocsp_responses.push_back(callbacks.tls_parse_ocsp_response( - entry.extensions.get()->get_ocsp_response())); + entry.extensions().get()->get_ocsp_response())); } else { ocsp_responses.emplace_back(); } } } - const auto& server_cert = m_entries.front().certificate; + const auto& server_cert = m_entries.front().certificate(); if(!certificate_allows_signing(server_cert)) { throw TLS_Exception(Alert::BadCertificate, "Certificate usage constraints do not allow signing"); } @@ -108,8 +141,7 @@ void Certificate_13::verify(Callbacks& callbacks, const auto trusted_CAs = creds.trusted_certificate_authorities( m_side == Connection_Side::Client ? "tls-server" : "tls-client", std::string(hostname)); - const auto usage = (m_side == Connection_Side::Client) ? Usage_Type::TLS_CLIENT_AUTH : Usage_Type::TLS_SERVER_AUTH; - callbacks.tls_verify_cert_chain(certs, ocsp_responses, trusted_CAs, usage, hostname, policy); + callbacks.tls_verify_cert_chain(certs, ocsp_responses, trusted_CAs, usage_type, hostname, policy); } void Certificate_13::setup_entries(std::vector cert_chain, @@ -127,10 +159,9 @@ void Certificate_13::setup_entries(std::vector cert_chain, } for(size_t i = 0; i < cert_chain.size(); ++i) { - auto& entry = m_entries.emplace_back(); - entry.certificate = cert_chain[i]; + auto& entry = m_entries.emplace_back(cert_chain[i]); if(!ocsp_responses[i].empty()) { - entry.extensions.add(new Certificate_Status_Request(ocsp_responses[i])); + entry.extensions().add(new Certificate_Status_Request(ocsp_responses[i])); } // This will call the modification callback multiple times. Once for @@ -141,26 +172,51 @@ void Certificate_13::setup_entries(std::vector cert_chain, // TODO: Callbacks::tls_modify_extensions() might need even more // context depending on the message whose extensions should be // manipulatable. - callbacks.tls_modify_extensions(entry.extensions, m_side, type()); + callbacks.tls_modify_extensions(entry.extensions(), m_side, type()); } } +void Certificate_13::setup_entry(std::shared_ptr raw_public_key, Callbacks& callbacks) { + BOTAN_ASSERT_NONNULL(raw_public_key); + auto& entry = m_entries.emplace_back(std::move(raw_public_key)); + callbacks.tls_modify_extensions(entry.extensions(), m_side, type()); +} + /** * Create a Client Certificate message */ Certificate_13::Certificate_13(const Certificate_Request_13& cert_request, std::string_view hostname, Credentials_Manager& credentials_manager, - Callbacks& callbacks) : + Callbacks& callbacks, + Certificate_Type cert_type) : m_request_context(cert_request.context()), m_side(Connection_Side::Client) { - setup_entries( - credentials_manager.find_cert_chain(filter_signature_schemes(cert_request.signature_schemes()), - to_algorithm_identifiers(cert_request.certificate_signature_schemes()), - cert_request.acceptable_CAs(), - "tls-client", - std::string(hostname)), - cert_request.extensions().get(), - callbacks); + const auto key_types = filter_signature_schemes(cert_request.signature_schemes()); + const auto op_type = "tls-client"; + + if(cert_type == Certificate_Type::X509) { + setup_entries( + credentials_manager.find_cert_chain(key_types, + to_algorithm_identifiers(cert_request.certificate_signature_schemes()), + cert_request.acceptable_CAs(), + op_type, + std::string(hostname)), + cert_request.extensions().get(), + callbacks); + } else if(cert_type == Certificate_Type::RawPublicKey) { + auto raw_public_key = credentials_manager.find_raw_public_key(key_types, op_type, std::string(hostname)); + + // RFC 8446 4.4.2 + // If the RawPublicKey certificate type was negotiated, then the + // certificate_list MUST contain no more than one CertificateEntry + // [...]. + // A client will send an empty certificate_list if it does not have + // an appropriate certificate to send in response to the server's + // authentication request. + if(raw_public_key) { + setup_entry(std::move(raw_public_key), callbacks); + } + } } /** @@ -168,33 +224,128 @@ Certificate_13::Certificate_13(const Certificate_Request_13& cert_request, */ Certificate_13::Certificate_13(const Client_Hello_13& client_hello, Credentials_Manager& credentials_manager, - Callbacks& callbacks) : + Callbacks& callbacks, + Certificate_Type cert_type) : // RFC 8446 4.4.2: // [In the case of server authentication], this field // SHALL be zero length m_request_context(), m_side(Connection_Side::Server) { BOTAN_ASSERT_NOMSG(client_hello.extensions().has()); - auto cert_chain = - credentials_manager.find_cert_chain(filter_signature_schemes(client_hello.signature_schemes()), - to_algorithm_identifiers(client_hello.certificate_signature_schemes()), - {}, - "tls-server", - client_hello.sni_hostname()); + const auto key_types = filter_signature_schemes(client_hello.signature_schemes()); + const auto op_type = "tls-server"; + const auto context = client_hello.sni_hostname(); - // RFC 8446 4.4.2 - // The server's certificate_list MUST always be non-empty. - if(cert_chain.empty()) { - throw TLS_Exception(Alert::HandshakeFailure, "No sufficient server certificate available"); + if(cert_type == Certificate_Type::X509) { + auto cert_chain = credentials_manager.find_cert_chain( + key_types, to_algorithm_identifiers(client_hello.certificate_signature_schemes()), {}, op_type, context); + + // RFC 8446 4.4.2 + // The server's certificate_list MUST always be non-empty. + if(cert_chain.empty()) { + throw TLS_Exception(Alert::HandshakeFailure, "No sufficient server certificate available"); + } + + setup_entries(std::move(cert_chain), client_hello.extensions().get(), callbacks); + } else if(cert_type == Certificate_Type::RawPublicKey) { + auto raw_public_key = credentials_manager.find_raw_public_key(key_types, op_type, context); + + // RFC 8446 4.4.2 + // If the RawPublicKey certificate type was negotiated, then the + // certificate_list MUST contain no more than one CertificateEntry + // [...]. + // The server's certificate_list MUST always be non-empty + if(!raw_public_key) { + throw TLS_Exception(Alert::HandshakeFailure, "No sufficient server raw public key available"); + } + + setup_entry(std::move(raw_public_key), callbacks); + } +} + +Certificate_13::Certificate_Entry::Certificate_Entry(TLS_Data_Reader& reader, + const Connection_Side side, + const Certificate_Type cert_type) { + switch(cert_type) { + case Certificate_Type::X509: + // RFC 8446 4.2.2 + // [...] each CertificateEntry contains a DER-encoded X.509 + // certificate. + m_certificate = X509_Certificate(reader.get_tls_length_value(3)); + m_raw_public_key = m_certificate->subject_public_key(); + break; + case Certificate_Type::RawPublicKey: + // RFC 7250 3. + // This specification uses raw public keys whereby the already + // available encoding used in a PKIX certificate in the form of a + // SubjectPublicKeyInfo structure is reused. + m_raw_public_key = X509::load_key(reader.get_tls_length_value(3)); + break; + default: + throw TLS_Exception(Alert::InternalError, "Unknown certificate type"); + } + + // Extensions are simply tacked at the end of the certificate entry. This + // is a departure from the typical "tag-length-value" in a sense that the + // Extensions deserializer needs the length value of the extensions. + const auto extensions_length = reader.peek_uint16_t(); + const auto exts_buf = reader.get_fixed(extensions_length + 2); + TLS_Data_Reader exts_reader("extensions reader", exts_buf); + m_extensions.deserialize(exts_reader, side, Handshake_Type::Certificate); + + if(cert_type == Certificate_Type::X509) { + // RFC 8446 4.4.2 + // Valid extensions for server certificates at present include the + // OCSP Status extension [RFC6066] and the SignedCertificateTimestamp + // extension [RFC6962]; future extensions may be defined for this + // message as well. + // + // RFC 8446 4.4.2.1 + // A server MAY request that a client present an OCSP response with its + // certificate by sending an empty "status_request" extension in its + // CertificateRequest message. + if(m_extensions.contains_implemented_extensions_other_than({ + Extension_Code::CertificateStatusRequest, + // Extension_Code::SignedCertificateTimestamp + })) { + throw TLS_Exception(Alert::IllegalParameter, "Certificate Entry contained an extension that is not allowed"); + } + } else if(m_extensions.contains_implemented_extensions_other_than({})) { + throw TLS_Exception( + Alert::IllegalParameter, + "Certificate Entry holding something else than a certificate contained unexpected extensions"); } +} + +Certificate_13::Certificate_Entry::Certificate_Entry(X509_Certificate cert) : + m_certificate(std::move(cert)), m_raw_public_key(m_certificate->subject_public_key()) {} + +Certificate_13::Certificate_Entry::Certificate_Entry(std::shared_ptr raw_public_key) : + m_certificate(std::nullopt), m_raw_public_key(std::move(raw_public_key)) { + BOTAN_ASSERT_NONNULL(m_raw_public_key); +} + +const X509_Certificate& Certificate_13::Certificate_Entry::certificate() const { + BOTAN_STATE_CHECK(has_certificate()); + return m_certificate.value(); +} + +std::shared_ptr Certificate_13::Certificate_Entry::public_key() const { + BOTAN_ASSERT_NONNULL(m_raw_public_key); + return m_raw_public_key; +} - setup_entries(std::move(cert_chain), client_hello.extensions().get(), callbacks); +std::vector Certificate_13::Certificate_Entry::serialize() const { + return (has_certificate()) ? m_certificate->BER_encode() : X509::BER_encode(*m_raw_public_key); } /** * Deserialize a Certificate message */ -Certificate_13::Certificate_13(const std::vector& buf, const Policy& policy, const Connection_Side side) : +Certificate_13::Certificate_13(const std::vector& buf, + const Policy& policy, + Connection_Side side, + Certificate_Type cert_type) : m_side(side) { TLS_Data_Reader reader("cert message reader", buf); @@ -218,48 +369,7 @@ Certificate_13::Certificate_13(const std::vector& buf, const Policy& po } while(reader.has_remaining()) { - Certificate_Entry entry; - entry.certificate = X509_Certificate(reader.get_tls_length_value(3)); - - // RFC 8446 4.4.2.2 - // The certificate type MUST be X.509v3 [RFC5280], unless explicitly - // negotiated otherwise (e.g., [RFC7250]). - // - // TLS 1.0 through 1.3 all seem to require that the certificate be - // precisely a v3 certificate. In fact the strict wording would seem - // to require that every certificate in the chain be v3. But often - // the intermediates are outside of the control of the server. - // But, require that the leaf certificate be v3. - if(m_entries.empty() && entry.certificate.x509_version() != 3) { - throw TLS_Exception(Alert::BadCertificate, "The leaf certificate must be v3"); - } - - // Extensions are simply tacked at the end of the certificate entry. This - // is a departure from the typical "tag-length-value" in a sense that the - // Extensions deserializer needs the length value of the extensions. - const auto extensions_length = reader.peek_uint16_t(); - const auto exts_buf = reader.get_fixed(extensions_length + 2); - TLS_Data_Reader exts_reader("extensions reader", exts_buf); - entry.extensions.deserialize(exts_reader, m_side, type()); - - // RFC 8446 4.4.2 - // Valid extensions for server certificates at present include the - // OCSP Status extension [RFC6066] and the SignedCertificateTimestamp - // extension [RFC6962]; future extensions may be defined for this - // message as well. - // - // RFC 8446 4.4.2.1 - // A server MAY request that a client present an OCSP response with its - // certificate by sending an empty "status_request" extension in its - // CertificateRequest message. - if(entry.extensions.contains_implemented_extensions_other_than({ - Extension_Code::CertificateStatusRequest, - // Extension_Code::SignedCertificateTimestamp - })) { - throw TLS_Exception(Alert::IllegalParameter, "Certificate Entry contained an extension that is not allowed"); - } - - m_entries.push_back(std::move(entry)); + m_entries.emplace_back(reader, side, cert_type); } // RFC 8446 4.4.2 @@ -274,15 +384,38 @@ Certificate_13::Certificate_13(const std::vector& buf, const Policy& po if(m_side == Connection_Side::Server) { throw TLS_Exception(Alert::DecodeError, "No certificates sent by server"); } - } else { - /* validation of provided certificate public key */ - auto key = m_entries.front().certificate.subject_public_key(); - policy.check_peer_key_acceptable(*key); + return; + } - if(!policy.allowed_signature_method(key->algo_name())) { - throw TLS_Exception(Alert::HandshakeFailure, "Rejecting " + key->algo_name() + " signature"); - } + BOTAN_ASSERT_NOMSG(!m_entries.empty()); + + // RFC 8446 4.4.2.2 + // The certificate type MUST be X.509v3 [RFC5280], unless explicitly + // negotiated otherwise (e.g., [RFC7250]). + // + // TLS 1.0 through 1.3 all seem to require that the certificate be + // precisely a v3 certificate. In fact the strict wording would seem + // to require that every certificate in the chain be v3. But often + // the intermediates are outside of the control of the server. + // But, require that the leaf certificate be v3. + if(cert_type == Certificate_Type::X509 && m_entries.front().certificate().x509_version() != 3) { + throw TLS_Exception(Alert::BadCertificate, "The leaf certificate must be v3"); + } + + // RFC 8446 4.4.2 + // If the RawPublicKey certificate type was negotiated, then the + // certificate_list MUST contain no more than one CertificateEntry. + if(cert_type == Certificate_Type::RawPublicKey && m_entries.size() != 1) { + throw TLS_Exception(Alert::IllegalParameter, "Certificate message contained more than one RawPublicKey"); + } + + // Validate the provided (certificate) public key against our policy + auto pubkey = public_key(); + policy.check_peer_key_acceptable(*pubkey); + + if(!policy.allowed_signature_method(pubkey->algo_name())) { + throw TLS_Exception(Alert::HandshakeFailure, "Rejecting " + pubkey->algo_name() + " signature"); } } @@ -296,7 +429,7 @@ std::vector Certificate_13::serialize() const { std::vector entries; for(const auto& entry : m_entries) { - append_tls_length_value(entries, entry.certificate.BER_encode(), 3); + append_tls_length_value(entries, entry.serialize(), 3); // Extensions are tacked at the end of certificate entries. Note that // Extensions::serialize() usually emits the required length field, @@ -305,7 +438,7 @@ std::vector Certificate_13::serialize() const { // // TODO: look into this issue more generally when overhauling the // message marshalling. - auto extensions = entry.extensions.serialize(m_side); + auto extensions = entry.extensions().serialize(m_side); entries += (!extensions.empty()) ? extensions : std::vector{0, 0}; } diff --git a/src/lib/tls/tls13/tls_client_impl_13.cpp b/src/lib/tls/tls13/tls_client_impl_13.cpp index 99b6bbd34df..b3ed5dfa6ed 100644 --- a/src/lib/tls/tls13/tls_client_impl_13.cpp +++ b/src/lib/tls/tls13/tls_client_impl_13.cpp @@ -465,7 +465,7 @@ void Client_Impl_13::handle(const Certificate_Verify_13& certificate_verify_msg) } bool sig_valid = certificate_verify_msg.verify( - m_handshake_state.server_certificate().leaf(), callbacks(), m_transcript_hash.previous()); + *m_handshake_state.server_certificate().public_key(), callbacks(), m_transcript_hash.previous()); if(!sig_valid) { throw TLS_Exception(Alert::DecryptError, "Server certificate verification failed"); @@ -482,8 +482,8 @@ void Client_Impl_13::send_client_authentication(Channel_Impl_13::AggregatedHands // certificate_request_context: If this message is in response to a // CertificateRequest, the value of certificate_request_context in // that message. - flight.add( - m_handshake_state.sending(Certificate_13(cert_request, m_info.hostname(), credentials_manager(), callbacks()))); + flight.add(m_handshake_state.sending( + Certificate_13(cert_request, m_info.hostname(), credentials_manager(), callbacks(), Certificate_Type::X509))); // RFC 4.4.2 // If the server requests client authentication but no suitable certificate @@ -575,7 +575,8 @@ void TLS::Client_Impl_13::handle(const New_Session_Ticket_13& new_session_ticket } std::vector Client_Impl_13::peer_cert_chain() const { - if(m_handshake_state.has_server_certificate_chain()) { + if(m_handshake_state.has_server_certificate_msg() && + m_handshake_state.server_certificate().has_certificate_chain()) { return m_handshake_state.server_certificate().cert_chain(); } diff --git a/src/lib/tls/tls13/tls_handshake_layer_13.cpp b/src/lib/tls/tls13/tls_handshake_layer_13.cpp index 77a8af4a249..f389b47330b 100644 --- a/src/lib/tls/tls13/tls_handshake_layer_13.cpp +++ b/src/lib/tls/tls13/tls_handshake_layer_13.cpp @@ -89,7 +89,7 @@ std::optional parse_message(TLS::TLS_Data_Reader& reader, case Handshake_Type::EncryptedExtensions: return Encrypted_Extensions(msg); case Handshake_Type::Certificate: - return Certificate_13(msg, policy, peer_side); + return Certificate_13(msg, policy, peer_side, Certificate_Type::X509); case Handshake_Type::CertificateRequest: return Certificate_Request_13(msg, peer_side); case Handshake_Type::CertificateVerify: diff --git a/src/lib/tls/tls13/tls_handshake_state_13.h b/src/lib/tls/tls13/tls_handshake_state_13.h index 20b2e89f892..a3fb17da8bc 100644 --- a/src/lib/tls/tls13/tls_handshake_state_13.h +++ b/src/lib/tls/tls13/tls_handshake_state_13.h @@ -28,9 +28,9 @@ class BOTAN_TEST_API Handshake_State_13_Base { bool has_server_hello() const { return m_server_hello.has_value(); } - bool has_server_certificate_chain() const { return m_server_certificate.has_value(); } + bool has_server_certificate_msg() const { return m_server_certificate.has_value(); } - bool has_client_certificate_chain() const { return m_client_certificate.has_value(); } + bool has_client_certificate_msg() const { return m_client_certificate.has_value(); } bool has_hello_retry_request() const { return m_hello_retry_request.has_value(); } diff --git a/src/lib/tls/tls13/tls_server_impl_13.cpp b/src/lib/tls/tls13/tls_server_impl_13.cpp index 90f06d0d6df..8a5fd49d1d6 100644 --- a/src/lib/tls/tls13/tls_server_impl_13.cpp +++ b/src/lib/tls/tls13/tls_server_impl_13.cpp @@ -45,7 +45,8 @@ std::string Server_Impl_13::application_protocol() const { std::vector Server_Impl_13::peer_cert_chain() const { if(m_resumed_session.has_value()) { return m_resumed_session->peer_certs(); - } else if(m_handshake_state.has_client_certificate_chain()) { + } else if(m_handshake_state.has_client_certificate_msg() && + m_handshake_state.client_certificate().has_certificate_chain()) { return m_handshake_state.client_certificate().cert_chain(); } else { return {}; @@ -299,7 +300,7 @@ void Server_Impl_13::handle_reply_to_client_hello(Server_Hello_13 server_hello) flight.add(m_handshake_state.sending(std::move(certificate_request.value()))); } - flight.add(m_handshake_state.sending(Certificate_13(client_hello, credentials_manager(), callbacks()))) + flight.add(m_handshake_state.sending(Certificate_13(client_hello, credentials_manager(), callbacks(), Certificate_Type::X509))) .add(m_handshake_state.sending(Certificate_Verify_13(m_handshake_state.server_certificate(), client_hello.signature_schemes(), client_hello.sni_hostname(), @@ -505,10 +506,10 @@ void Server_Impl_13::handle(const Certificate_Verify_13& certificate_verify_msg) " as a signature scheme"); } - BOTAN_ASSERT_NOMSG(m_handshake_state.has_client_certificate_chain() && + BOTAN_ASSERT_NOMSG(m_handshake_state.has_client_certificate_msg() && !m_handshake_state.client_certificate().empty()); bool sig_valid = certificate_verify_msg.verify( - m_handshake_state.client_certificate().leaf(), callbacks(), m_transcript_hash.previous()); + *m_handshake_state.client_certificate().public_key(), callbacks(), m_transcript_hash.previous()); // RFC 8446 4.4.3 // If the verification fails, the receiver MUST terminate the handshake diff --git a/src/lib/tls/tls_messages.h b/src/lib/tls/tls_messages.h index 38aeafad2fa..86d7008cf62 100644 --- a/src/lib/tls/tls_messages.h +++ b/src/lib/tls/tls_messages.h @@ -548,12 +548,27 @@ class Certificate_Request_13; */ class BOTAN_UNSTABLE_API Certificate_13 final : public Handshake_Message { public: - struct Certificate_Entry { - // TODO: RFC 8446 4.4.2 specifies the possibility to negotiate the usage - // of a single raw public key in lieu of the X.509 certificate - // chain. This is left for future work. - X509_Certificate certificate; - Extensions extensions; + class Certificate_Entry { + public: + Certificate_Entry(TLS_Data_Reader& reader, const Connection_Side side, const Certificate_Type cert_type); + Certificate_Entry(X509_Certificate cert); + Certificate_Entry(std::shared_ptr raw_public_key); + + bool has_certificate() const { return m_certificate.has_value(); } + + const X509_Certificate& certificate() const; + std::shared_ptr public_key() const; + + std::vector serialize() const; + + Extensions& extensions() { return m_extensions; } + + const Extensions& extensions() const { return m_extensions; } + + private: + std::optional m_certificate; + std::shared_ptr m_raw_public_key; + Extensions m_extensions; }; public: @@ -561,10 +576,14 @@ class BOTAN_UNSTABLE_API Certificate_13 final : public Handshake_Message { std::vector cert_chain() const; + bool has_certificate_chain() const; + bool is_raw_public_key() const; + size_t count() const { return m_entries.size(); } bool empty() const { return m_entries.empty(); } + std::shared_ptr public_key() const; const X509_Certificate& leaf() const; const std::vector& request_context() const { return m_request_context; } @@ -576,7 +595,8 @@ class BOTAN_UNSTABLE_API Certificate_13 final : public Handshake_Message { Certificate_13(const Certificate_Request_13& cert_request, std::string_view hostname, Credentials_Manager& credentials_manager, - Callbacks& callbacks); + Callbacks& callbacks, + Certificate_Type cert_type); /** * Create a Server Certificate message @@ -585,15 +605,20 @@ class BOTAN_UNSTABLE_API Certificate_13 final : public Handshake_Message { */ Certificate_13(const Client_Hello_13& client_hello, Credentials_Manager& credentials_manager, - Callbacks& callbacks); + Callbacks& callbacks, + Certificate_Type cert_type); /** * Deserialize a Certificate message * @param buf the serialized message * @param policy the TLS policy * @param side is this a Connection_Side::Server or Connection_Side::Client certificate message + * @param cert_type is the certificate type that was negotiated during the handshake */ - Certificate_13(const std::vector& buf, const Policy& policy, Connection_Side side); + Certificate_13(const std::vector& buf, + const Policy& policy, + Connection_Side side, + Certificate_Type cert_type); /** * Validate a Certificate message regarding what extensions are expected based on @@ -621,6 +646,14 @@ class BOTAN_UNSTABLE_API Certificate_13 final : public Handshake_Message { void setup_entries(std::vector cert_chain, const Certificate_Status_Request* csr, Callbacks& callbacks); + void setup_entry(std::shared_ptr raw_public_key, Callbacks& callbacks); + + void verify_certificate_chain(Callbacks& callbacks, + const Policy& policy, + Credentials_Manager& creds, + std::string_view hostname, + bool use_ocsp, + Usage_Type usage_type) const; private: std::vector m_request_context; @@ -783,7 +816,7 @@ class BOTAN_UNSTABLE_API Certificate_Verify_13 final : public Certificate_Verify Callbacks& callbacks, RandomNumberGenerator& rng); - bool verify(const X509_Certificate& cert, Callbacks& callbacks, const Transcript_Hash& transcript_hash) const; + bool verify(const Public_Key& public_key, Callbacks& callbacks, const Transcript_Hash& transcript_hash) const; private: Connection_Side m_side; From 76f925ac85b1a18a9d4041d0836c95881a10e85b Mon Sep 17 00:00:00 2001 From: Rene Meusel Date: Wed, 1 Nov 2023 13:47:32 +0100 Subject: [PATCH 4/8] Handshake_Layer can parse Certificate_13 w/ raw pukey Co-Authored-By: Fabian Albert --- src/lib/tls/tls13/tls_channel_impl_13.cpp | 4 +++ src/lib/tls/tls13/tls_channel_impl_13.h | 7 +++++ src/lib/tls/tls13/tls_client_impl_13.cpp | 26 +++++++++++++++-- src/lib/tls/tls13/tls_handshake_layer_13.cpp | 9 +++--- src/lib/tls/tls13/tls_handshake_layer_13.h | 30 +++++++++++++++++++- 5 files changed, 68 insertions(+), 8 deletions(-) diff --git a/src/lib/tls/tls13/tls_channel_impl_13.cpp b/src/lib/tls/tls13/tls_channel_impl_13.cpp index aa49fe81718..c0bb8dd788c 100644 --- a/src/lib/tls/tls13/tls_channel_impl_13.cpp +++ b/src/lib/tls/tls13/tls_channel_impl_13.cpp @@ -424,4 +424,8 @@ void Channel_Impl_13::set_record_size_limits(const uint16_t outgoing_limit, cons m_record_layer.set_record_size_limits(outgoing_limit, incoming_limit); } +void Channel_Impl_13::set_selected_certificate_type(const Certificate_Type cert_type) { + m_handshake_layer.set_selected_certificate_type(cert_type); +} + } // namespace Botan::TLS diff --git a/src/lib/tls/tls13/tls_channel_impl_13.h b/src/lib/tls/tls13/tls_channel_impl_13.h index b3f0e35c1ba..2577b277596 100644 --- a/src/lib/tls/tls13/tls_channel_impl_13.h +++ b/src/lib/tls/tls13/tls_channel_impl_13.h @@ -284,6 +284,13 @@ class Channel_Impl_13 : public Channel_Impl { */ void set_record_size_limits(uint16_t outgoing_limit, uint16_t incoming_limit); + /** + * Set the expected certificate type needed to parse Certificate + * messages in the handshake layer. See RFC 7250 and 8446 4.4.2 for + * further details. + */ + void set_selected_certificate_type(Certificate_Type cert_type); + private: /* callbacks */ std::shared_ptr m_callbacks; diff --git a/src/lib/tls/tls13/tls_client_impl_13.cpp b/src/lib/tls/tls13/tls_client_impl_13.cpp index b3ed5dfa6ed..a3f6de4268f 100644 --- a/src/lib/tls/tls13/tls_client_impl_13.cpp +++ b/src/lib/tls/tls13/tls_client_impl_13.cpp @@ -478,14 +478,34 @@ void Client_Impl_13::send_client_authentication(Channel_Impl_13::AggregatedHands BOTAN_ASSERT_NOMSG(m_handshake_state.has_certificate_request()); const auto& cert_request = m_handshake_state.certificate_request(); - // RFC 4.4.2 + const auto cert_type = [&] { + const auto& exts = m_handshake_state.encrypted_extensions().extensions(); + if(auto client_cert_type = exts.get()) { + // RFC 7250 4.2 + // This client_certificate_type extension in the server hello then + // indicates the type of certificates the client is requested to + // provide in a subsequent certificate payload. + // + // Note: TLS 1.3 carries this extension in the Encrypted Extensions + // message instead of the Server Hello. + return client_cert_type->selected_certificate_type(); + } else { + // RFC 8446 4.4.2 + // If the corresponding certificate type extension [...] was not + // negotiated in EncryptedExtensions, [...] then each + // CertificateEntry contains a DER-encoded X.509 certificate. + return Certificate_Type::X509; + } + }(); + + // RFC 8446 4.4.2 // certificate_request_context: If this message is in response to a // CertificateRequest, the value of certificate_request_context in // that message. flight.add(m_handshake_state.sending( - Certificate_13(cert_request, m_info.hostname(), credentials_manager(), callbacks(), Certificate_Type::X509))); + Certificate_13(cert_request, m_info.hostname(), credentials_manager(), callbacks(), cert_type))); - // RFC 4.4.2 + // RFC 8446 4.4.2 // If the server requests client authentication but no suitable certificate // is available, the client MUST send a Certificate message containing no // certificates. diff --git a/src/lib/tls/tls13/tls_handshake_layer_13.cpp b/src/lib/tls/tls13/tls_handshake_layer_13.cpp index f389b47330b..b17a15ffb18 100644 --- a/src/lib/tls/tls13/tls_handshake_layer_13.cpp +++ b/src/lib/tls/tls13/tls_handshake_layer_13.cpp @@ -57,7 +57,8 @@ Handshake_Type handshake_type_from_byte(uint8_t byte_value) { template std::optional parse_message(TLS::TLS_Data_Reader& reader, const Policy& policy, - const Connection_Side peer_side) { + const Connection_Side peer_side, + const Certificate_Type cert_type) { // read the message header if(reader.remaining_bytes() < HEADER_LENGTH) { return std::nullopt; @@ -89,7 +90,7 @@ std::optional parse_message(TLS::TLS_Data_Reader& reader, case Handshake_Type::EncryptedExtensions: return Encrypted_Extensions(msg); case Handshake_Type::Certificate: - return Certificate_13(msg, policy, peer_side, Certificate_Type::X509); + return Certificate_13(msg, policy, peer_side, cert_type); case Handshake_Type::CertificateRequest: return Certificate_Request_13(msg, peer_side); case Handshake_Type::CertificateVerify: @@ -119,7 +120,7 @@ std::optional Handshake_Layer::next_message(const Policy& Transcript_Hash_State& transcript_hash) { TLS::TLS_Data_Reader reader("handshake message", m_read_buffer); - auto msg = parse_message(reader, policy, m_peer); + auto msg = parse_message(reader, policy, m_peer, m_certificate_type); if(msg.has_value()) { BOTAN_ASSERT_NOMSG(m_read_buffer.size() >= reader.read_so_far()); transcript_hash.update(std::span{m_read_buffer.data(), reader.read_so_far()}); @@ -132,7 +133,7 @@ std::optional Handshake_Layer::next_message(const Policy& std::optional Handshake_Layer::next_post_handshake_message(const Policy& policy) { TLS::TLS_Data_Reader reader("post handshake message", m_read_buffer); - auto msg = parse_message(reader, policy, m_peer); + auto msg = parse_message(reader, policy, m_peer, m_certificate_type); if(msg.has_value()) { m_read_buffer.erase(m_read_buffer.cbegin(), m_read_buffer.cbegin() + reader.read_so_far()); } diff --git a/src/lib/tls/tls13/tls_handshake_layer_13.h b/src/lib/tls/tls13/tls_handshake_layer_13.h index ed793ba18a9..14ddd85ed9d 100644 --- a/src/lib/tls/tls13/tls_handshake_layer_13.h +++ b/src/lib/tls/tls13/tls_handshake_layer_13.h @@ -28,7 +28,17 @@ class Transcript_Hash_State; class BOTAN_TEST_API Handshake_Layer { public: Handshake_Layer(Connection_Side whoami) : - m_peer(whoami == Connection_Side::Server ? Connection_Side::Client : Connection_Side::Server) {} + m_peer(whoami == Connection_Side::Server ? Connection_Side::Client : Connection_Side::Server) + // RFC 8446 4.4.2 + // If the corresponding certificate type extension + // ("server_certificate_type" or "client_certificate_type") was not + // negotiated in EncryptedExtensions, or the X.509 certificate type + // was negotiated, then each CertificateEntry contains a DER-encoded + // X.509 certificate. + // + // We need the certificate_type info to parse Certificate messages. + , + m_certificate_type(Certificate_Type::X509) {} /** * Reads data that was received in handshake records and stores it internally for further @@ -84,9 +94,27 @@ class BOTAN_TEST_API Handshake_Layer { */ bool has_pending_data() const { return !m_read_buffer.empty(); } + /** + * Set the certificate_type used for parsing Certificate messages. This + * is determined via (client/server)_certificate_type extensions during + * the handshake. + * + * RFC 7250 4.3 and 4.4 + * When the TLS server has specified RawPublicKey as the + * [client_certificate_type/server_certificate_type], authentication + * of the TLS [client/server] to the TLS [server/client] is supported + * only through authentication of the received client + * SubjectPublicKeyInfo via an out-of-band method. + * + * If the peer sends a Certificate message containing an incompatible + * means of authentication, a 'decode_error' will be generated. + */ + void set_selected_certificate_type(Certificate_Type cert_type) { m_certificate_type = cert_type; } + private: std::vector m_read_buffer; Connection_Side m_peer; + Certificate_Type m_certificate_type; }; } // namespace Botan::TLS From 6b062374df7b173ae83f62c98f63e8299c52807d Mon Sep 17 00:00:00 2001 From: Rene Meusel Date: Wed, 1 Nov 2023 13:50:13 +0100 Subject: [PATCH 5/8] Session(_Summary) can handle raw public keys --- src/lib/tls/tls13/tls_client_impl_13.cpp | 2 ++ src/lib/tls/tls13/tls_server_impl_13.cpp | 2 ++ src/lib/tls/tls_session.cpp | 25 ++++++++++++++++--- src/lib/tls/tls_session.h | 11 ++++++-- src/tests/data/tls_13_rfc8448/transcripts.vec | 4 +-- src/tests/test_tls_session_manager.cpp | 2 ++ 6 files changed, 38 insertions(+), 8 deletions(-) diff --git a/src/lib/tls/tls13/tls_client_impl_13.cpp b/src/lib/tls/tls13/tls_client_impl_13.cpp index a3f6de4268f..0c0320663c7 100644 --- a/src/lib/tls/tls13/tls_client_impl_13.cpp +++ b/src/lib/tls/tls13/tls_client_impl_13.cpp @@ -538,6 +538,7 @@ void Client_Impl_13::handle(const Finished_13& finished_msg) { callbacks().tls_session_established(Session_Summary(m_handshake_state.server_hello(), Connection_Side::Server, peer_cert_chain(), + peer_raw_public_key(), external_psk_identity(), m_resumed_session.has_value(), m_info, @@ -586,6 +587,7 @@ void TLS::Client_Impl_13::handle(const New_Session_Ticket_13& new_session_ticket m_handshake_state.server_hello().ciphersuite(), Connection_Side::Client, peer_cert_chain(), + peer_raw_public_key(), m_info, callbacks().tls_current_timestamp()); diff --git a/src/lib/tls/tls13/tls_server_impl_13.cpp b/src/lib/tls/tls13/tls_server_impl_13.cpp index 8a5fd49d1d6..0a2dd73143d 100644 --- a/src/lib/tls/tls13/tls_server_impl_13.cpp +++ b/src/lib/tls/tls13/tls_server_impl_13.cpp @@ -93,6 +93,7 @@ size_t Server_Impl_13::send_new_session_tickets(const size_t tickets) { std::nullopt, // early data not yet implemented policy().session_ticket_lifetime(), peer_cert_chain(), + peer_raw_public_key(), m_handshake_state.client_hello(), m_handshake_state.server_hello(), callbacks(), @@ -536,6 +537,7 @@ void Server_Impl_13::handle(const Finished_13& finished_msg) { Session_Summary(m_handshake_state.server_hello(), Connection_Side::Server, peer_cert_chain(), + peer_raw_public_key(), m_psk_identity, m_resumed_session.has_value(), Server_Information(m_handshake_state.client_hello().sni_hostname()), diff --git a/src/lib/tls/tls_session.cpp b/src/lib/tls/tls_session.cpp index b69833e9cc3..5de8bcdb91a 100644 --- a/src/lib/tls/tls_session.cpp +++ b/src/lib/tls/tls_session.cpp @@ -14,12 +14,11 @@ #include #include #include -#include -#include - #include #include - +#include +#include +#include #include #include @@ -107,6 +106,7 @@ Session_Summary::Session_Summary(const Session_Base& base, Session_Summary::Session_Summary(const Server_Hello_13& server_hello, Connection_Side side, std::vector peer_certs, + std::shared_ptr peer_raw_public_key, std::optional psk_identity, bool session_was_resumed, Server_Information server_info, @@ -129,6 +129,7 @@ Session_Summary::Session_Summary(const Server_Hello_13& server_hello, // TLS 1.3 uses AEADs, so technically encrypt-then-MAC is not applicable. false, std::move(peer_certs), + std::move(peer_raw_public_key), std::move(server_info)), m_external_psk_identity(std::move(psk_identity)), m_was_resumption(session_was_resumed) { @@ -193,6 +194,7 @@ Session::Session(const secure_vector& master_secret, extended_master_secret, encrypt_then_mac, certs, + nullptr, // RFC 7250 (raw public keys) is NYI for TLS 1.2 server_info), m_master_secret(master_secret), m_early_data_allowed(false), @@ -212,6 +214,7 @@ Session::Session(const secure_vector& session_psk, uint16_t ciphersuite, Connection_Side side, const std::vector& peer_certs, + std::shared_ptr peer_raw_public_key, const Server_Information& server_info, std::chrono::system_clock::time_point current_timestamp) : Session_Base(current_timestamp, @@ -232,6 +235,7 @@ Session::Session(const secure_vector& session_psk, // TLS 1.3 uses AEADs, so technically encrypt-then-MAC is not applicable. false, peer_certs, + std::move(peer_raw_public_key), server_info), m_master_secret(session_psk), m_early_data_allowed(max_early_data_bytes.has_value()), @@ -245,6 +249,7 @@ Session::Session(secure_vector&& session_psk, const std::optional& max_early_data_bytes, std::chrono::seconds lifetime_hint, const std::vector& peer_certs, + std::shared_ptr peer_raw_public_key, const Client_Hello_13& client_hello, const Server_Hello_13& server_hello, Callbacks& callbacks, @@ -257,6 +262,7 @@ Session::Session(secure_vector&& session_psk, true, false, // see constructor above for rationales peer_certs, + std::move(peer_raw_public_key), Server_Information(client_hello.sni_hostname())), m_master_secret(std::move(session_psk)), m_early_data_allowed(max_early_data_bytes.has_value()), @@ -274,6 +280,8 @@ Session::Session(std::string_view pem) : Session(PEM_Code::decode_check_label(pe Session::Session(std::span ber_data) { uint8_t side_code = 0; + std::vector raw_pubkey_or_empty; + ASN1_String server_hostname; ASN1_String server_service; size_t server_port; @@ -298,6 +306,7 @@ Session::Session(std::span ber_data) { .decode(m_encrypt_then_mac) .decode(m_master_secret, ASN1_Type::OctetString) .decode_list(m_peer_certs) + .decode(raw_pubkey_or_empty, ASN1_Type::OctetString) .decode(server_hostname) .decode(server_service) .decode(server_port) @@ -325,10 +334,17 @@ Session::Session(std::span ber_data) { m_server_info = Server_Information(server_hostname.value(), server_service.value(), static_cast(server_port)); + if(!raw_pubkey_or_empty.empty()) { + m_peer_raw_public_key = X509::load_key(raw_pubkey_or_empty); + } + m_lifetime_hint = std::chrono::seconds(lifetime_hint); } secure_vector Session::DER_encode() const { + const auto raw_pubkey_or_empty = + m_peer_raw_public_key ? m_peer_raw_public_key->subject_public_key() : std::vector{}; + return DER_Encoder() .start_sequence() .encode(static_cast(TLS_SESSION_PARAM_STRUCT_VERSION)) @@ -343,6 +359,7 @@ secure_vector Session::DER_encode() const { .start_sequence() .encode_list(m_peer_certs) .end_cons() + .encode(raw_pubkey_or_empty, ASN1_Type::OctetString) .encode(ASN1_String(m_server_info.hostname(), ASN1_Type::Utf8String)) .encode(ASN1_String(m_server_info.service(), ASN1_Type::Utf8String)) .encode(static_cast(m_server_info.port())) diff --git a/src/lib/tls/tls_session.h b/src/lib/tls/tls_session.h index 6a03a5c1d4c..cf4f6220b39 100644 --- a/src/lib/tls/tls_session.h +++ b/src/lib/tls/tls_session.h @@ -144,6 +144,7 @@ class BOTAN_PUBLIC_API(3, 0) Session_Base { bool extended_master_secret, bool encrypt_then_mac, std::vector peer_certs, + std::shared_ptr peer_raw_public_key, Server_Information server_info) : m_start_time(start_time), m_version(version), @@ -153,6 +154,7 @@ class BOTAN_PUBLIC_API(3, 0) Session_Base { m_extended_master_secret(extended_master_secret), m_encrypt_then_mac(encrypt_then_mac), m_peer_certs(std::move(peer_certs)), + m_peer_raw_public_key(std::move(peer_raw_public_key)), m_server_info(std::move(server_info)) {} protected: @@ -209,7 +211,7 @@ class BOTAN_PUBLIC_API(3, 0) Session_Base { /** * Return the raw public key of the peer (possibly empty) */ - std::shared_ptr peer_raw_public_key() const { return nullptr; } + std::shared_ptr peer_raw_public_key() const { return m_peer_raw_public_key; } /** * Get information about the TLS server @@ -228,6 +230,7 @@ class BOTAN_PUBLIC_API(3, 0) Session_Base { bool m_encrypt_then_mac; std::vector m_peer_certs; + std::shared_ptr m_peer_raw_public_key; Server_Information m_server_info; }; @@ -298,6 +301,7 @@ class BOTAN_PUBLIC_API(3, 0) Session_Summary : public Session_Base { Session_Summary(const Server_Hello_13& server_hello, Connection_Side side, std::vector peer_certs, + std::shared_ptr peer_raw_public_key, std::optional psk_identity, bool session_was_resumed, Server_Information server_info, @@ -351,6 +355,7 @@ class BOTAN_PUBLIC_API(3, 0) Session final : public Session_Base { uint16_t ciphersuite, Connection_Side side, const std::vector& peer_certs, + std::shared_ptr peer_raw_public_key, const Server_Information& server_info, std::chrono::system_clock::time_point current_timestamp); @@ -362,6 +367,7 @@ class BOTAN_PUBLIC_API(3, 0) Session final : public Session_Base { const std::optional& max_early_data_bytes, std::chrono::seconds lifetime_hint, const std::vector& peer_certs, + std::shared_ptr peer_raw_public_key, const Client_Hello_13& client_hello, const Server_Hello_13& server_hello, Callbacks& callbacks, @@ -464,7 +470,8 @@ class BOTAN_PUBLIC_API(3, 0) Session final : public Session_Base { // - compression method (always 0) // - fragment size (always 0) // - SRP identifier (always "") - enum { TLS_SESSION_PARAM_STRUCT_VERSION = 20230222 }; + // 20231031 - Allow storage of peer's raw public key + enum { TLS_SESSION_PARAM_STRUCT_VERSION = 20231031 }; secure_vector m_master_secret; diff --git a/src/tests/data/tls_13_rfc8448/transcripts.vec b/src/tests/data/tls_13_rfc8448/transcripts.vec index 7517472ce3a..3330a9ed144 100644 --- a/src/tests/data/tls_13_rfc8448/transcripts.vec +++ b/src/tests/data/tls_13_rfc8448/transcripts.vec @@ -15,7 +15,7 @@ Message_Server_Finished = 140000209b9b141d906337fbd2cbdce71df4deda4ab42c309572cb Record_ClientFinished = 170303003575ec4dc238cce60b298044a71e219c56cc77b0517fe9b93c7a4bfc44d87f38f80338ac98fc46deb384bd1caeacab6867d726c40546 Record_NewSessionTicket = 17030300de3a6b8f90414a97d6959c3487680de5134a2b240e6cffac116e95d41d6af8f6b580dcf3d11d63c758db289a015940252f55713e061dc13e078891a38efbcf5753ad8ef170ad3c7353d16d9da773b9ca7f2b9fa1b6c0d4a3d03f75e09c30ba1e62972ac46f75f7b981be63439b2999ce13064615139891d5e4c5b406f16e3fc181a77ca475840025db2f0a77f81b5ab05b94c01346755f69232c86519d86cbeeac87aac347d143f9605d64f650db4d023e70e952ca49fe5137121c74bc2697687e248746d6df353005f3bce18696129c8153556b3b6c6779b37bf15985684f SessionTicket = 2c035d829359ee5ff7af4ec900000000262a6494dc486d2c8a34cb33fa90bf1b0070ad3c498883c9367c09a2be785abc55cd226097a3a982117283f82a03a143efd3ff5dd36d64e861be7fd61d2827db279cce145077d454a3664d4e6da4d29ee03725a6a4dafcd0fc67d2aea70529513e3da2677fa5906c5b3f7d8f92f228bda40dda721470f9fbf297b5aea617646fac5c03272e970727c621a79141ef5f7de6505e5bfbc388e93343694093934ae4d357 -Client_SessionData = 3082021602040134B04E02046274DF72020103020104020213010201010101FF01010004204ECD0EB6EC3B4D87F5D6028F922CA4C5851A277FD41311C9E62D2C9492E1C4F3308201B0308201AC30820115A003020102020102300D06092A864886F70D01010B0500300E310C300A06035504031303727361301E170D3136303733303031323335395A170D3236303733303031323335395A300E310C300A0603550403130372736130819F300D06092A864886F70D010101050003818D0030818902818100B4BB498F8279303D980836399B36C6988C0C68DE55E1BDB826D3901A2461EAFD2DE49A91D015ABBC9A95137ACE6C1AF19EAA6AF98C7CED43120998E187A80EE0CCB0524B1B018C3E0B63264D449A6D38E22A5FDA430846748030530EF0461C8CA9D9EFBFAE8EA6D1D03E2BD193EFF0AB9A8002C47428A6D35A8D88D79F7F1E3F0203010001A31A301830090603551D1304023000300B0603551D0F0404030205A0300D06092A864886F70D01010B05000381810085AAD2A0E5B9276B908C65F73A7267170618A54C5F8A7B337D2DF7A594365417F2EAE8F8A58C8F8172F9319CF36B7FD6C55B80F21A03015156726096FD335E5E67F2DBF102702E608CCAE6BEC1FC63A42A99BE5C3EB7107C3C54E9B9EB2BD5203B1C3B84E0A8B2F759409BA3EAC9D91D402DCC0CC8F8961229AC9187B42B4DE10C067365727665720C000201000201000101FF02020400020500FAD6AAC502011E +Client_SessionData = 3082021802040134B37702046274DF72020103020104020213010201010101FF01010004204ECD0EB6EC3B4D87F5D6028F922CA4C5851A277FD41311C9E62D2C9492E1C4F3308201B0308201AC30820115A003020102020102300D06092A864886F70D01010B0500300E310C300A06035504031303727361301E170D3136303733303031323335395A170D3236303733303031323335395A300E310C300A0603550403130372736130819F300D06092A864886F70D010101050003818D0030818902818100B4BB498F8279303D980836399B36C6988C0C68DE55E1BDB826D3901A2461EAFD2DE49A91D015ABBC9A95137ACE6C1AF19EAA6AF98C7CED43120998E187A80EE0CCB0524B1B018C3E0B63264D449A6D38E22A5FDA430846748030530EF0461C8CA9D9EFBFAE8EA6D1D03E2BD193EFF0AB9A8002C47428A6D35A8D88D79F7F1E3F0203010001A31A301830090603551D1304023000300B0603551D0F0404030205A0300D06092A864886F70D01010B05000381810085AAD2A0E5B9276B908C65F73A7267170618A54C5F8A7B337D2DF7A594365417F2EAE8F8A58C8F8172F9319CF36B7FD6C55B80F21A03015156726096FD335E5E67F2DBF102702E608CCAE6BEC1FC63A42A99BE5C3EB7107C3C54E9B9EB2BD5203B1C3B84E0A8B2F759409BA3EAC9D91D402DCC0CC8F8961229AC9187B42B4DE104000C067365727665720C000201000201000101FF02020400020500FAD6AAC502011E Client_AppData = 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031 Record_Client_AppData = 1703030043a23f7054b62c94d0affafe8228ba55cbefacea42f914aa66bcab3f2b9819a8a5b46b395bd54a9a20441e2b62974e1f5a6292a2977014bd1e3deae63aeebb21694915e4 Server_AppData = 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031 @@ -28,7 +28,7 @@ Client_RNG_Pool = 1bc3ceb6bbe39cff938355b5a50adb6db21b7a6af649d7b4bc419d7876487d Server_RNG_Pool = 3ccfd2dec890222763472ae8136777c9d7358777bb66e91ea5122495f559ea2dde5b4476e7b490b2652d338acbf2948066f255f9440e23b98fc69835298dc107 CurrentTimestamp = 1651826546006 SessionTicket = 2c035d829359ee5ff7af4ec900000000262a6494dc486d2c8a34cb33fa90bf1b0070ad3c498883c9367c09a2be785abc55cd226097a3a982117283f82a03a143efd3ff5dd36d64e861be7fd61d2827db279cce145077d454a3664d4e6da4d29ee03725a6a4dafcd0fc67d2aea70529513e3da2677fa5906c5b3f7d8f92f228bda40dda721470f9fbf297b5aea617646fac5c03272e970727c621a79141ef5f7de6505e5bfbc388e93343694093934ae4d357 -Client_SessionData = 3082021602040134B04E02046274DF72020103020104020213010201010101FF01010004204ECD0EB6EC3B4D87F5D6028F922CA4C5851A277FD41311C9E62D2C9492E1C4F3308201B0308201AC30820115A003020102020102300D06092A864886F70D01010B0500300E310C300A06035504031303727361301E170D3136303733303031323335395A170D3236303733303031323335395A300E310C300A0603550403130372736130819F300D06092A864886F70D010101050003818D0030818902818100B4BB498F8279303D980836399B36C6988C0C68DE55E1BDB826D3901A2461EAFD2DE49A91D015ABBC9A95137ACE6C1AF19EAA6AF98C7CED43120998E187A80EE0CCB0524B1B018C3E0B63264D449A6D38E22A5FDA430846748030530EF0461C8CA9D9EFBFAE8EA6D1D03E2BD193EFF0AB9A8002C47428A6D35A8D88D79F7F1E3F0203010001A31A301830090603551D1304023000300B0603551D0F0404030205A0300D06092A864886F70D01010B05000381810085AAD2A0E5B9276B908C65F73A7267170618A54C5F8A7B337D2DF7A594365417F2EAE8F8A58C8F8172F9319CF36B7FD6C55B80F21A03015156726096FD335E5E67F2DBF102702E608CCAE6BEC1FC63A42A99BE5C3EB7107C3C54E9B9EB2BD5203B1C3B84E0A8B2F759409BA3EAC9D91D402DCC0CC8F8961229AC9187B42B4DE10C067365727665720C000201000201000101FF02020400020500FAD6AAC502011E +Client_SessionData = 3082021802040134B37702046274DF72020103020104020213010201010101FF01010004204ECD0EB6EC3B4D87F5D6028F922CA4C5851A277FD41311C9E62D2C9492E1C4F3308201B0308201AC30820115A003020102020102300D06092A864886F70D01010B0500300E310C300A06035504031303727361301E170D3136303733303031323335395A170D3236303733303031323335395A300E310C300A0603550403130372736130819F300D06092A864886F70D010101050003818D0030818902818100B4BB498F8279303D980836399B36C6988C0C68DE55E1BDB826D3901A2461EAFD2DE49A91D015ABBC9A95137ACE6C1AF19EAA6AF98C7CED43120998E187A80EE0CCB0524B1B018C3E0B63264D449A6D38E22A5FDA430846748030530EF0461C8CA9D9EFBFAE8EA6D1D03E2BD193EFF0AB9A8002C47428A6D35A8D88D79F7F1E3F0203010001A31A301830090603551D1304023000300B0603551D0F0404030205A0300D06092A864886F70D01010B05000381810085AAD2A0E5B9276B908C65F73A7267170618A54C5F8A7B337D2DF7A594365417F2EAE8F8A58C8F8172F9319CF36B7FD6C55B80F21A03015156726096FD335E5E67F2DBF102702E608CCAE6BEC1FC63A42A99BE5C3EB7107C3C54E9B9EB2BD5203B1C3B84E0A8B2F759409BA3EAC9D91D402DCC0CC8F8961229AC9187B42B4DE104000C067365727665720C000201000201000101FF02020400020500FAD6AAC502011E Record_ClientHello_1 = 1603010200010001fc03031bc3ceb6bbe39cff938355b5a50adb6db21b7a6af649d7b4bc419d7876487d95000006130113031302010001cd0000000b0009000006736572766572ff01000100000a00140012001d00170018001901000101010201030104003300260024001d0020e4ffb68ac05f8d96c99da26698346c6be16482badddafe051a66b4f18d668f0b002a0000002b0003020304000d0020001e040305030603020308040805080604010501060102010402050206020202002d00020101001c0002400100150057000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002900dd00b800b22c035d829359ee5ff7af4ec900000000262a6494dc486d2c8a34cb33fa90bf1b0070ad3c498883c9367c09a2be785abc55cd226097a3a982117283f82a03a143efd3ff5dd36d64e861be7fd61d2827db279cce145077d454a3664d4e6da4d29ee03725a6a4dafcd0fc67d2aea70529513e3da2677fa5906c5b3f7d8f92f228bda40dda721470f9fbf297b5aea617646fac5c03272e970727c621a79141ef5f7de6505e5bfbc388e93343694093934ae4d357fad6aacb0021203add4fb2d8fdf822a0ca3cf7678ef5e88dae990141c5924d57bb6fa31b9e5f9d Client_EarlyAppData = 414243444546 Record_Client_EarlyAppData = 1703030017ab1df420e75c457a7cc5d2844f76d5aee4b4edbf049be0 diff --git a/src/tests/test_tls_session_manager.cpp b/src/tests/test_tls_session_manager.cpp index 6c11adcaec8..d20875dbfa9 100644 --- a/src/tests/test_tls_session_manager.cpp +++ b/src/tests/test_tls_session_manager.cpp @@ -126,6 +126,7 @@ decltype(auto) default_session(Botan::TLS::Connection_Side side, Botan::TLS::Ciphersuite::from_name("AES_128_GCM_SHA256")->ciphersuite_code(), side, {}, + nullptr, server_info, cbs.tls_current_timestamp()); #else @@ -413,6 +414,7 @@ std::vector test_session_manager_choose_ticket() { Botan::TLS::Ciphersuite::from_name(suite)->ciphersuite_code(), Botan::TLS::Connection_Side::Server, {}, + nullptr, server_info, mycbs.tls_current_timestamp()); }; From e26babd0687ee06c4a8e12c116c21573392bbb38 Mon Sep 17 00:00:00 2001 From: Rene Meusel Date: Wed, 1 Nov 2023 13:54:24 +0100 Subject: [PATCH 6/8] TLS 1.3 can use raw public key for authentication This allows for both client and server authentication using raw public keys instead of X.509 certificates. Co-Authored-By: Fabian Albert --- src/lib/tls/msg_client_hello.cpp | 7 +++ .../tls/tls13/msg_encrypted_extensions.cpp | 28 ++++++++++- src/lib/tls/tls13/tls_client_impl_13.cpp | 30 +++++++++++- src/lib/tls/tls13/tls_server_impl_13.cpp | 48 ++++++++++++++++--- 4 files changed, 104 insertions(+), 9 deletions(-) diff --git a/src/lib/tls/msg_client_hello.cpp b/src/lib/tls/msg_client_hello.cpp index 12f1fbc6e36..7c4c548591f 100644 --- a/src/lib/tls/msg_client_hello.cpp +++ b/src/lib/tls/msg_client_hello.cpp @@ -796,6 +796,13 @@ Client_Hello_13::Client_Hello_13(const Policy& policy, m_data->extensions().add(new Application_Layer_Protocol_Notification(next_protocols)); } + // RFC 7250 4.1 + // In order to indicate the support of raw public keys, clients include + // the client_certificate_type and/or the server_certificate_type + // extensions in an extended client hello message. + m_data->extensions().add(new Client_Certificate_Type(policy.accepted_client_certificate_types())); + m_data->extensions().add(new Server_Certificate_Type(policy.accepted_server_certificate_types())); + if(policy.allow_tls12()) { m_data->extensions().add(new Renegotiation_Extension()); m_data->extensions().add(new Session_Ticket_Extension()); diff --git a/src/lib/tls/tls13/msg_encrypted_extensions.cpp b/src/lib/tls/tls13/msg_encrypted_extensions.cpp index 1275f1a382a..266d8f16153 100644 --- a/src/lib/tls/tls13/msg_encrypted_extensions.cpp +++ b/src/lib/tls/tls13/msg_encrypted_extensions.cpp @@ -44,6 +44,29 @@ Encrypted_Extensions::Encrypted_Extensions(const Client_Hello_13& client_hello, "Server cannot enforce record size limit without the client supporting it"); } + // RFC 7250 4.2 + // If the TLS server wants to request a certificate from the client + // (via the certificate_request message), it MUST include the + // client_certificate_type extension in the server hello. + // [...] + // If the server does not send a certificate_request payload [...], + // then the client_certificate_type payload in the server hello MUST be + // omitted. + if(auto ch_client_cert_types = exts.get(); + ch_client_cert_types && policy.request_client_certificate_authentication()) { + m_extensions.add(new Client_Certificate_Type(*ch_client_cert_types, policy)); + } + + // RFC 7250 4.2 + // The server_certificate_type extension in the client hello indicates the + // types of certificates the client is able to process when provided by + // the server in a subsequent certificate payload. [...] With the + // server_certificate_type extension in the server hello, the TLS server + // indicates the certificate type carried in the Certificate payload. + if(auto ch_server_cert_types = exts.get()) { + m_extensions.add(new Server_Certificate_Type(*ch_server_cert_types, policy)); + } + // RFC 6066 3 // A server that receives a client hello containing the "server_name" // extension [...] SHALL include an extension of type "server_name" in the @@ -93,8 +116,9 @@ Encrypted_Extensions::Encrypted_Extensions(const std::vector& buf) { Extension_Code::UseSrtp, // HEARTBEAT Extension_Code::ApplicationLayerProtocolNegotiation, - // CLIENT_CERTIFICATE_TYPE - // SERVER_CERTIFICATE_TYPE + // RFC 7250 + Extension_Code::ClientCertificateType, + Extension_Code::ServerCertificateType, // EARLY_DATA // Allowed extensions not listed in RFC 8446 but acceptable as Botan implements them diff --git a/src/lib/tls/tls13/tls_client_impl_13.cpp b/src/lib/tls/tls13/tls_client_impl_13.cpp index 0c0320663c7..112b78a2571 100644 --- a/src/lib/tls/tls13/tls_client_impl_13.cpp +++ b/src/lib/tls/tls13/tls_client_impl_13.cpp @@ -402,6 +402,22 @@ void Client_Impl_13::handle(const Encrypted_Extensions& encrypted_extensions_msg set_record_size_limits(outgoing_limit->limit(), incoming_limit->limit()); } + if(exts.has() && + m_handshake_state.client_hello().extensions().has()) { + const auto* server_cert_type = exts.get(); + const auto* our_server_cert_types = m_handshake_state.client_hello().extensions().get(); + our_server_cert_types->validate_selection(*server_cert_type); + + // RFC 7250 4.2 + // With the server_certificate_type extension in the server hello, the + // TLS server indicates the certificate type carried in the Certificate + // payload. + // + // Note: TLS 1.3 carries this extension in the Encrypted Extensions + // message instead of the Server Hello. + set_selected_certificate_type(server_cert_type->selected_certificate_type()); + } + callbacks().tls_examine_extensions(exts, Connection_Side::Server, Handshake_Type::EncryptedExtensions); if(m_handshake_state.server_hello().extensions().has()) { @@ -480,7 +496,11 @@ void Client_Impl_13::send_client_authentication(Channel_Impl_13::AggregatedHands const auto cert_type = [&] { const auto& exts = m_handshake_state.encrypted_extensions().extensions(); - if(auto client_cert_type = exts.get()) { + const auto& chexts = m_handshake_state.client_hello().extensions(); + if(exts.has() && chexts.has()) { + const auto* client_cert_type = exts.get(); + chexts.get()->validate_selection(*client_cert_type); + // RFC 7250 4.2 // This client_certificate_type extension in the server hello then // indicates the type of certificates the client is requested to @@ -610,6 +630,14 @@ std::vector Client_Impl_13::peer_cert_chain() const { } std::shared_ptr Client_Impl_13::peer_raw_public_key() const { + if(m_handshake_state.has_server_certificate_msg() && m_handshake_state.server_certificate().is_raw_public_key()) { + return m_handshake_state.server_certificate().public_key(); + } + + if(m_resumed_session.has_value()) { + return m_resumed_session->session.peer_raw_public_key(); + } + return nullptr; } diff --git a/src/lib/tls/tls13/tls_server_impl_13.cpp b/src/lib/tls/tls13/tls_server_impl_13.cpp index 0a2dd73143d..b6edc9daabd 100644 --- a/src/lib/tls/tls13/tls_server_impl_13.cpp +++ b/src/lib/tls/tls13/tls_server_impl_13.cpp @@ -43,17 +43,27 @@ std::string Server_Impl_13::application_protocol() const { } std::vector Server_Impl_13::peer_cert_chain() const { + if(m_handshake_state.has_client_certificate_msg() && + m_handshake_state.client_certificate().has_certificate_chain()) { + return m_handshake_state.client_certificate().cert_chain(); + } + if(m_resumed_session.has_value()) { return m_resumed_session->peer_certs(); - } else if(m_handshake_state.has_client_certificate_msg() && - m_handshake_state.client_certificate().has_certificate_chain()) { - return m_handshake_state.client_certificate().cert_chain(); - } else { - return {}; } + + return {}; } std::shared_ptr Server_Impl_13::peer_raw_public_key() const { + if(m_handshake_state.has_client_certificate_msg() && m_handshake_state.client_certificate().is_raw_public_key()) { + return m_handshake_state.client_certificate().public_key(); + } + + if(m_resumed_session.has_value()) { + return m_resumed_session->peer_raw_public_key(); + } + return nullptr; } @@ -301,7 +311,33 @@ void Server_Impl_13::handle_reply_to_client_hello(Server_Hello_13 server_hello) flight.add(m_handshake_state.sending(std::move(certificate_request.value()))); } - flight.add(m_handshake_state.sending(Certificate_13(client_hello, credentials_manager(), callbacks(), Certificate_Type::X509))) + const auto& enc_exts = m_handshake_state.encrypted_extensions().extensions(); + + // RFC 7250 4.2 + // This client_certificate_type extension in the server hello then + // indicates the type of certificates the client is requested to provide + // in a subsequent certificate payload. + // + // Note: TLS 1.3 carries this extension in the Encrypted Extensions + // message instead of the Server Hello. + if(auto client_cert_type = enc_exts.get()) { + set_selected_certificate_type(client_cert_type->selected_certificate_type()); + } + + // RFC 8446 4.4.2 + // If the corresponding certificate type extension [...] was not + // negotiated in EncryptedExtensions, or the X.509 certificate type + // was negotiated, then each CertificateEntry contains a DER-encoded + // X.509 certificate. + const auto cert_type = [&] { + if(auto server_cert_type = enc_exts.get()) { + return server_cert_type->selected_certificate_type(); + } else { + return Certificate_Type::X509; + } + }(); + + flight.add(m_handshake_state.sending(Certificate_13(client_hello, credentials_manager(), callbacks(), cert_type))) .add(m_handshake_state.sending(Certificate_Verify_13(m_handshake_state.server_certificate(), client_hello.signature_schemes(), client_hello.sni_hostname(), From 4a302fbda6c5b00fb4ef304d73430b50f16177fe Mon Sep 17 00:00:00 2001 From: Rene Meusel Date: Wed, 1 Nov 2023 14:17:53 +0100 Subject: [PATCH 7/8] Integration Test: RFC8448-style fixed transcript --- .../data/tls-policy/rfc8448_rawpubkey.txt | 30 ++ .../client_raw_public_keypair.pem | 5 + .../server_raw_public_keypair.pem | 5 + src/tests/data/tls_13_rfc8448/transcripts.vec | 21 ++ src/tests/test_tls_rfc8448.cpp | 344 ++++++++++++++++++ 5 files changed, 405 insertions(+) create mode 100644 src/tests/data/tls-policy/rfc8448_rawpubkey.txt create mode 100644 src/tests/data/tls_13_rfc8448/client_raw_public_keypair.pem create mode 100644 src/tests/data/tls_13_rfc8448/server_raw_public_keypair.pem diff --git a/src/tests/data/tls-policy/rfc8448_rawpubkey.txt b/src/tests/data/tls-policy/rfc8448_rawpubkey.txt new file mode 100644 index 00000000000..7fc47e8e2f4 --- /dev/null +++ b/src/tests/data/tls-policy/rfc8448_rawpubkey.txt @@ -0,0 +1,30 @@ +allow_tls10 = false +allow_tls11 = false +allow_tls12 = false +allow_tls13 = true +allow_dtls10 = false +allow_dtls12 = false +accepted_server_certificate_types=RawPublicKey +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 +key_exchange_methods = ECDH DH ECDHE_PSK +key_exchange_groups = x25519 secp256r1 secp384r1 +allow_insecure_renegotiation = false +include_time_in_hello_random = false +allow_server_initiated_renegotiation = false +server_uses_own_ciphersuite_preferences = true +negotiate_encrypt_then_mac = true +session_ticket_lifetime = 86400 +new_session_tickets_upon_handshake_success = 0 +minimum_dh_group_size = 2048 +minimum_ecdh_group_size = 255 +minimum_rsa_bits = 1024 +minimum_signature_strength = 110 +record_size_limit = 16385 +tls_13_middlebox_compatibility_mode = false +hash_hello_random = false +support_cert_status_message = false diff --git a/src/tests/data/tls_13_rfc8448/client_raw_public_keypair.pem b/src/tests/data/tls_13_rfc8448/client_raw_public_keypair.pem new file mode 100644 index 00000000000..8fef865107c --- /dev/null +++ b/src/tests/data/tls_13_rfc8448/client_raw_public_keypair.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQguNjWJ8nQIaR7vOuJ +uRnzCxntbKVdC8IXdJLnh8VUCQKhRANCAATmgDCn4Qs02Qa/o89nqtHMCFQBEoMA +8BGSkiiRFt15uhDAzOnA+9Y1xBkm6R/ma9hs36SNw4owrD6P+u5vhLsA +-----END PRIVATE KEY----- diff --git a/src/tests/data/tls_13_rfc8448/server_raw_public_keypair.pem b/src/tests/data/tls_13_rfc8448/server_raw_public_keypair.pem new file mode 100644 index 00000000000..8d77fd836a0 --- /dev/null +++ b/src/tests/data/tls_13_rfc8448/server_raw_public_keypair.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgyCublmLZybUdzA6t +qfHKzOr/9tB/atUDvchq+7pAmKWhRANCAAQhBvRQBYyrD50Lt3tcjU6j1tZ8y7Qz +znd3EA8Efg7WKzxQlK3hPhGLI7geFAAX46u4xsg9wqyvbWViVkubWL2l +-----END PRIVATE KEY----- diff --git a/src/tests/data/tls_13_rfc8448/transcripts.vec b/src/tests/data/tls_13_rfc8448/transcripts.vec index 3330a9ed144..341af7ee03d 100644 --- a/src/tests/data/tls_13_rfc8448/transcripts.vec +++ b/src/tests/data/tls_13_rfc8448/transcripts.vec @@ -128,3 +128,24 @@ Server_AppData = 436c69656e74206d65737361676520776974682065787472612073747566663 Record_Server_AppData = 17030300B367BD43E94D4A2A56951664980D63EB7F117344B58ED941183637AD5CB6F9C389279211F540D0021A8D6D1DB960B8E444A400EBF53F7F026D033CAA46809A4B3EB8DB9D00FCC5B3826DB796CBB435CE7EB945D32A446AD04780B2838ED91CC32473444F66441633083E42548959ECD1B856AECF7DEC5584606ABD3A1BE27249F375A16E136C8BA374F541B4386BBA15ACA8E2A4F4E668704A72DF74FBC5D3E66EB643B31E2ADF2098C58B7EA3B8616536B70152 Record_Client_CloseNotify = 17030300132EC77D580924DA2EBA4C8B15A8B2CFE19DEEA3 Record_Server_CloseNotify = 1703030013B859F887252724451ED052EFE6D4469618D539 + +[RawPublicKey_With_Client_Authentication] +Client_RNG_Pool = e9b55a837df0e9aabae5d4163ecebf3a7b77507611ff501735e00839f8de60e297e206dba04ff7a08b98c1b1cd7eb86bc82ec8b7eb875134e743b9a9c5b5af60 +Server_RNG_Pool = e54182275289a717e972296a3220f7b8c8ff94d95c387aec3095723ca1c848cf78e86fb7d821550a5afa0e3da2bf1c1ef942fc0670f5a5914e7450eb6ee5bf8c +CurrentTimestamp = 1698671494000 +Record_ClientHello_1 = 16030100BB010000B70303E9B55A837DF0E9AABAE5D4163ECEBF3A7B77507611FF501735E00839F8DE60E2000006130113031302010000880000000B0009000006736572766572000A00080006001D00170018003300260024001D00209FF859B45C433F8807D73DFD0A3D4461A9F79CBEA5DD5D284695290CBE787C3B002B0003020304000D0020001E040305030603020308040805080604010501060102010402050206020202002D00020101001C00024001001300020102001400020102 +Record_ServerHello = 160303005A020000560303E54182275289A717E972296A3220F7B8C8FF94D95C387AEC3095723CA1C848CF00130100002E00330024001D0020AD897D804242753185A13D194FD75BD62864675BB22C36D06C72BC1E434EF707002B00020304 +Server_MessageToSign = 20202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020544C5320312E332C2073657276657220436572746966696361746556657269667900AF2C74D0E48C92B412C32943E2E4172ACE923C6199DCE170A7960D68A883E655 +Server_MessageSignature = 3044022071A89720162DF558E9E6E0BC611A6417582C86358306E028F6AA5420E19DC57F02207D8A4DA7DB574A6362D596A218FE397A6529F38784EB018BF95E360F8345E49B +Message_ServerHello = 020000560303E54182275289A717E972296A3220F7B8C8FF94D95C387AEC3095723CA1C848CF00130100002E00330024001D0020AD897D804242753185A13D194FD75BD62864675BB22C36D06C72BC1E434EF707002B00020304 +Record_ServerHandshakeMessages = 170303013C60E22B03CD550D880D91066C8BD5103892E9DC28C14E1C37D26BC5AB374FAB147986FE11665C4928B788081BAE8FB55C9E74B35FCC7825665D08250E090AC9F5521DE0920616F8B9A21C48296D723E499E2E45C1623AB4AAD8063663A133E79EA253FF2D5C32073FDB80C88FE3A4D42561FF847F823978E9069811CC64ACF7D2D900FE070E9E896DDAC350E962069C1D0655337A5B6517E872A6307D101D18F717955C073848C4A940169BFEC05ABD0FC847B196E0FDB1C3E4D5D9F5CBC1CA8912C8778F8B35A4A6802F74F91A7969B28183857EB2C7E8A41F3BD0E59B5055D0625DEB7BE94451F1EC82FEDAD4D978833E8B452556BE8025F23006FC86259B1E53D97FD112A7ED341E14C74857D9ADEFCD2E9AEB33906CCD1DF495E7AE266E1D920F2770EF9918EE8C5605AC2111368208FE1E5EB5E29607BBE55D47 +Message_EncryptedExtensions = 08000022002000130001020014000102000A00080006001D00170018001C0002400100000000 +Message_CertificateRequest = 0d000027000024000d0020001e040305030603020308040805080604010501060102010402050206020202 +Message_Server_Certificate = 0B0000640000006000005B3059301306072A8648CE3D020106082A8648CE3D030107034200042106F450058CAB0F9D0BB77B5C8D4EA3D6D67CCBB433CE7777100F047E0ED62B3C5094ADE13E118B23B81E140017E3ABB8C6C83DC2ACAF6D6562564B9B58BDA50000 +Message_Server_CertificateVerify = 0f00004A040300463044022071A89720162DF558E9E6E0BC611A6417582C86358306E028F6AA5420E19DC57F02207D8A4DA7DB574A6362D596A218FE397A6529F38784EB018BF95E360F8345E49B +Message_Server_Finished = 140000207DB871E00F61A49501BDD34B98CB35FE44C1881FEA6569532F2E74F19B654F9E +Client_MessageToSign = 20202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020544C5320312E332C20636C69656E7420436572746966696361746556657269667900C7856DF78275E8A2171F000BFBBB693ECE0363CD80EF79EF3C75644CA5335D6B +Client_MessageSignature = 30440220610EDE60E913D168379893EADE362B1BAEF3DD95E9A9DB90E77215D4827BEEB3022015EF4B036648FABFFAB93675C2ED64AED3487BD19A690CAAA493ABC4CED91C73 +Record_ClientFinished = 17030300EB3E34589344B6C20CCBC18B2AB6B0CFD6C81E5CA17ECE06A24C74A7C285A9400AC902813B0EFEF4B5D5482859ED430A0CAF8A494ACA3E46DCB204C6FD34D9176D832CF27B910029210B8DF54F021039F0469C9EFB7296A4426727DD77BB97BDAB9E2478EA71991398681F6B0B5E5BBA5B9BEFEF2C82500F3510044743DEC7BE50849C85A99D61975861C05E91633C6D6678D50B9C7AED775900F20EFEED79C204FD78F6C669C11646AB43F248BA0A376D79BA0CC15F64DDCF0A0395A7821A2A2974F18D0EE142FF22B0D04685105EF04E4B9A398BF787AEA398316E96C41837F86425B4F890756CB6A82255 +Record_Client_CloseNotify = 17030300137788BDD18450B531A6F46B863B3F489C2928BD +Record_Server_CloseNotify = 17030300133FFABAF73F9CDF2ABC6AD74932B52B0BAE7987 diff --git a/src/tests/test_tls_rfc8448.cpp b/src/tests/test_tls_rfc8448.cpp index a3dff9f713f..cb5eb40afd5 100644 --- a/src/tests/test_tls_rfc8448.cpp +++ b/src/tests/test_tls_rfc8448.cpp @@ -33,6 +33,7 @@ #include #include #include + #include #include #include #endif @@ -71,6 +72,18 @@ Botan::X509_Certificate client_certificate() { return Botan::X509_Certificate(in); } +std::unique_ptr client_raw_public_key_pair() { + // P-256 private key (independently generated) + Botan::DataSource_Memory in(Test::read_data_file("tls_13_rfc8448/client_raw_public_keypair.pem")); + return Botan::PKCS8::load_key(in); +} + +std::unique_ptr server_raw_public_key_pair() { + // P-256 private key (independently generated) + Botan::DataSource_Memory in(Test::read_data_file("tls_13_rfc8448/server_raw_public_keypair.pem")); + return Botan::PKCS8::load_key(in); +} + /** * Simple version of the Padding extension (RFC 7685) to reproduce the * 2nd Client_Hello in RFC8448 Section 5 (HelloRetryRequest) @@ -185,6 +198,15 @@ class Test_TLS_13_Callbacks : public Botan::TLS::Callbacks { certificate_chain = cert_chain; } + void tls_verify_raw_public_key(const Public_Key& raw_pk, + Usage_Type, + std::string_view, + const TLS::Policy&) override { + count_callback_invocation("tls_verify_raw_public_key"); + // TODO: is there a better way to copy a generic public key? + raw_public_key = Botan::X509::load_key(raw_pk.subject_public_key()); + } + std::chrono::milliseconds tls_verify_cert_chain_ocsp_timeout() const override { count_callback_invocation("tls_verify_cert_chain"); return std::chrono::milliseconds(0); @@ -326,6 +348,7 @@ class Test_TLS_13_Callbacks : public Botan::TLS::Callbacks { public: bool session_activated_called; // NOLINT(*-non-private-member-variables-in-classes) std::vector certificate_chain; // NOLINT(*-non-private-member-variables-in-classes) + std::unique_ptr raw_public_key; // NOLINT(*-non-private-member-variables-in-classes) std::string negotiated_psk_identity; // NOLINT(*-non-private-member-variables-in-classes) std::map>> serialized_messages; // NOLINT(*-non-private-member-variables-in-classes) @@ -368,6 +391,14 @@ class Test_Credentials : public Botan::Credentials_Manager { : ((m_alternative_server_certificate) ? alternative_server_certificate() : server_certificate())}; } + std::shared_ptr find_raw_public_key(const std::vector& key_types, + const std::string& type, + const std::string& context) override { + BOTAN_UNUSED(key_types, type, context); + return (type == "tls-client") ? client_raw_public_key_pair()->public_key() + : server_raw_public_key_pair()->public_key(); + } + std::shared_ptr private_key_for(const Botan::X509_Certificate& cert, const std::string& type, const std::string& context) override { @@ -384,6 +415,21 @@ class Test_Credentials : public Botan::Credentials_Manager { return m_server_private_key; } + std::shared_ptr private_key_for(const Public_Key& raw_public_key, + const std::string& type, + const std::string& context) override { + BOTAN_UNUSED(type, context); + std::vector> keys; + keys.emplace_back(client_raw_public_key_pair()); + keys.emplace_back(server_raw_public_key_pair()); + for(auto& key : keys) { + if(key->fingerprint_public() == raw_public_key.fingerprint_public()) { + return std::move(key); + } + } + return nullptr; + } + std::vector find_preshared_keys(std::string_view /* host */, TLS::Connection_Side /* whoami */, const std::vector& identities, @@ -823,6 +869,11 @@ std::vector make_mock_signatures(const VarMap& vars) { * tls_13_rfc8448/server_certificate_client_auth.pem * The server certificate used in the Client Authentication test case. * + * tls_13_rfc8448/client_raw_public_keypair.pem + * tls_13_rfc8448/server_raw_public_keypair.pem + * The raw public key pairs for client and server authentication in the + * equally named test cases. + * * tls-policy/rfc8448_*.txt * Each RFC 8448 section test required a slightly adapted Botan TLS policy * to enable/disable certain features under test. @@ -843,6 +894,7 @@ class Test_TLS_RFC8448 : public Text_Based_Test { // Those tests provide the same information as RFC8448 test vectors but // were sourced otherwise. Typically by temporarily instrumenting our implementation. virtual std::vector externally_provided_psk_with_ephemeral_key(const VarMap& vars) = 0; + virtual std::vector raw_public_key_with_client_authentication(const VarMap& vars) = 0; virtual std::string side() const = 0; @@ -901,6 +953,9 @@ class Test_TLS_RFC8448 : public Text_Based_Test { } else if(header == "Externally_Provided_PSK_with_Ephemeral_Key") { return Test::Result("Externally Provided PSK with ephemeral key (" + side() + " side)", externally_provided_psk_with_ephemeral_key(vars)); + } else if(header == "RawPublicKey_With_Client_Authentication") { + return Test::Result("RawPublicKey with Client Authentication (" + side() + " side)", + raw_public_key_with_client_authentication(vars)); } else { return Test::Result::Failure("test dispatcher", "unknown sub-test: " + header); } @@ -1614,6 +1669,136 @@ class Test_TLS_RFC8448_Client : public Test_TLS_RFC8448 { }), }; } + + std::vector raw_public_key_with_client_authentication(const VarMap& vars) override { + auto rng = std::make_unique(""); + + // 32 - for client hello random + // 32 - for KeyShare (eph. x25519 key pair) + add_entropy(*rng, vars.get_req_bin("Client_RNG_Pool")); + + auto sort_our_extensions = [&](Botan::TLS::Extensions& exts, + Botan::TLS::Connection_Side /* side */, + Botan::TLS::Handshake_Type /* which_message */) { + // This is the order of extensions when we first introduced the raw + // public key authentication implementation and generated the transcript. + // To stay compatible with the now hard-coded transcript, we pin the + // extension order. + sort_extensions(exts, + { + Botan::TLS::Extension_Code::ServerNameIndication, + Botan::TLS::Extension_Code::SupportedGroups, + Botan::TLS::Extension_Code::KeyShare, + Botan::TLS::Extension_Code::SupportedVersions, + Botan::TLS::Extension_Code::SignatureAlgorithms, + Botan::TLS::Extension_Code::PskKeyExchangeModes, + Botan::TLS::Extension_Code::RecordSizeLimit, + Botan::TLS::Extension_Code::ClientCertificateType, + Botan::TLS::Extension_Code::ServerCertificateType, + }); + }; + + std::unique_ptr ctx; + + return { + Botan_Tests::CHECK( + "Client Hello", + [&](Test::Result& result) { + ctx = std::make_unique(std::move(rng), + std::make_shared("rfc8448_rawpubkey"), + vars.get_req_u64("CurrentTimestamp"), + sort_our_extensions, + std::nullopt, + std::nullopt, + make_mock_signatures(vars)); + + ctx->check_callback_invocations(result, + "initial callbacks", + { + "tls_emit_data", + "tls_inspect_handshake_msg_client_hello", + "tls_modify_extensions_client_hello", + "tls_generate_ephemeral_key", + "tls_current_timestamp", + }); + + result.test_eq("Client Hello", ctx->pull_send_buffer(), vars.get_req_bin("Record_ClientHello_1")); + }), + + Botan_Tests::CHECK("Server Hello", + [&](auto& result) { + result.require("ctx is available", ctx != nullptr); + ctx->client.received_data(vars.get_req_bin("Record_ServerHello")); + + ctx->check_callback_invocations(result, + "callbacks after server hello", + { + "tls_examine_extensions_server_hello", + "tls_inspect_handshake_msg_server_hello", + "tls_ephemeral_key_agreement", + }); + }), + + Botan_Tests::CHECK("other handshake messages and client auth", + [&](Test::Result& result) { + result.require("ctx is available", ctx != nullptr); + ctx->client.received_data(vars.get_req_bin("Record_ServerHandshakeMessages")); + + ctx->check_callback_invocations(result, + "signing callbacks invoked", + { + "tls_sign_message", + "tls_emit_data", + "tls_examine_extensions_encrypted_extensions", + "tls_examine_extensions_certificate", + "tls_examine_extensions_certificate_request", + "tls_modify_extensions_certificate", + "tls_inspect_handshake_msg_certificate", + "tls_inspect_handshake_msg_certificate_request", + "tls_inspect_handshake_msg_certificate_verify", + "tls_inspect_handshake_msg_encrypted_extensions", + "tls_inspect_handshake_msg_finished", + "tls_current_timestamp", + "tls_session_established", + "tls_session_activated", + "tls_verify_raw_public_key", + "tls_verify_message", + }); + + const auto raw_pk = ctx->client.peer_raw_public_key(); + result.confirm("Received server's raw public key", + raw_pk && raw_pk->fingerprint_public() == + server_raw_public_key_pair()->fingerprint_public()); + + // ClientFinished contains the entire coalesced client authentication flight + // Messages: Certificate, CertificateVerify, Finished + result.test_eq("Client Auth and Finished", + ctx->pull_send_buffer(), + vars.get_req_bin("Record_ClientFinished")); + }), + + Botan_Tests::CHECK( + "Close Connection", + [&](Test::Result& result) { + result.require("ctx is available", ctx != nullptr); + ctx->client.close(); + result.test_eq( + "Client close_notify", ctx->pull_send_buffer(), vars.get_req_bin("Record_Client_CloseNotify")); + + ctx->check_callback_invocations(result, + "after sending close notify", + { + "tls_emit_data", + }); + + ctx->client.received_data(vars.get_req_bin("Record_Server_CloseNotify")); + result.confirm("connection closed", ctx->client.is_closed()); + + ctx->check_callback_invocations( + result, "after receiving close notify", {"tls_alert", "tls_peer_closed_connection"}); + }), + }; + } }; class Test_TLS_RFC8448_Server : public Test_TLS_RFC8448 { @@ -2429,6 +2614,165 @@ class Test_TLS_RFC8448_Server : public Test_TLS_RFC8448 { }), }; } + + std::vector raw_public_key_with_client_authentication(const VarMap& vars) override { + auto rng = std::make_unique(""); + + // 32 - for server hello random + // 32 - for KeyShare (eph. x25519 key pair) + add_entropy(*rng, vars.get_req_bin("Server_RNG_Pool")); + + auto sort_our_extensions = + [&](Botan::TLS::Extensions& exts, Botan::TLS::Connection_Side /* side */, Botan::TLS::Handshake_Type type) { + // This is the order of extensions when we first introduced the raw + // public key authentication implementation and generated the transcript. + // To stay compatible with the now hard-coded transcript, we pin the + // extension order. + if(type == Botan::TLS::Handshake_Type::EncryptedExtensions) { + sort_extensions(exts, + { + Botan::TLS::Extension_Code::ClientCertificateType, + Botan::TLS::Extension_Code::ServerCertificateType, + Botan::TLS::Extension_Code::SupportedGroups, + Botan::TLS::Extension_Code::RecordSizeLimit, + Botan::TLS::Extension_Code::ServerNameIndication, + }); + } else if(type == Botan::TLS::Handshake_Type::ServerHello) { + sort_extensions(exts, + { + Botan::TLS::Extension_Code::KeyShare, + Botan::TLS::Extension_Code::SupportedVersions, + }); + } + }; + + std::unique_ptr ctx; + + return { + Botan_Tests::CHECK("Receive Client Hello", + [&](Test::Result& result) { + ctx = std::make_unique( + std::move(rng), + std::make_shared("rfc8448_rawpubkey"), + vars.get_req_u64("CurrentTimestamp"), + sort_our_extensions, + make_mock_signatures(vars)); + result.confirm("server not closed", !ctx->server.is_closed()); + + ctx->server.received_data(vars.get_req_bin("Record_ClientHello_1")); + + ctx->check_callback_invocations(result, + "client hello received", + {"tls_emit_data", + "tls_examine_extensions_client_hello", + "tls_modify_extensions_server_hello", + "tls_modify_extensions_encrypted_extensions", + "tls_modify_extensions_certificate", + "tls_sign_message", + "tls_generate_ephemeral_key", + "tls_ephemeral_key_agreement", + "tls_inspect_handshake_msg_client_hello", + "tls_inspect_handshake_msg_server_hello", + "tls_inspect_handshake_msg_encrypted_extensions", + "tls_inspect_handshake_msg_certificate_request", + "tls_inspect_handshake_msg_certificate", + "tls_inspect_handshake_msg_certificate_verify", + "tls_inspect_handshake_msg_finished"}); + }), + + Botan_Tests::CHECK( + "Verify server's generated handshake messages", + [&](Test::Result& result) { + result.require("ctx is available", ctx != nullptr); + const auto& msgs = ctx->observed_handshake_messages(); + + result.test_eq("Server Hello", + msgs.at("server_hello")[0], + strip_message_header(vars.get_opt_bin("Message_ServerHello"))); + result.test_eq("Encrypted Extensions", + msgs.at("encrypted_extensions")[0], + strip_message_header(vars.get_opt_bin("Message_EncryptedExtensions"))); + result.test_eq("Certificate Request", + msgs.at("certificate_request")[0], + strip_message_header(vars.get_opt_bin("Message_CertificateRequest"))); + result.test_eq("Certificate", + msgs.at("certificate")[0], + strip_message_header(vars.get_opt_bin("Message_Server_Certificate"))); + result.test_eq("CertificateVerify", + msgs.at("certificate_verify")[0], + strip_message_header(vars.get_opt_bin("Message_Server_CertificateVerify"))); + result.test_eq("Finished", + msgs.at("finished")[0], + strip_message_header(vars.get_opt_bin("Message_Server_Finished"))); + + result.test_eq("Server's entire first flight", + ctx->pull_send_buffer(), + concat(vars.get_req_bin("Record_ServerHello"), + vars.get_req_bin("Record_ServerHandshakeMessages"))); + + result.confirm("Not yet aware of client's cert chain", ctx->server.peer_cert_chain().empty()); + result.confirm("Server could now send application data", ctx->server.is_active()); + result.confirm("handshake is not yet complete", + !ctx->server.is_handshake_complete()); // See RFC 8446 4.4.4 + }), + + Botan_Tests::CHECK("Receive Client's second flight", + [&](Test::Result& result) { + result.require("ctx is available", ctx != nullptr); + // This encrypted message contains the following messages: + // * client's Certificate message + // * client's Certificate_Verify message + // * client's Finished message + ctx->server.received_data(vars.get_req_bin("Record_ClientFinished")); + + ctx->check_callback_invocations(result, + "client finished received", + {"tls_inspect_handshake_msg_certificate", + "tls_inspect_handshake_msg_certificate_verify", + "tls_inspect_handshake_msg_finished", + "tls_examine_extensions_certificate", + "tls_verify_raw_public_key", + "tls_verify_message", + "tls_current_timestamp", + "tls_session_established", + "tls_session_activated"}); + + const auto raw_pk = ctx->server.peer_raw_public_key(); + result.confirm("Received client's raw public key", + raw_pk && raw_pk->fingerprint_public() == + client_raw_public_key_pair()->fingerprint_public()); + + result.confirm("TLS handshake finished", ctx->server.is_active()); + result.confirm("handshake is complete", ctx->server.is_handshake_complete()); + }), + + Botan_Tests::CHECK("Receive Client close_notify", + [&](Test::Result& result) { + result.require("ctx is available", ctx != nullptr); + ctx->server.received_data(vars.get_req_bin("Record_Client_CloseNotify")); + + ctx->check_callback_invocations( + result, "client finished received", {"tls_alert", "tls_peer_closed_connection"}); + + result.confirm("connection is not yet closed", !ctx->server.is_closed()); + result.confirm("connection is still active", ctx->server.is_active()); + result.confirm("handshake is still complete", ctx->server.is_handshake_complete()); + }), + + Botan_Tests::CHECK("Expect Server close_notify", + [&](Test::Result& result) { + result.require("ctx is available", ctx != nullptr); + ctx->server.close(); + + result.confirm("connection is now inactive", !ctx->server.is_active()); + result.confirm("connection is now closed", ctx->server.is_closed()); + result.confirm("handshake is still complete", ctx->server.is_handshake_complete()); + result.test_eq("Server's close notify", + ctx->pull_send_buffer(), + vars.get_req_bin("Record_Server_CloseNotify")); + }), + }; + } }; BOTAN_REGISTER_TEST("tls", "tls_rfc8448_client", Test_TLS_RFC8448_Client); From ba117600b6d8dd3cb971e51f4652d5af23616b2f Mon Sep 17 00:00:00 2001 From: Rene Meusel Date: Wed, 1 Nov 2023 12:35:33 +0100 Subject: [PATCH 8/8] allow using raw public key in ./botan tls_client/tls_server --- src/cli/tls_client.cpp | 24 +++++++++++++-- src/cli/tls_helpers.h | 66 ++++++++++++++++++++++++++++++++---------- src/cli/tls_server.cpp | 16 +++++++--- 3 files changed, 83 insertions(+), 23 deletions(-) diff --git a/src/cli/tls_client.cpp b/src/cli/tls_client.cpp index 7b19abc4121..5d2a3301fc5 100644 --- a/src/cli/tls_client.cpp +++ b/src/cli/tls_client.cpp @@ -43,6 +43,7 @@ class Callbacks : public Botan::TLS::Callbacks { std::ostream& output(); bool flag_set(const std::string& flag_name) const; + std::string get_arg(const std::string& arg_name) const; void send(std::span buffer); int peer_closed() const { return m_peer_closed; } @@ -75,6 +76,16 @@ class Callbacks : public Botan::TLS::Callbacks { } } + void tls_verify_raw_public_key(const Botan::Public_Key& raw_public_key, + Botan::Usage_Type /* usage */, + std::string_view /* hostname */, + const Botan::TLS::Policy& /* policy */) override { + const auto fingerprint = raw_public_key.fingerprint_public("SHA-256"); + const auto trusted = (fingerprint == get_arg("trusted-pubkey-sha256")); + output() << "Raw Public Key (" << fingerprint + << ") validation status: " << (trusted ? "trusted" : "NOT trusted") << "\n"; + } + void tls_session_activated() override { output() << "Handshake complete\n"; } void tls_session_established(const Botan::TLS::Session_Summary& session) override { @@ -148,9 +159,10 @@ class TLS_Client final : public Command { TLS_Client() : Command( "tls_client host --port=443 --print-certs --policy=default " - "--skip-system-cert-store --trusted-cas= --tls-version=default " - "--session-db= --session-db-pass= --next-protocols= --type=tcp " - "--client-cert= --client-cert-key= --psk= --psk-identity= --psk-prf=SHA-256 --debug") { + "--skip-system-cert-store --trusted-cas= --trusted-pubkey-sha256= " + "--tls-version=default --session-db= --session-db-pass= " + "--next-protocols= --type=tcp --client-cert= --client-cert-key= " + "--psk= --psk-identity= --psk-prf=SHA-256 --debug") { init_sockets(); } @@ -177,6 +189,7 @@ class TLS_Client final : public Command { const std::string next_protos = get_arg("next-protocols"); const bool use_system_cert_store = flag_set("skip-system-cert-store") == false; const std::string trusted_CAs = get_arg("trusted-cas"); + const std::string trusted_pubkey_sha256 = get_arg("trusted-pubkey-sha256"); const auto tls_version = get_arg("tls-version"); if(!sessions_db.empty()) { @@ -343,6 +356,7 @@ class TLS_Client final : public Command { public: using Command::flag_set; + using Command::get_arg; using Command::output; void send(std::span buf) const { @@ -421,6 +435,10 @@ bool Callbacks::flag_set(const std::string& flag_name) const { return m_client_command.flag_set(flag_name); } +std::string Callbacks::get_arg(const std::string& arg_name) const { + return m_client_command.get_arg(arg_name); +} + void Callbacks::send(std::span buffer) { m_client_command.send(buffer); } diff --git a/src/cli/tls_helpers.h b/src/cli/tls_helpers.h index 4dc5fe63add..9726ebea5e4 100644 --- a/src/cli/tls_helpers.h +++ b/src/cli/tls_helpers.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -42,13 +43,23 @@ inline std::string maybe_hex_encode(std::string_view v) { class Basic_Credentials_Manager : public Botan::Credentials_Manager { protected: - void load_credentials(const std::string& crt, const std::string& key) { - Certificate_Info cert; - + void load_credentials(const std::string& cred, const std::string& key) { Botan::DataSource_Stream key_in(key); - cert.key = Botan::PKCS8::load_key(key_in); + auto privkey = Botan::PKCS8::load_key(key_in); - Botan::DataSource_Stream in(crt); + // first try to read @p cred as a public key + try { + auto pubkey = Botan::X509::load_key(cred); + m_raw_pubkey = {std::exchange(privkey, {}), std::move(pubkey)}; + return; + } catch(const Botan::Decoding_Error&) {} + + // ... then try again assuming that @p cred contains a certificate chain + BOTAN_ASSERT_NONNULL(privkey); + Certificate_Info cert; + cert.key = std::move(privkey); + + Botan::DataSource_Stream in(cred); while(!in.end_of_data()) { try { cert.certs.push_back(Botan::X509_Certificate(in)); @@ -59,13 +70,13 @@ class Basic_Credentials_Manager : public Botan::Credentials_Manager { // TODO: attempt to validate chain ourselves - m_creds.push_back(cert); + m_certs.push_back(cert); } public: Basic_Credentials_Manager(bool use_system_store, const std::string& ca_path, - std::optional client_crt = std::nullopt, + std::optional client_cred = std::nullopt, std::optional client_key = std::nullopt, std::optional> psk = std::nullopt, std::optional psk_identity = std::nullopt, @@ -81,11 +92,11 @@ class Basic_Credentials_Manager : public Botan::Credentials_Manager { m_certstores.push_back(std::make_shared(ca_path)); } - BOTAN_ARG_CHECK(client_crt.has_value() == client_key.has_value(), + BOTAN_ARG_CHECK(client_cred.has_value() == client_key.has_value(), "either provide both client certificate and key or neither"); - if(client_crt.has_value() && client_key.has_value()) { - load_credentials(client_crt.value(), client_key.value()); + if(client_cred.has_value() && client_key.has_value()) { + load_credentials(client_cred.value(), client_key.value()); } #if defined(BOTAN_HAS_CERTSTOR_SYSTEM) @@ -97,7 +108,7 @@ class Basic_Credentials_Manager : public Botan::Credentials_Manager { #endif } - Basic_Credentials_Manager(const std::string& server_crt, + Basic_Credentials_Manager(const std::string& server_cred, const std::string& server_key, std::optional> psk = std::nullopt, std::optional psk_identity = std::nullopt, @@ -109,7 +120,7 @@ class Basic_Credentials_Manager : public Botan::Credentials_Manager { // the Hash algorithm MUST be set when the PSK is established or // default to SHA-256 if no such algorithm is defined. m_psk_prf(psk_prf.value_or("SHA-256")) { - load_credentials(server_crt, server_key); + load_credentials(server_cred, server_key); } std::vector trusted_certificate_authorities(const std::string& type, @@ -138,14 +149,14 @@ class Basic_Credentials_Manager : public Botan::Credentials_Manager { if(type == "tls-client") { for(const auto& dn : acceptable_cas) { - for(const auto& cred : m_creds) { + for(const auto& cred : m_certs) { if(dn == cred.certs[0].issuer_dn()) { return cred.certs; } } } } else if(type == "tls-server") { - for(const auto& i : m_creds) { + for(const auto& i : m_certs) { if(std::find(algos.begin(), algos.end(), i.key->algo_name()) == algos.end()) { continue; } @@ -161,10 +172,18 @@ class Basic_Credentials_Manager : public Botan::Credentials_Manager { return {}; } + std::shared_ptr find_raw_public_key(const std::vector& algos, + const std::string& type, + const std::string& hostname) override { + BOTAN_UNUSED(type, hostname); + return (algos.empty() || value_exists(algos, m_raw_pubkey.public_key->algo_name())) ? m_raw_pubkey.public_key + : nullptr; + } + std::shared_ptr private_key_for(const Botan::X509_Certificate& cert, const std::string& /*type*/, const std::string& /*context*/) override { - for(const auto& i : m_creds) { + for(const auto& i : m_certs) { if(cert == i.certs[0]) { return i.key; } @@ -173,6 +192,14 @@ class Basic_Credentials_Manager : public Botan::Credentials_Manager { return nullptr; } + std::shared_ptr private_key_for(const Botan::Public_Key& raw_public_key, + const std::string& /*type*/, + const std::string& /*context*/) override { + return (m_raw_pubkey.public_key->fingerprint_public() == raw_public_key.fingerprint_public()) + ? m_raw_pubkey.private_key + : nullptr; + } + std::string psk_identity(const std::string& type, const std::string& context, const std::string& identity_hint) override { @@ -208,7 +235,14 @@ class Basic_Credentials_Manager : public Botan::Credentials_Manager { std::shared_ptr key; }; - std::vector m_creds; + struct RawPublicKey_Info { + std::shared_ptr private_key; + std::shared_ptr public_key; + }; + + std::vector m_certs; + RawPublicKey_Info m_raw_pubkey; + std::vector> m_certstores; std::optional> m_psk; std::optional m_psk_identity; diff --git a/src/cli/tls_server.cpp b/src/cli/tls_server.cpp index aafd4271b4d..e854c499121 100644 --- a/src/cli/tls_server.cpp +++ b/src/cli/tls_server.cpp @@ -93,6 +93,14 @@ class Callbacks : public Botan::TLS::Callbacks { return "echo/0.1"; } + void tls_verify_raw_public_key(const Botan::Public_Key& raw_public_key, + Botan::Usage_Type /* usage */, + std::string_view /* hostname */, + const Botan::TLS::Policy& /* policy */) override { + const auto fingerprint = raw_public_key.fingerprint_public("SHA-256"); + output() << "received Raw Public Key (" << fingerprint << ")\n"; + } + private: TLS_Server& m_server_command; std::string m_line_buf; @@ -105,11 +113,11 @@ class TLS_Server final : public Command { #if defined(BOTAN_SO_SOCKETID) TLS_Server() : Command( - "tls_server cert key --port=443 --psk= --psk-identity= --psk-prf=SHA-256 --type=tcp --policy=default --dump-traces= --max-clients=0 --socket-id=0") + "tls_server cert-or-pubkey key --port=443 --psk= --psk-identity= --psk-prf=SHA-256 --type=tcp --policy=default --dump-traces= --max-clients=0 --socket-id=0") #else TLS_Server() : Command( - "tls_server cert key --port=443 --psk= --psk-identity= --psk-prf=SHA-256 --type=tcp --policy=default --dump-traces= --max-clients=0") + "tls_server cert-or-pubkey key --port=443 --psk= --psk-identity= --psk-prf=SHA-256 --type=tcp --policy=default --dump-traces= --max-clients=0") #endif { init_sockets(); @@ -127,7 +135,7 @@ class TLS_Server final : public Command { std::string description() const override { return "Accept TLS/DTLS connections from TLS/DTLS clients"; } void go() override { - const std::string server_crt = get_arg("cert"); + const std::string server_cred = get_arg("cert-or-pubkey"); const std::string server_key = get_arg("key"); const uint16_t port = get_arg_u16("port"); const size_t max_clients = get_arg_sz("max-clients"); @@ -158,7 +166,7 @@ class TLS_Server final : public Command { auto session_manager = std::make_shared(rng_as_shared()); // TODO sqlite3 auto creds = - std::make_shared(server_crt, server_key, std::move(psk), psk_identity, psk_prf); + std::make_shared(server_cred, server_key, std::move(psk), psk_identity, psk_prf); auto callbacks = std::make_shared(*this); output() << "Listening for new connections on " << transport << " port " << port << std::endl;