diff --git a/doc/deprecated.rst b/doc/deprecated.rst index e86e8f8ec77..c9ddf161f1d 100644 --- a/doc/deprecated.rst +++ b/doc/deprecated.rst @@ -36,6 +36,11 @@ in a future major release: - All ciphersuites using static RSA key exchange +- ``Credentials_Manager::psk()`` to provide various TLS-specific keys and + secrets, most notably "session-ticket", "dtls-cookie-secret" and the actual + TLS PSKs for given identities and hosts. Instead, use the dedicated methods in + ``Credentials_Manager`` and do not override the ``psk()`` method any longer. + Deprecated Functionality ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/src/bogo_shim/bogo_shim.cpp b/src/bogo_shim/bogo_shim.cpp index 3ee3b7aa517..b94524cfd33 100644 --- a/src/bogo_shim/bogo_shim.cpp +++ b/src/bogo_shim/bogo_shim.cpp @@ -1228,10 +1228,18 @@ std::vector Shim_Policy::ciphersuite_list(Botan::TLS::Protocol_Version class Shim_Credentials final : public Botan::Credentials_Manager { public: Shim_Credentials(const Shim_Arguments& args) : m_args(args) { - m_psk_identity = m_args.get_string_opt_or_else("psk-identity", ""); + const auto psk_identity = m_args.get_string_opt_or_else("psk-identity", ""); + const auto psk_str = m_args.get_string_opt_or_else("psk", ""); - const std::string psk_str = m_args.get_string_opt_or_else("psk", ""); - m_psk = Botan::SymmetricKey(reinterpret_cast(psk_str.data()), psk_str.size()); + if(!psk_identity.empty() || !psk_str.empty()) { + // If the shim received a -psk param but no -psk-identity param, + // we have to initialize the identity as "empty string". + m_psk_identity = psk_identity; + } + + if(!psk_str.empty()) { + m_psk = Botan::SymmetricKey(reinterpret_cast(psk_str.data()), psk_str.size()); + } if(m_args.option_used("key-file") && m_args.option_used("cert-file")) { Botan::DataSource_Stream key_stream(m_args.get_string_opt("key-file")); @@ -1267,28 +1275,51 @@ class Shim_Credentials final : public Botan::Credentials_Manager { std::string psk_identity(const std::string& /*type*/, const std::string& /*context*/, const std::string& /*identity_hint*/) override { - return m_psk_identity; + return m_psk_identity.value_or(""); } std::string psk_identity_hint(const std::string& /*type*/, const std::string& /*context*/) override { - return m_psk_identity; + return m_psk_identity.value_or(""); } - Botan::SymmetricKey psk(const std::string& type, - const std::string& context, - const std::string& identity) override { - if(type == "tls-server" && context == "session-ticket") { - return Botan::SymmetricKey("ABCDEF0123456789"); - } + Botan::secure_vector session_ticket_key() override { + return Botan::hex_decode_locked("ABCDEF0123456789"); + } + + Botan::secure_vector dtls_cookie_secret() override { + return Botan::hex_decode_locked("F00FB00FD00F100F700F"); + } - if(type == "tls-server" && context == "dtls-cookie-secret") { - return Botan::SymmetricKey("F00FB00FD00F100F700F"); + std::vector find_preshared_keys( + std::string_view host, + Botan::TLS::Connection_Side whoami, + const std::vector& identities = {}, + const std::optional& prf = std::nullopt) override { + if(!m_psk_identity.has_value()) { + return Botan::Credentials_Manager::find_preshared_keys(host, whoami, identities, prf); } - if(identity != m_psk_identity) { + auto id_matches = + identities.empty() || std::find(identities.begin(), identities.end(), m_psk_identity) != identities.end(); + + if(!id_matches) { throw Shim_Exception("Unexpected PSK identity"); } - return m_psk; + + if(!m_psk.has_value()) { + throw Shim_Exception("PSK identified but not set"); + } + + std::vector psks; + + // Currently, BoGo tests PSK with TLS 1.2 only. In TLS 1.2 the PRF does not + // need to be specified for PSKs. + // + // TODO: Once BoGo has tests for TLS 1.3 with externally provided PSKs, this + // will need to be handled somehow. + const std::string psk_prf = "SHA-256"; + psks.emplace_back(m_psk_identity.value(), psk_prf, m_psk->bits_of()); + return psks; } std::vector cert_chain( @@ -1320,8 +1351,8 @@ class Shim_Credentials final : public Botan::Credentials_Manager { private: const Shim_Arguments& m_args; - Botan::SymmetricKey m_psk; - std::string m_psk_identity; + std::optional m_psk; + std::optional m_psk_identity; std::shared_ptr m_key; std::vector m_cert_chain; }; diff --git a/src/cli/tls_client.cpp b/src/cli/tls_client.cpp index 32294ea931a..3cdee349963 100644 --- a/src/cli/tls_client.cpp +++ b/src/cli/tls_client.cpp @@ -215,10 +215,10 @@ class TLS_Client final : public Command { const auto client_crt_path = get_arg_maybe("client-cert"); const auto client_key_path = get_arg_maybe("client-cert-key"); - const auto psk = [this]() -> std::optional { + auto psk = [this]() -> std::optional> { auto psk_hex = get_arg_maybe("psk"); if(psk_hex) { - return Botan::SymmetricKey(Botan::hex_decode_locked(psk_hex.value())); + return Botan::hex_decode_locked(psk_hex.value()); } else { return {}; } @@ -226,7 +226,7 @@ class TLS_Client final : public Command { const std::optional psk_identity = get_arg_maybe("psk-identity"); auto creds = std::make_shared( - use_system_cert_store, trusted_CAs, client_crt_path, client_key_path, psk, psk_identity); + use_system_cert_store, trusted_CAs, client_crt_path, client_key_path, std::move(psk), psk_identity); Botan::TLS::Client client(callbacks, session_mgr, diff --git a/src/cli/tls_helpers.h b/src/cli/tls_helpers.h index 07d01cb9136..afdacaad4e2 100644 --- a/src/cli/tls_helpers.h +++ b/src/cli/tls_helpers.h @@ -56,7 +56,7 @@ class Basic_Credentials_Manager : public Botan::Credentials_Manager { std::string ca_path, std::optional client_crt = std::nullopt, std::optional client_key = std::nullopt, - std::optional psk = std::nullopt, + std::optional> psk = std::nullopt, std::optional psk_identity = std::nullopt) : m_psk(std::move(psk)), m_psk_identity(std::move(psk_identity)) { if(ca_path.empty() == false) { @@ -81,7 +81,7 @@ class Basic_Credentials_Manager : public Botan::Credentials_Manager { Basic_Credentials_Manager(const std::string& server_crt, const std::string& server_key, - std::optional psk = std::nullopt, + std::optional> psk = std::nullopt, std::optional psk_identity = std::nullopt) : m_psk(std::move(psk)), m_psk_identity(std::move(psk_identity)) { load_credentials(server_crt, server_key); @@ -155,13 +155,25 @@ class Basic_Credentials_Manager : public Botan::Credentials_Manager { : Botan::Credentials_Manager::psk_identity(type, context, identity_hint); } - Botan::SymmetricKey psk(const std::string& type, - const std::string& context, - const std::string& identity) override { - return (m_psk && m_psk_identity && identity == m_psk_identity.value() && - (type == "tls-client" || type == "tls-server")) - ? m_psk.value() - : Botan::Credentials_Manager::psk(type, context, identity); + std::vector find_preshared_keys( + std::string_view host, + Botan::TLS::Connection_Side peer_type, + const std::vector& ids = {}, + const std::optional& prf = std::nullopt) override { + if(!m_psk.has_value() || !m_psk_identity.has_value()) { + return Botan::Credentials_Manager::find_preshared_keys(host, peer_type, ids, prf); + } + + std::vector psks; + + const bool prf_matches = !prf.has_value() || m_psk_prf == prf.value(); + const bool id_matches = ids.empty() || std::find(ids.begin(), ids.end(), m_psk_identity.value()) != ids.end(); + + if(prf_matches && id_matches) { + psks.emplace_back(m_psk_identity.value(), m_psk_prf, m_psk.value()); + } + + return psks; } private: @@ -172,8 +184,9 @@ class Basic_Credentials_Manager : public Botan::Credentials_Manager { std::vector m_creds; std::vector> m_certstores; - std::optional m_psk; + std::optional> m_psk; std::optional m_psk_identity; + std::string m_psk_prf; }; class TLS_All_Policy final : public Botan::TLS::Policy { diff --git a/src/cli/tls_server.cpp b/src/cli/tls_server.cpp index 904ed3a17ee..467d3b023f4 100644 --- a/src/cli/tls_server.cpp +++ b/src/cli/tls_server.cpp @@ -127,10 +127,10 @@ class TLS_Server final : public Command { const size_t max_clients = get_arg_sz("max-clients"); const std::string transport = get_arg("type"); const std::string dump_traces_to = get_arg("dump-traces"); - const auto psk = [this]() -> std::optional { + auto psk = [this]() -> std::optional> { auto psk_hex = get_arg_maybe("psk"); if(psk_hex) { - return Botan::SymmetricKey(Botan::hex_decode_locked(psk_hex.value())); + return Botan::hex_decode_locked(psk_hex.value()); } else { return {}; } @@ -149,7 +149,7 @@ class TLS_Server final : public Command { auto policy = load_tls_policy(get_arg("policy")); auto session_manager = std::make_shared(rng_as_shared()); // TODO sqlite3 - auto creds = std::make_shared(server_crt, server_key, psk, psk_identity); + auto creds = std::make_shared(server_crt, server_key, std::move(psk), psk_identity); auto callbacks = std::make_shared(*this); output() << "Listening for new connections on " << transport << " port " << port << std::endl; diff --git a/src/fuzzer/tls_client.cpp b/src/fuzzer/tls_client.cpp index fe9ff0fcdcf..11d13a8905b 100644 --- a/src/fuzzer/tls_client.cpp +++ b/src/fuzzer/tls_client.cpp @@ -6,6 +6,7 @@ #include "fuzzers.h" +#include #include #include @@ -15,8 +16,26 @@ class Fuzzer_TLS_Client_Creds : public Botan::Credentials_Manager { std::string psk_identity(const std::string&, const std::string&, const std::string&) override { return "psk_id"; } - Botan::SymmetricKey psk(const std::string&, const std::string&, const std::string&) override { - return Botan::SymmetricKey("AABBCCDDEEFF00112233445566778899"); + Botan::secure_vector session_ticket_key() override { + return Botan::hex_decode_locked("AABBCCDDEEFF00112233445566778899"); + } + + Botan::secure_vector dtls_cookie_secret() override { + return Botan::hex_decode_locked("AABBCCDDEEFF00112233445566778899"); + } + + std::vector find_preshared_keys( + std::string_view host, + Botan::TLS::Connection_Side whoami, + const std::vector& identities = {}, + const std::optional& prf = std::nullopt) override { + if(!identities.empty() && std::find(identities.begin(), identities.end(), "psk_id") == identities.end()) { + return Botan::Credentials_Manager::find_preshared_keys(host, whoami, identities, prf); + } + + std::vector psks; + psks.emplace_back("psk_id", "SHA-256", Botan::hex_decode_locked("AABBCCDDEEFF00112233445566778899")); + return psks; } }; diff --git a/src/fuzzer/tls_server.cpp b/src/fuzzer/tls_server.cpp index 9c3559f4356..2cb0d1d651b 100644 --- a/src/fuzzer/tls_server.cpp +++ b/src/fuzzer/tls_server.cpp @@ -7,6 +7,7 @@ #include "fuzzers.h" #include +#include #include #include #include @@ -103,12 +104,30 @@ class Fuzzer_TLS_Server_Creds : public Botan::Credentials_Manager { return nullptr; } + Botan::secure_vector session_ticket_key() override { + return Botan::hex_decode_locked("AABBCCDDEEFF00112233445566778899"); + } + + Botan::secure_vector dtls_cookie_secret() override { + return Botan::hex_decode_locked("AABBCCDDEEFF00112233445566778899"); + } + std::string psk_identity_hint(const std::string&, const std::string&) override { return "psk_hint"; } std::string psk_identity(const std::string&, const std::string&, const std::string&) override { return "psk_id"; } - Botan::SymmetricKey psk(const std::string&, const std::string&, const std::string&) override { - return Botan::SymmetricKey("AABBCCDDEEFF00112233445566778899"); + std::vector find_preshared_keys( + std::string_view host, + Botan::TLS::Connection_Side whoami, + const std::vector& identities = {}, + const std::optional& prf = std::nullopt) override { + if(!identities.empty() && std::find(identities.begin(), identities.end(), "psk_id") == identities.end()) { + return Botan::Credentials_Manager::find_preshared_keys(host, whoami, identities, prf); + } + + std::vector psks; + psks.emplace_back("psk_id", "SHA-256", Botan::hex_decode_locked("AABBCCDDEEFF00112233445566778899")); + return psks; } private: diff --git a/src/lib/tls/credentials_manager.cpp b/src/lib/tls/credentials_manager.cpp index 714d10c20ea..2b3a91f1157 100644 --- a/src/lib/tls/credentials_manager.cpp +++ b/src/lib/tls/credentials_manager.cpp @@ -8,6 +8,7 @@ #include #include +#include namespace Botan { @@ -21,10 +22,60 @@ std::string Credentials_Manager::psk_identity(const std::string& /*unused*/, return ""; } -SymmetricKey Credentials_Manager::psk(const std::string& /*unused*/, - const std::string& /*unused*/, +SymmetricKey Credentials_Manager::psk(const std::string& type, + const std::string& context, const std::string& identity) { - throw Internal_Error("No PSK set for identity " + identity); + auto side = [&] { + if(type == "tls-client") { + return TLS::Connection_Side::Client; + } else if(type == "tls-server") { + return TLS::Connection_Side::Server; + } else { + throw Internal_Error(fmt("No PSK set for type {}", type)); + } + }(); + + // New applications should use the appropriate credentials methods. This is a + // retrofit of the behaviour before Botan 3.2.0 and will be removed in a + // future major release. + // + // TODO: deprecate `psk("...", "session-ticket" | "dtls-cookie-secret")` + if(side == TLS::Connection_Side::Server && context == "session-ticket") { + if(auto key = session_ticket_key(); !key.empty()) { + return SymmetricKey(std::move(key)); + } + } else if(side == TLS::Connection_Side::Server && context == "dtls-cookie-secret") { + if(auto key = dtls_cookie_secret(); !key.empty()) { + return SymmetricKey(std::move(key)); + } + } else /* context is a host name */ { + // Assuming that find_preshared_keys returns _exactly_ one or no keys when + // searching for a single specific identity. + if(auto psks = find_preshared_keys(context, side, {identity}); psks.size() == 1) { + return SymmetricKey(psks.front().extract_master_secret()); + } + } + + throw Internal_Error(fmt("No PSK set for identity {}", identity)); +} + +std::vector Credentials_Manager::find_preshared_keys(std::string_view /* host */, + TLS::Connection_Side /* whoami */, + const std::vector& /* identities */, + const std::optional& /* prf */) { + return {}; +} + +std::optional Credentials_Manager::choose_preshared_key(std::string_view host, + TLS::Connection_Side whoami, + const std::vector& identities, + const std::optional& prf) { + auto psks = find_preshared_keys(host, whoami, identities, prf); + if(psks.empty()) { + return std::nullopt; + } else { + return std::move(psks.front()); + } } std::vector Credentials_Manager::find_cert_chain( @@ -57,6 +108,14 @@ std::shared_ptr Credentials_Manager::private_key_for(const X509_Cer return std::shared_ptr(); } +secure_vector Credentials_Manager::session_ticket_key() { + return {}; +} + +secure_vector Credentials_Manager::dtls_cookie_secret() { + return {}; +} + std::vector Credentials_Manager::trusted_certificate_authorities(const std::string& /*unused*/, const std::string& /*unused*/) { return std::vector(); diff --git a/src/lib/tls/credentials_manager.h b/src/lib/tls/credentials_manager.h index d78b582836e..75d772f2551 100644 --- a/src/lib/tls/credentials_manager.h +++ b/src/lib/tls/credentials_manager.h @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include #include @@ -21,9 +23,6 @@ namespace Botan { class X509_DN; class BigInt; -/// @brief holds a PSK identity as used in TLS 1.3 -using PresharedKeyID = Strong, struct PresharedKeyID_>; - /** * Interface for a credentials manager. * @@ -135,6 +134,26 @@ class BOTAN_PUBLIC_API(2, 0) Credentials_Manager { 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 + * key that effectively disables session tickets. + * + * @returns a secret value to be used to encrypt session tickets in + * subclasses of Session_Manager_Stateless. + */ + virtual secure_vector session_ticket_key(); + + /** + * Provides a secret to authenticate DTLS hello cookies. The default + * implementation returns an empty key that effectively disables hello + * cookies. Applications that wish to use DTLS are strongly advised to + * implement this method. + * + * @returns a secret value to authenticate DTLS hello cookies + */ + virtual secure_vector dtls_cookie_secret(); + /** * @param type specifies the type of operation occurring * @param context specifies a context relative to type. @@ -153,14 +172,86 @@ class BOTAN_PUBLIC_API(2, 0) Credentials_Manager { const std::string& identity_hint); /** + * Retrieves the PSK with the given @p identity or throws an exception. + * It's default implementation uses find_preshared_keys() with @p identity + * as the single allowed identity. + * + * This method is called by the TLS 1.2 implementation exclusively and will + * eventually be deprecated in favor of find_preshared_keys(). Going + * forward, new applications should implement find_preshared_keys() and + * rely on psk()'s default implementation. + * + * Also, the default implementation delegates @p context "session-ticket" + * and "dtls-cookie-secret" to the methods session_ticket_key() and + * dtls_cookie_secret() respectively. New applications should implement + * those methods and rely on the default implementation of psk(). + * * @param type specifies the type of operation occurring * @param context specifies a context relative to type. * @param identity is a PSK identity previously returned by psk_identity for the same type and context. * @return the PSK used for identity, or throw an exception if no - * key exists + * key exists */ virtual SymmetricKey psk(const std::string& type, const std::string& context, const std::string& identity); + + /** + * Filters all available PSKs with the given criterions. Note that omitted + * criterions (like an empty @p identities list or an unspecified @p PRF) + * must be interpreted as "no restriction". + * + * Note that this is used as the underlying API for the legacy psk() + * method currently still used in TLS 1.2. New applications should override + * find_preshared_keys() and leave psk() with the default implementation. + * + * In TLS 1.3 the @p identities might contain opaque session ticket data + * that is not necessarily a printable string, despite the utilized + * std::string type. Implementations must be prepared to ignore identities + * generated via the TLS 1.3 resumption mechanism. + * + * @param host the host name for which a PSK is requested (may be empty) + * @param whoami the type of the host (client or server) that is requesting + * @param identities an optional filter for PSK identities to be returned + * (an empty list means: all identities are welcome) + * @param prf an optional filter for the Pseudo Random Function the PRFs + * must be provisioned for + * + * @returns a list of PSKs that meet the defined criterions in preference order + */ + virtual std::vector find_preshared_keys(std::string_view host, + TLS::Connection_Side whoami, + const std::vector& identities = {}, + const std::optional& prf = std::nullopt); + + /** + * Selects a single PSK identity from the given @p identities and returns + * its details (i.e. the secret value) for it to be used in the handshake. + * + * The default implementation relies on the filtering capabilities + * provided by find_preshared_keys() and simply selects the first PSK + * returned. If applications need finer grained control, they should + * override this method. + * + * In TLS 1.3 the @p identities might contain opaque session ticket data + * that is not necessarily a printable string, despite the utilized + * std::string type. Implementations must be prepared to ignore identities + * generated via the TLS 1.3 resumption mechanism. + * + * @param host the host name for which a PSK is requested (may be empty) + * @param whoami the type of the host (client or server) that is requesting + * @param identities an optional filter for PSK identities to be returned + * (an empty list means: all identities are welcome) + * @param prf an optional filter for the Pseudo Random Function the PRFs + * must be provisioned for + * + * @returns the PSK for the selected identity or std::nullopt if no PSK + * meets the requirements + */ + virtual std::optional choose_preshared_key( + std::string_view host, + TLS::Connection_Side whoami, + const std::vector& identities, + const std::optional& prf = std::nullopt); }; } // namespace Botan diff --git a/src/lib/tls/info.txt b/src/lib/tls/info.txt index f555b992ee7..1123775c8ad 100644 --- a/src/lib/tls/info.txt +++ b/src/lib/tls/info.txt @@ -19,6 +19,7 @@ tls_ciphersuite.h tls_client.h tls_exceptn.h tls_extensions.h +tls_external_psk.h tls_handshake_msg.h tls_magic.h tls_messages.h diff --git a/src/lib/tls/tls13/tls_psk_identity_13.cpp b/src/lib/tls/tls13/tls_psk_identity_13.cpp index 309a3bc3181..d4cdd595b53 100644 --- a/src/lib/tls/tls13/tls_psk_identity_13.cpp +++ b/src/lib/tls/tls13/tls_psk_identity_13.cpp @@ -8,6 +8,8 @@ #include +#include + namespace Botan::TLS { namespace { @@ -28,8 +30,20 @@ PskIdentity::PskIdentity(Opaque_Session_Handle identity, const uint32_t ticket_age_add) : PskIdentity(std::move(identity.get()), obfuscate_ticket_age(age.count(), ticket_age_add)) {} +PskIdentity::PskIdentity(PresharedKeyID identity) : + m_identity(to_byte_vector(identity.get())), + + // RFC 8446 4.2.11 + // For identities established externally, an obfuscated_ticket_age of + // 0 SHOULD be used, and servers MUST ignore the value. + m_obfuscated_age(0) {} + std::chrono::milliseconds PskIdentity::age(const uint32_t ticket_age_add) const { return std::chrono::milliseconds(obfuscate_ticket_age(m_obfuscated_age, ticket_age_add)); } +std::string PskIdentity::identity_as_string() const { + return Botan::to_string(m_identity); +} + } // namespace Botan::TLS diff --git a/src/lib/tls/tls13/tls_psk_identity_13.h b/src/lib/tls/tls13/tls_psk_identity_13.h index c6957d7bd56..7154fe911d9 100644 --- a/src/lib/tls/tls13/tls_psk_identity_13.h +++ b/src/lib/tls/tls13/tls_psk_identity_13.h @@ -19,6 +19,9 @@ namespace Botan::TLS { +/// @brief holds a PSK identity as used in TLS 1.3 +using PresharedKeyID = Strong; + /** * Represents a TLS 1.3 PSK identity as found in the Preshared Key extension * with an opaque identity and an associated (obfuscated) ticket age. The latter @@ -40,16 +43,12 @@ class BOTAN_PUBLIC_API(3, 1) PskIdentity { /** * Construct from an externally provided PSK in the client */ - PskIdentity(PresharedKeyID identity) : - m_identity(std::move(identity.get())), - - // RFC 8446 4.2.11 - // For identities established externally, an obfuscated_ticket_age of - // 0 SHOULD be used, and servers MUST ignore the value. - m_obfuscated_age(0) {} + PskIdentity(PresharedKeyID identity); const std::vector& identity() const { return m_identity; } + std::string identity_as_string() const; + /** * If this represents a PSK for session resumption, it returns the * session's age given the de-obfuscation parameter @p ticket_age_add. For diff --git a/src/lib/tls/tls_external_psk.h b/src/lib/tls/tls_external_psk.h new file mode 100644 index 00000000000..d41dcc7d15d --- /dev/null +++ b/src/lib/tls/tls_external_psk.h @@ -0,0 +1,66 @@ +/* + * TLS 1.3 Preshared Key Container + * (C) 2023 Jack Lloyd + * 2023 Fabian Albert, René Meusel - Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + */ + +#ifndef BOTAN_TLS_EXTERNAL_PSK_H_ +#define BOTAN_TLS_EXTERNAL_PSK_H_ + +#include +#include + +#include +#include + +namespace Botan::TLS { + +/** + * This is an externally provided PreSharedKey along with its identity, master + * secret and (in case of TLS 1.3) a pre-provisioned Pseudo Random Function. + */ +class ExternalPSK { + public: + ExternalPSK(const ExternalPSK&) = delete; + ExternalPSK& operator=(const ExternalPSK&) = delete; + ExternalPSK(ExternalPSK&&) = default; + ExternalPSK& operator=(ExternalPSK&&) = default; + ~ExternalPSK() = default; + + ExternalPSK(std::string_view identity, std::string_view prf_algo, secure_vector psk) : + m_identity(identity), m_prf_algo(prf_algo), m_master_secret(std::move(psk)) {} + + /** + * Identity (e.g. username of the PSK owner) of the preshared key. + * Despite the std::string return type, this may or may not be a + * human-readable/printable string. + */ + const std::string& identity() const { return m_identity; } + + /** + * Returns the master secret by moving it out of this object. Do not call + * this method more than once. + */ + secure_vector extract_master_secret() { + BOTAN_STATE_CHECK(!m_master_secret.empty()); + return std::exchange(m_master_secret, {}); + } + + /** + * External preshared keys in TLS 1.3 must be provisioned with a + * pseudo-random function (typically SHA-256 or the like). This is + * needed to calculate/verify the PSK binder values in the client hello. + */ + const std::string& prf_algo() const { return m_prf_algo; } + + private: + std::string m_identity; + std::string m_prf_algo; + secure_vector m_master_secret; +}; + +} // namespace Botan::TLS + +#endif diff --git a/src/lib/utils/stl_util.h b/src/lib/utils/stl_util.h index 9854552ed7e..ec2fefeaad6 100644 --- a/src/lib/utils/stl_util.h +++ b/src/lib/utils/stl_util.h @@ -23,12 +23,13 @@ namespace Botan { -inline std::vector to_byte_vector(std::string_view s) { - return std::vector(s.cbegin(), s.cend()); +template > +inline T to_byte_vector(std::string_view s) { + return T(s.cbegin(), s.cend()); } -inline std::string to_string(const secure_vector& bytes) { - return std::string(bytes.cbegin(), bytes.cend()); +inline std::string to_string(std::span bytes) { + return std::string(bytes.begin(), bytes.end()); } /** diff --git a/src/tests/unit_tls.cpp b/src/tests/unit_tls.cpp index 82788c377b3..ec9867879cf 100644 --- a/src/tests/unit_tls.cpp +++ b/src/tests/unit_tls.cpp @@ -130,26 +130,40 @@ class Credentials_Manager_Test final : public Botan::Credentials_Manager { return nullptr; } - Botan::SymmetricKey psk(const std::string& type, - const std::string& context, - const std::string& /*identity*/) override { - if(type == "tls-server" && context == "session-ticket") { - return Botan::SymmetricKey("AABBCCDDEEFF012345678012345678"); - } + Botan::secure_vector session_ticket_key() override { + return Botan::hex_decode_locked("AABBCCDDEEFF012345678012345678"); + } - if(type == "tls-server" && context == "dtls-cookie-secret") { - return Botan::SymmetricKey("4AEA5EAD279CADEB537A594DA0E9DE3A"); - } + Botan::secure_vector dtls_cookie_secret() override { + return Botan::hex_decode_locked("4AEA5EAD279CADEB537A594DA0E9DE3A"); + } - if(context == "server.example.com" && type == "tls-client") { - return Botan::SymmetricKey("20B602D1475F2DF888FCB60D2AE03AFD"); + std::vector find_preshared_keys( + std::string_view host, + Botan::TLS::Connection_Side whoami, + const std::vector& identities = {}, + const std::optional& prf = std::nullopt) override { + if(identities.empty()) { + return find_preshared_keys(host, whoami, identities, prf); } - if(context == "server.example.com" && type == "tls-server") { - return Botan::SymmetricKey("20B602D1475F2DF888FCB60D2AE03AFD"); + std::vector psks; + + if(host == "server.example.com") { + // PSKs for server and client are equal. For the sake of clarity, + // we're not simplifying this code further + if(whoami == Botan::TLS::Connection_Side::Client) { + psks.emplace_back( + std::string{}, "SHA-256", Botan::hex_decode_locked("20B602D1475F2DF888FCB60D2AE03AFD")); + } + + if(whoami == Botan::TLS::Connection_Side::Server) { + psks.emplace_back( + std::string{}, "SHA-256", Botan::hex_decode_locked("20B602D1475F2DF888FCB60D2AE03AFD")); + } } - throw Test_Error("No PSK set for " + type + "/" + context); + return psks; } const std::vector& get_acceptable_cas() const { return m_acceptable_cas; }