1 | /*
|
---|
2 | * Copyright 2022-2024 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, 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, NULL)))
|
---|
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_extensions_cb(QTEST_FAULT *fault,
|
---|
165 | QTEST_ENCRYPTED_EXTENSIONS *ee,
|
---|
166 | size_t eelen, void *encextcbarg)
|
---|
167 | {
|
---|
168 | int *ext = (int *)encextcbarg;
|
---|
169 |
|
---|
170 | if (!qtest_fault_delete_extension(fault, *ext, ee->extensions,
|
---|
171 | &ee->extensionslen, NULL))
|
---|
172 | return 0;
|
---|
173 |
|
---|
174 | return 1;
|
---|
175 | }
|
---|
176 |
|
---|
177 | static int test_drop_extensions(int idx)
|
---|
178 | {
|
---|
179 | int testresult = 0;
|
---|
180 | SSL_CTX *cctx = SSL_CTX_new(OSSL_QUIC_client_method());
|
---|
181 | QUIC_TSERVER *qtserv = NULL;
|
---|
182 | SSL *cssl = NULL;
|
---|
183 | QTEST_FAULT *fault = NULL;
|
---|
184 | int ext, err;
|
---|
185 |
|
---|
186 | if (!TEST_ptr(cctx))
|
---|
187 | goto err;
|
---|
188 |
|
---|
189 | if (!TEST_true(qtest_create_quic_objects(NULL, cctx, NULL, cert, privkey, 0,
|
---|
190 | &qtserv, &cssl, &fault, NULL)))
|
---|
191 | goto err;
|
---|
192 |
|
---|
193 | if (idx == 0) {
|
---|
194 | ext = TLSEXT_TYPE_quic_transport_parameters;
|
---|
195 | err = OSSL_QUIC_ERR_CRYPTO_MISSING_EXT;
|
---|
196 | } else {
|
---|
197 | ext = TLSEXT_TYPE_application_layer_protocol_negotiation;
|
---|
198 | err = OSSL_QUIC_ERR_CRYPTO_NO_APP_PROTO;
|
---|
199 | }
|
---|
200 |
|
---|
201 | if (!TEST_true(qtest_fault_set_hand_enc_ext_listener(fault,
|
---|
202 | drop_extensions_cb,
|
---|
203 | &ext)))
|
---|
204 | goto err;
|
---|
205 |
|
---|
206 | /*
|
---|
207 | * We expect the connection to fail because the server failed to provide
|
---|
208 | * transport parameters
|
---|
209 | */
|
---|
210 | if (!TEST_false(qtest_create_quic_connection(qtserv, cssl)))
|
---|
211 | goto err;
|
---|
212 |
|
---|
213 | if (!TEST_true(qtest_check_server_transport_err(qtserv, err)))
|
---|
214 | goto err;
|
---|
215 |
|
---|
216 | testresult = 1;
|
---|
217 | err:
|
---|
218 | qtest_fault_free(fault);
|
---|
219 | SSL_free(cssl);
|
---|
220 | ossl_quic_tserver_free(qtserv);
|
---|
221 | SSL_CTX_free(cctx);
|
---|
222 | return testresult;
|
---|
223 | }
|
---|
224 |
|
---|
225 | /*
|
---|
226 | * Test that corrupted packets/datagrams are dropped and retransmitted
|
---|
227 | */
|
---|
228 | static int docorrupt = 0;
|
---|
229 |
|
---|
230 | static int on_packet_cipher_cb(QTEST_FAULT *fault, QUIC_PKT_HDR *hdr,
|
---|
231 | unsigned char *buf, size_t len, void *cbarg)
|
---|
232 | {
|
---|
233 | if (!docorrupt || len == 0)
|
---|
234 | return 1;
|
---|
235 |
|
---|
236 | buf[(size_t)test_random() % len] ^= 0xff;
|
---|
237 | docorrupt = 0;
|
---|
238 |
|
---|
239 | return 1;
|
---|
240 | }
|
---|
241 |
|
---|
242 | static int on_datagram_cb(QTEST_FAULT *fault, BIO_MSG *m, size_t stride,
|
---|
243 | void *cbarg)
|
---|
244 | {
|
---|
245 | if (!docorrupt || m->data_len == 0)
|
---|
246 | return 1;
|
---|
247 |
|
---|
248 | if (!qtest_fault_resize_datagram(fault, m->data_len - 1))
|
---|
249 | return 1;
|
---|
250 |
|
---|
251 | docorrupt = 0;
|
---|
252 |
|
---|
253 | return 1;
|
---|
254 | }
|
---|
255 |
|
---|
256 | /*
|
---|
257 | * Test 1: Corrupt by flipping bits in an encrypted packet
|
---|
258 | * Test 2: Corrupt by truncating an entire datagram
|
---|
259 | */
|
---|
260 | static int test_corrupted_data(int idx)
|
---|
261 | {
|
---|
262 | QTEST_FAULT *fault = NULL;
|
---|
263 | int testresult = 0;
|
---|
264 | SSL_CTX *cctx = SSL_CTX_new(OSSL_QUIC_client_method());
|
---|
265 | QUIC_TSERVER *qtserv = NULL;
|
---|
266 | SSL *cssl = NULL;
|
---|
267 | char *msg = "Hello World!";
|
---|
268 | size_t msglen = strlen(msg);
|
---|
269 | unsigned char buf[80];
|
---|
270 | size_t bytesread, byteswritten;
|
---|
271 | uint64_t sid = UINT64_MAX;
|
---|
272 |
|
---|
273 | if (!TEST_ptr(cctx))
|
---|
274 | goto err;
|
---|
275 |
|
---|
276 | if (!TEST_true(qtest_create_quic_objects(NULL, cctx, NULL, cert, privkey,
|
---|
277 | QTEST_FLAG_FAKE_TIME, &qtserv,
|
---|
278 | &cssl, &fault, NULL)))
|
---|
279 | goto err;
|
---|
280 |
|
---|
281 | if (idx == 0) {
|
---|
282 | /* Listen for encrypted packets being sent */
|
---|
283 | if (!TEST_true(qtest_fault_set_packet_cipher_listener(fault,
|
---|
284 | on_packet_cipher_cb,
|
---|
285 | NULL)))
|
---|
286 | goto err;
|
---|
287 | } else {
|
---|
288 | /* Listen for datagrams being sent */
|
---|
289 | if (!TEST_true(qtest_fault_set_datagram_listener(fault,
|
---|
290 | on_datagram_cb,
|
---|
291 | NULL)))
|
---|
292 | goto err;
|
---|
293 | }
|
---|
294 | if (!TEST_true(qtest_create_quic_connection(qtserv, cssl)))
|
---|
295 | goto err;
|
---|
296 |
|
---|
297 | /* Corrupt the next server packet*/
|
---|
298 | docorrupt = 1;
|
---|
299 |
|
---|
300 | if (!TEST_true(ossl_quic_tserver_stream_new(qtserv, /*is_uni=*/0, &sid))
|
---|
301 | || !TEST_uint64_t_eq(sid, 1))
|
---|
302 | goto err;
|
---|
303 |
|
---|
304 | /*
|
---|
305 | * Send first 5 bytes of message. This will get corrupted and is treated as
|
---|
306 | * "lost"
|
---|
307 | */
|
---|
308 | if (!TEST_true(ossl_quic_tserver_write(qtserv, sid, (unsigned char *)msg, 5,
|
---|
309 | &byteswritten)))
|
---|
310 | goto err;
|
---|
311 |
|
---|
312 | if (!TEST_size_t_eq(byteswritten, 5))
|
---|
313 | goto err;
|
---|
314 |
|
---|
315 | /*
|
---|
316 | * Introduce a small delay so that the above packet has time to be detected
|
---|
317 | * as lost. Loss detection times are based on RTT which should be very
|
---|
318 | * fast for us since there isn't really a network. The loss delay timer is
|
---|
319 | * always at least 1ms though. We skip forward 100ms
|
---|
320 | */
|
---|
321 | qtest_add_time(100);
|
---|
322 |
|
---|
323 | /* Send rest of message */
|
---|
324 | if (!TEST_true(ossl_quic_tserver_write(qtserv, sid, (unsigned char *)msg + 5,
|
---|
325 | msglen - 5, &byteswritten)))
|
---|
326 | goto err;
|
---|
327 |
|
---|
328 | if (!TEST_size_t_eq(byteswritten, msglen - 5))
|
---|
329 | goto err;
|
---|
330 |
|
---|
331 | /*
|
---|
332 | * Receive the corrupted packet. This should get dropped and is effectively
|
---|
333 | * "lost". We also process the second packet which should be decrypted
|
---|
334 | * successfully. Therefore we ack the frames in it
|
---|
335 | */
|
---|
336 | if (!TEST_true(SSL_handle_events(cssl)))
|
---|
337 | goto err;
|
---|
338 |
|
---|
339 | /*
|
---|
340 | * Process the ack. Detect that the first part of the message must have
|
---|
341 | * been lost due to the time elapsed since it was sent and resend it
|
---|
342 | */
|
---|
343 | ossl_quic_tserver_tick(qtserv);
|
---|
344 |
|
---|
345 | /* Receive and process the newly arrived message data resend */
|
---|
346 | if (!TEST_true(SSL_handle_events(cssl)))
|
---|
347 | goto err;
|
---|
348 |
|
---|
349 | /* The whole message should now have arrived */
|
---|
350 | if (!TEST_true(SSL_read_ex(cssl, buf, sizeof(buf), &bytesread)))
|
---|
351 | goto err;
|
---|
352 |
|
---|
353 | if (!TEST_mem_eq(msg, msglen, buf, bytesread))
|
---|
354 | goto err;
|
---|
355 |
|
---|
356 | /*
|
---|
357 | * If the test was successful then we corrupted exactly one packet and
|
---|
358 | * docorrupt was reset
|
---|
359 | */
|
---|
360 | if (!TEST_false(docorrupt))
|
---|
361 | goto err;
|
---|
362 |
|
---|
363 | testresult = 1;
|
---|
364 | err:
|
---|
365 | qtest_fault_free(fault);
|
---|
366 | SSL_free(cssl);
|
---|
367 | ossl_quic_tserver_free(qtserv);
|
---|
368 | SSL_CTX_free(cctx);
|
---|
369 | return testresult;
|
---|
370 | }
|
---|
371 |
|
---|
372 | OPT_TEST_DECLARE_USAGE("certsdir\n")
|
---|
373 |
|
---|
374 | int setup_tests(void)
|
---|
375 | {
|
---|
376 | char *certsdir = NULL;
|
---|
377 |
|
---|
378 | if (!test_skip_common_options()) {
|
---|
379 | TEST_error("Error parsing test options\n");
|
---|
380 | return 0;
|
---|
381 | }
|
---|
382 |
|
---|
383 | if (!TEST_ptr(certsdir = test_get_argument(0)))
|
---|
384 | return 0;
|
---|
385 |
|
---|
386 | cert = test_mk_file_path(certsdir, "servercert.pem");
|
---|
387 | if (cert == NULL)
|
---|
388 | goto err;
|
---|
389 |
|
---|
390 | privkey = test_mk_file_path(certsdir, "serverkey.pem");
|
---|
391 | if (privkey == NULL)
|
---|
392 | goto err;
|
---|
393 |
|
---|
394 | ADD_TEST(test_basic);
|
---|
395 | ADD_TEST(test_unknown_frame);
|
---|
396 | ADD_ALL_TESTS(test_drop_extensions, 2);
|
---|
397 | ADD_ALL_TESTS(test_corrupted_data, 2);
|
---|
398 |
|
---|
399 | return 1;
|
---|
400 |
|
---|
401 | err:
|
---|
402 | OPENSSL_free(cert);
|
---|
403 | OPENSSL_free(privkey);
|
---|
404 | return 0;
|
---|
405 | }
|
---|
406 |
|
---|
407 | void cleanup_tests(void)
|
---|
408 | {
|
---|
409 | OPENSSL_free(cert);
|
---|
410 | OPENSSL_free(privkey);
|
---|
411 | }
|
---|