Skip to content

Commit

Permalink
Merge pull request #2974 from randombit/tls13/resumption
Browse files Browse the repository at this point in the history
[TLS 1.3] Session Resumption
  • Loading branch information
reneme committed Oct 20, 2022
2 parents 5688284 + 410bd69 commit 3188de0
Show file tree
Hide file tree
Showing 31 changed files with 1,738 additions and 139 deletions.
15 changes: 15 additions & 0 deletions doc/migration_guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,21 @@ Functionality removed from the TLS implementation includes
* AES-128 OCB ciphersuites
* DHE_PSK ciphersuites

TLS 1.3 API adaptions
---------------------

Sessions
^^^^^^^^

Old (pre-Botan 3.0) sessions won't load in Botan 3.0 anymore and should be
discarded.

``Session::session_id()`` is equal to the "session ticket" for TLS 1.3 sessions.
This ticket might be longer than a typical ID (up to 64kB). If your application
depends on a short ID for each session, it is safe to just hash the returned
buffer.


Algorithms Removed
-------------------

Expand Down
7 changes: 7 additions & 0 deletions src/bogo_shim/bogo_shim.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -153,17 +153,21 @@ std::string map_to_bogo_error(const std::string& e)
{ "No shared DTLS version", ":UNSUPPORTED_PROTOCOL:" },
{ "No shared TLS version", ":UNSUPPORTED_PROTOCOL:" },
{ "OS2ECP: Unknown format type 251", ":BAD_ECPOINT:" },
{ "Peer sent signature algorithm that is not suitable for TLS 1.3", ":WRONG_SIGNATURE_TYPE:" },
{ "Policy forbids all available DTLS version", ":NO_SUPPORTED_VERSIONS_ENABLED:" },
{ "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 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:" },
{ "Received an encrypted record that exceeds maximum size", ":ENCRYPTED_LENGTH_TOO_LONG:" },
{ "Received application data after connection closure", ":APPLICATION_DATA_ON_SHUTDOWN:" },
{ "Received handshake data after connection closure", ":NO_RENEGOTIATION:" },
{ "Received unexpected record version in initial record", ":WRONG_VERSION_NUMBER:" },
{ "Received unexpected record version", ":WRONG_VERSION_NUMBER:" },
{ "Rejecting ALPN request with alert", ":NO_APPLICATION_PROTOCOL:" },
{ "RSA signatures must use an RSASSA-PSS algorithm", ":WRONG_SIGNATURE_TYPE:" },
{ "Server attempting to negotiate SSLv3 which is not supported", ":UNSUPPORTED_PROTOCOL:" },
{ "Server certificate changed during renegotiation", ":SERVER_CERT_CHANGED:" },
{ "Server changed its mind about extended master secret", ":RENEGOTIATION_EMS_MISMATCH:" },
Expand Down Expand Up @@ -197,6 +201,7 @@ std::string map_to_bogo_error(const std::string& e)
{ "Version downgrade received after Hello Retry", ":SECOND_SERVERHELLO_VERSION_MISMATCH:" },
{ "protected change cipher spec received", ":UNEXPECTED_RECORD:" },
{ "Server sent an unsupported extension", ":UNEXPECTED_EXTENSION:" },
{ "Unsupported extension found in Server Hello", ":UNEXPECTED_EXTENSION:" },
{ "Unexpected extension received", ":UNEXPECTED_EXTENSION:" },
{ "server hello must contain key exchange information", ":MISSING_KEY_SHARE:"},
{ "Peer sent duplicated extensions", ":DUPLICATE_EXTENSION:" },
Expand Down Expand Up @@ -761,6 +766,8 @@ std::unique_ptr<Shim_Arguments> parse_options(char* argv[])
"max-version",
"min-version",
"mtu",
"on-initial-expect-curve-id",
"on-resume-expect-curve-id",
"port",
"read-size",
"resume-count",
Expand Down
37 changes: 8 additions & 29 deletions src/bogo_shim/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"HelloRetryRequest-DuplicateCurve-TLS13": "expects 'illegal parameter' but we want to stick with 'decode error'",
"HelloRetryRequest-DuplicateCookie-TLS13": "expects 'illegal parameter' but we want to stick with 'decode error'",
"EncryptedExtensionsWithKeyShare-TLS13": "expects 'unsupported extension' but RFC requires 'illegal parameter'",
"ClientSkipCertificateVerify-TLS13": "would require ambiguous error mapping"
"ClientSkipCertificateVerify-TLS13": "would require ambiguous error mapping",
"Resume-Client-Mismatch-TLS13-TLS12-TLS": "server requests a downgrade to TLS 1.2, echoing the random session ID during a TLS 1.3 resumption. => error mapping conflict"
},

