Skip to content

Commit 5b4d608

Browse files
author
Calle Wilund
committed
tls: Make session resume key shared across credentials builders creds
Fixes #2708 Session resume key (if enabled) was only generated/kept on credentials level. This means that access patterns where we create a new such object per shard, using same builder, would have different session keys, and thus not be able to resume each others sessions. This moves key generation to builder level and re-uses this for all generated server credentials. Note that a reload of any file (in reloadable) will re-create the key (though it should become invalid anyway, but...). Add tests for this, and a hefty warning that session keys are shared, should anyone try to re-use a builder for disparate credentials.
1 parent 528264d commit 5b4d608

File tree

3 files changed

+172
-9
lines changed

3 files changed

+172
-9
lines changed

Diff for: include/seastar/net/tls.hh

+8
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,13 @@ namespace tls {
315315
future<> set_system_trust();
316316
void set_client_auth(client_auth);
317317
void set_priority_string(const sstring&);
318+
/**
319+
* Sets session resume mode to be applied to all created server credential sets
320+
* Note: setting this will generate a session key that will be reused across all
321+
* built server credentials, i.e. they will share resume key.
322+
* If you wish to reuse a builder to create disparate server crendentials,
323+
* simply call this method again to regenerate the key.
324+
*/
318325
void set_session_resume_mode(session_resume_mode);
319326

320327
void apply_to(certificate_credentials&) const;
@@ -339,6 +346,7 @@ namespace tls {
339346
client_auth _client_auth = client_auth::NONE;
340347
session_resume_mode _session_resume_mode = session_resume_mode::NONE;
341348
sstring _priority;
349+
std::vector<uint8_t> _session_resume_key;
342350
};
343351

344352
using session_data = std::vector<uint8_t>;

Diff for: src/net/tls.cc

+23-6
Original file line numberDiff line numberDiff line change
@@ -339,9 +339,9 @@ future<tls::x509_cert> tls::x509_cert::from_file(
339339

340340
// wrapper for gnutls_datum, with raii free
341341
struct gnutls_datum : public gnutls_datum_t {
342-
gnutls_datum() {
343-
data = nullptr;
344-
size = 0;
342+
gnutls_datum(void* d = nullptr, size_t s = 0) {
343+
data = reinterpret_cast<unsigned char*>(d);
344+
size = s;
345345
}
346346
gnutls_datum(const gnutls_datum&) = delete;
347347
gnutls_datum& operator=(gnutls_datum&& other) {
@@ -436,12 +436,20 @@ class tls::certificate_credentials::impl: public gnutlsobj {
436436
client_auth get_client_auth() const {
437437
return _client_auth;
438438
}
439-
void set_session_resume_mode(session_resume_mode m) {
439+
void set_session_resume_mode(session_resume_mode m, const std::vector<uint8_t>& key = {}) {
440440
_session_resume_mode = m;
441441
// (re-)generate session key
442442
if (m != session_resume_mode::NONE) {
443443
_session_resume_key = {};
444-
gnutls_session_ticket_key_generate(&_session_resume_key);
444+
if (key.empty()) {
445+
gnutls_session_ticket_key_generate(&_session_resume_key);
446+
} else {
447+
_session_resume_key = gnutls_datum(gnutls_malloc(key.size()), key.size());
448+
if (_session_resume_key.data == nullptr) {
449+
throw std::bad_alloc();
450+
}
451+
std::copy(key.begin(), key.end(), _session_resume_key.data);
452+
}
445453
}
446454
}
447455
session_resume_mode get_session_resume_mode() const {
@@ -710,6 +718,11 @@ void tls::credentials_builder::set_priority_string(const sstring& prio) {
710718

711719
void tls::credentials_builder::set_session_resume_mode(session_resume_mode m) {
712720
_session_resume_mode = m;
721+
if (m != session_resume_mode::NONE) {
722+
gnutls_datum key;
723+
gnutls_session_ticket_key_generate(&key);
724+
_session_resume_key.assign(key.data, key.data + key.size);
725+
}
713726
}
714727

715728
template<typename Blobs, typename Visitor>
@@ -760,7 +773,7 @@ void tls::credentials_builder::apply_to(certificate_credentials& creds) const {
760773

761774
creds._impl->set_client_auth(_client_auth);
762775
// Note: this causes server session key rotation on cert reload
763-
creds._impl->set_session_resume_mode(_session_resume_mode);
776+
creds._impl->set_session_resume_mode(_session_resume_mode, _session_resume_key);
764777
}
765778

766779
shared_ptr<tls::certificate_credentials> tls::credentials_builder::build_certificate_credentials() const {
@@ -925,6 +938,10 @@ class tls::reloadable_credentials_base {
925938
return;
926939
}
927940
try {
941+
// force rebuilding session resume mode key if
942+
// enabled. should not reuse sessions across certificate
943+
// change (should not work anyway)
944+
set_session_resume_mode(_session_resume_mode);
928945
if (_creds) {
929946
_creds->rebuild(*this);
930947
}

Diff for: tests/unit/tls_test.cc

+141-3
Original file line numberDiff line numberDiff line change
@@ -1554,19 +1554,153 @@ SEASTAR_THREAD_TEST_CASE(test_skip_wait_for_eof) {
15541554
}
15551555
}
15561556

1557+
static void do_test_tls13_session_tickets(bool reset_server) {
1558+
tls::credentials_builder b;
1559+
1560+
b.set_x509_key_file(certfile("test.crt"), certfile("test.key"), tls::x509_crt_format::PEM).get();
1561+
b.set_x509_trust_file(certfile("catest.pem"), tls::x509_crt_format::PEM).get();
1562+
b.set_session_resume_mode(tls::session_resume_mode::TLS13_SESSION_TICKET);
1563+
b.set_priority_string("SECURE128:+SECURE192:-VERS-TLS-ALL:+VERS-TLS1.3");
1564+
1565+
auto creds = b.build_certificate_credentials();
1566+
auto serv = b.build_server_credentials();
1567+
1568+
::listen_options opts;
1569+
opts.reuse_address = true;
1570+
opts.set_fixed_cpu(this_shard_id());
1571+
1572+
auto addr = ::make_ipv4_address( {0x7f000001, 4712});
1573+
auto server = tls::listen(serv, addr, opts);
1574+
1575+
tls::session_data sess_data;
1576+
1577+
{
1578+
auto sa = server.accept();
1579+
auto c = tls::connect(creds, addr).get();
1580+
auto s = sa.get();
1581+
1582+
auto in = s.connection.input();
1583+
auto cin = c.input();
1584+
output_stream<char> out(c.output().detach(), 1024);
1585+
output_stream<char> sout(s.connection.output().detach(), 1024);
1586+
1587+
// write data in both directions. Required for session data to
1588+
// become available.
1589+
out.write("nils").get();
1590+
auto fin = in.read();
1591+
auto fout = out.flush();
1592+
1593+
fout.get();
1594+
fin.get();
1595+
1596+
sout.write("banan").get();
1597+
fin = cin.read();
1598+
fout = sout.flush();
1599+
1600+
fout.get();
1601+
fin.get();
1602+
1603+
BOOST_REQUIRE(!tls::check_session_is_resumed(c).get()); // no resume data
1604+
1605+
// get ticket data
1606+
sess_data = tls::get_session_resume_data(c).get();
1607+
BOOST_REQUIRE(!sess_data.empty());
1608+
1609+
in.close().get();
1610+
out.close().get();
1611+
1612+
s.connection.shutdown_input();
1613+
s.connection.shutdown_output();
1614+
1615+
c.shutdown_input();
1616+
c.shutdown_output();
1617+
}
1618+
1619+
if (reset_server) {
1620+
server = {};
1621+
// rebuild creds
1622+
serv = b.build_server_credentials();
1623+
server = tls::listen(serv, addr, opts);
1624+
}
1625+
1626+
{
1627+
auto sa = server.accept();
1628+
1629+
// tell client to try resuming.
1630+
tls::tls_options tls_opts;
1631+
tls_opts.session_resume_data = sess_data;
1632+
1633+
auto c = tls::connect(creds, addr, tls_opts).get();
1634+
auto s = sa.get();
1635+
1636+
// This is ok. Will force a handshake.
1637+
auto f = tls::check_session_is_resumed(c);
1638+
1639+
// But we need to force some IO to make the
1640+
// handshake actually happen.
1641+
auto in = s.connection.input();
1642+
output_stream<char> out(c.output().detach(), 1024);
1643+
1644+
auto fin = in.read();
1645+
1646+
out.write("nils").get();
1647+
auto fout = out.flush();
1648+
1649+
fout.get();
1650+
fin.get();
1651+
1652+
BOOST_REQUIRE(f.get()); // Should work
1653+
1654+
in.close().get();
1655+
out.close().get();
1656+
1657+
s.connection.shutdown_input();
1658+
s.connection.shutdown_output();
1659+
1660+
c.shutdown_input();
1661+
c.shutdown_output();
1662+
}
1663+
1664+
}
1665+
15571666
/**
15581667
* Test TLS13 session ticket support.
15591668
*/
15601669
SEASTAR_THREAD_TEST_CASE(test_tls13_session_tickets) {
1670+
do_test_tls13_session_tickets(false);
1671+
}
1672+
1673+
SEASTAR_THREAD_TEST_CASE(test_tls13_session_tickets_retain_session_key) {
1674+
do_test_tls13_session_tickets(true);
1675+
}
1676+
1677+
SEASTAR_THREAD_TEST_CASE(test_tls13_session_tickets_invalidated_by_reload) {
15611678
tls::credentials_builder b;
1679+
tmpdir tmp;
15621680

1563-
b.set_x509_key_file(certfile("test.crt"), certfile("test.key"), tls::x509_crt_format::PEM).get();
1681+
namespace fs = std::filesystem;
1682+
1683+
// copy the wrong certs. We don't trust these
1684+
// blocking calls, but this is a test and seastar does not have a copy
1685+
// util and I am lazy...
1686+
fs::copy_file(certfile("test.crt"), tmp.path() / "test.crt");
1687+
fs::copy_file(certfile("test.key"), tmp.path() / "test.key");
1688+
1689+
auto cert = (tmp.path() / "test.crt").native();
1690+
auto key = (tmp.path() / "test.key").native();
1691+
promise<> p;
1692+
1693+
b.set_x509_key_file(cert, key, tls::x509_crt_format::PEM).get();
15641694
b.set_x509_trust_file(certfile("catest.pem"), tls::x509_crt_format::PEM).get();
15651695
b.set_session_resume_mode(tls::session_resume_mode::TLS13_SESSION_TICKET);
15661696
b.set_priority_string("SECURE128:+SECURE192:-VERS-TLS-ALL:+VERS-TLS1.3");
15671697

15681698
auto creds = b.build_certificate_credentials();
1569-
auto serv = b.build_server_credentials();
1699+
auto serv = b.build_reloadable_server_credentials([&p](const std::unordered_set<sstring>&, std::exception_ptr) {
1700+
p.set_value();
1701+
}).get();
1702+
1703+
auto reloaded = p.get_future();
15701704

15711705
::listen_options opts;
15721706
opts.reuse_address = true;
@@ -1619,6 +1753,10 @@ SEASTAR_THREAD_TEST_CASE(test_tls13_session_tickets) {
16191753
c.shutdown_output();
16201754
}
16211755

1756+
BOOST_REQUIRE(!reloaded.available());
1757+
1758+
fs::copy_file(certfile("test.crt"), tmp.path() / "test.crt", fs::copy_options::overwrite_existing);
1759+
reloaded.get();
16221760

16231761
{
16241762
auto sa = server.accept();
@@ -1646,7 +1784,7 @@ SEASTAR_THREAD_TEST_CASE(test_tls13_session_tickets) {
16461784
fout.get();
16471785
fin.get();
16481786

1649-
BOOST_REQUIRE(f.get()); // Should work
1787+
BOOST_REQUIRE(!f.get()); // Should NOT work. Keys should have been replaced
16501788

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

0 commit comments

Comments
 (0)