1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
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.
|