Skip to content

Commit

Permalink
TLS 1.3 server session resumption
Browse files Browse the repository at this point in the history
Co-Authored-By: Ingo Roda <ingo.roda@rohde-schwarz.com>
  • Loading branch information
reneme and Ingo Roda committed Dec 28, 2022
1 parent a336d42 commit 9613928
Show file tree
Hide file tree
Showing 36 changed files with 790 additions and 87 deletions.
13 changes: 13 additions & 0 deletions src/bogo_shim/bogo_shim.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,9 @@ std::string map_to_bogo_error(const std::string& e)
{ "Client cert verify failed", ":BAD_SIGNATURE:" },
{ "Client certificate does not support signing", ":KEY_USAGE_BIT_INCORRECT:" },
{ "Client did not offer NULL compression", ":INVALID_COMPRESSION_LIST:" },
{ "Client did not comply with the requested key exchange group", ":WRONG_CURVE:" },
{ "Client Hello must either contain both key_share and supported_groups extensions or neither", ":MISSING_KEY_SHARE:" },
{ "Client Hello offered a PSK without a psk_key_exchange_modes extension", ":MISSING_EXTENSION:" },
{ "Client offered DTLS version with major version 0xFF", ":UNSUPPORTED_PROTOCOL:" },
{ "Client offered SSLv3 which is not supported", ":UNSUPPORTED_PROTOCOL:" },
{ "Client offered TLS version with major version under 3", ":UNSUPPORTED_PROTOCOL:" },
Expand All @@ -125,13 +127,17 @@ std::string map_to_bogo_error(const std::string& e)
{ "Non-PSK Client Hello did not contain supported_groups and signature_algorithms extensions", ":NO_SHARED_GROUP:" },
{ "No certificates sent by server", ":PEER_DID_NOT_RETURN_A_CERTIFICATE:" },
{ "Not enough data to read another KeyShareEntry", ":DECODE_ERROR:" },
{ "Not enough PSK binders", ":PSK_IDENTITY_BINDER_COUNT_MISMATCH:" },
{ "Counterparty sent inconsistent key and sig types", ":WRONG_SIGNATURE_TYPE:" },
{ "Downgrade attack detected", ":TLS13_DOWNGRADE:" },
{ "Empty ALPN protocol not allowed", ":PARSE_TLSEXT:" },
{ "Empty PSK binders list", ":DECODE_ERROR: "},
{ "Encoding error: Cannot encode PSS string, output length too small", ":NO_COMMON_SIGNATURE_ALGORITHMS:" },
{ "Expected TLS but got a record with DTLS version", ":WRONG_VERSION_NUMBER:" },
{ "Extension removed in updated Client Hello", ":INCONSISTENT_CLIENT_HELLO:" },
{ "Failed to agree on a signature algorithm", ":NO_COMMON_SIGNATURE_ALGORITHMS:" },
{ "Failed to negotiate a common signature algorithm for client authentication", ":NO_COMMON_SIGNATURE_ALGORITHMS:" },
{ "PSK extension was not at the very end of the Client Hello", ":PRE_SHARED_KEY_MUST_BE_LAST:" },
{ "Finished message didn't verify", ":DIGEST_CHECK_FAILED:" },
{ "Have data remaining in buffer after ClientHello", ":EXCESS_HANDSHAKE_DATA:" },
{ "Have data remaining in buffer after Finished", ":EXCESS_HANDSHAKE_DATA:" },
Expand Down Expand Up @@ -162,6 +168,7 @@ std::string map_to_bogo_error(const std::string& e)
{ "Policy forbids all available TLS version", ":NO_SUPPORTED_VERSIONS_ENABLED:" },
{ "Policy refuses to accept signing with any hash supported by peer", ":NO_COMMON_SIGNATURE_ALGORITHMS:" },
{ "Policy requires client send a certificate, but it did not", ":PEER_DID_NOT_RETURN_A_CERTIFICATE:" },
{ "PSK binder does not check out", ":DIGEST_CHECK_FAILED:" },
{ "PSK identity selected by server is out of bounds", ":PSK_IDENTITY_NOT_FOUND:" },
{ "PSK and ciphersuite selected by server are not compatible", ":OLD_SESSION_PRF_HASH_MISMATCH:" },
{ "Received a record that exceeds maximum size", ":ENCRYPTED_LENGTH_TOO_LONG:" },
Expand Down Expand Up @@ -226,6 +233,7 @@ std::string map_to_bogo_error(const std::string& e)
{ "TLS record type had unexpected value", ":UNEXPECTED_RECORD:" },
{ "TLS record version had unexpected value", ":WRONG_VERSION_NUMBER:" },
{ "Test requires rejecting cert", ":CERTIFICATE_VERIFY_FAILED:" },
{ "Too many PSK binders", ":PSK_IDENTITY_BINDER_COUNT_MISMATCH:" },
{ "Unexpected ALPN protocol", ":INVALID_ALPN_PROTOCOL:" },
{ "Unexpected record type 42 from counterparty", ":UNEXPECTED_RECORD:" },
{ "Unexpected state transition in handshake got a certificate_request expected server_hello_done seen server_hello+server_key_exchange", ":UNEXPECTED_MESSAGE:" },
Expand Down Expand Up @@ -1058,6 +1066,11 @@ class Shim_Policy final : public Botan::TLS::Policy

