Skip to content

Commit

Permalink
Merge pull request #4043 from volok-aleksej/ssl_key_log_file
Browse files Browse the repository at this point in the history
Add callback to export secrets for SSLKEYLOGFILE
  • Loading branch information
randombit authored May 23, 2024
2 parents 8a77f73 + caa04f4 commit 9a5a367
Show file tree
Hide file tree
Showing 17 changed files with 788 additions and 129 deletions.
338 changes: 338 additions & 0 deletions src/examples/tls_ssl_key_log_file.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,338 @@
#include <condition_variable>
#include <fstream>
#include <iostream>
#include <thread>

#include <botan/auto_rng.h>
#include <botan/certstor_system.h>
#include <botan/credentials_manager.h>
#include <botan/data_src.h>
#include <botan/hex.h>
#include <botan/pk_keys.h>
#include <botan/pkcs8.h>
#include <botan/tls_callbacks.h>
#include <botan/tls_channel.h>
#include <botan/tls_client.h>
#include <botan/tls_policy.h>
#include <botan/tls_server.h>
#include <botan/tls_session_manager_memory.h>

#if defined(BOTAN_TARGET_OS_HAS_SOCKETS)
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#endif

#define SERVER_PORT 5060
#define CLIENT_PORT 5070

class Client_Credential : public Botan::Credentials_Manager {
public:
Client_Credential() = default;

std::vector<Botan::Certificate_Store*> 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<Botan::Certificate_Store*> trusted_certificate_authorities(const std::string&,
const std::string&) override {
return {&m_cert_store};
}

std::vector<Botan::X509_Certificate> cert_chain(
const std::vector<std::string>& cert_key_types,
const std::vector<Botan::AlgorithmIdentifier>& 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<Botan::X509_Certificate> 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<Botan::Private_Key> 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<Botan::Private_Key> m_key;
std::vector<Botan::X509_Certificate> 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<const uint8_t> data) override { parent.tls_emit_data(data); }

void tls_record_received(uint64_t seq_no, std::span<const uint8_t> 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<const uint8_t> client_random,
std::span<const uint8_t> 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<Botan::TLS::Channel> dtls_channel;
std::function<void()> 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<BotanTLSCallbacksProxy>(*this);
auto rng = std::make_shared<Botan::AutoSeeded_RNG>();
auto session_mgr = std::make_shared<Botan::TLS::Session_Manager_In_Memory>(rng);
if(is_server) {
auto policy = std::make_shared<Allow_Secrets_Policy>();
auto creds = std::make_shared<Server_Credential>();
dtls_channel =
std::make_unique<Botan::TLS::Server>(tls_callbacks_proxy, session_mgr, creds, policy, rng, true);
} else {
auto policy = std::make_shared<Botan::TLS::Datagram_Policy>();
auto creds = std::make_shared<Client_Credential>();
dtls_channel =
std::make_unique<Botan::TLS::Client>(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<const uint8_t> data) override {
#if defined(BOTAN_TARGET_OS_HAS_SOCKETS)
sendto(fd, data.data(), data.size(), 0, reinterpret_cast<const sockaddr*>(&remote_addr), sizeof(sockaddr_in));
#else
// send data to the other side
// ...
#endif
}

void tls_record_received(uint64_t seq_no, std::span<const uint8_t> 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<const uint8_t> client_random,
std::span<const uint8_t> 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<const uint8_t> data) { dtls_channel->received_data(data); }

void set_activated_callback(std::function<void()> 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<void(std::shared_ptr<DtlsConnection> 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<void*>(&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<sockaddr*>(&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<DtlsConnection>("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<sockaddr*>(&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<void(std::shared_ptr<DtlsConnection> 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<void*>(&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<sockaddr*>(&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<DtlsConnection>("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<sockaddr*>(&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<std::shared_ptr<DtlsConnection>> connections;
std::thread server(server_proc, [&](std::shared_ptr<DtlsConnection> 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<DtlsConnection> 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<bool> 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;
}
16 changes: 16 additions & 0 deletions src/lib/tls/tls12/tls_client_impl_12.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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 =
Expand Down
16 changes: 16 additions & 0 deletions src/lib/tls/tls12/tls_server_impl_12.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit 9a5a367

Please sign in to comment.