]> git.ipfire.org Git - thirdparty/openssl.git/blob - test/quicfaultstest.c
Copyright year updates
[thirdparty/openssl.git] / test / quicfaultstest.c
1 /*
2 * Copyright 2022-2023 The OpenSSL Project Authors. All Rights Reserved.
3 *
4 * Licensed under the Apache License 2.0 (the "License"). You may not use
5 * this file except in compliance with the License. You can obtain a copy
6 * in the file LICENSE in the source distribution or at
7 * https://www.openssl.org/source/license.html
8 */
9
10 #include <string.h>
11 #include <openssl/ssl.h>
12 #include "helpers/quictestlib.h"
13 #include "internal/quic_error.h"
14 #include "testutil.h"
15
16 static char *cert = NULL;
17 static char *privkey = NULL;
18
19 /*
20 * Basic test that just creates a connection and sends some data without any
21 * faults injected.
22 */
23 static int test_basic(void)
24 {
25 int testresult = 0;
26 SSL_CTX *cctx = SSL_CTX_new(OSSL_QUIC_client_method());
27 QUIC_TSERVER *qtserv = NULL;
28 SSL *cssl = NULL;
29 char *msg = "Hello World!";
30 size_t msglen = strlen(msg);
31 unsigned char buf[80];
32 size_t bytesread;
33
34 if (!TEST_ptr(cctx))
35 goto err;
36
37 if (!TEST_true(qtest_create_quic_objects(NULL, cctx, NULL, cert, privkey, 0,
38 &qtserv, &cssl, NULL)))
39 goto err;
40
41 if (!TEST_true(qtest_create_quic_connection(qtserv, cssl)))
42 goto err;
43
44 if (!TEST_int_eq(SSL_write(cssl, msg, msglen), msglen))
45 goto err;
46
47 ossl_quic_tserver_tick(qtserv);
48 if (!TEST_true(ossl_quic_tserver_read(qtserv, 0, buf, sizeof(buf), &bytesread)))
49 goto err;
50
51 /*
52 * We assume the entire message is read from the server in one go. In
53 * theory this could get fragmented but its a small message so we assume
54 * not.
55 */
56 if (!TEST_mem_eq(msg, msglen, buf, bytesread))
57 goto err;
58
59 testresult = 1;
60 err:
61 SSL_free(cssl);
62 ossl_quic_tserver_free(qtserv);
63 SSL_CTX_free(cctx);
64 return testresult;
65 }
66
67 /*
68 * Test that adding an unknown frame type is handled correctly
69 */
70 static int add_unknown_frame_cb(QTEST_FAULT *fault, QUIC_PKT_HDR *hdr,
71 unsigned char *buf, size_t len, void *cbarg)
72 {
73 static size_t done = 0;
74 /*
75 * There are no "reserved" frame types which are definitately safe for us
76 * to use for testing purposes - but we just use the highest possible
77 * value (8 byte length integer) and with no payload bytes
78 */
79 unsigned char unknown_frame[] = {
80 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
81 };
82
83 /* We only ever add the unknown frame to one packet */
84 if (done++)
85 return 1;
86
87 return qtest_fault_prepend_frame(fault, unknown_frame,
88 sizeof(unknown_frame));
89 }
90
91 static int test_unknown_frame(void)
92 {
93 int testresult = 0, ret;
94 SSL_CTX *cctx = SSL_CTX_new(OSSL_QUIC_client_method());
95 QUIC_TSERVER *qtserv = NULL;
96 SSL *cssl = NULL;
97 char *msg = "Hello World!";
98 size_t msglen = strlen(msg);
99 unsigned char buf[80];
100 size_t byteswritten;
101 QTEST_FAULT *fault = NULL;
102 uint64_t sid = UINT64_MAX;
103
104 if (!TEST_ptr(cctx))
105 goto err;
106
107 if (!TEST_true(qtest_create_quic_objects(NULL, cctx, NULL, cert, privkey, 0,
108 &qtserv, &cssl, &fault)))
109 goto err;
110
111 if (!TEST_true(qtest_create_quic_connection(qtserv, cssl)))
112 goto err;
113
114 /*
115 * Write a message from the server to the client and add an unknown frame
116 * type
117 */
118 if (!TEST_true(qtest_fault_set_packet_plain_listener(fault,
119 add_unknown_frame_cb,
120 NULL)))
121 goto err;
122
123 if (!TEST_true(ossl_quic_tserver_stream_new(qtserv, /*is_uni=*/0, &sid))
124 || !TEST_uint64_t_eq(sid, 1))
125 goto err;
126
127 if (!TEST_true(ossl_quic_tserver_write(qtserv, sid, (unsigned char *)msg, msglen,
128 &byteswritten)))
129 goto err;
130
131 if (!TEST_size_t_eq(msglen, byteswritten))
132 goto err;
133
134 ossl_quic_tserver_tick(qtserv);
135 if (!TEST_true(SSL_handle_events(cssl)))
136 goto err;
137
138 if (!TEST_int_le(ret = SSL_read(cssl, buf, sizeof(buf)), 0))
139 goto err;
140
141 if (!TEST_int_eq(SSL_get_error(cssl, ret), SSL_ERROR_SSL))
142 goto err;
143
144 if (!TEST_int_eq(ERR_GET_REASON(ERR_peek_error()),
145 SSL_R_QUIC_PROTOCOL_ERROR))
146 goto err;
147
148 if (!TEST_true(qtest_check_server_frame_encoding_err(qtserv)))
149 goto err;
150
151 testresult = 1;
152 err:
153 qtest_fault_free(fault);
154 SSL_free(cssl);
155 ossl_quic_tserver_free(qtserv);
156 SSL_CTX_free(cctx);
157 return testresult;
158 }
159
160 /*
161 * Test that a server that fails to provide transport params cannot be
162 * connected to.
163 */
164 static int drop_transport_params_cb(QTEST_FAULT *fault,
165 QTEST_ENCRYPTED_EXTENSIONS *ee,
166 size_t eelen, void *encextcbarg)
167 {
168 if (!qtest_fault_delete_extension(fault,
169 TLSEXT_TYPE_quic_transport_parameters,
170 ee->extensions, &ee->extensionslen))
171 return 0;
172
173 return 1;
174 }
175
176 static int test_no_transport_params(void)
177 {
178 int testresult = 0;
179 SSL_CTX *cctx = SSL_CTX_new(OSSL_QUIC_client_method());
180 QUIC_TSERVER *qtserv = NULL;
181 SSL *cssl = NULL;
182 QTEST_FAULT *fault = NULL;
183
184 if (!TEST_ptr(cctx))
185 goto err;
186
187 if (!TEST_true(qtest_create_quic_objects(NULL, cctx, NULL, cert, privkey, 0,
188 &qtserv, &cssl, &fault)))
189 goto err;
190
191 if (!TEST_true(qtest_fault_set_hand_enc_ext_listener(fault,
192 drop_transport_params_cb,
193 NULL)))
194 goto err;
195
196 /*
197 * We expect the connection to fail because the server failed to provide
198 * transport parameters
199 */
200 if (!TEST_false(qtest_create_quic_connection(qtserv, cssl)))
201 goto err;
202
203 if (!TEST_true(qtest_check_server_transport_err(qtserv,
204 QUIC_ERR_CRYPTO_MISSING_EXT)))
205 goto err;
206
207 testresult = 1;
208 err:
209 qtest_fault_free(fault);
210 SSL_free(cssl);
211 ossl_quic_tserver_free(qtserv);
212 SSL_CTX_free(cctx);
213 return testresult;
214 }
215
216 /*
217 * Test that corrupted packets/datagrams are dropped and retransmitted
218 */
219 static int docorrupt = 0;
220
221 static int on_packet_cipher_cb(QTEST_FAULT *fault, QUIC_PKT_HDR *hdr,
222 unsigned char *buf, size_t len, void *cbarg)
223 {
224 if (!docorrupt || len == 0)
225 return 1;
226
227 buf[(size_t)test_random() % len] ^= 0xff;
228 docorrupt = 0;
229
230 return 1;
231 }
232
233 static int on_datagram_cb(QTEST_FAULT *fault, BIO_MSG *m, size_t stride,
234 void *cbarg)
235 {
236 if (!docorrupt || m->data_len == 0)
237 return 1;
238
239 if (!qtest_fault_resize_datagram(fault, m->data_len - 1))
240 return 1;
241
242 docorrupt = 0;
243
244 return 1;
245 }
246
247 /*
248 * Test 1: Corrupt by flipping bits in an encrypted packet
249 * Test 2: Corrupt by truncating an entire datagram
250 */
251 static int test_corrupted_data(int idx)
252 {
253 QTEST_FAULT *fault = NULL;
254 int testresult = 0;
255 SSL_CTX *cctx = SSL_CTX_new(OSSL_QUIC_client_method());
256 QUIC_TSERVER *qtserv = NULL;
257 SSL *cssl = NULL;
258 char *msg = "Hello World!";
259 size_t msglen = strlen(msg);
260 unsigned char buf[80];
261 size_t bytesread, byteswritten;
262 uint64_t sid = UINT64_MAX;
263
264 if (!TEST_ptr(cctx))
265 goto err;
266
267 if (!TEST_true(qtest_create_quic_objects(NULL, cctx, NULL, cert, privkey,
268 QTEST_FLAG_FAKE_TIME, &qtserv,
269 &cssl, &fault)))
270 goto err;
271
272 if (idx == 0) {
273 /* Listen for encrypted packets being sent */
274 if (!TEST_true(qtest_fault_set_packet_cipher_listener(fault,
275 on_packet_cipher_cb,
276 NULL)))
277 goto err;
278 } else {
279 /* Listen for datagrams being sent */
280 if (!TEST_true(qtest_fault_set_datagram_listener(fault,
281 on_datagram_cb,
282 NULL)))
283 goto err;
284 }
285 if (!TEST_true(qtest_create_quic_connection(qtserv, cssl)))
286 goto err;
287
288 /* Corrupt the next server packet*/
289 docorrupt = 1;
290
291 if (!TEST_true(ossl_quic_tserver_stream_new(qtserv, /*is_uni=*/0, &sid))
292 || !TEST_uint64_t_eq(sid, 1))
293 goto err;
294
295 /*
296 * Send first 5 bytes of message. This will get corrupted and is treated as
297 * "lost"
298 */
299 if (!TEST_true(ossl_quic_tserver_write(qtserv, sid, (unsigned char *)msg, 5,
300 &byteswritten)))
301 goto err;
302
303 if (!TEST_size_t_eq(byteswritten, 5))
304 goto err;
305
306 /*
307 * Introduce a small delay so that the above packet has time to be detected
308 * as lost. Loss detection times are based on RTT which should be very
309 * fast for us since there isn't really a network. The loss delay timer is
310 * always at least 1ms though. We skip forward 100ms
311 */
312 qtest_add_time(100);
313
314 /* Send rest of message */
315 if (!TEST_true(ossl_quic_tserver_write(qtserv, sid, (unsigned char *)msg + 5,
316 msglen - 5, &byteswritten)))
317 goto err;
318
319 if (!TEST_size_t_eq(byteswritten, msglen - 5))
320 goto err;
321
322 /*
323 * Receive the corrupted packet. This should get dropped and is effectively
324 * "lost". We also process the second packet which should be decrypted
325 * successfully. Therefore we ack the frames in it
326 */
327 if (!TEST_true(SSL_handle_events(cssl)))
328 goto err;
329
330 /*
331 * Process the ack. Detect that the first part of the message must have
332 * been lost due to the time elapsed since it was sent and resend it
333 */
334 ossl_quic_tserver_tick(qtserv);
335
336 /* Receive and process the newly arrived message data resend */
337 if (!TEST_true(SSL_handle_events(cssl)))
338 goto err;
339
340 /* The whole message should now have arrived */
341 if (!TEST_true(SSL_read_ex(cssl, buf, sizeof(buf), &bytesread)))
342 goto err;
343
344 if (!TEST_mem_eq(msg, msglen, buf, bytesread))
345 goto err;
346
347 /*
348 * If the test was successful then we corrupted exactly one packet and
349 * docorrupt was reset
350 */
351 if (!TEST_false(docorrupt))
352 goto err;
353
354 testresult = 1;
355 err:
356 qtest_fault_free(fault);
357 SSL_free(cssl);
358 ossl_quic_tserver_free(qtserv);
359 SSL_CTX_free(cctx);
360 return testresult;
361 }
362
363 OPT_TEST_DECLARE_USAGE("certsdir\n")
364
365 int setup_tests(void)
366 {
367 char *certsdir = NULL;
368
369 if (!test_skip_common_options()) {
370 TEST_error("Error parsing test options\n");
371 return 0;
372 }
373
374 if (!TEST_ptr(certsdir = test_get_argument(0)))
375 return 0;
376
377 cert = test_mk_file_path(certsdir, "servercert.pem");
378 if (cert == NULL)
379 goto err;
380
381 privkey = test_mk_file_path(certsdir, "serverkey.pem");
382 if (privkey == NULL)
383 goto err;
384
385 ADD_TEST(test_basic);
386 ADD_TEST(test_unknown_frame);
387 ADD_TEST(test_no_transport_params);
388 ADD_ALL_TESTS(test_corrupted_data, 2);
389
390 return 1;
391
392 err:
393 OPENSSL_free(cert);
394 OPENSSL_free(privkey);
395 return 0;
396 }
397
398 void cleanup_tests(void)
399 {
400 OPENSSL_free(cert);
401 OPENSSL_free(privkey);
402 }