Skip to content

Commit

Permalink
tls: Add a way to inspect peer certificate chain
Browse files Browse the repository at this point in the history
Closes #2630
  • Loading branch information
nielsdevreede-rl authored and xemul committed Feb 4, 2025
1 parent 5b95d1d commit 2c941b9
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 1 deletion.
13 changes: 13 additions & 0 deletions include/seastar/net/tls.hh
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,19 @@ namespace tls {
*/
future<std::vector<subject_alt_name>> get_alt_name_information(connected_socket& socket, std::unordered_set<subject_alt_name_type> types = {});

using certificate_data = std::vector<uint8_t>;

/**
* Get the raw certificate (chain) that the connected peer is using.
* This function forces the TLS handshake. If the handshake didn't happen before the
* call to 'get_peer_certificate_chain' it will be completed when the returned future
* will become ready.
* The function returns the certificate chain on success. If the peer didn't send the
* certificate during the handshake, the function returns an empty certificate chain.
* If the socket is not connected the system_error exception will be thrown.
*/
future<std::vector<certificate_data>> get_peer_certificate_chain(connected_socket& socket);

/**
* Checks if the socket was connected using session resume.
* Will force handshake if not already done.
Expand Down
23 changes: 23 additions & 0 deletions src/net/tls.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1790,6 +1790,22 @@ class session : public enable_lw_shared_from_this<session> {
}, std::move(types));
}

future<std::vector<certificate_data>> get_peer_certificate_chain() {
return state_checked_access([this] {
unsigned int list_size = 0;
const gnutls_datum_t* client_cert_list = gnutls_certificate_get_peers(*this, &list_size);
auto res = std::vector<certificate_data>{};
res.reserve(list_size);
if (client_cert_list) {
for (auto const& client_cert : std::span{client_cert_list, list_size}) {
res.emplace_back(client_cert.size);
std::copy_n(client_cert.data, client_cert.size, res.back().data());
}
}
return res;
});
}

struct session_ref;
private:

Expand Down Expand Up @@ -1922,6 +1938,9 @@ class tls_connected_socket_impl : public net::connected_socket_impl, public sess
future<std::vector<subject_alt_name>> get_alt_name_information(std::unordered_set<subject_alt_name_type> types) {
return _session->get_alt_name_information(std::move(types));
}
future<std::vector<certificate_data>> get_peer_certificate_chain() {
return _session->get_peer_certificate_chain();
}
future<> wait_input_shutdown() override {
return _session->socket().wait_input_shutdown();
}
Expand Down Expand Up @@ -2113,6 +2132,10 @@ future<std::vector<tls::subject_alt_name>> tls::get_alt_name_information(connect
return get_tls_socket(socket)->get_alt_name_information(std::move(types));
}

future<std::vector<tls::certificate_data>> tls::get_peer_certificate_chain(connected_socket& socket) {
return get_tls_socket(socket)->get_peer_certificate_chain();
}

future<bool> tls::check_session_is_resumed(connected_socket& socket) {
return get_tls_socket(socket)->check_session_is_resumed();
}
Expand Down
9 changes: 8 additions & 1 deletion tests/unit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,7 @@ function(seastar_add_certgen name)
set(CERT_PRIVKEY ${CERT_NAME}.key)
set(CERT_REQ ${CERT_NAME}.csr)
set(CERT_CERT ${CERT_NAME}.crt)
set(CERT_CERT_DER ${CERT_NAME}.crt.der)

set(CERT_CAPRIVKEY ca${CERT_NAME}.key)
set(CERT_CAROOT ca${CERT_NAME}.pem)
Expand Down Expand Up @@ -581,8 +582,14 @@ function(seastar_add_certgen name)
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
)

add_custom_target(${name}
add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${CERT_CERT_DER}"
COMMAND ${OPENSSL} x509 -in ${CERT_CERT} -out ${CERT_CERT_DER} -outform der
DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/${CERT_CERT}"
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
)

add_custom_target(${name}
DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/${CERT_CERT_DER}"
)
endfunction()

Expand Down
60 changes: 60 additions & 0 deletions tests/unit/tls_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1435,6 +1435,66 @@ SEASTAR_THREAD_TEST_CASE(test_alt_names) {

}

SEASTAR_THREAD_TEST_CASE(test_peer_certificate_chain_handling) {
tls::credentials_builder b;

b.set_x509_key_file(certfile("test.crt"), certfile("test.key"), tls::x509_crt_format::PEM).get();
b.set_x509_trust_file(certfile("catest.pem"), tls::x509_crt_format::PEM).get();
b.set_client_auth(tls::client_auth::REQUIRE);

auto creds = b.build_certificate_credentials();
auto serv = b.build_server_credentials();

::listen_options opts;
opts.reuse_address = true;
opts.set_fixed_cpu(this_shard_id());

auto addr = ::make_ipv4_address( {0x7f000001, 4712});
auto server = tls::listen(serv, addr, opts);

{
auto sa = server.accept();
auto c = tls::connect(creds, addr).get();
auto s = sa.get();

auto in = s.connection.input();
output_stream<char> out(c.output().detach(), 1024);
out.write("nils").get();

auto fscrts = tls::get_peer_certificate_chain(s.connection);
auto fccrts = tls::get_peer_certificate_chain(c);

auto fout = out.flush();
auto fin = in.read();

fout.get();

auto scrts = fscrts.get();
auto ccrts = fccrts.get();
fin.get();

in.close().get();
out.close().get();

s.connection.shutdown_input();
s.connection.shutdown_output();

c.shutdown_input();
c.shutdown_output();

auto read_file = [](std::filesystem::path const& path) {
auto contents = tls::certificate_data(std::filesystem::file_size(path));
std::ifstream{path, std::ios_base::binary}.read(reinterpret_cast<char *>(contents.data()), contents.size());
return contents;
};

auto ders = {read_file(certfile("test.crt.der"))};

BOOST_REQUIRE(std::ranges::equal(scrts, ders));
BOOST_REQUIRE(std::ranges::equal(ccrts, ders));
}
}

SEASTAR_THREAD_TEST_CASE(test_skip_wait_for_eof) {
tls::credentials_builder b;

Expand Down

0 comments on commit 2c941b9

Please sign in to comment.