summaryrefslogtreecommitdiffstats
path: root/doc/designs/quic-design/quic-fault-injector.md
blob: 703ea9cdae5cab90e24a6aad6603b26135a96a80 (plain)
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
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
QUIC Fault Injector
===================

The OpenSSL QUIC implementation receives QUIC packets from the network layer and
processes them accordingly. It will need to behave appropriately in the event of
a misbehaving peer, i.e. one which is sending protocol elements (e.g. datagrams,
packets, frames, etc) that are not in accordance with the specifications or
OpenSSL's expectations.

The QUIC Fault Injector is a component within the OpenSSL test framework that
can be used to simulate misbehaving peers and confirm that OpenSSL QUIC
implementation behaves in the expected manner in the event of such misbehaviour.

Typically an individual test will inject one particular misbehaviour (i.e. a
fault) into an otherwise normal QUIC connection. Therefore the fault injector
will have to be capable of creating fully normal QUIC protocol elements, but
also offer the flexibility for a test to modify those normal protocol elements
as required for the specific test circumstances. The OpenSSL QUIC implementation
in libssl does not offer the capability to send faults since it is designed to
be RFC compliant.

The QUIC Fault Injector will be external to libssl (it will be in the test
framework) but it will reuse the standards compliant QUIC implementation in
libssl and will make use of 3 integration points to inject faults. 2 of these
integration points will use new callbacks added to libssl. The final integration
point does not require any changes to libssl to work.

QUIC Integration Points
-----------------------

### TLS Handshake

Fault Injector based tests may need to inject faults directly into the TLS
handshake data (i.e. the contents of CRYPTO frames). However such faults may
need to be done in handshake messages that would normally be encrypted.
Additionally the contents of handshake messages are hashed and each peer
confirms that the other peer has the same calculated hash value as part of the
"Finished" message exchange - so any modifications would be rejected and the
handshake would fail.

An example test might be to confirm that an OpenSSL QUIC client behaves
correctly in the case that the server provides incorrectly formatted transport
parameters. These transport parameters are sent from the server in the
EncryptedExtensions message. That message is encrypted and so cannot be
modified by a "man-in-the-middle".

To support this integration point two new callbacks will be introduced to libssl
that enables modification of handshake data prior to it being encrypted and
hashed. These callbacks will be internal only (i.e. not part of the public API)
and so only usable by the Fault Injector.

The new libssl callbacks will be as follows:

```` C
typedef int (*ossl_statem_mutate_handshake_cb)(const unsigned char *msgin,
                                               size_t inlen,
                                               unsigned char **msgout,
                                               size_t *outlen,
                                               void *arg);

typedef void (*ossl_statem_finish_mutate_handshake_cb)(void *arg);

int ossl_statem_set_mutator(SSL *s,
                            ossl_statem_mutate_handshake_cb mutate_handshake_cb,
                            ossl_statem_finish_mutate_handshake_cb finish_mutate_handshake_cb,
                            void *mutatearg);
````

The two callbacks are set via a single internal function call
`ossl_statem_set_mutator`. The mutator callback `mutate_handshake_cb` will be
called after each handshake message has been constructed and is ready to send, but
before it has been passed through the handshake hashing code. It will be passed
a pointer to the constructed handshake message in `msgin` along with its
associated length in `inlen`. The mutator will construct a replacement handshake
message (typically by copying the input message and modifying it) and store it
in a newly allocated buffer. A pointer to the new buffer will be passed back
in `*msgout` and its length will be stored in `*outlen`. Optionally the mutator
can choose to not mutate by simply creating a new buffer with a copy of the data
in it. A return value of 1 indicates that the callback completed successfully. A
return value of 0 indicates a fatal error.

Once libssl has finished using the mutated buffer it will call the
`finish_mutate_handshake_cb` callback which can then release the buffer and
perform any other cleanup as required.

### QUIC Pre-Encryption Packets

QUIC Packets are the primary mechanism for exchanging protocol data within QUIC.
Multiple packets may be held within a single datagram, and each packet may
itself contain multiple frames. A packet gets protected via an AEAD encryption
algorithm prior to it being sent. Fault Injector based tests may need to inject
faults into these packets prior to them being encrypted.

