From 88113f5dc6828694820d39612c3a760e386a0aa5 Mon Sep 17 00:00:00 2001 From: Matt Caswell Date: Mon, 14 Nov 2022 16:41:17 +0000 Subject: Design document for the QUIC-TLS integration Reviewed-by: Hugo Landau Reviewed-by: Tomas Mraz (Merged from https://github.com/openssl/openssl/pull/19683) --- doc/designs/quic-design/quic-tls.md | 261 ++++++++++++++++++++++++++++++++++++ 1 file changed, 261 insertions(+) create mode 100644 doc/designs/quic-design/quic-tls.md diff --git a/doc/designs/quic-design/quic-tls.md b/doc/designs/quic-design/quic-tls.md new file mode 100644 index 0000000000..122e3deb9d --- /dev/null +++ b/doc/designs/quic-design/quic-tls.md @@ -0,0 +1,261 @@ +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. -- cgit v1.2.3