"DisabledTests": {
Expand Down Expand Up @@ -41,33 +42,10 @@
"TooManyChangeCipherSpec-Client-TLS13": "Limits on the number of CCS are not implemented",
"TooManyKeyUpdates": "Limits on the number of KeyUpdates are not implemented",

"CertificateVerificationSucceed-Client-TLS13-*" : "TLS 1.3 session resumption is NYI",
"Client-VerifyDefault-*-TLS13" : "TLS 1.3 session resumption is NYI",
"Client-Verify-*-TLS13" : "TLS 1.3 session resumption is NYI",
"ExportKeyingMaterial-TLS13" : "TLS 1.3 session resumption is NYI",
"InvalidPSKIdentity-TLS13" : "TLS 1.3 session resumption is NYI",
"NegotiatePSKResumption-TLS13" : "TLS 1.3 session resumption is NYI",
"Resume-Client-CipherMismatch-TLS13" : "TLS 1.3 session resumption is NYI",
"Resume-Client-Mismatch-TLS13-TLS12-TLS" : "TLS 1.3 session resumption is NYI",
"Resume-Client-TLS13-TLS13-TLS" : "TLS 1.3 session resumption is NYI",
"TLS-TLS13-AES_128_GCM_SHA256-client" : "TLS 1.3 session resumption is NYI",
"TLS-TLS13-AES_256_GCM_SHA384-client" : "TLS 1.3 session resumption is NYI",
"TLS-TLS13-CHACHA20_POLY1305_SHA256-client" : "TLS 1.3 session resumption is NYI",
"TLS12SessionID-TLS13" : "TLS 1.3 session resumption is NYI",
"TLS13-HonorServerSessionTicketLifetime" : "TLS 1.3 session resumption is NYI",
"TLS13-TestBadTicketAge-Client" : "TLS 1.3 session resumption is NYI",
"TLS13-TestValidTicketAge-Client" : "TLS 1.3 session resumption is NYI",
"TLS13SessionID-TLS13" : "TLS 1.3 session resumption is NYI",
"TolerateServerNameAck-TLS-TLS13" : "TLS 1.3 session resumption is NYI",
"OCSPStapling-Client-TLS13-*" : "TLS 1.3 session resumption is NYI",
"Resume-Client-NoResume-TLS12-TLS13-TLS": "TLS 1.3 session resumption is NYI",
"Resume-Client-Mismatch-TLS12-TLS13-TLS": "TLS 1.3 session resumption is NYI",
"Ticket-Forbidden-TLS13": "TLS 1.3 session resumption is NYI",
"Resume-Client-PRFMismatch-TLS13": "TLS 1.3 session resumption is NYI",
"TLS13-HelloRetryRequest-Client-*": "TLS 1.3 session resumption is NYI",
"TLS13-TicketAgeSkew-*": "TLS 1.3 session resumption is NYI",
"CurveID-Resume-Client-TLS13": "TLS 1.3 session resumption is NYI",
"ALPNClient-TLS-TLS13": "TLS 1.3 session resumption is NYI",
"TLS12SessionID-TLS13": "We don't offer TLS 1.3 when a TLS 1.2 session was found",
"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",

"KeyUpdate-FromServer": "No TLS 1.3 server, yet",
"FragmentedClientVersion": "No TLS 1.3 server, yet",
Expand All @@ -82,6 +60,7 @@
"ECDSACurveMismatch-Sign-TLS13": "No TLS 1.3 server, yet",
"MinimumVersion-Server*-TLS13*": "No TLS 1.3 server, yet",
"Resume-Server*TLS13*": "No TLS 1.3 server, yet",
"Resume-Server-*": "No TLS 1.3 server, yet",
"Server-Sign-*-TLS13": "No TLS 1.3 server, yet",
"Server-Verify-*-TLS13": "No TLS 1.3 server, yet",
"Server-VerifyDefault-*-TLS13": "No TLS 1.3 server, yet",
Expand Down Expand Up @@ -110,14 +89,14 @@
"UnexpectedClientEncryptedExtensions-TLS-TLS13": "No TLS 1.3 server, yet",
"UnknownCipher-TLS13": "No TLS 1.3 server, yet",
"VersionTolerance-TLS13": "No TLS 1.3 server, yet",
"TLS13-TicketAgeSkew-*": "No TLS 1.3 server, yet",

"*EarlyData*": "No TLS 1.3 Early Data, yet",
"TLS13-1RTT-Client-*": "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",

"*Binder*": "No TLS 1.3",
"NoExportEarlyKeyingMaterial*": "No TLS 1.3",
"EarlyDataEnabled*": "No TLS 1.3",
"TLS-ECH*": "No ECH support",
Expand Down
67 changes: 66 additions & 1 deletion src/lib/tls/msg_client_hello.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@
#include <botan/internal/tls_handshake_io.h>
#include <botan/internal/tls_handshake_hash.h>

#ifdef BOTAN_HAS_TLS_13
#include <botan/internal/tls_transcript_hash_13.h>
#include <botan/internal/tls_handshake_layer_13.h>
#endif

#include <chrono>

namespace Botan::TLS {
Expand Down Expand Up @@ -485,7 +490,8 @@ Client_Hello_13::Client_Hello_13(const Policy& policy,
Callbacks& cb,
RandomNumberGenerator& rng,
const std::string& hostname,
const std::vector<std::string>& next_protocols)
const std::vector<std::string>& next_protocols,
const std::optional<Session>& session)
{
// RFC 8446 4.1.2
// In TLS 1.3, the client indicates its version preferences in the
Expand Down Expand Up @@ -522,6 +528,11 @@ Client_Hello_13::Client_Hello_13(const Policy& policy,

m_extensions.add(new Signature_Algorithms(policy.acceptable_signature_schemes()));

// TODO: Support for PSK-only mode without a key exchange.
// This should be configurable in TLS::Policy and should allow no PSK
// support at all (e.g. to disable support for session resumption).
m_extensions.add(new PSK_Key_Exchange_Modes({PSK_Key_Exchange_Mode::PSK_DHE_KE}));

// TODO: Add a signature_algorithms_cert extension negotiating the acceptable
// signature algorithms in a server certificate chain's certificates.

Expand Down Expand Up @@ -552,10 +563,26 @@ Client_Hello_13::Client_Hello_13(const Policy& policy,
m_extensions.add(new Supported_Point_Formats(policy.use_ecc_point_compression()));
}

// TODO: Some extensions require a certain order or pose other assumptions.
// We should check those after the user was allowed to make changes to
// the extensions.
cb.tls_modify_extensions(m_extensions, CLIENT);

// RFC 8446 4.2.11
// The "pre_shared_key" extension MUST be the last extension in the
// ClientHello (this facilitates implementation [...]).
//
// The PSK extension takes the partial transcript hash into account. Passing
// into Callbacks::tls_modify_extensions() does not make sense therefore.
if(session.has_value())
{
m_extensions.add(new PSK(session.value(), cb));
calculate_psk_binders({});
}
}

void Client_Hello_13::retry(const Hello_Retry_Request& hrr,
const Transcript_Hash_State& transcript_hash_state,
Callbacks& cb,
RandomNumberGenerator& rng)
{
Expand Down Expand Up @@ -586,6 +613,44 @@ void Client_Hello_13::retry(const Hello_Retry_Request& hrr,
// invocations to this callback due to the first Client_Hello or the
// retried Client_Hello after receiving a Hello_Retry_Request.
cb.tls_modify_extensions(m_extensions, CLIENT);

auto psk = m_extensions.get<PSK>();
if(psk)
{
// Cipher suite should always be a known suite as this is checked upstream
const auto cipher = Ciphersuite::by_id(hrr.ciphersuite());
BOTAN_ASSERT_NOMSG(cipher.has_value());

// RFC 8446 4.1.4
// In [...] its updated ClientHello, the client SHOULD NOT offer
// any pre-shared keys associated with a hash other than that of the
// selected cipher suite.
psk->filter(cipher.value());

// RFC 8446 4.2.11.2
// If the server responds with a HelloRetryRequest and the client
// then sends ClientHello2, its binder will be computed over: [...].
calculate_psk_binders(transcript_hash_state.clone());
}
}

void Client_Hello_13::calculate_psk_binders(Transcript_Hash_State ths)
{
auto psk = m_extensions.get<PSK>();
if(!psk || psk->empty())
return;

// RFC 8446 4.2.11.2
// Each entry in the binders list is computed as an HMAC over a
// transcript hash (see Section 4.4.1) containing a partial ClientHello
// [...].
//
// Therefore we marshal the entire message prematurely to obtain the
// (truncated) transcript hash, calculate the PSK binders with it, update
// the Client Hello thus finalizing the message. Down the road, it will be
// re-marshalled with the correct binders and sent over the wire.
Handshake_Layer::prepare_message(*this, ths);
psk->calculate_binders(ths);
}

#endif // BOTAN_HAS_TLS_13
Expand Down
1 change: 1 addition & 0 deletions src/lib/tls/msg_server_hello.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,7 @@ Server_Hello_13::Server_Hello_13(std::unique_ptr<Server_Hello_Internal> data,
TLSEXT_KEY_SHARE,
TLSEXT_PSK_KEY_EXCHANGE_MODES,
TLSEXT_SUPPORTED_VERSIONS,
TLSEXT_PSK,
};

// As the ServerHello shall only contain essential extensions, we don't give
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 @@ -11,6 +11,8 @@
#include <botan/internal/tls_handshake_hash.h>
#include <botan/internal/loadstor.h>

#include <botan/tls_exceptn.h>

namespace Botan::TLS {

New_Session_Ticket_12::New_Session_Ticket_12(Handshake_IO& io,
Expand Down Expand Up @@ -51,14 +53,45 @@ std::vector<uint8_t> New_Session_Ticket_12::serialize() const

#if defined (BOTAN_HAS_TLS_13)

New_Session_Ticket_13::New_Session_Ticket_13(const std::vector<uint8_t>&)
New_Session_Ticket_13::New_Session_Ticket_13(const std::vector<uint8_t>& buf,
Connection_Side from)
{
TLS_Data_Reader reader("New_Session_Ticket_13", buf);

m_ticket_lifetime_hint = reader.get_uint32_t();

// RFC 8446 4.6.1
// Servers MUST NOT use any value [of ticket_lifetime] greater than 604800
// seconds (7 days).
if(m_ticket_lifetime_hint > 604800)
{
throw TLS_Exception(Alert::ILLEGAL_PARAMETER,
"Received a session ticket with lifetime longer than one week.");
}

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

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

reader.assert_done();
}

std::optional<uint32_t> New_Session_Ticket_13::early_data_byte_limit() const
{
// TODO: Implement
if(!m_extensions.has<EarlyDataIndication>())
return std::nullopt;

const auto ext = m_extensions.get<EarlyDataIndication>();
BOTAN_ASSERT_NOMSG(ext->max_early_data_size().has_value());
return ext->max_early_data_size().value();
}

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

#endif
Expand Down
Loading

0 comments on commit 3188de0

Please sign in to comment.