//uint32_t session_ticket_lifetime() const override;

size_t new_session_tickets_upon_handshake_success() const override
{
return m_args.flag_set("no-ticket") ? 0 : 1;
}

std::vector<uint16_t> srtp_profiles() const override
{
if(m_args.option_used("srtp-profiles"))
Expand Down
35 changes: 10 additions & 25 deletions src/bogo_shim/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"TLS11-*": "No TLS 1.1",

"CBCRecordSplitting*": "No need to split CBC records in TLS 1.2",
"DelegatedCredentials*": "No support of -delegated-cerdential",

"*SCSV*": "SCSV is meaningless without TLS 1.0/1.1 support",

Expand All @@ -50,22 +51,14 @@
"Ticket-Forbidden-TLS13": "We don't offer TLS 1.3 when a TLS 1.2 session was found",
"Resume-Client-NoResume-TLS12-TLS13-TLS": "We don't offer TLS 1.3 when a TLS 1.2 session was found",
"Resume-Client-Mismatch-TLS12-TLS13-TLS": "We don't offer TLS 1.3 when a TLS 1.2 session was found",
"Resume-Server-UnofferedCipher-TLS13": "BoringSSL will not allow switching ciphers during TLS 1.3 resumption, we do, though.",

"KeyUpdate-FromServer": "No TLS 1.3 server, yet",
"DelegatedCredentials*": "No TLS 1.3 server, yet",
"Resume-Server-OmitPSKsOnSecondClientHello": "No TLS 1.3 server, yet",
"PartialClientFinishedWithSecondClientHello": "No TLS 1.3 server, yet",

"Resume-Server*TLS13*": "No TLS 1.3 server, yet",
"Resume-Server-*": "No TLS 1.3 server, yet",

"ExportKeyingMaterial-Server-HalfRTT-TLS13": "No TLS 1.3 server, yet",
"RequireAnyClientCertificate-TLS13": "No TLS 1.3 server, yet",
"SecondClientHelloMissingKeyShare-TLS13": "No TLS 1.3 server, yet",
"SecondClientHelloWrongCurve-TLS13": "No TLS 1.3 server, yet",
"SendExtensionOnClientCertificate-TLS13": "No TLS 1.3 server, yet",
"SkipClientCertificate-TLS13": "No TLS 1.3 server, yet",
"TLS13-TicketAgeSkew-*": "No TLS 1.3 server, yet",

"HttpGET": "TLS 1.3 server does not detect HTTP",
"HttpPOST": "TLS 1.3 server does not detect HTTP",
Expand All @@ -86,22 +79,12 @@
"TLS13-1RTT-Server-TLS-Async-PackHandshake": "TLS 1.3 server UNIMPLEMENTED",
"CertCompressionNoCommonAlgs-TLS13": "TLS 1.3 server UNIMPLEMENTED",

"TLS13-1RTT-Client-*": "TLS 1.3 client UNIMPLEMENTED",

"Server-Verify-*-TLS13": "TODO: FIXME - missing deny-list for outdated signature schemes",
"Server-VerifyDefault-*-TLS13": "TODO: FIXME - missing deny-list for outdated signature schemes",

"ALPNServer-Decline-TLS-TLS13": "TLS 1.3 server session resumption NYI",
"CertificateVerificationSucceed-Server-TLS13-*": "TLS 1.3 server session resumption NYI",
"CurveID-Resume-Server-TLS13": "TLS 1.3 server session resumption NYI",
"ExtraPSKIdentity-TLS13": "TLS 1.3 server session resumption NYI",
"NoClientCertificate-Server-TLS13": "TLS 1.3 server session resumption NYI",
"NoClientCertificateRequested-Server-TLS13": "TLS 1.3 server session resumption NYI",
"Server-Verify-*-TLS13": "TLS 1.3 server session resumption NYI",
"Server-VerifyDefault-*-TLS13": "TLS 1.3 server session resumption NYI",
"ServerNameExtensionServer-TLS-TLS13": "TLS 1.3 server session resumption NYI",
"TLS-TLS13-*-server": "TLS 1.3 server session resumption NYI",
"TLS13-SendNoKEMModesWithPSK-Server": "TLS 1.3 server session resumption NYI",
"TrailingDataWithFinished-Resume-Server-TLS13": "TLS 1.3 server session resumption NYI",
"TLS13-ExpectNoSessionTicketOnBadKEMode-Server": "TLS 1.3 server session resumption NYI",
"TLS13-SendUnknownModeSessionTicket-Server": "TLS 1.3 server session resumption NYI",
"TLS13-SendBadKEModeSessionTicket-Server": "TLS 1.3 server session resumption NYI",
"TLS13-HelloRetryRequest-Server-*": "TLS 1.3 server session resumption NYI",

"ALPNServer-TLS-TLS13": "TLS 1.3 server ALPN NYI",
"ALPNServer-Async-TLS-TLS13": "TLS 1.3 server ALPN NYI",
Expand All @@ -113,8 +96,10 @@
"ServerOCSPCallback-SetInCallback-TLS13-*": "TLS 1.3 server OCSP NYI",

"*EarlyData*": "No TLS 1.3 Early Data, yet",
"TLS13-1RTT-Client-*": "No TLS 1.3 Early Data, yet",
"TLS13-TicketAgeSkew-*": "No TLS 1.3 Early Data, yet",

"NoClientCertificate-Server-TLS13": "No TLS 1.3 (server-side) Client Authentication, yet",
"NoClientCertificateRequested-Server-TLS13": "No TLS 1.3 (server-side) Client Authentication, yet",
"Server-InvalidSignature-*": "No TLS 1.3 (server-side) Client Authentication, yet",
"ClientAuth-Enforced-TLS13": "No TLS 1.3 (server-side) Client Authentication, yet",
"CertificateVerificationFail-Server-TLS13-*": "No TLS 1.3 (server-side) Client Authentication, yet",
Expand Down
45 changes: 44 additions & 1 deletion src/lib/tls/msg_server_hello.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <botan/tls_extensions.h>
#include <botan/tls_exceptn.h>
#include <botan/tls_callbacks.h>
#include <botan/tls_session_manager.h>
#include <botan/internal/tls_reader.h>
#include <botan/mem_ops.h>
#include <botan/internal/tls_session_key.h>
Expand Down Expand Up @@ -501,6 +502,7 @@ Server_Hello_13::Hello_Retry_Request_Creation_Tag Server_Hello_13::as_new_hello_

std::variant<Hello_Retry_Request, Server_Hello_13> Server_Hello_13::create(const Client_Hello_13& ch,
bool hello_retry_request_allowed,
Session_Manager& session_mgr,
RandomNumberGenerator& rng, const Policy& policy, Callbacks& cb)
{
const auto& exts = ch.extensions();
Expand Down Expand Up @@ -537,7 +539,7 @@ std::variant<Hello_Retry_Request, Server_Hello_13> Server_Hello_13::create(const
}
else
{
return Server_Hello_13(ch, selected_group, rng, cb, policy);
return Server_Hello_13(ch, selected_group, session_mgr, rng, cb, policy);
}
}

Expand Down Expand Up @@ -718,6 +720,8 @@ uint16_t choose_ciphersuite(const Client_Hello_13& ch, const Policy& policy)

for(auto suite_id : pref_list)
{
// TODO: take potentially available PSKs into account to select
// a compatible ciphersuite (if possible).
if(value_exists(other_list, suite_id))
{ return suite_id; }
}
Expand All @@ -733,13 +737,24 @@ uint16_t choose_ciphersuite(const Client_Hello_13& ch, const Policy& policy)

Server_Hello_13::Server_Hello_13(const Client_Hello_13& ch,
std::optional<Named_Group> key_exchange_group,
Session_Manager& session_mgr,
RandomNumberGenerator& rng,
Callbacks& cb,
const Policy& policy)
: Server_Hello(std::make_unique<Server_Hello_Internal>(
Protocol_Version::TLS_V12,
ch.session_id(),
make_server_hello_random(rng, Protocol_Version::TLS_V13, cb, policy),

// RFC 8446 4.2.11
// When session resumption is the primary use case of
// PSKs, the most straightforward way to implement the
// PSK/cipher suite matching requirements is to negotiate
// the cipher suite first [...]. If backward compatibility
// is important, client-provided, externally established
// PSKs SHOULD influence cipher suite selection.
//
// We go the easy route and select a ciphersuite first...
choose_ciphersuite(ch, policy),
uint8_t(0) /* compression method */
))
Expand All @@ -757,6 +772,34 @@ Server_Hello_13::Server_Hello_13(const Client_Hello_13& ch,
if(key_exchange_group.has_value())
{ m_data->extensions.add(new Key_Share(key_exchange_group.value(), cb, rng)); }

auto& ch_exts = ch.extensions();

if(ch_exts.has<PSK>())
{
const auto cs = Ciphersuite::by_id(m_data->ciphersuite);
BOTAN_ASSERT_NOMSG(cs);

// RFC 8446 4.2.9
// A client MUST provide a "psk_key_exchange_modes" extension if it
// offers a "pre_shared_key" extension.
//
// Note: Client_Hello_13 constructor already performed a graceful check.
const auto psk_modes = ch_exts.get<PSK_Key_Exchange_Modes>();
BOTAN_ASSERT_NONNULL(psk_modes);

// TODO: also support non-DHE PSK Key-Exchange mode
if(value_exists(psk_modes->modes(), PSK_Key_Exchange_Mode::PSK_DHE_KE))
{
if(auto server_psk = ch_exts.get<PSK>()->select_offered_psk(cs.value(), session_mgr))
{
// RFC 8446 4.2.11
// In order to accept PSK key establishment, the server sends a
// "pre_shared_key" extension indicating the selected identity.
m_data->extensions.add(std::move(server_psk));
}
}
}

cb.tls_modify_extensions(m_data->extensions, SERVER, type());
}

Expand Down
38 changes: 36 additions & 2 deletions src/lib/tls/msg_session_ticket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
*/

#include <botan/tls_messages.h>
#include <botan/rng.h>
#include <botan/tls_session.h>
#include <botan/tls_session_manager.h>
#include <botan/tls_callbacks.h>
#include <botan/internal/tls_reader.h>
#include <botan/internal/tls_handshake_io.h>
#include <botan/internal/tls_handshake_hash.h>
Expand Down Expand Up @@ -67,6 +71,19 @@ std::vector<uint8_t> New_Session_Ticket_12::serialize() const

#if defined (BOTAN_HAS_TLS_13)

New_Session_Ticket_13::New_Session_Ticket_13(std::vector<uint8_t> nonce,
std::vector<uint8_t> ticket,
const Session& session,
Callbacks& callbacks)
: m_ticket_lifetime_hint(session.lifetime_hint())
, m_ticket_age_add(session.session_age_add())
, m_ticket_nonce(std::move(nonce))
, m_ticket(std::move(ticket))
{
BOTAN_ARG_CHECK(!m_ticket.empty(), "Application failed to provide a valid session ticket");
callbacks.tls_modify_extensions(m_extensions, Connection_Side::SERVER, type());
}

New_Session_Ticket_13::New_Session_Ticket_13(const std::vector<uint8_t>& buf,
Connection_Side from)
{
Expand Down Expand Up @@ -114,8 +131,25 @@ std::optional<uint32_t> New_Session_Ticket_13::early_data_byte_limit() const

std::vector<uint8_t> New_Session_Ticket_13::serialize() const
{
// TODO: might be needed once TLS 1.3 server is implemented
throw Not_Implemented("serializing New_Session_Ticket_13 is NYI");
std::vector<uint8_t> result(8);

store_lifetime(std::span<uint8_t, 4>{result}, m_ticket_lifetime_hint);
store_be(m_ticket_age_add, result.data() + 4);
append_tls_length_value(result, m_ticket_nonce, 1);
append_tls_length_value(result, m_ticket, 2);

// TODO: re-evaluate this construction when reworking message marshalling
if(m_extensions.size() == 0)
{
result.push_back(0x00);
result.push_back(0x00);
}
else
{
result += m_extensions.serialize(Connection_Side::SERVER);
}

return result;
}

#endif
Expand Down
38 changes: 35 additions & 3 deletions src/lib/tls/sessions_sql/tls_session_manager_sql.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,26 @@ Session_Manager_SQL::Session_Manager_SQL(std::shared_ptr<SQL_Database> db,
}
}

std::optional<std::pair<Session, uint16_t>>
Session_Manager_SQL::choose_from_offered_tickets(const std::vector<Ticket>& tickets,
const std::string& hash_function)
{
BOTAN_ASSERT_NOMSG(tickets.size() <= std::numeric_limits<uint16_t>::max());
for(uint16_t i = 0; i < tickets.size(); ++i)
{
Session s;
if(!load_from_session_id(tickets[i].identity(), s))
{ continue; }

if(s.ciphersuite().prf_algo() != hash_function)
{ continue; }

return std::pair{s, i};
}

return std::nullopt;
}

bool Session_Manager_SQL::load_from_session_id(const std::vector<uint8_t>& session_id,
Session& session)
{
Expand Down Expand Up @@ -175,15 +195,25 @@ size_t Session_Manager_SQL::remove_all()
return stmt->spin();
}

void Session_Manager_SQL::save(const Session& session)
std::vector<uint8_t> Session_Manager_SQL::save(const Session& session)
{
if(session.server_info().hostname().empty())
return;
return {};

auto stmt = m_db->new_statement("insert or replace into tls_sessions"
" values(?1, ?2, ?3, ?4, ?5)");

stmt->bind(1, hex_encode(session.session_id()));
auto session_id = session.session_id();
if(session_id.empty())
{
// TODO: re-evaluate once TLS 1.3 resumption is fully operational;
// in the hope to unify TLS 1.2 and 1.3 session handling.
// TLS 1.3 does not associate Session tickets or Session IDs with the
// session object. In that case we use a random internal identifier.
m_rng.random_vec(session_id, 16);
}

stmt->bind(1, hex_encode(session_id));
stmt->bind(2, session.start_time());
stmt->bind(3, session.server_info().hostname());
stmt->bind(4, session.server_info().port());
Expand All @@ -192,6 +222,8 @@ void Session_Manager_SQL::save(const Session& session)
stmt->spin();

prune_session_cache();

return session_id;
}

void Session_Manager_SQL::prune_session_cache()
Expand Down
6 changes: 5 additions & 1 deletion src/lib/tls/sessions_sql/tls_session_manager_sql.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ class BOTAN_PUBLIC_API(2,0) Session_Manager_SQL : public Session_Manager

Session_Manager_SQL& operator=(const Session_Manager_SQL&) = delete;

std::optional<std::pair<Session, uint16_t>>
choose_from_offered_tickets(const std::vector<Ticket>& tickets,
const std::string& hash_function) override;

bool load_from_session_id(const std::vector<uint8_t>& session_id,
Session& session) override;

Expand All @@ -59,7 +63,7 @@ class BOTAN_PUBLIC_API(2,0) Session_Manager_SQL : public Session_Manager

size_t remove_all() override;

void save(const Session& session_data) override;
std::vector<uint8_t> save(const Session& session_data) override;

std::chrono::seconds session_lifetime() const override
{ return m_session_lifetime; }
Expand Down
5 changes: 5 additions & 0 deletions src/lib/tls/tls12/tls_channel_impl_12.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,11 @@ void Channel_Impl_12::renegotiate(bool force_full_renegotiation)
throw Invalid_State("Cannot renegotiate on inactive connection");
}

