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..7d1f44a38f2 --- /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, + std::span client_random, + 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, + 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()) << " " + << 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_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_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 340b4e8901a..206b905f6d7 100644 --- a/src/lib/tls/tls13/tls_cipher_state.cpp +++ b/src/lib/tls/tls13/tls_cipher_state.cpp @@ -98,9 +98,11 @@ #include #include +#include #include #include #include +#include namespace Botan::TLS { @@ -119,10 +121,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 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); + cs->advance_with_server_hello(cipher, std::move(shared_secret), transcript_hash, loggger); return cs; } @@ -135,7 +138,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) { +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); @@ -148,13 +151,19 @@ 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 + 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); 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 Secret_Logger& loggger) { BOTAN_ASSERT_NOMSG(m_state == State::HandshakeTraffic); const auto master_secret = hkdf_extract(secure_vector(m_hash->output_length(), 0x00)); @@ -162,6 +171,13 @@ 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. + 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 // was seen and the handshake can be considered finished. @@ -177,6 +193,12 @@ 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). + loggger.maybe_log_secret("EXPORTER_SECRET", m_exporter_master_secret); + m_state = State::ServerApplicationTraffic; } @@ -424,6 +446,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; @@ -461,7 +485,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 Secret_Logger& loggger) { BOTAN_ASSERT_NOMSG(m_state == State::EarlyTraffic); BOTAN_ASSERT_NOMSG(!m_encrypt); BOTAN_ASSERT_NOMSG(!m_decrypt); @@ -475,6 +500,13 @@ 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. + 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); derive_write_traffic_key(server_handshake_traffic_secret, true); @@ -565,20 +597,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 8ec1af8de3b..c14955a8c11 100644 --- a/src/lib/tls/tls13/tls_cipher_state.h +++ b/src/lib/tls/tls13/tls_cipher_state.h @@ -27,6 +27,7 @@ class HKDF_Expand; namespace Botan::TLS { class Ciphersuite; +class Secret_Logger; /** * This class implements the key schedule for TLS 1.3 as described in RFC 8446 7.1. @@ -82,25 +83,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 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); + void advance_with_client_hello(const Transcript_Hash& transcript_hash, const Secret_Logger& channel); /** * 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 Secret_Logger& channel); /** * 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 Secret_Logger& channel); /** * Transition to the final internal state allowing to create resumptions. @@ -234,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 @@ -243,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 @@ -325,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/tls13/tls_client_impl_13.cpp b/src/lib/tls/tls13/tls_client_impl_13.cpp index 112b78a2571..922ad67c939 100644 --- a/src/lib/tls/tls13/tls_client_impl_13.cpp +++ b/src/lib/tls/tls13/tls_client_impl_13.cpp @@ -322,12 +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()); - 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(), *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()); + 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); @@ -566,7 +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()); + m_cipher_state->advance_with_server_finished(m_transcript_hash.current(), *this); auto flight = aggregate_handshake_messages(); @@ -649,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 f8f376fcaef..7b7c12aa5b0 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 @@ -83,6 +84,7 @@ class Client_Impl_13 : public Channel_Impl_13 { 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 b6edc9daabd..50f2719c073 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::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); + } +} + void Server_Impl_13::downgrade() { BOTAN_ASSERT_NOMSG(expects_downgrade()); @@ -287,14 +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()); + 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()); + 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()); + m_side, my_keyshare->take_shared_secret(), cipher, m_transcript_hash.current(), *this); } }(); @@ -375,7 +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()); + 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 0b543cad957..f78a5cf4c76 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 @@ -52,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 4a889c5ccdb..1aeb7d7ba6a 100644 --- a/src/lib/tls/tls_callbacks.h +++ b/src/lib/tls/tls_callbacks.h @@ -560,6 +560,28 @@ 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 + * + * Invoked if Policy::allow_ssl_key_log_file returns true. + * + * 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, + std::span client_random, + 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..2e2c4d05580 100644 --- a/src/lib/tls/tls_policy.cpp +++ b/src/lib/tls/tls_policy.cpp @@ -14,12 +14,17 @@ #include #include #include +#include #include #include #include namespace Botan::TLS { +bool Policy::allow_ssl_key_log_file() const { + return false; +} + std::vector Policy::allowed_signature_schemes() const { std::vector schemes; @@ -617,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/lib/tls/tls_policy.h b/src/lib/tls/tls_policy.h index 6e384b42941..259af56cf24 100644 --- a/src/lib/tls/tls_policy.h +++ b/src/lib/tls/tls_policy.h @@ -31,6 +31,13 @@ namespace TLS { */ 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; + /** * Returns a list of ciphers we are willing to negotiate, in * order of preference. @@ -629,6 +636,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..b1b81d7a76b 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,13 +21,26 @@ using Test = Botan_Tests::Test; using namespace Botan; using namespace Botan::TLS; -decltype(auto) make_CHECK_both(Cipher_State* cs_client, Cipher_State* cs_server) { +class Journaling_Secret_Logger : public Secret_Logger { + public: + 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, + 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))}; }; } @@ -110,6 +124,36 @@ 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"); + + // 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", @@ -256,16 +300,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_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); + 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); + 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", @@ -282,13 +343,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()); @@ -311,7 +372,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, *sl); }); if(side == Connection_Side::Client) { result.confirm("can read application data", cs->can_decrypt_application_traffic()); @@ -325,6 +386,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" @@ -374,7 +445,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})); @@ -394,7 +465,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 */); @@ -402,14 +473,31 @@ std::vector test_secret_derivation_rfc8448_rtt1() { }), CHECK_both("key update", - [&](Cipher_State* cs, 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()); }), - 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()); @@ -472,6 +560,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"; @@ -543,6 +655,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()), @@ -552,18 +667,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()) && @@ -576,8 +696,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); + [&](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), @@ -592,97 +712,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) { - cs->advance_with_server_hello(cipher, secure_vector(shared_secret), th_server_hello); - - // 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); }); - - 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 diff --git a/src/tests/test_tls_record_layer_13.cpp b/src/tests/test_tls_record_layer_13.cpp index 6b1c2951633..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,7 +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(); - return TLS::Cipher_State::init_with_server_hello(side, std::move(shared_secret), cipher, transcript_hash); + 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() { @@ -655,8 +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 + Mocked_Secret_Logger logger; cs->advance_with_server_finished( - Botan::hex_decode("e1935a480babfc4403b2517f0ad414bed0ca51fa671e2061804afa78fd71d55c")); + Botan::hex_decode("e1935a480babfc4403b2517f0ad414bed0ca51fa671e2061804afa78fd71d55c"), logger); cs->advance_with_client_finished( Botan::hex_decode("305e4a0a7cee581b282c571b251b20138a1a6a21918937a6bb95b1e9ba1b5cac"));