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 <[email protected]>
  • Loading branch information
reneme and Ingo Roda committed Feb 2, 2023
1 parent cb0859b commit 80a73dc
Show file tree
Hide file tree
Showing 35 changed files with 653 additions and 102 deletions.
15 changes: 15 additions & 0 deletions src/bogo_shim/bogo_shim.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,9 @@ std::string map_to_bogo_error(const std::string& e)
{ "Client certificate verification failed", ":BAD_SIGNATURE:" },
{ "Client did not comply with the requested key exchange group", ":WRONG_CURVE:" },
{ "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 @@ -129,13 +131,18 @@ 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 agree on any 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 @@ -166,6 +173,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 @@ -230,6 +238,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 @@ -738,6 +747,7 @@ std::unique_ptr<Shim_Arguments> parse_options(char* argv[])
"expect-advertised-alpn",
"expect-alpn",
"expect-client-ca-list",
"expect-early-data-reason",
"expect-late-alpn",
"expect-msg-callback",
//"expect-next-proto",
Expand Down Expand Up @@ -1072,6 +1082,11 @@ class Shim_Policy final : public Botan::TLS::Policy

//std::chrono::seconds 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: 6 additions & 29 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 @@ -49,16 +50,9 @@
"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",

"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",
"Resume-Server-UnofferedCipher-TLS13": "BoringSSL will not allow switching ciphers during TLS 1.3 resumption, we do, though.",

"ExportKeyingMaterial-Server-HalfRTT-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 @@ -68,30 +62,13 @@

"ExtraClientEncryptedExtension-TLS-TLS13": "TLS 1.3 server 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",
"ALPNServer-TLS-TLS13": "TLS 1.3 server session resumption NYI",
"ALPNServer-Async-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",
"OCSPStapling-Server-TLS13-*": "TLS 1.3 server session resumption NYI",
"ServerOCSPCallback*-TLS13-*": "TLS 1.3 server session resumption NYI",
"TLS13-1RTT-Server-*": "TLS 1.3 server session resumption 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",

"SendNoClientCertificateExtensions-TLS13": "-signed-cert-timestamps currently not supported in the shim",
"KeyUpdate-RequestACK-UnfinishedWrite": "-read-with-unfinished-write currently not supported in the shim",
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, cb, policy))
{
// 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, Connection_Side::Server, type());
}

Expand Down
39 changes: 36 additions & 3 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,18 @@ 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,
const Session& session,
const Session_Handle& handle,
Callbacks& callbacks)
: m_ticket_lifetime_hint(session.lifetime_hint())
, m_ticket_age_add(session.session_age_add())
, m_ticket_nonce(std::move(nonce))
, m_handle(handle.opaque_handle())
{
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 All @@ -85,7 +101,7 @@ New_Session_Ticket_13::New_Session_Ticket_13(const std::vector<uint8_t>& buf,

m_ticket_age_add = reader.get_uint32_t();
m_ticket_nonce = reader.get_tls_length_value(1);
m_ticket = Session_Ticket(reader.get_tls_length_value(2));
m_handle = Opaque_Session_Handle(reader.get_tls_length_value(2));

m_extensions.deserialize(reader, from, type());

Expand Down Expand Up @@ -114,8 +130,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_handle.get(), 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
2 changes: 1 addition & 1 deletion src/lib/tls/sessions_sql/tls_session_manager_sql.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ void Session_Manager_SQL::initialize_existing_database(const std::string& passph
void Session_Manager_SQL::store(const Session& session, const Session_Handle& handle)
{
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, ?6)");
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
22 changes: 20 additions & 2 deletions src/lib/tls/tls13/tls_cipher_state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,9 @@ void Cipher_State::advance_with_client_finished(const Transcript_Hash& transcrip
m_state = State::Completed;
}

std::vector<uint8_t> Cipher_State::current_nonce(const uint64_t seq_no, const secure_vector<uint8_t>& iv) const
namespace {

std::vector<uint8_t> current_nonce(const uint64_t seq_no, const secure_vector<uint8_t>& iv)
{
// RFC 8446 5.3
// The per-record nonce for the AEAD construction is formed as follows:
Expand All @@ -233,6 +235,8 @@ std::vector<uint8_t> Cipher_State::current_nonce(const uint64_t seq_no, const se
return nonce;
}

}

uint64_t Cipher_State::encrypt_record_fragment(const std::vector<uint8_t>& header, secure_vector<uint8_t>& fragment)
{
BOTAN_ASSERT_NONNULL(m_encrypt);
Expand Down Expand Up @@ -400,6 +404,19 @@ secure_vector<uint8_t> Cipher_State::psk(const std::vector<uint8_t>& nonce) cons
return derive_secret(m_resumption_master_secret, "resumption", nonce);
}

std::vector<uint8_t> Cipher_State::next_ticket_nonce()
{
BOTAN_STATE_CHECK(m_state == State::Completed);
if(m_ticket_nonce == std::numeric_limits<decltype(m_ticket_nonce)>::max())
{
throw Botan::Invalid_State("ticket nonce pool exhausted");
}

std::vector<uint8_t> retval(sizeof(m_ticket_nonce));
store_be(m_ticket_nonce++, retval.data());

return retval;
}

secure_vector<uint8_t> Cipher_State::export_key(const std::string& label,
const std::string& context,
Expand Down Expand Up @@ -431,7 +448,8 @@ Cipher_State::Cipher_State(Connection_Side whoami, const std::string& hash_funct
, m_hash(HashFunction::create_or_throw(hash_function))
, m_salt(m_hash->output_length(), 0x00)
, m_write_seq_no(0)
, m_read_seq_no(0) {}
, m_read_seq_no(0)
, m_ticket_nonce(0) {}

Cipher_State::~Cipher_State() = default;

Expand Down
Loading

0 comments on commit 80a73dc

Please sign in to comment.