An example test might insert an unrecognised frame type into a QUIC packet to
confirm that an OpenSSL QUIC client handles it appropriately (e.g. by raising a
protocol error).

The above functionality will be supported by the following two new callbacks
which will provide the ability to mutate packets before they are encrypted and
sent. As for the TLS callbacks these will be internal only and not part of the
public API.

```` C
typedef int (*ossl_mutate_packet_cb)(const QUIC_PKT_HDR *hdrin,
                                     const OSSL_QTX_IOVEC *iovecin, size_t numin,
                                     QUIC_PKT_HDR **hdrout,
                                     const OSSL_QTX_IOVEC **iovecout,
                                     size_t *numout,
                                     void *arg);

typedef void (*ossl_finish_mutate_cb)(void *arg);

void ossl_qtx_set_mutator(OSSL_QTX *qtx, ossl_mutate_packet_cb mutatecb,
                          ossl_finish_mutate_cb finishmutatecb, void *mutatearg);
````

A single new function call will set both callbacks. The `mutatecb` callback will
be invoked after each packet has been constructed but before protection has
been applied to it. The header for the packet will be pointed to by `hdrin` and
the payload will be in an iovec array pointed to by `iovecin` and containing
`numin` iovecs. The `mutatecb` callback is expected to allocate a new header
structure and return it in `*hdrout` and a new set of iovecs to be stored in
`*iovecout`. The number of iovecs need not be the same as the input. The number
of iovecs in the output array is stored in `*numout`. Optionally the callback
can choose to not mutate by simply creating new iovecs/headers with a copy of the
data in it. A return value of 1 indicates that the callback completed
successfully. A return value of 0 indicates a fatal error.

Once the OpenSSL QUIC implementation has finished using the mutated buffers the
`finishmutatecb` callback is called. This is expected to free any resources and
buffers that were allocated as part of the `mutatecb` call.

### QUIC Datagrams

Encrypted QUIC packets are sent in datagrams. There may be more than one QUIC
packet in a single datagram. Fault Injector based tests may need to inject
faults directly into these datagrams.

An example test might modify an encrypted packet to confirm that the AEAD
decryption process rejects it.

In order to provide this functionality the QUIC Fault Injector will insert
itself as a man-in-the-middle between the client and server. A BIO_s_dgram_pair()
will be used with one of the pair being used on the client end and the other
being associated with the Fault Injector. Similarly a second BIO_s_dgram_pair()
will be created with one used on the server and other used with the Fault
Injector.

With this setup the Fault Injector will act as a proxy and simply pass
datagrams sent from the client on to the server, and vice versa. Where a test
requires a modification to be made, that will occur prior to the datagram being
sent on.

This will all be implemented using public BIO APIs without requiring any
additional internal libssl callbacks.

Fault Injector API
------------------

The Fault Injector will utilise the callbacks described above in order to supply
a more test friendly API to test authors.

This API will primarily take the form of a set of event listener callbacks. A
test will be able to "listen" for a specifc event occuring and be informed about
it when it does. Examples of events might include:

- An EncryptedExtensions handshake message being sent
- An ACK frame being sent
- A Datagram being sent

Each listener will be provided with additional data about the specific event.
For example a listener that is listening for an EncryptedExtensions message will
be provided with the parsed contents of that message in an easy to use
structure. Additional helper functions will be provided to make changes to the
message (such as to resize it).

Initially listeners will only be able to listen for events on the server side.
This is because, in MVP, it will be the client side that is under test - so the
faults need to be injected into protocol elements sent from the server. Post
MVP this will be extended in order to be able to test the server. It may be that
we need to do this during MVP in order to be able to observe protocol elements
sent from the client without modifying them (i.e. in order to confirm that the
client is behaving as we expect). This will be added if required as we develop
the tests.

It is expected that the Fault Injector API will expand over time as new
listeners and helper functions are added to support specific test scenarios. The
initial API will provide a basic set of listeners and helper functions in order
to provide the basis for future work.

The following outlines an illustrative set of functions that will initially be
provided. A number of `TODO(QUIC)` comments are inserted to explain how we
might expand the API over time:

```` C
/* Type to represent the Fault Injector */
typedef struct ossl_quic_fault OSSL_QUIC_FAULT;

/*
 * Structure representing a parsed EncryptedExtension message. Listeners can
 * make changes to the contents of structure objects as required and the fault
 * injector will reconstruct the message to be sent on
 */
typedef struct ossl_qf_encrypted_extensions {
    /* EncryptedExtension messages just have an extensions block */
    unsigned char *extensions;
    size_t extensionslen;
} OSSL_QF_ENCRYPTED_EXTENSIONS;

/*
 * Given an SSL_CTX for the client and filenames for the server certificate and
 * keyfile, create a server and client instances as well as a fault injector
 * instance
 */
int qtest_create_quic_objects(SSL_CTX *clientctx, char *certfile, char *keyfile,
                              QUIC_TSERVER **qtserv, SSL **cssl,
                              OSSL_QUIC_FAULT **fault);

/*
 * Free up a Fault Injector instance
 */
void ossl_quic_fault_free(OSSL_QUIC_FAULT *fault);

/*
 * Run the TLS handshake to create a QUIC connection between the client and
 * server.
 */
int qtest_create_quic_connection(QUIC_TSERVER *qtserv, SSL *clientssl);

/*
 * Confirm that the server has received the given transport error code.
 */
int qtest_check_server_transport_err(QUIC_TSERVER *qtserv, uint64_t code);

/*
 * Confirm the server has received a protocol error. Equivalent to calling
 * qtest_check_server_transport_err with a code of QUIC_ERR_PROTOCOL_VIOLATION
 */
int qtest_check_server_protocol_err(QUIC_TSERVER *qtserv);

/*
 * Enable tests to listen for pre-encryption QUIC packets being sent
 */
typedef int (*ossl_quic_fault_on_packet_plain_cb)(OSSL_QUIC_FAULT *fault,
                                                  QUIC_PKT_HDR *hdr,
                                                  unsigned char *buf,
                                                  size_t len,
                                                  void *cbarg);

int ossl_quic_fault_set_packet_plain_listener(OSSL_QUIC_FAULT *fault,
                                    ossl_quic_fault_on_packet_plain_cb pplaincb,
                                    void *pplaincbarg);


/*
 * Helper function to be called from a packet_plain_listener callback if it
 * wants to resize the packet (either to add new data to it, or to truncate it).
 * The buf provided to packet_plain_listener is over allocated, so this just
 * changes the logical size and never changes the actual address of the buf.
 * This will fail if a large resize is attempted that exceeds the over
 * allocation.
 */
int ossl_quic_fault_resize_plain_packet(OSSL_QUIC_FAULT *fault, size_t newlen);

/*
 * Prepend frame data into a packet. To be called from a packet_plain_listener
 * callback
 */
int ossl_quic_fault_prepend_frame(OSSL_QUIC_FAULT *fault, unsigned char *frame,
                                  size_t frame_len);

/*
 * The general handshake message listener is sent the entire handshake message
 * data block, including the handshake header itself
 */
typedef int (*ossl_quic_fault_on_handshake_cb)(OSSL_QUIC_FAULT *fault,
                                               unsigned char *msg,
                                               size_t msglen,
                                               void *handshakecbarg);

int ossl_quic_fault_set_handshake_listener(OSSL_QUIC_FAULT *fault,
                                           ossl_quic_fault_on_handshake_cb handshakecb,
                                           void *handshakecbarg);

/*
 * Helper function to be called from a handshake_listener callback if it wants
 * to resize the handshake message (either to add new data to it, or to truncate
 * it). newlen must include the length of the handshake message header. The
 * handshake message buffer is over allocated, so this just changes the logical
 * size and never changes the actual address of the buf.
 * This will fail if a large resize is attempted that exceeds the over
 * allocation.
 */
int ossl_quic_fault_resize_handshake(OSSL_QUIC_FAULT *fault, size_t newlen);

/*
 * TODO(QUIC): Add listeners for specifc types of frame here. E.g. we might
 * expect to see an "ACK" frame listener which will be passed pre-parsed ack
 * data that can be modified as required.
 */

/*
 * Handshake message specific listeners. Unlike the general handshake message
 * listener these messages are pre-parsed and supplied with message specific
 * data and exclude the handshake header
 */
