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: ```c QUIC_TLS *ossl_quic_tls_new(const QUIC_TLS_ARGS *args); ``` 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`. ```c void ossl_quic_tls_free(QUIC_TLS *qtls); ``` When the QUIC Connection no longer needs the handshake object it can be freed via the `ossl_quic_tls_free` function. ```c int ossl_quic_tls_tick(QUIC_TLS *qtls); ``` 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: ```c typedef struct quic_tls_args_st { /* * The "inner" SSL object for the QUIC Connection. Contains an * SSL_CONNECTION */ SSL *s; /* * Called to send data on the crypto stream. We use a callback rather than * passing the crypto stream QUIC_SSTREAM directly because this lets the CSM * dynamically select the correct outgoing crypto stream based on the * current EL. */ int (*crypto_send_cb)(const unsigned char *buf, size_t buf_len, size_t *consumed, void *arg); void *crypto_send_cb_arg; int (*crypto_recv_cb)(unsigned char *buf, size_t buf_len, size_t *bytes_read, void *arg); void *crypto_recv_cb_arg; /* Called when a traffic secret is available for a given encryption level. */ int (*yield_secret_cb)(uint32_t enc_level, int direction /* 0=RX, 1=TX */, uint32_t suite_id, EVP_MD *md, const unsigned char *secret, size_t secret_len, void *arg); void *yield_secret_cb_arg; /* * Called when we receive transport parameters from the peer. * * Note: These parameters are not authenticated until the handshake is * marked as completed. */ int (*got_transport_params_cb)(const unsigned char *params, size_t params_len, void *arg); void *got_transport_params_cb_arg; /* * Called when the handshake has been completed as far as the handshake * protocol is concerned, meaning that the connection has been * authenticated. */ int (*handshake_complete_cb)(void *arg); void *handshake_complete_cb_arg; /* * Called when something has gone wrong with the connection as far as the * handshake layer is concerned, meaning that it should be immediately torn * down. Note that this may happen at any time, including after a connection * has been fully established. */ int (*alert_cb)(void *arg, unsigned char alert_code); void *alert_cb_arg; /* * Transport parameters which client should send. Buffer lifetime must * exceed the lifetime of the QUIC_TLS object. */ const unsigned char *transport_params; size_t transport_params_len; } QUIC_TLS_ARGS; ``` 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`: ```C void ossl_ssl_set_custom_record_layer(SSL_CONNECTION *s, const OSSL_RECORD_METHOD *meth, void *rlarg); ``` 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.