Path: blob/main/crypto/openssl/doc/designs/quic-design/quic-tls.md
34876 views
QUIC-TLS Handshake Integration
QUIC reuses the TLS handshake for the establishment of keys. It does not use the standard TLS record layer and instead assumes responsibility for the confidentiality and integrity of QUIC packets itself. Only the TLS handshake is used. Application data is entirely protected by QUIC.
QUIC_TLS Object
A QUIC-TLS handshake is managed by a QUIC_TLS object. This object provides 3 core functions to the rest of the QUIC implementation:
The ossl_quic_tls_new
function instantiates a new QUIC_TLS
object associated with the QUIC Connection and initialises it with a set of callbacks and other arguments provided in the args
parameter. These callbacks are called at various key points during the handshake lifecycle such as when new keys are established, crypto frame data is ready to be sent or consumed, or when the handshake is complete.
A key field of the args
structure is the SSL
object (s
). This "inner" SSL
object is initialised with an SSL_CONNECTION
to represent the TLS handshake state. This is a different SSL
object to the "user" visible SSL
object which contains a QUIC_CONNECTION
, i.e. the user visible SSL
object contains a QUIC_CONNECTION
which contains the inner SSL
object which contains an SSL_CONNECTION
.
When the QUIC Connection no longer needs the handshake object it can be freed via the ossl_quic_tls_free
function.
Finally the ossl_quic_tls_tick
function is responsible for advancing the state of the QUIC-TLS handshake. On each call to ossl_quic_tls_tick
newly received crypto frame data may be consumed, or new crypto frame data may be queued for sending, or one or more of the various callbacks may be invoked.
QUIC_TLS_ARGS
A QUIC_TLS_ARGS
object is passed to the ossl_quic_tls_new
function by the OpenSSL QUIC implementation to supply a set of callbacks and other essential parameters. The QUIC_TLS_ARGS
structure is as follows:
The crypto_send_cb
and crypto_recv_cb
callbacks will be called by the QUIC-TLS handshake when there is new CRYPTO frame data to be sent, or when it wants to consume queued CRYPTO frame data from the peer.
When the TLS handshake generates secrets they will be communicated to the OpenSSL QUIC implementation via the yield_secret_cb
, and when the handshake has successfully completed this will be communicated via handshake_complete_cb
.
In the event that an error occurs a normal TLS handshake would send a TLS alert record. QUIC handles this differently and so the QUIC_TLS object will intercept attempts to send an alert and will communicate this via the alert_cb
callback.
QUIC requires the use of a TLS extension in order to send and receive "transport parameters". These transport parameters are opaque to the QUIC_TLS
object. It does not need to use them directly but instead simply includes them in an extension to be sent in the ClientHello and receives them back from the peer in the EncryptedExtensions message. The data to be sent is provided in the transport_params
argument. When the peer's parameters are received the got_transport_params_cb
callback is invoked.
QUIC_TLS Implementation
The QUIC_TLS
object utilises two main mechanisms for fulfilling its functions:
It registers itself as a custom TLS record layer
It supplies callbacks to register a custom TLS extension
Custom TLS Record Layer
A TLS record layer is defined via an OSSL_RECORD_METHOD
object. This object consists of a set of function pointers which need to be implemented by any record layer. Existing record layers include one for TLS, one for DTLS and one for KTLS.
QUIC_TLS
registers itself as a custom TLS record layer. A new internal function is used to provide the custom record method data and associate it with an SSL_CONNECTION
:
The internal function ssl_select_next_record_layer
which is used in the TLS implementation to work out which record method should be used next is modified to first check whether a custom record method has been specified and always use that one if so.
The TLS record layer code is further modified to provide the following capabilities which are needed in order to support QUIC.
The custom record layer will need a record layer specific argument (rlarg
above). This is passed as part of a modified new_record_layer
call.
Existing TLS record layers use TLS keys and IVs that are calculated using a KDF from a higher level secret. Instead of this QUIC needs direct access to the higher level secret as well as the digest to be used in the KDF - so these values are now also passed through as part of the new_record_layer
call.
The most important function pointers in the OSSL_RECORD_METHOD
for the QUIC_TLS
object are:
new_record_layer
Invoked every time a new record layer object is created by the TLS implementation. This occurs every time new keys are provisioned (once for the "read" side and once for the "write" side). This function is responsible for invoking the yield_secret_cb
callback.
write_records
Invoked every time the TLS implementation wants to send TLS handshake data. This is responsible for calling the crypto_send_cb
callback. It also includes special processing in the event that the TLS implementation wants to send an alert. This manifests itself as a call to write_records
indicating a type of SSL3_RT_ALERT
. The QUIC_TLS
implementation of write_records
must parse the alert data supplied by the TLS implementation (always a 2 byte record payload) and pull out the alert description (a one byte integer) and invoke the alert_cb
callback. Note that while the TLS RFC strictly allows the 2 byte alert record to be fragmented across two 1 byte records this is never done in practice by OpenSSL's TLS stack and the write_records
implementation can make the optimising assumption that both bytes of an alert are always sent together.
quic_read_record
Invoked when the TLS implementation wants to read more handshake data. This results in a call to crypto_recv_cb
.
This design does introduce an extra "copy" in the process when crypto_recv_cb
is invoked. CRYPTO frame data will be queued within internal QUIC "Stream Receive Buffers" when it is received by the peer. However the TLS implementation expects to request data from the record layer, get a handle on that data, and then inform the record layer when it has finished using that data. The current design of the Stream Receive Buffers does not allow for this model. Therefore when crypto_recv_cb
is invoked the data is copied into a QUIC_TLS object managed buffer. This is inefficient, so it is expected that a later phase of development will resolve this problem.
Custom TLS extension
Libssl already has the ability for an application to supply a custom extension via the SSL_CTX_add_custom_ext()
API. There is no equivalent SSL_add_custom_ext()
and therefore an internal API is used to do this. This mechanism is used for supporting QUIC transport parameters. An extension type TLSEXT_TYPE_quic_transport_parameters
with value 57 is used for this purpose.
The custom extension API enables the caller to supply add
, free
and parse
callbacks. The add
callback simply adds the transport_params
data from QUIC_TLS_ARGS
. The parse
callback invokes the got_transport_params_cb
callback when the transport parameters have been received from the peer.
ALPN
QUIC requires the use of ALPN (Application-Layer Protocol Negotiation). This is normally optional in OpenSSL but is mandatory for QUIC connections. Therefore a QUIC client must call one of SSL_CTX_set_alpn_protos
or SSL_set_alpn_protos
prior to initiating the handshake. If the ALPN data has not been set then the QUIC_TLS
object immediately fails.
Other Implementation Details
The SSL_CONNECTION
used for the TLS handshake is held alongside the QUIC related data in the SSL
object. Public API functions that are only relevant to TLS will modify this internal SSL_CONNECTION
as appropriate. This enables the end application to configure the TLS connection parameters as it sees fit (e.g. setting ciphersuites, providing client certificates, etc). However there are certain settings that may be optional in a normal TLS connection but are mandatory for QUIC. Where possible these settings will be automatically configured just before the handshake starts.
One of these settings is the minimum TLS protocol version. QUIC requires that TLSv1.3 is used as a minimum. Therefore the QUIC_TLS
object automatically calls SSL_set_min_proto_version()
and specifies TLS1_3_VERSION
as the minimum version.
Secondly, QUIC enforces that the TLS "middlebox" mode must not be used. For normal TLS this is "on" by default. Therefore the QUIC_TLS
object will automatically clear the SSL_OP_ENABLE_MIDDLEBOX_COMPAT
option if it is set.