typedef int (*ossl_quic_fault_on_enc_ext_cb)(OSSL_QUIC_FAULT *fault,
                                             OSSL_QF_ENCRYPTED_EXTENSIONS *ee,
                                             size_t eelen,
                                             void *encextcbarg);

int ossl_quic_fault_set_hand_enc_ext_listener(OSSL_QUIC_FAULT *fault,
                                              ossl_quic_fault_on_enc_ext_cb encextcb,
                                              void *encextcbarg);

/* TODO(QUIC): Add listeners for other types of handshake message here */


/*
 * Helper function to be called from message specific listener callbacks. newlen
 * is the new length of the specific message excluding the handshake message
 * header.  The buffers provided to the message specific listeners are over
 * allocated, so this just changes the logical size and never changes the actual
 * address of the buffer. This will fail if a large resize is attempted that
 * exceeds the over allocation.
 */
int ossl_quic_fault_resize_message(OSSL_QUIC_FAULT *fault, size_t newlen);

/*
 * Helper function to delete an extension from an extension block. |exttype| is
 * the type of the extension to be deleted. |ext| points to the extension block.
 * On entry |*extlen| contains the length of the extension block. It is updated
 * with the new length on exit.
 */
int ossl_quic_fault_delete_extension(OSSL_QUIC_FAULT *fault,
                                     unsigned int exttype, unsigned char *ext,
                                     size_t *extlen);

/*
 * TODO(QUIC): Add additional helper functions for quering extensions here (e.g.
 * finding or adding them). We could also provide a "listener" API for listening
 * for specific extension types
 */

/*
 * Enable tests to listen for post-encryption QUIC packets being sent
 */
typedef int (*ossl_quic_fault_on_packet_cipher_cb)(OSSL_QUIC_FAULT *fault,
                                                   /* The parsed packet header */
                                                   QUIC_PKT_HDR *hdr,
                                                   /* The packet payload data */
                                                   unsigned char *buf,
                                                   /* Length of the payload */
                                                   size_t len,
                                                   void *cbarg);

int ossl_quic_fault_set_packet_cipher_listener(OSSL_QUIC_FAULT *fault,
                                ossl_quic_fault_on_packet_cipher_cb pciphercb,
                                void *picphercbarg);

/*
 * Enable tests to listen for datagrams being sent
 */
typedef int (*ossl_quic_fault_on_datagram_cb)(OSSL_QUIC_FAULT *fault,
                                              BIO_MSG *m,
                                              size_t stride,
                                              void *cbarg);

int ossl_quic_fault_set_datagram_listener(OSSL_QUIC_FAULT *fault,
                                          ossl_quic_fault_on_datagram_cb datagramcb,
                                          void *datagramcbarg);

/*
 * To be called from a datagram_listener callback. The datagram buffer is over
 * allocated, so this just changes the logical size and never changes the actual
 * address of the buffer. This will fail if a large resize is attempted that
 * exceeds the over allocation.
 */
int ossl_quic_fault_resize_datagram(OSSL_QUIC_FAULT *fault, size_t newlen);

````

Example Tests
-------------

This section provides some example tests to illustrate how the Fault Injector
might be used to create tests.

### Unknown Frame Test

An example test showing a server sending a frame of an unknown type to the
client:

```` C
/*
 * Test that adding an unknown frame type is handled correctly
 */
static int add_unknown_frame_cb(OSSL_QUIC_FAULT *fault, QUIC_PKT_HDR *hdr,
                                unsigned char *buf, size_t len, void *cbarg)
{
    static size_t done = 0;
    /*
     * There are no "reserved" frame types which are definitately safe for us
     * to use for testing purposes - but we just use the highest possible
     * value (8 byte length integer) and with no payload bytes
     */
    unsigned char unknown_frame[] = {
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
    };

    /* We only ever add the unknown frame to one packet */
    if (done++)
        return 1;

    return ossl_quic_fault_prepend_frame(fault, unknown_frame,
                                         sizeof(unknown_frame));
}