void Channel_Impl_12::send_new_session_tickets(const size_t)
{
throw Invalid_Argument("cannot send new session tickets on a TLS 1.2 channel");
}

void Channel_Impl_12::update_traffic_keys(bool)
{
throw Invalid_Argument("cannot update traffic keys on a TLS 1.2 channel");
Expand Down
2 changes: 2 additions & 0 deletions src/lib/tls/tls12/tls_channel_impl_12.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ class Channel_Impl_12 : public Channel_Impl
*/
void renegotiate(bool force_full_renegotiation = false) override;

void send_new_session_tickets(const size_t tickets) override;

/**
* Attempt to update the session's traffic key material
* Note that this is possible with a TLS 1.3 channel, only.
Expand Down
5 changes: 5 additions & 0 deletions src/lib/tls/tls13/tls_channel_impl_13.h
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,11 @@ class Channel_Impl_13 : public Channel_Impl
throw Invalid_Argument("renegotiation is not allowed in TLS 1.3");
}

void send_new_session_tickets(const size_t /*tickets*/) override
{
throw Invalid_Argument("Sending new session ticket is not allowed in this configuration");
}

/**
* Attempt to update the session's traffic key material
* Note that this is possible with a TLS 1.3 channel, only.
Expand Down
Loading

0 comments on commit 9613928

Please sign in to comment.