Skip to content

Commit 2c941b9

Browse files
nielsdevreede-rlxemul
authored andcommitted
tls: Add a way to inspect peer certificate chain
Closes #2630
1 parent 5b95d1d commit 2c941b9

File tree

4 files changed

+104
-1
lines changed

4 files changed

+104
-1
lines changed

include/seastar/net/tls.hh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,19 @@ namespace tls {
494494
*/
495495
future<std::vector<subject_alt_name>> get_alt_name_information(connected_socket& socket, std::unordered_set<subject_alt_name_type> types = {});
496496

497+
using certificate_data = std::vector<uint8_t>;
498+
499+
/**
500+
* Get the raw certificate (chain) that the connected peer is using.
501+
* This function forces the TLS handshake. If the handshake didn't happen before the
502+
* call to 'get_peer_certificate_chain' it will be completed when the returned future
503+
* will become ready.
504+
* The function returns the certificate chain on success. If the peer didn't send the
505+
* certificate during the handshake, the function returns an empty certificate chain.
506+
* If the socket is not connected the system_error exception will be thrown.
507+
*/
508+
future<std::vector<certificate_data>> get_peer_certificate_chain(connected_socket& socket);
509+
497510
/**
498511
* Checks if the socket was connected using session resume.
499512
* Will force handshake if not already done.

src/net/tls.cc

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1790,6 +1790,22 @@ class session : public enable_lw_shared_from_this<session> {
17901790
}, std::move(types));
17911791
}
17921792

1793+
future<std::vector<certificate_data>> get_peer_certificate_chain() {
1794+
return state_checked_access([this] {
1795+
unsigned int list_size = 0;
1796+
const gnutls_datum_t* client_cert_list = gnutls_certificate_get_peers(*this, &list_size);
1797+
auto res = std::vector<certificate_data>{};
1798+
res.reserve(list_size);
1799+
if (client_cert_list) {
1800+
for (auto const& client_cert : std::span{client_cert_list, list_size}) {
1801+
res.emplace_back(client_cert.size);
1802+
std::copy_n(client_cert.data, client_cert.size, res.back().data());
1803+
}
1804+
}
1805+
return res;
1806+
});
1807+
}
1808+
17931809
struct session_ref;
17941810
private:
17951811

@@ -1922,6 +1938,9 @@ class tls_connected_socket_impl : public net::connected_socket_impl, public sess
19221938
future<std::vector<subject_alt_name>> get_alt_name_information(std::unordered_set<subject_alt_name_type> types) {
19231939
return _session->get_alt_name_information(std::move(types));
19241940
}
1941+
future<std::vector<certificate_data>> get_peer_certificate_chain() {
1942+
return _session->get_peer_certificate_chain();
1943+
}
19251944
future<> wait_input_shutdown() override {
19261945
return _session->socket().wait_input_shutdown();
19271946
}
@@ -2113,6 +2132,10 @@ future<std::vector<tls::subject_alt_name>> tls::get_alt_name_information(connect
21132132
return get_tls_socket(socket)->get_alt_name_information(std::move(types));
21142133
}
21152134

2135+
future<std::vector<tls::certificate_data>> tls::get_peer_certificate_chain(connected_socket& socket) {
2136+
return get_tls_socket(socket)->get_peer_certificate_chain();
2137+
}
2138+
21162139
future<bool> tls::check_session_is_resumed(connected_socket& socket) {
21172140
return get_tls_socket(socket)->check_session_is_resumed();
21182141
}

tests/unit/CMakeLists.txt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,7 @@ function(seastar_add_certgen name)
541541
set(CERT_PRIVKEY ${CERT_NAME}.key)
542542
set(CERT_REQ ${CERT_NAME}.csr)
543543
set(CERT_CERT ${CERT_NAME}.crt)
544+
set(CERT_CERT_DER ${CERT_NAME}.crt.der)
544545

545546
set(CERT_CAPRIVKEY ca${CERT_NAME}.key)
546547
set(CERT_CAROOT ca${CERT_NAME}.pem)
@@ -581,8 +582,14 @@ function(seastar_add_certgen name)
581582
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
582583
)
583584

584-
add_custom_target(${name}
585+
add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${CERT_CERT_DER}"
586+
COMMAND ${OPENSSL} x509 -in ${CERT_CERT} -out ${CERT_CERT_DER} -outform der
585587
DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/${CERT_CERT}"
588+
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
589+
)
590+
591+
add_custom_target(${name}
592+
DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/${CERT_CERT_DER}"
586593
)
587594
endfunction()
588595

tests/unit/tls_test.cc

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1435,6 +1435,66 @@ SEASTAR_THREAD_TEST_CASE(test_alt_names) {
14351435

14361436
}
14371437

1438+
SEASTAR_THREAD_TEST_CASE(test_peer_certificate_chain_handling) {
1439+
tls::credentials_builder b;
1440+
1441+
b.set_x509_key_file(certfile("test.crt"), certfile("test.key"), tls::x509_crt_format::PEM).get();
1442+
b.set_x509_trust_file(certfile("catest.pem"), tls::x509_crt_format::PEM).get();
1443+
b.set_client_auth(tls::client_auth::REQUIRE);
1444+
1445+
auto creds = b.build_certificate_credentials();
1446+
auto serv = b.build_server_credentials();
1447+
1448+
::listen_options opts;
1449+
opts.reuse_address = true;
1450+
opts.set_fixed_cpu(this_shard_id());
1451+
1452+
auto addr = ::make_ipv4_address( {0x7f000001, 4712});
1453+
auto server = tls::listen(serv, addr, opts);
1454+
1455+
{
1456+
auto sa = server.accept();
1457+
auto c = tls::connect(creds, addr).get();
1458+
auto s = sa.get();
1459+
1460+
auto in = s.connection.input();
1461+
output_stream<char> out(c.output().detach(), 1024);
1462+
out.write("nils").get();
1463+
1464+
auto fscrts = tls::get_peer_certificate_chain(s.connection);
1465+
auto fccrts = tls::get_peer_certificate_chain(c);
1466+
1467+
auto fout = out.flush();
1468+
auto fin = in.read();
1469+
1470+
fout.get();
1471+
1472+
auto scrts = fscrts.get();
1473+
auto ccrts = fccrts.get();
1474+
fin.get();
1475+
1476+
in.close().get();
1477+
out.close().get();
1478+
1479+
s.connection.shutdown_input();
1480+
s.connection.shutdown_output();
1481+
1482+
c.shutdown_input();
1483+
c.shutdown_output();
1484+
1485+
auto read_file = [](std::filesystem::path const& path) {
1486+
auto contents = tls::certificate_data(std::filesystem::file_size(path));
1487+
std::ifstream{path, std::ios_base::binary}.read(reinterpret_cast<char *>(contents.data()), contents.size());
1488+
return contents;
1489+
};
1490+
1491+
auto ders = {read_file(certfile("test.crt.der"))};
1492+
1493+
BOOST_REQUIRE(std::ranges::equal(scrts, ders));
1494+
BOOST_REQUIRE(std::ranges::equal(ccrts, ders));
1495+
}
1496+
}
1497+
14381498
SEASTAR_THREAD_TEST_CASE(test_skip_wait_for_eof) {
14391499
tls::credentials_builder b;
14401500

0 commit comments

Comments
 (0)