static int test_unknown_frame(void)
{
    int testresult = 0, ret;
    SSL_CTX *cctx = SSL_CTX_new(OSSL_QUIC_client_method());
    QUIC_TSERVER *qtserv = NULL;
    SSL *cssl = NULL;
    char *msg = "Hello World!";
    size_t msglen = strlen(msg);
    unsigned char buf[80];
    size_t byteswritten;
    OSSL_QUIC_FAULT *fault = NULL;

    if (!TEST_ptr(cctx))
        goto err;

    if (!TEST_true(qtest_create_quic_objects(cctx, cert, privkey, &qtserv,
                                             &cssl, &fault)))
        goto err;

    if (!TEST_true(qtest_create_quic_connection(qtserv, cssl)))
        goto err;

    /*
     * Write a message from the server to the client and add an unknown frame
     * type
     */
    if (!TEST_true(ossl_quic_fault_set_packet_plain_listener(fault,
                                                             add_unknown_frame_cb,
                                                             NULL)))
        goto err;

    if (!TEST_true(ossl_quic_tserver_write(qtserv, (unsigned char *)msg, msglen,
                                           &byteswritten)))
        goto err;

    if (!TEST_size_t_eq(msglen, byteswritten))
        goto err;

    ossl_quic_tserver_tick(qtserv);
    if (!TEST_true(SSL_tick(cssl)))
        goto err;

    if (!TEST_int_le(ret = SSL_read(cssl, buf, sizeof(buf)), 0))
        goto err;

    if (!TEST_int_eq(SSL_get_error(cssl, ret), SSL_ERROR_SSL))
        goto err;

#if 0
    /*
     * TODO(QUIC): We should expect an error on the queue after this - but we
     * don't have it yet.
     * Note, just raising the error in the obvious place causes SSL_tick() to
     * succeed, but leave a suprious error on the stack. We need to either
     * allow SSL_tick() to fail, or somehow delay the raising of the error
     * until the SSL_read() call.
     */
    if (!TEST_int_eq(ERR_GET_REASON(ERR_peek_error()),
                     SSL_R_UNKNOWN_FRAME_TYPE_RECEIVED))
        goto err;
#endif

    if (!TEST_true(qtest_check_server_protocol_err(qtserv)))
        goto err;

    testresult = 1;
 err:
    ossl_quic_fault_free(fault);
    SSL_free(cssl);
    ossl_quic_tserver_free(qtserv);
    SSL_CTX_free(cctx);
    return testresult;
}
````

### No Transport Parameters test

An example test showing the case where a server does not supply any transport
parameters in the TLS handshake:

```` C
/*
 * Test that a server that fails to provide transport params cannot be
 * connected to.
 */
static int drop_transport_params_cb(OSSL_QUIC_FAULT *fault,
                                    OSSL_QF_ENCRYPTED_EXTENSIONS *ee,
                                    size_t eelen, void *encextcbarg)
{
    if (!ossl_quic_fault_delete_extension(fault,
                                          TLSEXT_TYPE_quic_transport_parameters,
                                          ee->extensions, &ee->extensionslen))
        return 0;

    return 1;
}

static int test_no_transport_params(void)
{
    int testresult = 0;
    SSL_CTX *cctx = SSL_CTX_new(OSSL_QUIC_client_method());
    QUIC_TSERVER *qtserv = NULL;
    SSL *cssl = NULL;
    OSSL_QUIC_FAULT *fault = NULL;

    if (!TEST_ptr(cctx))
        goto err;

    if (!TEST_true(qtest_create_quic_objects(cctx, cert, privkey, &qtserv,
                                             &cssl, &fault)))
        goto err;

    if (!TEST_true(ossl_quic_fault_set_hand_enc_ext_listener(fault,
                                                             drop_transport_params_cb,
                                                             NULL)))
        goto err;

    /*
     * We expect the connection to fail because the server failed to provide
     * transport parameters
     */
    if (!TEST_false(qtest_create_quic_connection(qtserv, cssl)))
        goto err;

    if (!TEST_true(qtest_check_server_protocol_err(qtserv)))
        goto err;

    testresult = 1;
 err:
    ossl_quic_fault_free(fault);
    SSL_free(cssl);
    ossl_quic_tserver_free(qtserv);
    SSL_CTX_free(cctx);
    return testresult;

}
````