From 65dc230ad7a872cd9758a6e0e0c58e22434c6186 Mon Sep 17 00:00:00 2001 From: Alexey Volokitin Date: Thu, 23 May 2024 10:38:35 +0300 Subject: [PATCH 1/6] add callback to export secrets for SSLKEYLOGFILE --- src/examples/tls_ssl_key_log_file.cpp | 338 +++++++++++++++++++++++ src/lib/tls/tls12/tls_client_impl_12.cpp | 16 ++ src/lib/tls/tls12/tls_server_impl_12.cpp | 16 ++ src/lib/tls/tls13/tls_cipher_state.cpp | 46 ++- src/lib/tls/tls13/tls_cipher_state.h | 37 ++- src/lib/tls/tls13/tls_client_impl_13.cpp | 24 +- src/lib/tls/tls13/tls_client_impl_13.h | 9 +- src/lib/tls/tls13/tls_server_impl_13.cpp | 25 +- src/lib/tls/tls13/tls_server_impl_13.h | 6 +- src/lib/tls/tls_callbacks.h | 20 ++ src/lib/tls/tls_policy.cpp | 6 + src/lib/tls/tls_policy.h | 7 + src/lib/tls/tls_text_policy.cpp | 4 + src/tests/test_tls_cipher_state.cpp | 15 +- src/tests/test_tls_record_layer_13.cpp | 6 +- 15 files changed, 545 insertions(+), 30 deletions(-) create mode 100644 src/examples/tls_ssl_key_log_file.cpp diff --git a/src/examples/tls_ssl_key_log_file.cpp b/src/examples/tls_ssl_key_log_file.cpp new file mode 100644 index 00000000000..b0fc239edee --- /dev/null +++ b/src/examples/tls_ssl_key_log_file.cpp @@ -0,0 +1,338 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(BOTAN_TARGET_OS_HAS_SOCKETS) + #include + #include + #include + #include +#endif + +#define SERVER_PORT 5060 +#define CLIENT_PORT 5070 + +class Client_Credential : public Botan::Credentials_Manager { + public: + Client_Credential() = default; + + std::vector trusted_certificate_authorities(const std::string&, + const std::string&) override { + return {&m_cert_store}; + } + + private: + Botan::System_Certificate_Store m_cert_store; +}; + +class Server_Credential : public Botan::Credentials_Manager { + public: + Server_Credential() { + { + Botan::DataSource_Stream in("botan.randombit.net.key"); + m_key.reset(Botan::PKCS8::load_key(in).release()); + } + { + Botan::DataSource_Stream in("botan.randombit.net.crt"); + while(true) { + try { + certificates.push_back(Botan::X509_Certificate(in)); + } catch(const Botan::Exception&) { + break; + } + } + } + } + + std::vector trusted_certificate_authorities(const std::string&, + const std::string&) override { + return {&m_cert_store}; + } + + std::vector cert_chain( + const std::vector& cert_key_types, + const std::vector& cert_signature_schemes, + const std::string& type, + const std::string& context) override { + BOTAN_UNUSED(cert_signature_schemes, type, context); + + // return the certificate chain being sent to the tls client + // e.g., the certificate file "botan.randombit.net.crt" + std::vector certs; + for(auto& cert : certificates) { + std::string algorithm = cert.subject_public_key()->algo_name(); + for(auto& key : cert_key_types) { + if(algorithm == key) { + certs.push_back(cert); + } + } + } + return certs; + } + + std::shared_ptr private_key_for(const Botan::X509_Certificate& cert, + const std::string& type, + const std::string& context) override { + BOTAN_UNUSED(cert, type, context); + // return the private key associated with the leaf certificate, + // in this case the one associated with "botan.randombit.net.crt" + return m_key; + } + + private: + Botan::System_Certificate_Store m_cert_store; + std::shared_ptr m_key; + std::vector certificates; +}; + +class Allow_Secrets_Policy : public Botan::TLS::Datagram_Policy { + public: + bool allow_ssl_key_log_file() const override { return true; } +}; + +class BotanTLSCallbacksProxy : public Botan::TLS::Callbacks { + Botan::TLS::Callbacks& parent; + + public: + BotanTLSCallbacksProxy(Botan::TLS::Callbacks& callbacks) : parent(callbacks) {} + + void tls_emit_data(std::span data) override { parent.tls_emit_data(data); } + + void tls_record_received(uint64_t seq_no, std::span data) override { BOTAN_UNUSED(seq_no, data); } + + void tls_alert(Botan::TLS::Alert alert) override { BOTAN_UNUSED(alert); } + + void tls_ssl_key_log_data(std::string_view label, + const std::span& client_random, + const std::span& secret) const override { + parent.tls_ssl_key_log_data(label, client_random, secret); + } + + void tls_session_activated() override { parent.tls_session_activated(); } +}; + +class DtlsConnection : public Botan::TLS::Callbacks { + int fd; +#if defined(BOTAN_TARGET_OS_HAS_SOCKETS) + sockaddr_in remote_addr; +#endif + std::unique_ptr dtls_channel; + std::function activated_callback; + + public: + DtlsConnection(const std::string& r_addr, int r_port, int socket, bool is_server) : fd(socket) { +#if defined(BOTAN_TARGET_OS_HAS_SOCKETS) + remote_addr.sin_family = AF_INET; + inet_aton(r_addr.c_str(), &remote_addr.sin_addr); + remote_addr.sin_port = htons(r_port); +#endif + auto tls_callbacks_proxy = std::make_shared(*this); + auto rng = std::make_shared(); + auto session_mgr = std::make_shared(rng); + if(is_server) { + auto policy = std::make_shared(); + auto creds = std::make_shared(); + dtls_channel = + std::make_unique(tls_callbacks_proxy, session_mgr, creds, policy, rng, true); + } else { + auto policy = std::make_shared(); + auto creds = std::make_shared(); + dtls_channel = + std::make_unique(tls_callbacks_proxy, + session_mgr, + creds, + policy, + rng, + Botan::TLS::Server_Information("127.0.0.1", SERVER_PORT), + Botan::TLS::Protocol_Version::DTLS_V12); + } + } + + void tls_emit_data(std::span data) override { +#if defined(BOTAN_TARGET_OS_HAS_SOCKETS) + sendto(fd, data.data(), data.size(), 0, reinterpret_cast(&remote_addr), sizeof(sockaddr_in)); +#else + // send data to the other side + // ... +#endif + } + + void tls_record_received(uint64_t seq_no, std::span data) override { BOTAN_UNUSED(seq_no, data); } + + void tls_alert(Botan::TLS::Alert alert) override { BOTAN_UNUSED(alert); } + + void tls_session_activated() override { + std::cout << "************ on_dtls_connect() ***********" << std::endl; + activated_callback(); + } + + void tls_ssl_key_log_data(std::string_view label, + const std::span& client_random, + const std::span& secret) const override { + std::ofstream stream; + stream.open("test.skl", std::ofstream::out | std::ofstream::app); + stream << label << " " << Botan::hex_encode(client_random.data(), client_random.size()) << " " + << Botan::hex_encode(secret.data(), secret.size()) << std::endl; + stream.close(); + } + + void received_data(std::span data) { dtls_channel->received_data(data); } + + void set_activated_callback(std::function callback) { activated_callback = callback; } + + void close() { + if(fd) { +#if defined(BOTAN_TARGET_OS_HAS_SOCKETS) + shutdown(fd, SHUT_RDWR); + ::close(fd); +#endif + } + } +}; + +static void server_proc(std::function conn)> conn_callback) { + std::cout << "Start Server" << std::endl; + + int fd = 0; +#if defined(BOTAN_TARGET_OS_HAS_SOCKETS) + fd = socket(AF_INET, SOCK_DGRAM, 0); + if(fd == -1) + return; + int true_opt = 1; + if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, static_cast(&true_opt), sizeof(true_opt)) == -1) + return; + sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(SERVER_PORT); + inet_aton("127.0.0.1", &addr.sin_addr); + if(bind(fd, reinterpret_cast(&addr), sizeof(sockaddr_in)) == -1) + return; + sockaddr_in fromaddr; + fromaddr.sin_family = AF_INET; + socklen_t len = sizeof(sockaddr_in); +#else + // create BSD UDP socket and bind it on SERVER_PORT + // ... +#endif + + auto connection = std::make_shared("127.0.0.1", CLIENT_PORT, fd, true); + conn_callback(connection); + +#if defined(BOTAN_TARGET_OS_HAS_SOCKETS) + static uint8_t data[8192]; + int recvlen = 0; + while((recvlen = recvfrom(fd, data, sizeof(data), 0, reinterpret_cast(&fromaddr), &len)) > 0) { + connection->received_data(std::span(data, recvlen)); + } +#else + // read data received from the tls client, e.g., using BSD sockets + // and pass it to connection->received_data. + // ... +#endif + + std::cout << "Server closed" << std::endl; +} + +static void client_proc(std::function conn)> conn_callback) { + std::cout << "Start Client" << std::endl; + + int fd = 0; +#if defined(BOTAN_TARGET_OS_HAS_SOCKETS) + fd = socket(AF_INET, SOCK_DGRAM, 0); + if(fd == -1) + return; + int true_opt = 1; + if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, static_cast(&true_opt), sizeof(true_opt)) == -1) + return; + sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(CLIENT_PORT); + inet_aton("127.0.0.1", &addr.sin_addr); + if(bind(fd, reinterpret_cast(&addr), sizeof(sockaddr_in)) == -1) + return; + sockaddr_in fromaddr; + fromaddr.sin_family = AF_INET; + socklen_t len = sizeof(sockaddr_in); +#else + // create BSD UDP socket and bind it on CLIENT_PORT + // ... +#endif + + auto connection = std::make_shared("127.0.0.1", SERVER_PORT, fd, false); + conn_callback(connection); +#if defined(BOTAN_TARGET_OS_HAS_SOCKETS) + static uint8_t data[8192]; + int recvlen = 0; + while((recvlen = recvfrom(fd, data, sizeof(data), 0, reinterpret_cast(&fromaddr), &len)) > 0) { + connection->received_data(std::span(data, recvlen)); + } +#else + // read data received from the tls server, e.g., using BSD sockets + // and pass it to connection->received_data. + // ... +#endif + + std::cout << "Client closed" << std::endl; +} + +int main() { + std::mutex m; + std::condition_variable conn_cond; + std::vector> connections; + std::thread server(server_proc, [&](std::shared_ptr conn) { + std::lock_guard lk(m); + connections.push_back(std::move(conn)); + if(connections.size() == 2) { + conn_cond.notify_one(); + } + }); + std::thread client(client_proc, [&](std::shared_ptr conn) { + std::lock_guard lk(m); + connections.push_back(std::move(conn)); + if(connections.size() == 2) { + conn_cond.notify_one(); + } + }); + { + std::unique_lock lk(m); + conn_cond.wait(lk); + } + + std::vector activated; + for(auto& conn : connections) { + conn->set_activated_callback([&]() { + activated.push_back(true); + if(activated.size() == 2) { + conn_cond.notify_one(); + } + }); + } + + { + std::unique_lock lk(m); + conn_cond.wait(lk); + } + + for(auto& conn : connections) { + conn->close(); + } + + client.join(); + server.join(); + return 0; +} diff --git a/src/lib/tls/tls12/tls_client_impl_12.cpp b/src/lib/tls/tls12/tls_client_impl_12.cpp index 48867fc0932..db10698e3f5 100644 --- a/src/lib/tls/tls12/tls_client_impl_12.cpp +++ b/src/lib/tls/tls12/tls_client_impl_12.cpp @@ -403,6 +403,14 @@ void Client_Impl_12::process_handshake_msg(const Handshake_State* active_state, } state.compute_session_keys(state.resume_master_secret()); + if(policy().allow_ssl_key_log_file()) { + // draft-thomson-tls-keylogfile-00 Section 3.2 + // An implementation of TLS 1.2 (and also earlier versions) use + // the label "CLIENT_RANDOM" to identify the "master" secret for + // the connection. + callbacks().tls_ssl_key_log_data( + "CLIENT_RANDOM", state.client_hello()->random(), state.session_keys().master_secret()); + } if(state.server_hello()->supports_session_ticket()) { state.set_expected_next(Handshake_Type::NewSessionTicket); @@ -598,6 +606,14 @@ void Client_Impl_12::process_handshake_msg(const Handshake_State* active_state, state.handshake_io(), state, policy(), *m_creds, state.maybe_server_public_key(), m_info.hostname(), rng())); state.compute_session_keys(); + if(policy().allow_ssl_key_log_file()) { + // draft-thomson-tls-keylogfile-00 Section 3.2 + // An implementation of TLS 1.2 (and also earlier versions) use + // the label "CLIENT_RANDOM" to identify the "master" secret for + // the connection. + callbacks().tls_ssl_key_log_data( + "CLIENT_RANDOM", state.client_hello()->random(), state.session_keys().master_secret()); + } if(state.received_handshake_msg(Handshake_Type::CertificateRequest) && !state.client_certs()->empty()) { auto private_key = diff --git a/src/lib/tls/tls12/tls_server_impl_12.cpp b/src/lib/tls/tls12/tls_server_impl_12.cpp index 20d27fe9963..e19da15ac16 100644 --- a/src/lib/tls/tls12/tls_server_impl_12.cpp +++ b/src/lib/tls/tls12/tls_server_impl_12.cpp @@ -486,6 +486,14 @@ void Server_Impl_12::process_client_key_exchange_msg(Server_Handshake_State& pen new Client_Key_Exchange(contents, pending_state, pending_state.server_rsa_kex_key(), *m_creds, policy(), rng())); pending_state.compute_session_keys(); + if(policy().allow_ssl_key_log_file()) { + // draft-thomson-tls-keylogfile-00 Section 3.2 + // An implementation of TLS 1.2 (and also earlier versions) use + // the label "CLIENT_RANDOM" to identify the "master" secret for + // the connection. + callbacks().tls_ssl_key_log_data( + "CLIENT_RANDOM", pending_state.client_hello()->random(), pending_state.session_keys().master_secret()); + } } void Server_Impl_12::process_change_cipher_spec_msg(Server_Handshake_State& pending_state) { @@ -676,6 +684,14 @@ void Server_Impl_12::session_resume(Server_Handshake_State& pending_state, const pending_state.mark_as_resumption(); pending_state.compute_session_keys(session.session.master_secret()); + if(policy().allow_ssl_key_log_file()) { + // draft-thomson-tls-keylogfile-00 Section 3.2 + // An implementation of TLS 1.2 (and also earlier versions) use + // the label "CLIENT_RANDOM" to identify the "master" secret for + // the connection. + callbacks().tls_ssl_key_log_data( + "CLIENT_RANDOM", pending_state.client_hello()->random(), pending_state.session_keys().master_secret()); + } pending_state.set_resume_certs(session.session.peer_certs()); // Give the application a chance for a final veto before fully diff --git a/src/lib/tls/tls13/tls_cipher_state.cpp b/src/lib/tls/tls13/tls_cipher_state.cpp index 8ab5d17d5eb..834c9c1e9a0 100644 --- a/src/lib/tls/tls13/tls_cipher_state.cpp +++ b/src/lib/tls/tls13/tls_cipher_state.cpp @@ -119,10 +119,11 @@ constexpr size_t NONCE_LENGTH = 12; std::unique_ptr Cipher_State::init_with_server_hello(const Connection_Side side, secure_vector&& shared_secret, const Ciphersuite& cipher, - const Transcript_Hash& transcript_hash) { + const Transcript_Hash& transcript_hash, + const Secrets_Callback& callbacks) { auto cs = std::unique_ptr(new Cipher_State(side, cipher.prf_algo())); cs->advance_without_psk(); - cs->advance_with_server_hello(cipher, std::move(shared_secret), transcript_hash); + cs->advance_with_server_hello(cipher, std::move(shared_secret), transcript_hash, callbacks); return cs; } @@ -135,7 +136,8 @@ std::unique_ptr Cipher_State::init_with_psk(const Connection_Side return cs; } -void Cipher_State::advance_with_client_hello(const Transcript_Hash& transcript_hash) { +void Cipher_State::advance_with_client_hello(const Transcript_Hash& transcript_hash, + const Secrets_Callback& callbacks) { BOTAN_ASSERT_NOMSG(m_state == State::PskBinder); zap(m_binder_key); @@ -148,13 +150,21 @@ void Cipher_State::advance_with_client_hello(const Transcript_Hash& transcript_h m_exporter_master_secret = derive_secret(m_early_secret, "e exp master", transcript_hash); + // draft-thomson-tls-keylogfile-00 Section 3.1 + // An implementation of TLS 1.3 use + // the label "EARLY_EXPORTER_MASTER_SECRET" + // to identify the secret + // that is using for early exporters + callbacks.tls_log_secret("EARLY_EXPORTER_MASTER_SECRET", m_exporter_master_secret); + m_salt = derive_secret(m_early_secret, "derived", empty_hash()); zap(m_early_secret); m_state = State::EarlyTraffic; } -void Cipher_State::advance_with_server_finished(const Transcript_Hash& transcript_hash) { +void Cipher_State::advance_with_server_finished(const Transcript_Hash& transcript_hash, + const Secrets_Callback& callbacks) { BOTAN_ASSERT_NOMSG(m_state == State::HandshakeTraffic); const auto master_secret = hkdf_extract(secure_vector(m_hash->output_length(), 0x00)); @@ -162,6 +172,15 @@ void Cipher_State::advance_with_server_finished(const Transcript_Hash& transcrip auto client_application_traffic_secret = derive_secret(master_secret, "c ap traffic", transcript_hash); auto server_application_traffic_secret = derive_secret(master_secret, "s ap traffic", transcript_hash); + // draft-thomson-tls-keylogfile-00 Section 3.1 + // An implementation of TLS 1.3 use + // the label "CLIENT_TRAFFIC_SECRET_0" + // and "SERVER_TRAFFIC_SECRET_0" + // to identify the secrets are using to protect + // the connection. + callbacks.tls_log_secret("CLIENT_TRAFFIC_SECRET_0", client_application_traffic_secret); + callbacks.tls_log_secret("SERVER_TRAFFIC_SECRET_0", server_application_traffic_secret); + // Note: the secrets for processing client's application data // are not derived before the client's Finished message // was seen and the handshake can be considered finished. @@ -177,6 +196,13 @@ void Cipher_State::advance_with_server_finished(const Transcript_Hash& transcrip m_exporter_master_secret = derive_secret(master_secret, "exp master", transcript_hash); + // draft-thomson-tls-keylogfile-00 Section 3.1 + // An implementation of TLS 1.3 use + // the label "EXPORTER_SECRET" + // to identify the secret + // that is used in generating exporters(rfc8446 Section 7.5). + callbacks.tls_log_secret("EXPORTER_SECRET", m_exporter_master_secret); + m_state = State::ServerApplicationTraffic; } @@ -460,7 +486,8 @@ void Cipher_State::advance_with_psk(PSK_Type type, secure_vector&& psk) void Cipher_State::advance_with_server_hello(const Ciphersuite& cipher, secure_vector&& shared_secret, - const Transcript_Hash& transcript_hash) { + const Transcript_Hash& transcript_hash, + const Secrets_Callback& callbacks) { BOTAN_ASSERT_NOMSG(m_state == State::EarlyTraffic); BOTAN_ASSERT_NOMSG(!m_encrypt); BOTAN_ASSERT_NOMSG(!m_decrypt); @@ -474,6 +501,15 @@ void Cipher_State::advance_with_server_hello(const Ciphersuite& cipher, const auto client_handshake_traffic_secret = derive_secret(handshake_secret, "c hs traffic", transcript_hash); const auto server_handshake_traffic_secret = derive_secret(handshake_secret, "s hs traffic", transcript_hash); + // draft-thomson-tls-keylogfile-00 Section 3.1 + // An implementation of TLS 1.3 use + // the label "CLIENT_HANDSHAKE_TRAFFIC_SECRET" + // and "SERVER_HANDSHAKE_TRAFFIC_SECRET" + // to identify the secrets + // are using to protect handshake messages. + callbacks.tls_log_secret("CLIENT_HANDSHAKE_TRAFFIC_SECRET", client_handshake_traffic_secret); + callbacks.tls_log_secret("SERVER_HANDSHAKE_TRAFFIC_SECRET", server_handshake_traffic_secret); + if(m_connection_side == Connection_Side::Server) { derive_read_traffic_key(client_handshake_traffic_secret, true); derive_write_traffic_key(server_handshake_traffic_secret, true); diff --git a/src/lib/tls/tls13/tls_cipher_state.h b/src/lib/tls/tls13/tls_cipher_state.h index 8ec1af8de3b..8e457b75dcf 100644 --- a/src/lib/tls/tls13/tls_cipher_state.h +++ b/src/lib/tls/tls13/tls_cipher_state.h @@ -9,6 +9,8 @@ #ifndef BOTAN_TLS_CIPHER_STATE_H_ #define BOTAN_TLS_CIPHER_STATE_H_ +#include + #include #include #include @@ -28,6 +30,31 @@ namespace Botan::TLS { class Ciphersuite; +/* + * Encapsulates the callbacks in the state machine described in RFC 8446 7.1, + * that will make the realisation the SSLKEYLOGFILE for connection debugging + * specified in ietf.org/archive/id/draft-thomson-tls-keylogfile-00.html + */ +class Secrets_Callback { + public: + Secrets_Callback() {} + + virtual ~Secrets_Callback() {} + + /** + * Allows access to a connection's secret data + * + * Default implementation simply ignores the inputs. + * + * @param label Identifies the reported secret type + * See draft-thomson-tls-keylogfile-00 Section 3.1 + * @param secret the actual secret value + */ + virtual void tls_log_secret(std::string_view label, const std::span& secret) const { + BOTAN_UNUSED(label, secret); + } +}; + /** * This class implements the key schedule for TLS 1.3 as described in RFC 8446 7.1. * @@ -82,25 +109,27 @@ class BOTAN_TEST_API Cipher_State { static std::unique_ptr init_with_server_hello(Connection_Side side, secure_vector&& shared_secret, const Ciphersuite& cipher, - const Transcript_Hash& transcript_hash); + const Transcript_Hash& transcript_hash, + const Secrets_Callback& callbacks); /** * Transition internal secrets/keys for transporting early application data. * Note that this state transition is legal only for handshakes using PSK. */ - void advance_with_client_hello(const Transcript_Hash& transcript_hash); + void advance_with_client_hello(const Transcript_Hash& transcript_hash, const Secrets_Callback& callbacks); /** * Transition internal secrets/keys for transporting handshake data. */ void advance_with_server_hello(const Ciphersuite& cipher, secure_vector&& shared_secret, - const Transcript_Hash& transcript_hash); + const Transcript_Hash& transcript_hash, + const Secrets_Callback& callbacks); /** * Transition internal secrets/keys for transporting application data. */ - void advance_with_server_finished(const Transcript_Hash& transcript_hash); + void advance_with_server_finished(const Transcript_Hash& transcript_hash, const Secrets_Callback& callbacks); /** * Transition to the final internal state allowing to create resumptions. diff --git a/src/lib/tls/tls13/tls_client_impl_13.cpp b/src/lib/tls/tls13/tls_client_impl_13.cpp index 112b78a2571..b9d1296dd02 100644 --- a/src/lib/tls/tls13/tls_client_impl_13.cpp +++ b/src/lib/tls/tls13/tls_client_impl_13.cpp @@ -118,6 +118,12 @@ bool Client_Impl_13::is_handshake_complete() const { return m_handshake_state.handshake_finished(); } +void Client_Impl_13::tls_log_secret(std::string_view label, const std::span& secret) const { + if(policy().allow_ssl_key_log_file()) { + callbacks().tls_ssl_key_log_data(label, m_handshake_state.client_hello().random(), secret); + } +} + std::optional Client_Impl_13::find_session_for_resumption() { // RFC 8446 4.6.1 // Clients MUST only resume if the new SNI value is valid for the @@ -322,12 +328,19 @@ void Client_Impl_13::handle(const Server_Hello_13& sh) { // TODO: When implementing early data, `advance_with_client_hello` must // happen _before_ encrypting any early application data. // Same when we want to support early key export. - m_cipher_state->advance_with_client_hello(m_transcript_hash.previous()); - m_cipher_state->advance_with_server_hello(cipher.value(), std::move(shared_secret), m_transcript_hash.current()); + m_cipher_state->advance_with_client_hello(m_transcript_hash.previous(), + static_cast(*this)); + m_cipher_state->advance_with_server_hello(cipher.value(), + std::move(shared_secret), + m_transcript_hash.current(), + static_cast(*this)); } else { m_resumed_session.reset(); // might have been set if we attempted a resumption - m_cipher_state = Cipher_State::init_with_server_hello( - m_side, std::move(shared_secret), cipher.value(), m_transcript_hash.current()); + m_cipher_state = Cipher_State::init_with_server_hello(m_side, + std::move(shared_secret), + cipher.value(), + m_transcript_hash.current(), + static_cast(*this)); } callbacks().tls_examine_extensions(sh.extensions(), Connection_Side::Server, Handshake_Type::ServerHello); @@ -566,7 +579,8 @@ void Client_Impl_13::handle(const Finished_13& finished_msg) { // Derives the secrets for receiving application data but defers // the derivation of sending application data. - m_cipher_state->advance_with_server_finished(m_transcript_hash.current()); + m_cipher_state->advance_with_server_finished(m_transcript_hash.current(), + static_cast(*this)); auto flight = aggregate_handshake_messages(); diff --git a/src/lib/tls/tls13/tls_client_impl_13.h b/src/lib/tls/tls13/tls_client_impl_13.h index f8f376fcaef..c967726ff15 100644 --- a/src/lib/tls/tls13/tls_client_impl_13.h +++ b/src/lib/tls/tls13/tls_client_impl_13.h @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -24,7 +25,8 @@ namespace TLS { /** * SSL/TLS Client 1.3 implementation */ -class Client_Impl_13 : public Channel_Impl_13 { +class Client_Impl_13 : public Channel_Impl_13, + public Secrets_Callback { public: /** * Set up a new TLS client session @@ -78,6 +80,11 @@ class Client_Impl_13 : public Channel_Impl_13 { */ bool is_handshake_complete() const override; + /* + * Allows access to a connection's secret data + */ + void tls_log_secret(std::string_view label, const std::span& secret) const override; + private: void process_handshake_msg(Handshake_Message_13 msg) override; void process_post_handshake_msg(Post_Handshake_Message_13 msg) override; diff --git a/src/lib/tls/tls13/tls_server_impl_13.cpp b/src/lib/tls/tls13/tls_server_impl_13.cpp index b6edc9daabd..e614275706b 100644 --- a/src/lib/tls/tls13/tls_server_impl_13.cpp +++ b/src/lib/tls/tls13/tls_server_impl_13.cpp @@ -167,6 +167,12 @@ bool Server_Impl_13::is_handshake_complete() const { return m_handshake_state.handshake_finished(); } +void Server_Impl_13::tls_log_secret(std::string_view label, const std::span& secret) const { + if(policy().allow_ssl_key_log_file()) { + callbacks().tls_ssl_key_log_data(label, m_handshake_state.client_hello().random(), secret); + } +} + void Server_Impl_13::downgrade() { BOTAN_ASSERT_NOMSG(expects_downgrade()); @@ -287,14 +293,20 @@ void Server_Impl_13::handle_reply_to_client_hello(Server_Hello_13 server_hello) if(uses_psk) { BOTAN_ASSERT_NONNULL(psk_cipher_state); - psk_cipher_state->advance_with_client_hello(m_transcript_hash.previous()); - psk_cipher_state->advance_with_server_hello( - cipher, my_keyshare->take_shared_secret(), m_transcript_hash.current()); + psk_cipher_state->advance_with_client_hello(m_transcript_hash.previous(), + static_cast(*this)); + psk_cipher_state->advance_with_server_hello(cipher, + my_keyshare->take_shared_secret(), + m_transcript_hash.current(), + static_cast(*this)); return std::move(psk_cipher_state); } else { - return Cipher_State::init_with_server_hello( - m_side, my_keyshare->take_shared_secret(), cipher, m_transcript_hash.current()); + return Cipher_State::init_with_server_hello(m_side, + my_keyshare->take_shared_secret(), + cipher, + m_transcript_hash.current(), + static_cast(*this)); } }(); @@ -375,7 +387,8 @@ void Server_Impl_13::handle_reply_to_client_hello(Server_Hello_13 server_hello) flight.send(); - m_cipher_state->advance_with_server_finished(m_transcript_hash.current()); + m_cipher_state->advance_with_server_finished(m_transcript_hash.current(), + static_cast(*this)); if(m_handshake_state.has_certificate_request()) { // RFC 8446 4.4.2 diff --git a/src/lib/tls/tls13/tls_server_impl_13.h b/src/lib/tls/tls13/tls_server_impl_13.h index 0b543cad957..ec0e12a9beb 100644 --- a/src/lib/tls/tls13/tls_server_impl_13.h +++ b/src/lib/tls/tls13/tls_server_impl_13.h @@ -10,6 +10,7 @@ #define BOTAN_TLS_SERVER_IMPL_13_H_ #include +#include #include #include @@ -18,7 +19,8 @@ namespace Botan::TLS { /** * SSL/TLS Server 1.3 implementation */ -class Server_Impl_13 : public Channel_Impl_13 { +class Server_Impl_13 : public Channel_Impl_13, + public Secrets_Callback { public: explicit Server_Impl_13(const std::shared_ptr& callbacks, const std::shared_ptr& session_manager, @@ -36,6 +38,8 @@ class Server_Impl_13 : public Channel_Impl_13 { bool is_handshake_complete() const override; + void tls_log_secret(std::string_view label, const std::span& secret) const override; + private: void process_handshake_msg(Handshake_Message_13 msg) override; void process_post_handshake_msg(Post_Handshake_Message_13 msg) override; diff --git a/src/lib/tls/tls_callbacks.h b/src/lib/tls/tls_callbacks.h index 4a889c5ccdb..00ee7c6d57b 100644 --- a/src/lib/tls/tls_callbacks.h +++ b/src/lib/tls/tls_callbacks.h @@ -560,6 +560,26 @@ class BOTAN_PUBLIC_API(2, 0) Callbacks { virtual void tls_log_debug_bin(const char* descr, const uint8_t val[], size_t val_len) { BOTAN_UNUSED(descr, val, val_len); } + + /** + * Optional callback: Allows access to a connection's secret data + * + * Useful to implement the SSLKEYLOGFILE for connection debugging as + * specified in ietf.org/archive/id/draft-thomson-tls-keylogfile-00.html + * + * Default implementation simply ignores the inputs. + * + * @param label Identifies the reported secret type + * See draft-thomson-tls-keylogfile-00 Section 3.1 and 3.2 + * @param client_random random value from ClientHello message acting as + * an identifier of the TLS sessions + * @param secret the actual secret value + */ + virtual void tls_ssl_key_log_data(std::string_view label, + const std::span& client_random, + const std::span& secret) const { + BOTAN_UNUSED(label, client_random, secret); + } }; } // namespace TLS diff --git a/src/lib/tls/tls_policy.cpp b/src/lib/tls/tls_policy.cpp index 172964b340c..70e43488f32 100644 --- a/src/lib/tls/tls_policy.cpp +++ b/src/lib/tls/tls_policy.cpp @@ -14,12 +14,18 @@ #include #include #include +#include #include #include #include namespace Botan::TLS { +bool Policy::allow_ssl_key_log_file() const { + std::string data; + return Botan::OS::read_env_variable(data, "SSLKEYLOGFILE"); +} + std::vector Policy::allowed_signature_schemes() const { std::vector schemes; diff --git a/src/lib/tls/tls_policy.h b/src/lib/tls/tls_policy.h index 6e384b42941..1aa3863e1ee 100644 --- a/src/lib/tls/tls_policy.h +++ b/src/lib/tls/tls_policy.h @@ -31,6 +31,11 @@ namespace TLS { */ class BOTAN_PUBLIC_API(2, 0) Policy { public: + /** + * Allow ssl key log file + */ + virtual bool allow_ssl_key_log_file() const; + /** * Returns a list of ciphers we are willing to negotiate, in * order of preference. @@ -629,6 +634,8 @@ class BOTAN_PUBLIC_API(2, 0) Strict_Policy : public Policy { class BOTAN_PUBLIC_API(2, 0) Text_Policy : public Policy { public: + bool allow_ssl_key_log_file() const override; + std::vector allowed_ciphers() const override; std::vector allowed_signature_hashes() const override; diff --git a/src/lib/tls/tls_text_policy.cpp b/src/lib/tls/tls_text_policy.cpp index d363648857e..3f3b15060c3 100644 --- a/src/lib/tls/tls_text_policy.cpp +++ b/src/lib/tls/tls_text_policy.cpp @@ -15,6 +15,10 @@ namespace Botan::TLS { +bool Text_Policy::allow_ssl_key_log_file() const { + return get_bool("allow_ssl_key_log_file", Policy::allow_ssl_key_log_file()); +} + std::vector Text_Policy::allowed_ciphers() const { return get_list("ciphers", Policy::allowed_ciphers()); } diff --git a/src/tests/test_tls_cipher_state.cpp b/src/tests/test_tls_cipher_state.cpp index 1875bb82ba3..295cc5ce2da 100644 --- a/src/tests/test_tls_cipher_state.cpp +++ b/src/tests/test_tls_cipher_state.cpp @@ -256,10 +256,11 @@ std::vector test_secret_derivation_rfc8448_rtt1() { auto cipher = Ciphersuite::from_name("AES_128_GCM_SHA256").value(); // initialize Cipher_State with client_hello...server_hello + Secrets_Callback sc; auto cs_client = Cipher_State::init_with_server_hello( - Connection_Side::Client, secure_vector(shared_secret), cipher, th_server_hello); + Connection_Side::Client, secure_vector(shared_secret), cipher, th_server_hello, sc); auto cs_server = Cipher_State::init_with_server_hello( - Connection_Side::Server, secure_vector(shared_secret), cipher, th_server_hello); + Connection_Side::Server, secure_vector(shared_secret), cipher, th_server_hello, sc); auto CHECK_both = make_CHECK_both(cs_client.get(), cs_server.get()); @@ -311,7 +312,7 @@ std::vector test_secret_derivation_rfc8448_rtt1() { // advance Cipher_State with client_hello...server_Finished // (allows receiving of application data, but does not yet allow such sending) result.test_no_throw("state advancement is legal", - [&] { cs->advance_with_server_finished(th_server_finished); }); + [&] { cs->advance_with_server_finished(th_server_finished, sc); }); if(side == Connection_Side::Client) { result.confirm("can read application data", cs->can_decrypt_application_traffic()); @@ -577,7 +578,8 @@ std::vector test_secret_derivation_rfc8448_rtt0() { CHECK_both("calculate the early traffic secrets", [&](Cipher_State* cs, Connection_Side side, Test::Result& result) { - cs->advance_with_client_hello(th_client_hello); + Secrets_Callback sc; + cs->advance_with_client_hello(th_client_hello, sc); result.require("early key export is possible", cs->can_export_keys()); result.test_eq("early key export produces expected result", cs->export_key(early_export_label, early_export_context, 16), @@ -600,7 +602,8 @@ std::vector test_secret_derivation_rfc8448_rtt0() { CHECK_both("handshake traffic after PSK", [&](Cipher_State* cs, Connection_Side side, Test::Result& result) { - cs->advance_with_server_hello(cipher, secure_vector(shared_secret), th_server_hello); + Secrets_Callback sc; + cs->advance_with_server_hello(cipher, secure_vector(shared_secret), th_server_hello, sc); // decrypt encrypted extensions from server encrypted_extensions.xxcrypt(result, cs, side); @@ -630,7 +633,7 @@ std::vector test_secret_derivation_rfc8448_rtt0() { // advance Cipher_State with client_hello...server_Finished // (allows receiving of application data, but no such sending) result.test_no_throw("state advancement is legal", - [&] { cs->advance_with_server_finished(th_server_finished); }); + [&] { cs->advance_with_server_finished(th_server_finished, sc); }); if(side == Connection_Side::Client) { result.confirm("can read application data", cs->can_decrypt_application_traffic()); diff --git a/src/tests/test_tls_record_layer_13.cpp b/src/tests/test_tls_record_layer_13.cpp index 6b1c2951633..4c1c1ead265 100644 --- a/src/tests/test_tls_record_layer_13.cpp +++ b/src/tests/test_tls_record_layer_13.cpp @@ -59,7 +59,8 @@ std::unique_ptr rfc8448_rtt1_handshake_traffic( "8b d4 05 4f b5 5b 9d 63 fd fb ac f9 f0 4b 9f 0d" "35 e6 d6 3f 53 75 63 ef d4 62 72 90 0f 89 49 2d"); auto cipher = TLS::Ciphersuite::from_name("AES_128_GCM_SHA256").value(); - return TLS::Cipher_State::init_with_server_hello(side, std::move(shared_secret), cipher, transcript_hash); + Botan::TLS::Secrets_Callback sc; + return TLS::Cipher_State::init_with_server_hello(side, std::move(shared_secret), cipher, transcript_hash, sc); } std::vector read_full_records() { @@ -655,8 +656,9 @@ std::vector read_encrypted_records() { auto cs = rfc8448_rtt1_handshake_traffic(); // advance with arbitrary hashes that were used to produce the input data + Botan::TLS::Secrets_Callback sc; cs->advance_with_server_finished( - Botan::hex_decode("e1935a480babfc4403b2517f0ad414bed0ca51fa671e2061804afa78fd71d55c")); + Botan::hex_decode("e1935a480babfc4403b2517f0ad414bed0ca51fa671e2061804afa78fd71d55c"), sc); cs->advance_with_client_finished( Botan::hex_decode("305e4a0a7cee581b282c571b251b20138a1a6a21918937a6bb95b1e9ba1b5cac")); From 9d75f8121ced432dac78518b8ad65e57dc4347ac Mon Sep 17 00:00:00 2001 From: Rene Meusel Date: Wed, 22 May 2024 14:59:51 +0200 Subject: [PATCH 2/6] Simplify Secret_Logger class somewhat --- src/lib/tls/tls13/tls_channel_impl_13.h | 27 +++++++++++++++++- src/lib/tls/tls13/tls_cipher_state.cpp | 25 ++++++++-------- src/lib/tls/tls13/tls_cipher_state.h | 36 ++++-------------------- src/lib/tls/tls13/tls_client_impl_13.cpp | 31 ++++++++------------ src/lib/tls/tls13/tls_client_impl_13.h | 9 ++---- src/lib/tls/tls13/tls_server_impl_13.cpp | 21 +++++--------- src/lib/tls/tls13/tls_server_impl_13.h | 6 ++-- src/lib/tls/tls_callbacks.h | 4 +-- src/tests/test_tls_cipher_state.cpp | 24 ++++++++++------ src/tests/test_tls_record_layer_13.cpp | 14 ++++++--- 10 files changed, 93 insertions(+), 104 deletions(-) diff --git a/src/lib/tls/tls13/tls_channel_impl_13.h b/src/lib/tls/tls13/tls_channel_impl_13.h index 2577b277596..fe0a81fecbe 100644 --- a/src/lib/tls/tls13/tls_channel_impl_13.h +++ b/src/lib/tls/tls13/tls_channel_impl_13.h @@ -18,10 +18,35 @@ namespace Botan::TLS { +class Cipher_State; + +/** + * Encapsulates the callbacks in the state machine described in RFC 8446 7.1, + * that will make the realisation the SSLKEYLOGFILE for connection debugging + * specified in ietf.org/archive/id/draft-thomson-tls-keylogfile-00.html + * + * The class is split from the rest of the Channel_Impl_13 for mockability. + */ +class Secret_Logger { + public: + virtual ~Secret_Logger() = default; + + friend class Cipher_State; + + protected: + /** + * Used exclusively in the Cipher_State to pass secret data to + * a user-provided Callbacks::tls_ssl_key_log_data() iff + * Policy::allow_ssl_key_log_file() returns true. + */ + virtual void maybe_log_secret(std::string_view label, std::span secret) const = 0; +}; + /** * Generic interface for TLS 1.3 endpoint */ -class Channel_Impl_13 : public Channel_Impl { +class Channel_Impl_13 : public Channel_Impl, + protected Secret_Logger { protected: /** * Helper class to coalesce handshake messages into a single TLS record diff --git a/src/lib/tls/tls13/tls_cipher_state.cpp b/src/lib/tls/tls13/tls_cipher_state.cpp index 834c9c1e9a0..9433edd0b81 100644 --- a/src/lib/tls/tls13/tls_cipher_state.cpp +++ b/src/lib/tls/tls13/tls_cipher_state.cpp @@ -101,6 +101,7 @@ #include #include #include +#include namespace Botan::TLS { @@ -120,10 +121,10 @@ std::unique_ptr Cipher_State::init_with_server_hello(const Connect secure_vector&& shared_secret, const Ciphersuite& cipher, const Transcript_Hash& transcript_hash, - const Secrets_Callback& callbacks) { + const Secret_Logger& loggger) { auto cs = std::unique_ptr(new Cipher_State(side, cipher.prf_algo())); cs->advance_without_psk(); - cs->advance_with_server_hello(cipher, std::move(shared_secret), transcript_hash, callbacks); + cs->advance_with_server_hello(cipher, std::move(shared_secret), transcript_hash, loggger); return cs; } @@ -136,8 +137,7 @@ std::unique_ptr Cipher_State::init_with_psk(const Connection_Side return cs; } -void Cipher_State::advance_with_client_hello(const Transcript_Hash& transcript_hash, - const Secrets_Callback& callbacks) { +void Cipher_State::advance_with_client_hello(const Transcript_Hash& transcript_hash, const Secret_Logger& loggger) { BOTAN_ASSERT_NOMSG(m_state == State::PskBinder); zap(m_binder_key); @@ -155,7 +155,7 @@ void Cipher_State::advance_with_client_hello(const Transcript_Hash& transcript_h // the label "EARLY_EXPORTER_MASTER_SECRET" // to identify the secret // that is using for early exporters - callbacks.tls_log_secret("EARLY_EXPORTER_MASTER_SECRET", m_exporter_master_secret); + loggger.maybe_log_secret("EARLY_EXPORTER_MASTER_SECRET", m_exporter_master_secret); m_salt = derive_secret(m_early_secret, "derived", empty_hash()); zap(m_early_secret); @@ -163,8 +163,7 @@ void Cipher_State::advance_with_client_hello(const Transcript_Hash& transcript_h m_state = State::EarlyTraffic; } -void Cipher_State::advance_with_server_finished(const Transcript_Hash& transcript_hash, - const Secrets_Callback& callbacks) { +void Cipher_State::advance_with_server_finished(const Transcript_Hash& transcript_hash, const Secret_Logger& loggger) { BOTAN_ASSERT_NOMSG(m_state == State::HandshakeTraffic); const auto master_secret = hkdf_extract(secure_vector(m_hash->output_length(), 0x00)); @@ -178,8 +177,8 @@ void Cipher_State::advance_with_server_finished(const Transcript_Hash& transcrip // and "SERVER_TRAFFIC_SECRET_0" // to identify the secrets are using to protect // the connection. - callbacks.tls_log_secret("CLIENT_TRAFFIC_SECRET_0", client_application_traffic_secret); - callbacks.tls_log_secret("SERVER_TRAFFIC_SECRET_0", server_application_traffic_secret); + loggger.maybe_log_secret("CLIENT_TRAFFIC_SECRET_0", client_application_traffic_secret); + loggger.maybe_log_secret("SERVER_TRAFFIC_SECRET_0", server_application_traffic_secret); // Note: the secrets for processing client's application data // are not derived before the client's Finished message @@ -201,7 +200,7 @@ void Cipher_State::advance_with_server_finished(const Transcript_Hash& transcrip // the label "EXPORTER_SECRET" // to identify the secret // that is used in generating exporters(rfc8446 Section 7.5). - callbacks.tls_log_secret("EXPORTER_SECRET", m_exporter_master_secret); + loggger.maybe_log_secret("EXPORTER_SECRET", m_exporter_master_secret); m_state = State::ServerApplicationTraffic; } @@ -487,7 +486,7 @@ void Cipher_State::advance_with_psk(PSK_Type type, secure_vector&& psk) void Cipher_State::advance_with_server_hello(const Ciphersuite& cipher, secure_vector&& shared_secret, const Transcript_Hash& transcript_hash, - const Secrets_Callback& callbacks) { + const Secret_Logger& loggger) { BOTAN_ASSERT_NOMSG(m_state == State::EarlyTraffic); BOTAN_ASSERT_NOMSG(!m_encrypt); BOTAN_ASSERT_NOMSG(!m_decrypt); @@ -507,8 +506,8 @@ void Cipher_State::advance_with_server_hello(const Ciphersuite& cipher, // and "SERVER_HANDSHAKE_TRAFFIC_SECRET" // to identify the secrets // are using to protect handshake messages. - callbacks.tls_log_secret("CLIENT_HANDSHAKE_TRAFFIC_SECRET", client_handshake_traffic_secret); - callbacks.tls_log_secret("SERVER_HANDSHAKE_TRAFFIC_SECRET", server_handshake_traffic_secret); + loggger.maybe_log_secret("CLIENT_HANDSHAKE_TRAFFIC_SECRET", client_handshake_traffic_secret); + loggger.maybe_log_secret("SERVER_HANDSHAKE_TRAFFIC_SECRET", server_handshake_traffic_secret); if(m_connection_side == Connection_Side::Server) { derive_read_traffic_key(client_handshake_traffic_secret, true); diff --git a/src/lib/tls/tls13/tls_cipher_state.h b/src/lib/tls/tls13/tls_cipher_state.h index 8e457b75dcf..9a4d09feaa8 100644 --- a/src/lib/tls/tls13/tls_cipher_state.h +++ b/src/lib/tls/tls13/tls_cipher_state.h @@ -9,8 +9,6 @@ #ifndef BOTAN_TLS_CIPHER_STATE_H_ #define BOTAN_TLS_CIPHER_STATE_H_ -#include - #include #include #include @@ -29,31 +27,7 @@ class HKDF_Expand; namespace Botan::TLS { class Ciphersuite; - -/* - * Encapsulates the callbacks in the state machine described in RFC 8446 7.1, - * that will make the realisation the SSLKEYLOGFILE for connection debugging - * specified in ietf.org/archive/id/draft-thomson-tls-keylogfile-00.html - */ -class Secrets_Callback { - public: - Secrets_Callback() {} - - virtual ~Secrets_Callback() {} - - /** - * Allows access to a connection's secret data - * - * Default implementation simply ignores the inputs. - * - * @param label Identifies the reported secret type - * See draft-thomson-tls-keylogfile-00 Section 3.1 - * @param secret the actual secret value - */ - virtual void tls_log_secret(std::string_view label, const std::span& secret) const { - BOTAN_UNUSED(label, secret); - } -}; +class Secret_Logger; /** * This class implements the key schedule for TLS 1.3 as described in RFC 8446 7.1. @@ -110,13 +84,13 @@ class BOTAN_TEST_API Cipher_State { secure_vector&& shared_secret, const Ciphersuite& cipher, const Transcript_Hash& transcript_hash, - const Secrets_Callback& callbacks); + const Secret_Logger& channel); /** * Transition internal secrets/keys for transporting early application data. * Note that this state transition is legal only for handshakes using PSK. */ - void advance_with_client_hello(const Transcript_Hash& transcript_hash, const Secrets_Callback& callbacks); + void advance_with_client_hello(const Transcript_Hash& transcript_hash, const Secret_Logger& channel); /** * Transition internal secrets/keys for transporting handshake data. @@ -124,12 +98,12 @@ class BOTAN_TEST_API Cipher_State { void advance_with_server_hello(const Ciphersuite& cipher, secure_vector&& shared_secret, const Transcript_Hash& transcript_hash, - const Secrets_Callback& callbacks); + const Secret_Logger& channel); /** * Transition internal secrets/keys for transporting application data. */ - void advance_with_server_finished(const Transcript_Hash& transcript_hash, const Secrets_Callback& callbacks); + void advance_with_server_finished(const Transcript_Hash& transcript_hash, const Secret_Logger& channel); /** * Transition to the final internal state allowing to create resumptions. diff --git a/src/lib/tls/tls13/tls_client_impl_13.cpp b/src/lib/tls/tls13/tls_client_impl_13.cpp index b9d1296dd02..922ad67c939 100644 --- a/src/lib/tls/tls13/tls_client_impl_13.cpp +++ b/src/lib/tls/tls13/tls_client_impl_13.cpp @@ -118,12 +118,6 @@ bool Client_Impl_13::is_handshake_complete() const { return m_handshake_state.handshake_finished(); } -void Client_Impl_13::tls_log_secret(std::string_view label, const std::span& secret) const { - if(policy().allow_ssl_key_log_file()) { - callbacks().tls_ssl_key_log_data(label, m_handshake_state.client_hello().random(), secret); - } -} - std::optional Client_Impl_13::find_session_for_resumption() { // RFC 8446 4.6.1 // Clients MUST only resume if the new SNI value is valid for the @@ -328,19 +322,13 @@ void Client_Impl_13::handle(const Server_Hello_13& sh) { // TODO: When implementing early data, `advance_with_client_hello` must // happen _before_ encrypting any early application data. // Same when we want to support early key export. - m_cipher_state->advance_with_client_hello(m_transcript_hash.previous(), - static_cast(*this)); - m_cipher_state->advance_with_server_hello(cipher.value(), - std::move(shared_secret), - m_transcript_hash.current(), - static_cast(*this)); + m_cipher_state->advance_with_client_hello(m_transcript_hash.previous(), *this); + m_cipher_state->advance_with_server_hello( + cipher.value(), std::move(shared_secret), m_transcript_hash.current(), *this); } else { m_resumed_session.reset(); // might have been set if we attempted a resumption - m_cipher_state = Cipher_State::init_with_server_hello(m_side, - std::move(shared_secret), - cipher.value(), - m_transcript_hash.current(), - static_cast(*this)); + m_cipher_state = Cipher_State::init_with_server_hello( + m_side, std::move(shared_secret), cipher.value(), m_transcript_hash.current(), *this); } callbacks().tls_examine_extensions(sh.extensions(), Connection_Side::Server, Handshake_Type::ServerHello); @@ -579,8 +567,7 @@ void Client_Impl_13::handle(const Finished_13& finished_msg) { // Derives the secrets for receiving application data but defers // the derivation of sending application data. - m_cipher_state->advance_with_server_finished(m_transcript_hash.current(), - static_cast(*this)); + m_cipher_state->advance_with_server_finished(m_transcript_hash.current(), *this); auto flight = aggregate_handshake_messages(); @@ -663,6 +650,12 @@ bool Client_Impl_13::prepend_ccs() { return std::exchange(m_should_send_ccs, false); // test-and-set } +void Client_Impl_13::maybe_log_secret(std::string_view label, std::span secret) const { + if(policy().allow_ssl_key_log_file()) { + callbacks().tls_ssl_key_log_data(label, m_handshake_state.client_hello().random(), secret); + } +} + std::string Client_Impl_13::application_protocol() const { if(is_handshake_complete()) { const auto& eee = m_handshake_state.encrypted_extensions().extensions(); diff --git a/src/lib/tls/tls13/tls_client_impl_13.h b/src/lib/tls/tls13/tls_client_impl_13.h index c967726ff15..7b7c12aa5b0 100644 --- a/src/lib/tls/tls13/tls_client_impl_13.h +++ b/src/lib/tls/tls13/tls_client_impl_13.h @@ -25,8 +25,7 @@ namespace TLS { /** * SSL/TLS Client 1.3 implementation */ -class Client_Impl_13 : public Channel_Impl_13, - public Secrets_Callback { +class Client_Impl_13 : public Channel_Impl_13 { public: /** * Set up a new TLS client session @@ -80,16 +79,12 @@ class Client_Impl_13 : public Channel_Impl_13, */ bool is_handshake_complete() const override; - /* - * Allows access to a connection's secret data - */ - void tls_log_secret(std::string_view label, const std::span& secret) const override; - private: void process_handshake_msg(Handshake_Message_13 msg) override; void process_post_handshake_msg(Post_Handshake_Message_13 msg) override; void process_dummy_change_cipher_spec() override; + void maybe_log_secret(std::string_view label, std::span secret) const override; bool prepend_ccs() override; using Channel_Impl_13::handle; diff --git a/src/lib/tls/tls13/tls_server_impl_13.cpp b/src/lib/tls/tls13/tls_server_impl_13.cpp index e614275706b..50f2719c073 100644 --- a/src/lib/tls/tls13/tls_server_impl_13.cpp +++ b/src/lib/tls/tls13/tls_server_impl_13.cpp @@ -167,7 +167,7 @@ bool Server_Impl_13::is_handshake_complete() const { return m_handshake_state.handshake_finished(); } -void Server_Impl_13::tls_log_secret(std::string_view label, const std::span& secret) const { +void Server_Impl_13::maybe_log_secret(std::string_view label, std::span secret) const { if(policy().allow_ssl_key_log_file()) { callbacks().tls_ssl_key_log_data(label, m_handshake_state.client_hello().random(), secret); } @@ -293,20 +293,14 @@ void Server_Impl_13::handle_reply_to_client_hello(Server_Hello_13 server_hello) if(uses_psk) { BOTAN_ASSERT_NONNULL(psk_cipher_state); - psk_cipher_state->advance_with_client_hello(m_transcript_hash.previous(), - static_cast(*this)); - psk_cipher_state->advance_with_server_hello(cipher, - my_keyshare->take_shared_secret(), - m_transcript_hash.current(), - static_cast(*this)); + psk_cipher_state->advance_with_client_hello(m_transcript_hash.previous(), *this); + psk_cipher_state->advance_with_server_hello( + cipher, my_keyshare->take_shared_secret(), m_transcript_hash.current(), *this); return std::move(psk_cipher_state); } else { - return Cipher_State::init_with_server_hello(m_side, - my_keyshare->take_shared_secret(), - cipher, - m_transcript_hash.current(), - static_cast(*this)); + return Cipher_State::init_with_server_hello( + m_side, my_keyshare->take_shared_secret(), cipher, m_transcript_hash.current(), *this); } }(); @@ -387,8 +381,7 @@ void Server_Impl_13::handle_reply_to_client_hello(Server_Hello_13 server_hello) flight.send(); - m_cipher_state->advance_with_server_finished(m_transcript_hash.current(), - static_cast(*this)); + m_cipher_state->advance_with_server_finished(m_transcript_hash.current(), *this); if(m_handshake_state.has_certificate_request()) { // RFC 8446 4.4.2 diff --git a/src/lib/tls/tls13/tls_server_impl_13.h b/src/lib/tls/tls13/tls_server_impl_13.h index ec0e12a9beb..f78a5cf4c76 100644 --- a/src/lib/tls/tls13/tls_server_impl_13.h +++ b/src/lib/tls/tls13/tls_server_impl_13.h @@ -19,8 +19,7 @@ namespace Botan::TLS { /** * SSL/TLS Server 1.3 implementation */ -class Server_Impl_13 : public Channel_Impl_13, - public Secrets_Callback { +class Server_Impl_13 : public Channel_Impl_13 { public: explicit Server_Impl_13(const std::shared_ptr& callbacks, const std::shared_ptr& session_manager, @@ -38,8 +37,6 @@ class Server_Impl_13 : public Channel_Impl_13, bool is_handshake_complete() const override; - void tls_log_secret(std::string_view label, const std::span& secret) const override; - private: void process_handshake_msg(Handshake_Message_13 msg) override; void process_post_handshake_msg(Post_Handshake_Message_13 msg) override; @@ -56,6 +53,7 @@ class Server_Impl_13 : public Channel_Impl_13, void handle_reply_to_client_hello(Hello_Retry_Request hello_retry_request); void maybe_handle_compatibility_mode(); + void maybe_log_secret(std::string_view label, std::span secret) const override; void downgrade(); diff --git a/src/lib/tls/tls_callbacks.h b/src/lib/tls/tls_callbacks.h index 00ee7c6d57b..7b1fccd5cef 100644 --- a/src/lib/tls/tls_callbacks.h +++ b/src/lib/tls/tls_callbacks.h @@ -576,8 +576,8 @@ class BOTAN_PUBLIC_API(2, 0) Callbacks { * @param secret the actual secret value */ virtual void tls_ssl_key_log_data(std::string_view label, - const std::span& client_random, - const std::span& secret) const { + std::span client_random, + std::span secret) const { BOTAN_UNUSED(label, client_random, secret); } }; diff --git a/src/tests/test_tls_cipher_state.cpp b/src/tests/test_tls_cipher_state.cpp index 295cc5ce2da..350e7a6019e 100644 --- a/src/tests/test_tls_cipher_state.cpp +++ b/src/tests/test_tls_cipher_state.cpp @@ -12,6 +12,7 @@ #include #include + #include #include namespace { @@ -20,6 +21,11 @@ using Test = Botan_Tests::Test; using namespace Botan; using namespace Botan::TLS; +class Journaling_Secret_Logger : public Secret_Logger { + public: + void maybe_log_secret(std::string_view, std::span) const override {} +}; + decltype(auto) make_CHECK_both(Cipher_State* cs_client, Cipher_State* cs_server) { using namespace std::placeholders; return [=](const std::string& name, auto lambda) -> std::vector { @@ -256,11 +262,11 @@ std::vector test_secret_derivation_rfc8448_rtt1() { auto cipher = Ciphersuite::from_name("AES_128_GCM_SHA256").value(); // initialize Cipher_State with client_hello...server_hello - Secrets_Callback sc; + Journaling_Secret_Logger sl; auto cs_client = Cipher_State::init_with_server_hello( - Connection_Side::Client, secure_vector(shared_secret), cipher, th_server_hello, sc); + Connection_Side::Client, secure_vector(shared_secret), cipher, th_server_hello, sl); auto cs_server = Cipher_State::init_with_server_hello( - Connection_Side::Server, secure_vector(shared_secret), cipher, th_server_hello, sc); + Connection_Side::Server, secure_vector(shared_secret), cipher, th_server_hello, sl); auto CHECK_both = make_CHECK_both(cs_client.get(), cs_server.get()); @@ -312,7 +318,7 @@ std::vector test_secret_derivation_rfc8448_rtt1() { // advance Cipher_State with client_hello...server_Finished // (allows receiving of application data, but does not yet allow such sending) result.test_no_throw("state advancement is legal", - [&] { cs->advance_with_server_finished(th_server_finished, sc); }); + [&] { cs->advance_with_server_finished(th_server_finished, sl); }); if(side == Connection_Side::Client) { result.confirm("can read application data", cs->can_decrypt_application_traffic()); @@ -578,8 +584,8 @@ std::vector test_secret_derivation_rfc8448_rtt0() { CHECK_both("calculate the early traffic secrets", [&](Cipher_State* cs, Connection_Side side, Test::Result& result) { - Secrets_Callback sc; - cs->advance_with_client_hello(th_client_hello, sc); + Journaling_Secret_Logger sl; + cs->advance_with_client_hello(th_client_hello, sl); result.require("early key export is possible", cs->can_export_keys()); result.test_eq("early key export produces expected result", cs->export_key(early_export_label, early_export_context, 16), @@ -602,8 +608,8 @@ std::vector test_secret_derivation_rfc8448_rtt0() { CHECK_both("handshake traffic after PSK", [&](Cipher_State* cs, Connection_Side side, Test::Result& result) { - Secrets_Callback sc; - cs->advance_with_server_hello(cipher, secure_vector(shared_secret), th_server_hello, sc); + Journaling_Secret_Logger sl; + cs->advance_with_server_hello(cipher, secure_vector(shared_secret), th_server_hello, sl); // decrypt encrypted extensions from server encrypted_extensions.xxcrypt(result, cs, side); @@ -633,7 +639,7 @@ std::vector test_secret_derivation_rfc8448_rtt0() { // advance Cipher_State with client_hello...server_Finished // (allows receiving of application data, but no such sending) result.test_no_throw("state advancement is legal", - [&] { cs->advance_with_server_finished(th_server_finished, sc); }); + [&] { cs->advance_with_server_finished(th_server_finished, sl); }); if(side == Connection_Side::Client) { result.confirm("can read application data", cs->can_decrypt_application_traffic()); diff --git a/src/tests/test_tls_record_layer_13.cpp b/src/tests/test_tls_record_layer_13.cpp index 4c1c1ead265..971ed4199ca 100644 --- a/src/tests/test_tls_record_layer_13.cpp +++ b/src/tests/test_tls_record_layer_13.cpp @@ -16,6 +16,7 @@ #include #include + #include #include #include @@ -50,6 +51,11 @@ TLS::Record_Layer record_layer_server(const bool skip_client_hello = false) { return rl; } +class Mocked_Secret_Logger : public Botan::TLS::Secret_Logger { + public: + void maybe_log_secret(std::string_view, std::span) const override {} +}; + std::unique_ptr rfc8448_rtt1_handshake_traffic( Botan::TLS::Connection_Side side = Botan::TLS::Connection_Side::Client) { const auto transcript_hash = Botan::hex_decode( @@ -59,8 +65,8 @@ std::unique_ptr rfc8448_rtt1_handshake_traffic( "8b d4 05 4f b5 5b 9d 63 fd fb ac f9 f0 4b 9f 0d" "35 e6 d6 3f 53 75 63 ef d4 62 72 90 0f 89 49 2d"); auto cipher = TLS::Ciphersuite::from_name("AES_128_GCM_SHA256").value(); - Botan::TLS::Secrets_Callback sc; - return TLS::Cipher_State::init_with_server_hello(side, std::move(shared_secret), cipher, transcript_hash, sc); + Mocked_Secret_Logger logger; + return TLS::Cipher_State::init_with_server_hello(side, std::move(shared_secret), cipher, transcript_hash, logger); } std::vector read_full_records() { @@ -656,9 +662,9 @@ std::vector read_encrypted_records() { auto cs = rfc8448_rtt1_handshake_traffic(); // advance with arbitrary hashes that were used to produce the input data - Botan::TLS::Secrets_Callback sc; + Mocked_Secret_Logger logger; cs->advance_with_server_finished( - Botan::hex_decode("e1935a480babfc4403b2517f0ad414bed0ca51fa671e2061804afa78fd71d55c"), sc); + Botan::hex_decode("e1935a480babfc4403b2517f0ad414bed0ca51fa671e2061804afa78fd71d55c"), logger); cs->advance_with_client_finished( Botan::hex_decode("305e4a0a7cee581b282c571b251b20138a1a6a21918937a6bb95b1e9ba1b5cac")); From 24196cdebd7482b4e3ab1026a7f3e43557012b4d Mon Sep 17 00:00:00 2001 From: Rene Meusel Date: Wed, 22 May 2024 15:07:53 +0200 Subject: [PATCH 3/6] Test: extend tls_cipher_state test for secret logging --- src/tests/test_tls_cipher_state.cpp | 324 +++++++++++++++++++--------- 1 file changed, 219 insertions(+), 105 deletions(-) diff --git a/src/tests/test_tls_cipher_state.cpp b/src/tests/test_tls_cipher_state.cpp index 350e7a6019e..7c05409f1f6 100644 --- a/src/tests/test_tls_cipher_state.cpp +++ b/src/tests/test_tls_cipher_state.cpp @@ -23,16 +23,24 @@ using namespace Botan::TLS; class Journaling_Secret_Logger : public Secret_Logger { public: - void maybe_log_secret(std::string_view, std::span) const override {} + void maybe_log_secret(std::string_view label, std::span secret) const override { + secrets[std::string(label)] = std::vector(secret.begin(), secret.end()); + } + + public: + mutable std::map> secrets; // NOLINT(*-non-private-member-variables-in-classes) }; -decltype(auto) make_CHECK_both(Cipher_State* cs_client, Cipher_State* cs_server) { +decltype(auto) make_CHECK_both(Cipher_State* cs_client, + Journaling_Secret_Logger* sl_client, + Cipher_State* cs_server, + Journaling_Secret_Logger* sl_server) { using namespace std::placeholders; return [=](const std::string& name, auto lambda) -> std::vector { return {Botan_Tests::CHECK(std::string(name + " (client)").c_str(), - std::bind(lambda, cs_client, Connection_Side::Client, _1)), + std::bind(lambda, cs_client, sl_client, Connection_Side::Client, _1)), Botan_Tests::CHECK(std::string(name + " (server)").c_str(), - std::bind(lambda, cs_server, Connection_Side::Server, _1))}; + std::bind(lambda, cs_server, sl_server, Connection_Side::Server, _1))}; }; } @@ -116,6 +124,26 @@ std::vector test_secret_derivation_rfc8448_rtt1() { "20 91 45 a9 6e e8 e2 a1 22 ff 81 00 47 cc 95 26" "84 65 8d 60 49 e8 64 29 42 6d b8 7c 54 ad 14 3d"); + // handshake traffic secret for the client + const auto client_handshake_traffic_secret = Botan::hex_decode( + "b3 ed db 12 6e 06 7f 35 a7 80 b3 ab f4 5e 2d 8f" + "3b 1a 95 07 38 f5 2e 96 00 74 6a 0e 27 a5 5a 21"); + + // handshake traffic secret for the server + const auto server_handshake_traffic_secret = Botan::hex_decode( + "b6 7b 7d 69 0c c1 6c 4e 75 e5 42 13 cb 2d 37 b4" + "e9 c9 12 bc de d9 10 5d 42 be fd 59 d3 91 ad 38"); + + // application traffic secret (0) for the client + const auto client_traffic_secret = Botan::hex_decode( + "9e 40 64 6c e7 9a 7f 9d c0 5a f8 88 9b ce 65 52" + "87 5a fa 0b 06 df 00 87 f7 92 eb b7 c1 75 04 a5"); + + // application traffic secret (0) for the server + const auto server_traffic_secret = Botan::hex_decode( + "a1 1a f9 f0 55 31 f8 56 ad 47 11 6b 45 a9 50 32" + "82 04 b4 f4 4b fb 6b 3a 4b 4f 1f 3f cb 63 16 43"); + // encrypted with server_handshake_traffic_secret const auto encrypted_extensions = RFC8448_TestData("encrypted_extensions", @@ -262,17 +290,33 @@ std::vector test_secret_derivation_rfc8448_rtt1() { auto cipher = Ciphersuite::from_name("AES_128_GCM_SHA256").value(); // initialize Cipher_State with client_hello...server_hello - Journaling_Secret_Logger sl; + Journaling_Secret_Logger sl_client; + Journaling_Secret_Logger sl_server; auto cs_client = Cipher_State::init_with_server_hello( - Connection_Side::Client, secure_vector(shared_secret), cipher, th_server_hello, sl); + Connection_Side::Client, secure_vector(shared_secret), cipher, th_server_hello, sl_client); auto cs_server = Cipher_State::init_with_server_hello( - Connection_Side::Server, secure_vector(shared_secret), cipher, th_server_hello, sl); + Connection_Side::Server, secure_vector(shared_secret), cipher, th_server_hello, sl_server); - auto CHECK_both = make_CHECK_both(cs_client.get(), cs_server.get()); + auto CHECK_both = make_CHECK_both(cs_client.get(), &sl_client, cs_server.get(), &sl_server); return Test::flatten_result_lists( - {CHECK_both("ciphersuite compatibility", - [&](Cipher_State* cs, Connection_Side side, Test::Result& result) { + {CHECK_both( + "secret logging during initialization", + [&](Cipher_State*, Journaling_Secret_Logger* sl, Connection_Side, Test::Result& result) { + result.test_eq("logged expected secrets", sl->secrets.size(), 2); + result.require("has client traffic secret", sl->secrets.contains("CLIENT_HANDSHAKE_TRAFFIC_SECRET")); + result.require("has server traffic secret", sl->secrets.contains("SERVER_HANDSHAKE_TRAFFIC_SECRET")); + + result.test_is_eq("client traffic secret", + sl->secrets.at("CLIENT_HANDSHAKE_TRAFFIC_SECRET"), + client_handshake_traffic_secret); + result.test_is_eq("server traffic secret", + sl->secrets.at("SERVER_HANDSHAKE_TRAFFIC_SECRET"), + server_handshake_traffic_secret); + }), + + CHECK_both("ciphersuite compatibility", + [&](Cipher_State* cs, Journaling_Secret_Logger*, Connection_Side side, Test::Result& result) { result.confirm("self-compatibility", cs->is_compatible_with(cipher)); result.confirm( "fully defined state is not compatible to other suites", @@ -289,13 +333,13 @@ std::vector test_secret_derivation_rfc8448_rtt1() { }), CHECK_both("ticket nonce counter is not yet available", - [&](Cipher_State* cs, Connection_Side, Test::Result& result) { + [&](Cipher_State* cs, Journaling_Secret_Logger*, Connection_Side, Test::Result& result) { result.test_throws("nonce counter is disabled", [&] { cs->next_ticket_nonce(); }); }), CHECK_both("handshake traffic without PSK", - [&](Cipher_State* cs, Connection_Side side, Test::Result& result) { + [&](Cipher_State* cs, Journaling_Secret_Logger* sl, Connection_Side side, Test::Result& result) { result.confirm("can not yet write application data", !cs->can_encrypt_application_traffic()); result.confirm("can not yet export key material", !cs->can_export_keys()); @@ -318,7 +362,7 @@ std::vector test_secret_derivation_rfc8448_rtt1() { // advance Cipher_State with client_hello...server_Finished // (allows receiving of application data, but does not yet allow such sending) result.test_no_throw("state advancement is legal", - [&] { cs->advance_with_server_finished(th_server_finished, sl); }); + [&] { cs->advance_with_server_finished(th_server_finished, *sl); }); if(side == Connection_Side::Client) { result.confirm("can read application data", cs->can_decrypt_application_traffic()); @@ -332,6 +376,16 @@ std::vector test_secret_derivation_rfc8448_rtt1() { cs->must_expect_unprotected_alert_traffic()); } + // check the logged key material + result.test_eq("contains expected number of keys", sl->secrets.size(), 5); + result.require("has client traffic secret", sl->secrets.contains("CLIENT_TRAFFIC_SECRET_0")); + result.require("has server traffic secret", sl->secrets.contains("SERVER_TRAFFIC_SECRET_0")); + result.require("has exporter secret", sl->secrets.contains("EXPORTER_SECRET")); + result.test_eq( + "client traffic secret (0)", sl->secrets.at("CLIENT_TRAFFIC_SECRET_0"), client_traffic_secret); + result.test_eq( + "server traffic secret (0)", sl->secrets.at("SERVER_TRAFFIC_SECRET_0"), server_traffic_secret); + // generate the MAC for the client Finished message const auto expected_client_mac = Botan::hex_decode( "a8 ec 43 6d 67 76 34 ae 52 5a c1 fc eb e1 1a 03" @@ -381,7 +435,7 @@ std::vector test_secret_derivation_rfc8448_rtt1() { }), CHECK_both("ticket nonce counter counts", - [&](Cipher_State* cs, Connection_Side, Test::Result& result) { + [&](Cipher_State* cs, Journaling_Secret_Logger*, Connection_Side, Test::Result& result) { result.test_is_eq("nonce is 0x00, 0x00", cs->next_ticket_nonce(), Botan::TLS::Ticket_Nonce(std::vector{0x00, 0x00})); @@ -401,7 +455,7 @@ std::vector test_secret_derivation_rfc8448_rtt1() { }), CHECK_both("PSK", - [&](Cipher_State* cs, Connection_Side, Test::Result& result) { + [&](Cipher_State* cs, Journaling_Secret_Logger*, Connection_Side, Test::Result& result) { // derive PSK for resumption const auto psk = cs->psk(Botan::TLS::Ticket_Nonce( std::vector{0x00, 0x00}) /* ticket_nonce as defined in RFC 8448 */); @@ -409,14 +463,14 @@ std::vector test_secret_derivation_rfc8448_rtt1() { }), CHECK_both("key update", - [&](Cipher_State* cs, Connection_Side, Test::Result& result) { + [&](Cipher_State* cs, Journaling_Secret_Logger*, Connection_Side, Test::Result& result) { cs->update_read_keys(); cs->update_write_keys(); result.confirm("can encrypt application traffic", cs->can_encrypt_application_traffic()); }), - CHECK_both("cleanup", [&](Cipher_State* cs, Connection_Side, Test::Result& result) { + CHECK_both("cleanup", [&](Cipher_State* cs, Journaling_Secret_Logger*, Connection_Side, Test::Result& result) { // cleanup cs->clear_write_keys(); result.confirm("can no longer write application data", !cs->can_encrypt_application_traffic()); @@ -479,6 +533,30 @@ std::vector test_secret_derivation_rfc8448_rtt0() { "3a dd 4f b2 d8 fd f8 22 a0 ca 3c f7 67 8e f5 e8" "8d ae 99 01 41 c5 92 4d 57 bb 6f a3 1b 9e 5f 9d"); + const auto early_exporter_secret = Botan::hex_decode( + "b2 02 68 66 61 09 37 d7 42 3e 5b e9 08 62 cc f2" + "4c 0e 60 91 18 6d 34 f8 12 08 9f f5 be 2e f7 df"); + + const auto client_handshake_traffic_secret = Botan::hex_decode( + "2f aa c0 8f 85 1d 35 fe a3 60 4f cb 4d e8 2d c6" + "2c 9b 16 4a 70 97 4d 04 62 e2 7f 1a b2 78 70 0f"); + + const auto server_handshake_traffic_secret = Botan::hex_decode( + "fe 92 7a e2 71 31 2e 8b f0 27 5b 58 1c 54 ee f0" + "20 45 0d c4 ec ff aa 05 a1 a3 5d 27 51 8e 78 03"); + + const auto client_traffic_secret = Botan::hex_decode( + "2a bb f2 b8 e3 81 d2 3d be be 1d d2 a7 d1 6a 8b" + "f4 84 cb 49 50 d2 3f b7 fb 7f a8 54 70 62 d9 a1"); + + const auto server_traffic_secret = Botan::hex_decode( + "cc 21 f1 bf 8f eb 7d d5 fa 50 5b d9 c4 b4 68 a9" + "98 4d 55 4a 99 3d c4 9e 6d 28 55 98 fb 67 26 91"); + + const auto exporter_secret = Botan::hex_decode( + "3f d9 3d 4f fd dc 98 e6 4b 14 dd 10 7a ed f8 ee" + "4a dd 23 f4 51 0f 58 a4 59 2d 0b 20 1b ee 56 b4"); + // this is not part of RFC 8448 const std::string export_label = "export_test_label"; const std::string export_context = "rfc8448_psk"; @@ -550,6 +628,9 @@ std::vector test_secret_derivation_rfc8448_rtt0() { auto cipher = Ciphersuite::from_name("AES_128_GCM_SHA256").value(); + Journaling_Secret_Logger sl_client; + Journaling_Secret_Logger sl_server; + auto cs_client = Cipher_State::init_with_psk(Connection_Side::Client, Cipher_State::PSK_Type::Resumption, secure_vector(psk.begin(), psk.end()), @@ -559,18 +640,23 @@ std::vector test_secret_derivation_rfc8448_rtt0() { secure_vector(psk.begin(), psk.end()), cipher.prf_algo()); - auto CHECK_both = make_CHECK_both(cs_client.get(), cs_server.get()); + auto CHECK_both = make_CHECK_both(cs_client.get(), &sl_client, cs_server.get(), &sl_server); return Test::flatten_result_lists( - {CHECK_both("calculating PSK binder", - [&](Cipher_State* cs, Connection_Side, Test::Result& result) { + {CHECK_both("no secrets logged for PSK initialization", + [&](Cipher_State*, Journaling_Secret_Logger* sl, Connection_Side, Test::Result& result) { + result.test_eq("no secrets logged", sl->secrets.size(), 0); + }), + + CHECK_both("calculating PSK binder", + [&](Cipher_State* cs, Journaling_Secret_Logger*, Connection_Side, Test::Result& result) { const auto mac = cs->psk_binder_mac(th_client_hello_prefix); result.test_eq("PSK binder is as expected", mac, expected_psk_binder); }), CHECK_both( "ciphersuite compatibility", - [&](Cipher_State* cs, Connection_Side, Test::Result& result) { + [&](Cipher_State* cs, Journaling_Secret_Logger*, Connection_Side, Test::Result& result) { result.confirm("self-compatibility", cs->is_compatible_with(cipher)); result.confirm("partially defined state is compatible with suites using the same hash", cs->is_compatible_with(Ciphersuite::from_name("CHACHA20_POLY1305_SHA256").value()) && @@ -583,9 +669,8 @@ std::vector test_secret_derivation_rfc8448_rtt0() { }), CHECK_both("calculate the early traffic secrets", - [&](Cipher_State* cs, Connection_Side side, Test::Result& result) { - Journaling_Secret_Logger sl; - cs->advance_with_client_hello(th_client_hello, sl); + [&](Cipher_State* cs, Journaling_Secret_Logger* sl, Connection_Side side, Test::Result& result) { + cs->advance_with_client_hello(th_client_hello, *sl); result.require("early key export is possible", cs->can_export_keys()); result.test_eq("early key export produces expected result", cs->export_key(early_export_label, early_export_context, 16), @@ -600,98 +685,127 @@ std::vector test_secret_derivation_rfc8448_rtt0() { !cs->must_expect_unprotected_alert_traffic()); } + result.test_eq("logged early secrets", sl->secrets.size(), 1); + result.require("has early exporter secret", sl->secrets.contains("EARLY_EXPORTER_MASTER_SECRET")); + result.test_eq( + "early exporter secret", sl->secrets.at("EARLY_EXPORTER_MASTER_SECRET"), early_exporter_secret); + // TODO: Once 0-RTT traffic is implemented this will likely allow handling of // application traffic in this state. result.confirm("can not yet write application data", !cs->can_encrypt_application_traffic()); result.confirm("can not yet read application data", !cs->can_decrypt_application_traffic()); }), - CHECK_both("handshake traffic after PSK", - [&](Cipher_State* cs, Connection_Side side, Test::Result& result) { - Journaling_Secret_Logger sl; - cs->advance_with_server_hello(cipher, secure_vector(shared_secret), th_server_hello, sl); - - // decrypt encrypted extensions from server - encrypted_extensions.xxcrypt(result, cs, side); - - // TODO: Handling of early traffic is left out as 0-RTT is not implemented yet. - - // validate the MAC we receive in server Finished message - const auto expected_server_mac = Botan::hex_decode( - "48 d3 e0 e1 b3 d9 07 c6 ac ff 14 5e 16 09 03 88" - "c7 7b 05 c0 50 b6 34 ab 1a 88 bb d0 dd 1a 34 b2"); - if(side == Connection_Side::Client) { - result.confirm("expecting the correct MAC for server finished", - cs->verify_peer_finished_mac(th_pre_server_finished, expected_server_mac)); - result.confirm("Clients don't expect unprotected alerts after server hello", - !cs->must_expect_unprotected_alert_traffic()); - } else { - result.test_eq("expecting the correct MAC for server finished", - cs->finished_mac(th_pre_server_finished), - expected_server_mac); - result.confirm("Servers must expect unprotected alerts in response to their server hello", - cs->must_expect_unprotected_alert_traffic()); - } - - result.confirm("cannot read application data", !cs->can_decrypt_application_traffic()); - result.confirm("cannot write application data", !cs->can_encrypt_application_traffic()); + CHECK_both( + "handshake traffic after PSK", + [&](Cipher_State* cs, Journaling_Secret_Logger* sl, Connection_Side side, Test::Result& result) { + cs->advance_with_server_hello(cipher, secure_vector(shared_secret), th_server_hello, *sl); + + // decrypt encrypted extensions from server + encrypted_extensions.xxcrypt(result, cs, side); + + // check the logged key material + result.test_eq("contains expected number of keys", sl->secrets.size(), 3); + result.require("has client handshake traffic secret", + sl->secrets.contains("CLIENT_HANDSHAKE_TRAFFIC_SECRET")); + result.require("has server handshake traffic secret", + sl->secrets.contains("SERVER_HANDSHAKE_TRAFFIC_SECRET")); + result.test_eq("client handshake traffic secret", + sl->secrets.at("CLIENT_HANDSHAKE_TRAFFIC_SECRET"), + client_handshake_traffic_secret); + result.test_eq("server handshake traffic secret", + sl->secrets.at("SERVER_HANDSHAKE_TRAFFIC_SECRET"), + server_handshake_traffic_secret); + + // TODO: Handling of early traffic is left out as 0-RTT is not implemented yet. + + // validate the MAC we receive in server Finished message + const auto expected_server_mac = Botan::hex_decode( + "48 d3 e0 e1 b3 d9 07 c6 ac ff 14 5e 16 09 03 88" + "c7 7b 05 c0 50 b6 34 ab 1a 88 bb d0 dd 1a 34 b2"); + if(side == Connection_Side::Client) { + result.confirm("expecting the correct MAC for server finished", + cs->verify_peer_finished_mac(th_pre_server_finished, expected_server_mac)); + result.confirm("Clients don't expect unprotected alerts after server hello", + !cs->must_expect_unprotected_alert_traffic()); + } else { + result.test_eq("expecting the correct MAC for server finished", + cs->finished_mac(th_pre_server_finished), + expected_server_mac); + result.confirm("Servers must expect unprotected alerts in response to their server hello", + cs->must_expect_unprotected_alert_traffic()); + } + + result.confirm("cannot read application data", !cs->can_decrypt_application_traffic()); + result.confirm("cannot write application data", !cs->can_encrypt_application_traffic()); + + // advance Cipher_State with client_hello...server_Finished + // (allows receiving of application data, but no such sending) + result.test_no_throw("state advancement is legal", + [&] { cs->advance_with_server_finished(th_server_finished, *sl); }); + + if(side == Connection_Side::Client) { + result.confirm("can read application data", cs->can_decrypt_application_traffic()); + result.confirm("cannot write application data", !cs->can_encrypt_application_traffic()); + result.confirm("Clients don't expect unprotected alerts after server hello", + !cs->must_expect_unprotected_alert_traffic()); + } else { + result.confirm("cannot read application data", !cs->can_decrypt_application_traffic()); + result.confirm("can write application data", cs->can_encrypt_application_traffic()); + result.confirm("Servers must expect unprotected alerts in response to their first flight", + cs->must_expect_unprotected_alert_traffic()); + } + + // check the logged key material + result.test_eq("contains expected number of keys", sl->secrets.size(), 6); + result.require("has client traffic secret", sl->secrets.contains("CLIENT_TRAFFIC_SECRET_0")); + result.require("has server traffic secret", sl->secrets.contains("SERVER_TRAFFIC_SECRET_0")); + result.require("has exporter secret", sl->secrets.contains("EXPORTER_SECRET")); + result.test_eq("client traffic secret", sl->secrets.at("CLIENT_TRAFFIC_SECRET_0"), client_traffic_secret); + result.test_eq("server traffic secret", sl->secrets.at("SERVER_TRAFFIC_SECRET_0"), server_traffic_secret); + result.test_eq("exporter secret", sl->secrets.at("EXPORTER_SECRET"), exporter_secret); + + // generate the MAC for the client Finished message + const auto expected_client_mac = Botan::hex_decode( + "72 30 a9 c9 52 c2 5c d6 13 8f c5 e6 62 83 08 c4" + "1c 53 35 dd 81 b9 f9 6b ce a5 0f d3 2b da 41 6d"); + if(side == Connection_Side::Client) { + result.test_eq("generating the correct MAC for client finished", + cs->finished_mac(th_end_of_early_data), + expected_client_mac); + } else { + result.confirm("verify the correct MAC for client finished", + cs->verify_peer_finished_mac(th_end_of_early_data, expected_client_mac)); + } + + // encrypt client Finished message by client + // (under the client handshake traffic secret) + encrypted_client_finished_message.xxcrypt(result, cs, side); + }), - // advance Cipher_State with client_hello...server_Finished - // (allows receiving of application data, but no such sending) + CHECK_both("application traffic after PSK", + [&](Cipher_State* cs, Journaling_Secret_Logger*, Connection_Side side, Test::Result& result) { + // advance Cipher_State with client_hello...client_Finished + // (allows generation of resumption PSKs) result.test_no_throw("state advancement is legal", - [&] { cs->advance_with_server_finished(th_server_finished, sl); }); - - if(side == Connection_Side::Client) { - result.confirm("can read application data", cs->can_decrypt_application_traffic()); - result.confirm("cannot write application data", !cs->can_encrypt_application_traffic()); - result.confirm("Clients don't expect unprotected alerts after server hello", - !cs->must_expect_unprotected_alert_traffic()); - } else { - result.confirm("cannot read application data", !cs->can_decrypt_application_traffic()); - result.confirm("can write application data", cs->can_encrypt_application_traffic()); - result.confirm("Servers must expect unprotected alerts in response to their first flight", - cs->must_expect_unprotected_alert_traffic()); - } + [&] { cs->advance_with_client_finished(th_client_finished); }); - // generate the MAC for the client Finished message - const auto expected_client_mac = Botan::hex_decode( - "72 30 a9 c9 52 c2 5c d6 13 8f c5 e6 62 83 08 c4" - "1c 53 35 dd 81 b9 f9 6b ce a5 0f d3 2b da 41 6d"); - if(side == Connection_Side::Client) { - result.test_eq("generating the correct MAC for client finished", - cs->finished_mac(th_end_of_early_data), - expected_client_mac); - } else { - result.confirm("verify the correct MAC for client finished", - cs->verify_peer_finished_mac(th_end_of_early_data, expected_client_mac)); - } + result.confirm("can read application data", cs->can_decrypt_application_traffic()); + result.confirm("can write application data", cs->can_encrypt_application_traffic()); + result.confirm("doesn't need to expect unprotected alerts", + !cs->must_expect_unprotected_alert_traffic()); + result.confirm("can export key material", cs->can_export_keys()); + result.test_eq("key export produces expected result", + cs->export_key(export_label, export_context, 16), + expected_key_export); - // encrypt client Finished message by client - // (under the client handshake traffic secret) - encrypted_client_finished_message.xxcrypt(result, cs, side); - }), + // encrypt application data by client + encrypted_application_data_client.xxcrypt(result, cs, side); - CHECK_both("application traffic after PSK", [&](Cipher_State* cs, Connection_Side side, Test::Result& result) { - // advance Cipher_State with client_hello...client_Finished - // (allows generation of resumption PSKs) - result.test_no_throw("state advancement is legal", - [&] { cs->advance_with_client_finished(th_client_finished); }); - - result.confirm("can read application data", cs->can_decrypt_application_traffic()); - result.confirm("can write application data", cs->can_encrypt_application_traffic()); - result.confirm("doesn't need to expect unprotected alerts", !cs->must_expect_unprotected_alert_traffic()); - result.confirm("can export key material", cs->can_export_keys()); - result.test_eq("key export produces expected result", - cs->export_key(export_label, export_context, 16), - expected_key_export); - - // encrypt application data by client - encrypted_application_data_client.xxcrypt(result, cs, side); - - // decrypt application data from server - // (encrypted under the application traffic secret -- and a new sequence number) - encrypted_application_data_server.xxcrypt(result, cs, side); - })}); + // decrypt application data from server + // (encrypted under the application traffic secret -- and a new sequence number) + encrypted_application_data_server.xxcrypt(result, cs, side); + })}); } } // namespace From c284815e6464324975fa402fccb2f7369e78eaee Mon Sep 17 00:00:00 2001 From: Rene Meusel Date: Wed, 22 May 2024 15:13:49 +0200 Subject: [PATCH 4/6] Fix: rewrap some comments --- src/lib/tls/tls13/tls_cipher_state.cpp | 30 +++++++++++--------------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/src/lib/tls/tls13/tls_cipher_state.cpp b/src/lib/tls/tls13/tls_cipher_state.cpp index 9433edd0b81..2284f3a3c1c 100644 --- a/src/lib/tls/tls13/tls_cipher_state.cpp +++ b/src/lib/tls/tls13/tls_cipher_state.cpp @@ -151,10 +151,9 @@ void Cipher_State::advance_with_client_hello(const Transcript_Hash& transcript_h m_exporter_master_secret = derive_secret(m_early_secret, "e exp master", transcript_hash); // draft-thomson-tls-keylogfile-00 Section 3.1 - // An implementation of TLS 1.3 use - // the label "EARLY_EXPORTER_MASTER_SECRET" - // to identify the secret - // that is using for early exporters + // An implementation of TLS 1.3 use the label + // "EARLY_EXPORTER_MASTER_SECRET" to identify the secret that is using for + // early exporters loggger.maybe_log_secret("EARLY_EXPORTER_MASTER_SECRET", m_exporter_master_secret); m_salt = derive_secret(m_early_secret, "derived", empty_hash()); @@ -172,11 +171,9 @@ void Cipher_State::advance_with_server_finished(const Transcript_Hash& transcrip auto server_application_traffic_secret = derive_secret(master_secret, "s ap traffic", transcript_hash); // draft-thomson-tls-keylogfile-00 Section 3.1 - // An implementation of TLS 1.3 use - // the label "CLIENT_TRAFFIC_SECRET_0" - // and "SERVER_TRAFFIC_SECRET_0" - // to identify the secrets are using to protect - // the connection. + // An implementation of TLS 1.3 use the label "CLIENT_TRAFFIC_SECRET_0" + // and "SERVER_TRAFFIC_SECRET_0" to identify the secrets are using to + // protect the connection. loggger.maybe_log_secret("CLIENT_TRAFFIC_SECRET_0", client_application_traffic_secret); loggger.maybe_log_secret("SERVER_TRAFFIC_SECRET_0", server_application_traffic_secret); @@ -196,10 +193,9 @@ void Cipher_State::advance_with_server_finished(const Transcript_Hash& transcrip m_exporter_master_secret = derive_secret(master_secret, "exp master", transcript_hash); // draft-thomson-tls-keylogfile-00 Section 3.1 - // An implementation of TLS 1.3 use - // the label "EXPORTER_SECRET" - // to identify the secret - // that is used in generating exporters(rfc8446 Section 7.5). + // An implementation of TLS 1.3 use the label "EXPORTER_SECRET" to + // identify the secret that is used in generating exporters(rfc8446 + // Section 7.5). loggger.maybe_log_secret("EXPORTER_SECRET", m_exporter_master_secret); m_state = State::ServerApplicationTraffic; @@ -501,11 +497,9 @@ void Cipher_State::advance_with_server_hello(const Ciphersuite& cipher, const auto server_handshake_traffic_secret = derive_secret(handshake_secret, "s hs traffic", transcript_hash); // draft-thomson-tls-keylogfile-00 Section 3.1 - // An implementation of TLS 1.3 use - // the label "CLIENT_HANDSHAKE_TRAFFIC_SECRET" - // and "SERVER_HANDSHAKE_TRAFFIC_SECRET" - // to identify the secrets - // are using to protect handshake messages. + // An implementation of TLS 1.3 use the label + // "CLIENT_HANDSHAKE_TRAFFIC_SECRET" and "SERVER_HANDSHAKE_TRAFFIC_SECRET" + // to identify the secrets are using to protect handshake messages. loggger.maybe_log_secret("CLIENT_HANDSHAKE_TRAFFIC_SECRET", client_handshake_traffic_secret); loggger.maybe_log_secret("SERVER_HANDSHAKE_TRAFFIC_SECRET", server_handshake_traffic_secret); From 87589eb8d074b724e148348ac17a62079bb5f278 Mon Sep 17 00:00:00 2001 From: Alexey Volokitin Date: Thu, 23 May 2024 11:19:20 +0300 Subject: [PATCH 5/6] some improvements fixed build of test of sslkeylogfile updated some function description reverted default value of Policy::allow_ssl_key_log_file to false --- src/examples/tls_ssl_key_log_file.cpp | 8 ++++---- src/lib/tls/tls_callbacks.h | 2 ++ src/lib/tls/tls_policy.cpp | 3 +-- src/lib/tls/tls_policy.h | 2 ++ 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/examples/tls_ssl_key_log_file.cpp b/src/examples/tls_ssl_key_log_file.cpp index b0fc239edee..7d1f44a38f2 100644 --- a/src/examples/tls_ssl_key_log_file.cpp +++ b/src/examples/tls_ssl_key_log_file.cpp @@ -118,8 +118,8 @@ class BotanTLSCallbacksProxy : public Botan::TLS::Callbacks { void tls_alert(Botan::TLS::Alert alert) override { BOTAN_UNUSED(alert); } void tls_ssl_key_log_data(std::string_view label, - const std::span& client_random, - const std::span& secret) const override { + std::span client_random, + std::span secret) const override { parent.tls_ssl_key_log_data(label, client_random, secret); } @@ -182,8 +182,8 @@ class DtlsConnection : public Botan::TLS::Callbacks { } void tls_ssl_key_log_data(std::string_view label, - const std::span& client_random, - const std::span& secret) const override { + std::span client_random, + std::span secret) const override { std::ofstream stream; stream.open("test.skl", std::ofstream::out | std::ofstream::app); stream << label << " " << Botan::hex_encode(client_random.data(), client_random.size()) << " " diff --git a/src/lib/tls/tls_callbacks.h b/src/lib/tls/tls_callbacks.h index 7b1fccd5cef..1aeb7d7ba6a 100644 --- a/src/lib/tls/tls_callbacks.h +++ b/src/lib/tls/tls_callbacks.h @@ -566,6 +566,8 @@ class BOTAN_PUBLIC_API(2, 0) Callbacks { * * Useful to implement the SSLKEYLOGFILE for connection debugging as * specified in ietf.org/archive/id/draft-thomson-tls-keylogfile-00.html + * + * Invoked if Policy::allow_ssl_key_log_file returns true. * * Default implementation simply ignores the inputs. * diff --git a/src/lib/tls/tls_policy.cpp b/src/lib/tls/tls_policy.cpp index 70e43488f32..55f979a547f 100644 --- a/src/lib/tls/tls_policy.cpp +++ b/src/lib/tls/tls_policy.cpp @@ -22,8 +22,7 @@ namespace Botan::TLS { bool Policy::allow_ssl_key_log_file() const { - std::string data; - return Botan::OS::read_env_variable(data, "SSLKEYLOGFILE"); + return false; } std::vector Policy::allowed_signature_schemes() const { diff --git a/src/lib/tls/tls_policy.h b/src/lib/tls/tls_policy.h index 1aa3863e1ee..259af56cf24 100644 --- a/src/lib/tls/tls_policy.h +++ b/src/lib/tls/tls_policy.h @@ -33,6 +33,8 @@ class BOTAN_PUBLIC_API(2, 0) Policy { public: /** * Allow ssl key log file + * @note If function returns true, then Callbacks::tls_ssl_key_log_data + * will be invoked containing secret information for logging purposes */ virtual bool allow_ssl_key_log_file() const; From caa04f440c13e96a4df8b4fc0346ab5463516557 Mon Sep 17 00:00:00 2001 From: Alexey Volokitin Date: Thu, 23 May 2024 15:38:11 +0300 Subject: [PATCH 6/6] logging of updating keys(CLIENT_TRAFFIC_SECRET_N and SERVER_TRAFFIC_SECRET_N where N>0) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: René Meusel --- src/lib/tls/tls13/tls_channel_impl_13.cpp | 4 +-- src/lib/tls/tls13/tls_cipher_state.cpp | 17 ++++++++++-- src/lib/tls/tls13/tls_cipher_state.h | 7 +++-- src/lib/tls/tls_policy.cpp | 1 + src/tests/test_tls_cipher_state.cpp | 33 ++++++++++++++++++++--- 5 files changed, 53 insertions(+), 9 deletions(-) diff --git a/src/lib/tls/tls13/tls_channel_impl_13.cpp b/src/lib/tls/tls13/tls_channel_impl_13.cpp index c0bb8dd788c..243f1a23e1c 100644 --- a/src/lib/tls/tls13/tls_channel_impl_13.cpp +++ b/src/lib/tls/tls13/tls_channel_impl_13.cpp @@ -198,7 +198,7 @@ void Channel_Impl_13::handle(const Key_Update& key_update) { throw Unexpected_Message("Unexpected additional post-handshake message data found in record"); } - m_cipher_state->update_read_keys(); + m_cipher_state->update_read_keys(*this); // TODO: introduce some kind of rate limit of key updates, otherwise we // might be forced into an endless loop of key updates. @@ -319,7 +319,7 @@ void Channel_Impl_13::update_traffic_keys(bool request_peer_update) { BOTAN_STATE_CHECK(is_handshake_complete()); BOTAN_ASSERT_NONNULL(m_cipher_state); send_post_handshake_message(Key_Update(request_peer_update)); - m_cipher_state->update_write_keys(); + m_cipher_state->update_write_keys(*this); } void Channel_Impl_13::send_record(Record_Type record_type, const std::vector& record) { diff --git a/src/lib/tls/tls13/tls_cipher_state.cpp b/src/lib/tls/tls13/tls_cipher_state.cpp index 2284f3a3c1c..a5c91c29fab 100644 --- a/src/lib/tls/tls13/tls_cipher_state.cpp +++ b/src/lib/tls/tls13/tls_cipher_state.cpp @@ -98,6 +98,7 @@ #include #include +#include #include #include #include @@ -444,6 +445,8 @@ Cipher_State::Cipher_State(Connection_Side whoami, std::string_view hash_functio m_salt(m_hash->output_length(), 0x00), m_write_seq_no(0), m_read_seq_no(0), + m_write_key_update_count(0), + m_read_key_update_count(0), m_ticket_nonce(0) {} Cipher_State::~Cipher_State() = default; @@ -593,20 +596,30 @@ std::vector Cipher_State::empty_hash() const { return m_hash->final_stdvec(); } -void Cipher_State::update_read_keys() { +void Cipher_State::update_read_keys(const Secret_Logger& logger) { BOTAN_ASSERT_NOMSG(m_state == State::ServerApplicationTraffic || m_state == State::Completed); m_read_application_traffic_secret = hkdf_expand_label(m_read_application_traffic_secret, "traffic upd", {}, m_hash->output_length()); + const auto secret_label = fmt("{}_TRAFFIC_SECRET_{}", + m_connection_side == Connection_Side::Server ? "CLIENT" : "SERVER", + ++m_read_key_update_count); + logger.maybe_log_secret(secret_label, m_read_application_traffic_secret); + derive_read_traffic_key(m_read_application_traffic_secret); } -void Cipher_State::update_write_keys() { +void Cipher_State::update_write_keys(const Secret_Logger& logger) { BOTAN_ASSERT_NOMSG(m_state == State::ServerApplicationTraffic || m_state == State::Completed); m_write_application_traffic_secret = hkdf_expand_label(m_write_application_traffic_secret, "traffic upd", {}, m_hash->output_length()); + const auto secret_label = fmt("{}_TRAFFIC_SECRET_{}", + m_connection_side == Connection_Side::Server ? "SERVER" : "CLIENT", + ++m_write_key_update_count); + logger.maybe_log_secret(secret_label, m_write_application_traffic_secret); + derive_write_traffic_key(m_write_application_traffic_secret); } diff --git a/src/lib/tls/tls13/tls_cipher_state.h b/src/lib/tls/tls13/tls_cipher_state.h index 9a4d09feaa8..c14955a8c11 100644 --- a/src/lib/tls/tls13/tls_cipher_state.h +++ b/src/lib/tls/tls13/tls_cipher_state.h @@ -237,7 +237,7 @@ class BOTAN_TEST_API Cipher_State { * Note that this must not be called before the connection is ready for * application traffic. */ - void update_read_keys(); + void update_read_keys(const Secret_Logger& channel); /** * Updates the key material used for encrypting data @@ -246,7 +246,7 @@ class BOTAN_TEST_API Cipher_State { * Note that this must not be called before the connection is ready for * application traffic. */ - void update_write_keys(); + void update_write_keys(const Secret_Logger& channel); /** * Remove handshake/traffic secrets for decrypting data from peer @@ -328,6 +328,9 @@ class BOTAN_TEST_API Cipher_State { uint64_t m_write_seq_no; uint64_t m_read_seq_no; + uint32_t m_write_key_update_count; + uint32_t m_read_key_update_count; + uint16_t m_ticket_nonce; secure_vector m_finished_key; diff --git a/src/lib/tls/tls_policy.cpp b/src/lib/tls/tls_policy.cpp index 55f979a547f..2e2c4d05580 100644 --- a/src/lib/tls/tls_policy.cpp +++ b/src/lib/tls/tls_policy.cpp @@ -622,6 +622,7 @@ void Policy::print(std::ostream& o) const { print_bool(o, "allow_tls12", allow_tls12()); print_bool(o, "allow_tls13", allow_tls13()); print_bool(o, "allow_dtls12", allow_dtls12()); + print_bool(o, "allow_ssl_key_log_file", allow_ssl_key_log_file()); print_vec(o, "ciphers", allowed_ciphers()); print_vec(o, "macs", allowed_macs()); print_vec(o, "signature_hashes", allowed_signature_hashes()); diff --git a/src/tests/test_tls_cipher_state.cpp b/src/tests/test_tls_cipher_state.cpp index 7c05409f1f6..b1b81d7a76b 100644 --- a/src/tests/test_tls_cipher_state.cpp +++ b/src/tests/test_tls_cipher_state.cpp @@ -144,6 +144,16 @@ std::vector test_secret_derivation_rfc8448_rtt1() { "a1 1a f9 f0 55 31 f8 56 ad 47 11 6b 45 a9 50 32" "82 04 b4 f4 4b fb 6b 3a 4b 4f 1f 3f cb 63 16 43"); + // application traffic secret (1) for the client (not in RFC 8448) + const auto updated_client_traffic_secret = Botan::hex_decode( + "fc df cc 72 72 5a ae e4 8b f6 4e 4f d8 b7 49 cd" + "bd ba b3 9d 90 da 0b 26 e2 24 5c a6 ea 16 72 07"); + + // application traffic secret (1) for the server (not in RFC 8448) + const auto updated_server_traffic_secret = Botan::hex_decode( + "51 92 1b 8a a3 00 19 76 eb 40 1d 0a 43 19 a8 51" + "64 16 a6 c5 60 01 a3 57 e5 d1 62 03 1e 84 f9 16"); + // encrypted with server_handshake_traffic_secret const auto encrypted_extensions = RFC8448_TestData("encrypted_extensions", @@ -463,9 +473,26 @@ std::vector test_secret_derivation_rfc8448_rtt1() { }), CHECK_both("key update", - [&](Cipher_State* cs, Journaling_Secret_Logger*, Connection_Side, Test::Result& result) { - cs->update_read_keys(); - cs->update_write_keys(); + [&](Cipher_State* cs, Journaling_Secret_Logger* sl, Connection_Side side, Test::Result& result) { + const auto read_label = + side == Connection_Side::Client ? "SERVER_TRAFFIC_SECRET_1" : "CLIENT_TRAFFIC_SECRET_1"; + const auto write_label = + side == Connection_Side::Client ? "CLIENT_TRAFFIC_SECRET_1" : "SERVER_TRAFFIC_SECRET_1"; + + cs->update_read_keys(*sl); + result.test_eq("read secret update is here", sl->secrets.size(), 6); + result.require("has new read traffic secret", sl->secrets.contains(read_label)); + + cs->update_write_keys(*sl); + result.test_eq("write secret update is here", sl->secrets.size(), 7); + result.require("has new write traffic secret", sl->secrets.contains(write_label)); + + result.test_eq("client traffic secret (1)", + sl->secrets.at("CLIENT_TRAFFIC_SECRET_1"), + updated_client_traffic_secret); + result.test_eq("server traffic secret (1)", + sl->secrets.at("SERVER_TRAFFIC_SECRET_1"), + updated_server_traffic_secret); result.confirm("can encrypt application traffic", cs->can_encrypt_application_traffic()); }),