]>
Commit | Line | Data |
---|---|---|
adef87a2 MC |
1 | /* |
2 | * Copyright 2022 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" | |
71587f2b | 13 | #include "internal/quic_error.h" |
adef87a2 MC |
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(cctx, cert, privkey, &qtserv, | |
38 | &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, 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 | ||
71587f2b MC |
67 | /* |
68 | * Test that adding an unknown frame type is handled correctly | |
69 | */ | |
70 | static int add_unknown_frame_cb(OSSL_QUIC_FAULT *fault, QUIC_PKT_HDR *hdr, | |
71 | unsigned char *buf, size_t len, void *cbarg) | |
72 | { | |
7eaaaaaa | 73 | static size_t done = 0; |
71587f2b MC |
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 */ | |
7eaaaaaa | 84 | if (done++) |
71587f2b | 85 | return 1; |
71587f2b | 86 | |
7eaaaaaa MC |
87 | return ossl_quic_fault_prepend_frame(fault, unknown_frame, |
88 | sizeof(unknown_frame)); | |
71587f2b MC |
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 | OSSL_QUIC_FAULT *fault = NULL; | |
71587f2b MC |
102 | |
103 | if (!TEST_ptr(cctx)) | |
104 | goto err; | |
105 | ||
106 | if (!TEST_true(qtest_create_quic_objects(cctx, cert, privkey, &qtserv, | |
107 | &cssl, &fault))) | |
108 | goto err; | |
109 | ||
110 | if (!TEST_true(qtest_create_quic_connection(qtserv, cssl))) | |
111 | goto err; | |
112 | ||
113 | /* | |
da81f1e5 | 114 | * Write a message from the server to the client and add an unknown frame |
71587f2b MC |
115 | * type |
116 | */ | |
117 | if (!TEST_true(ossl_quic_fault_set_packet_plain_listener(fault, | |
118 | add_unknown_frame_cb, | |
119 | NULL))) | |
120 | goto err; | |
121 | ||
122 | if (!TEST_true(ossl_quic_tserver_write(qtserv, (unsigned char *)msg, msglen, | |
123 | &byteswritten))) | |
124 | goto err; | |
125 | ||
126 | if (!TEST_size_t_eq(msglen, byteswritten)) | |
127 | goto err; | |
128 | ||
129 | ossl_quic_tserver_tick(qtserv); | |
130 | if (!TEST_true(SSL_tick(cssl))) | |
131 | goto err; | |
132 | ||
133 | if (!TEST_int_le(ret = SSL_read(cssl, buf, sizeof(buf)), 0)) | |
134 | goto err; | |
135 | ||
136 | if (!TEST_int_eq(SSL_get_error(cssl, ret), SSL_ERROR_SSL)) | |
137 | goto err; | |
138 | ||
139 | #if 0 | |
140 | /* | |
141 | * TODO(QUIC): We should expect an error on the queue after this - but we | |
142 | * don't have it yet. | |
143 | * Note, just raising the error in the obvious place causes SSL_tick() to | |
144 | * succeed, but leave a suprious error on the stack. We need to either | |
145 | * allow SSL_tick() to fail, or somehow delay the raising of the error | |
146 | * until the SSL_read() call. | |
147 | */ | |
148 | if (!TEST_int_eq(ERR_GET_REASON(ERR_peek_error()), | |
149 | SSL_R_UNKNOWN_FRAME_TYPE_RECEIVED)) | |
150 | goto err; | |
151 | #endif | |
152 | ||
f10e5885 MC |
153 | if (!TEST_true(qtest_check_server_protocol_err(qtserv))) |
154 | goto err; | |
71587f2b | 155 | |
f10e5885 MC |
156 | testresult = 1; |
157 | err: | |
158 | ossl_quic_fault_free(fault); | |
159 | SSL_free(cssl); | |
160 | ossl_quic_tserver_free(qtserv); | |
161 | SSL_CTX_free(cctx); | |
162 | return testresult; | |
163 | } | |
164 | ||
165 | /* | |
166 | * Test that a server that fails to provide transport params cannot be | |
167 | * connected to. | |
168 | */ | |
169 | static int drop_transport_params_cb(OSSL_QUIC_FAULT *fault, | |
170 | OSSL_QF_ENCRYPTED_EXTENSIONS *ee, | |
171 | size_t eelen, void *encextcbarg) | |
172 | { | |
173 | if (!ossl_quic_fault_delete_extension(fault, | |
174 | TLSEXT_TYPE_quic_transport_parameters, | |
175 | ee->extensions, &ee->extensionslen)) | |
176 | return 0; | |
177 | ||
178 | return 1; | |
179 | } | |
180 | ||
181 | static int test_no_transport_params(void) | |
182 | { | |
183 | int testresult = 0; | |
184 | SSL_CTX *cctx = SSL_CTX_new(OSSL_QUIC_client_method()); | |
185 | QUIC_TSERVER *qtserv = NULL; | |
186 | SSL *cssl = NULL; | |
187 | OSSL_QUIC_FAULT *fault = NULL; | |
188 | ||
189 | if (!TEST_ptr(cctx)) | |
190 | goto err; | |
191 | ||
192 | if (!TEST_true(qtest_create_quic_objects(cctx, cert, privkey, &qtserv, | |
193 | &cssl, &fault))) | |
194 | goto err; | |
195 | ||
196 | if (!TEST_true(ossl_quic_fault_set_hand_enc_ext_listener(fault, | |
197 | drop_transport_params_cb, | |
198 | NULL))) | |
199 | goto err; | |
71587f2b MC |
200 | |
201 | /* | |
f10e5885 MC |
202 | * We expect the connection to fail because the server failed to provide |
203 | * transport parameters | |
71587f2b | 204 | */ |
f10e5885 MC |
205 | if (!TEST_false(qtest_create_quic_connection(qtserv, cssl))) |
206 | goto err; | |
207 | ||
208 | if (!TEST_true(qtest_check_server_protocol_err(qtserv))) | |
71587f2b MC |
209 | goto err; |
210 | ||
211 | testresult = 1; | |
212 | err: | |
213 | ossl_quic_fault_free(fault); | |
214 | SSL_free(cssl); | |
215 | ossl_quic_tserver_free(qtserv); | |
216 | SSL_CTX_free(cctx); | |
217 | return testresult; | |
be5b3b37 MC |
218 | } |
219 | ||
220 | /* | |
6a9ab9bc | 221 | * Test that corrupted packets/datagrams are dropped and retransmitted |
be5b3b37 MC |
222 | */ |
223 | static int docorrupt = 0; | |
224 | ||
225 | static int on_packet_cipher_cb(OSSL_QUIC_FAULT *fault, QUIC_PKT_HDR *hdr, | |
226 | unsigned char *buf, size_t len, void *cbarg) | |
227 | { | |
228 | if (!docorrupt || len == 0) | |
229 | return 1; | |
230 | ||
231 | buf[(size_t)test_random() % len] ^= 0xff; | |
232 | docorrupt = 0; | |
f10e5885 | 233 | |
be5b3b37 MC |
234 | return 1; |
235 | } | |
236 | ||
6a9ab9bc MC |
237 | static int on_datagram_cb(OSSL_QUIC_FAULT *fault, BIO_MSG *m, size_t stride, |
238 | void *cbarg) | |
239 | { | |
240 | if (!docorrupt || m->data_len == 0) | |
241 | return 1; | |
242 | ||
243 | if (!ossl_quic_fault_resize_datagram(fault, m->data_len - 1)) | |
244 | return 1; | |
245 | ||
246 | docorrupt = 0; | |
247 | ||
248 | return 1; | |
249 | } | |
250 | ||
251 | /* | |
252 | * Test 1: Corrupt by flipping bits in an encrypted packet | |
253 | * Test 2: Corrupt by truncating an entire datagram | |
254 | */ | |
255 | static int test_corrupted_data(int idx) | |
be5b3b37 MC |
256 | { |
257 | OSSL_QUIC_FAULT *fault = NULL; | |
258 | int testresult = 0; | |
259 | SSL_CTX *cctx = SSL_CTX_new(OSSL_QUIC_client_method()); | |
260 | QUIC_TSERVER *qtserv = NULL; | |
261 | SSL *cssl = NULL; | |
262 | char *msg = "Hello World!"; | |
263 | size_t msglen = strlen(msg); | |
264 | unsigned char buf[80]; | |
265 | size_t bytesread, byteswritten; | |
266 | ||
267 | if (!TEST_ptr(cctx)) | |
268 | goto err; | |
269 | ||
270 | if (!TEST_true(qtest_create_quic_objects(cctx, cert, privkey, &qtserv, | |
271 | &cssl, &fault))) | |
272 | goto err; | |
273 | ||
6a9ab9bc MC |
274 | if (idx == 0) { |
275 | /* Listen for encrypted packets being sent */ | |
276 | if (!TEST_true(ossl_quic_fault_set_packet_cipher_listener(fault, | |
277 | on_packet_cipher_cb, | |
278 | NULL))) | |
279 | goto err; | |
280 | } else { | |
281 | /* Listen for datagrams being sent */ | |
282 | if (!TEST_true(ossl_quic_fault_set_datagram_listener(fault, | |
283 | on_datagram_cb, | |
284 | NULL))) | |
285 | goto err; | |
286 | } | |
be5b3b37 MC |
287 | if (!TEST_true(qtest_create_quic_connection(qtserv, cssl))) |
288 | goto err; | |
289 | ||
290 | /* Corrupt the next server packet*/ | |
291 | docorrupt = 1; | |
292 | ||
293 | /* | |
294 | * Send first 5 bytes of message. This will get corrupted and is treated as | |
295 | * "lost" | |
296 | */ | |
297 | if (!TEST_true(ossl_quic_tserver_write(qtserv, (unsigned char *)msg, 5, | |
298 | &byteswritten))) | |
299 | goto err; | |
300 | ||
301 | if (!TEST_size_t_eq(byteswritten, 5)) | |
302 | goto err; | |
303 | ||
304 | /* | |
305 | * Introduce a small delay so that the above packet has time to be detected | |
306 | * as lost. Loss detection times are based on RTT which should be very | |
307 | * fast for us since there isn't really a network. The loss delay timer is | |
308 | * always at least 1ms though. We sleep for 100ms. | |
309 | * TODO(QUIC): This assumes the calculated RTT will always be way less than | |
310 | * 100ms - which it should be...but can we always guarantee this? An | |
311 | * alternative might be to put in our own ossl_time_now() implementation for | |
312 | * these tests and control the timer as part of the test. This approach has | |
313 | * the added advantage that the test will behave reliably when run in a | |
314 | * debugger. Without it may get unreliable debugging results. This would | |
315 | * require some significant refactoring of the ssl/quic code though. | |
316 | */ | |
317 | OSSL_sleep(100); | |
318 | ||
319 | /* Send rest of message */ | |
320 | if (!TEST_true(ossl_quic_tserver_write(qtserv, (unsigned char *)msg + 5, | |
321 | msglen - 5, &byteswritten))) | |
322 | goto err; | |
323 | ||
324 | if (!TEST_size_t_eq(byteswritten, msglen - 5)) | |
325 | goto err; | |
326 | ||
327 | /* | |
328 | * Receive the corrupted packet. This should get dropped and is effectively | |
329 | * "lost". We also process the second packet which should be decrypted | |
330 | * successfully. Therefore we ack the frames in it | |
331 | */ | |
332 | if (!TEST_true(SSL_tick(cssl))) | |
333 | goto err; | |
334 | ||
335 | /* | |
336 | * Process the ack. Detect that the first part of the message must have | |
337 | * been lost due to the time elapsed since it was sent and resend it | |
338 | */ | |
339 | ossl_quic_tserver_tick(qtserv); | |
340 | ||
341 | /* Receive and process the newly arrived message data resend */ | |
342 | if (!TEST_true(SSL_tick(cssl))) | |
343 | goto err; | |
344 | ||
345 | /* The whole message should now have arrived */ | |
346 | if (!TEST_true(SSL_read_ex(cssl, buf, sizeof(buf), &bytesread))) | |
347 | goto err; | |
348 | ||
349 | if (!TEST_mem_eq(msg, msglen, buf, bytesread)) | |
350 | goto err; | |
351 | ||
352 | /* | |
353 | * If the test was successful then we corrupted exactly one packet and | |
354 | * docorrupt was reset | |
355 | */ | |
356 | if (!TEST_false(docorrupt)) | |
357 | goto err; | |
358 | ||
359 | testresult = 1; | |
360 | err: | |
361 | ossl_quic_fault_free(fault); | |
362 | SSL_free(cssl); | |
363 | ossl_quic_tserver_free(qtserv); | |
364 | SSL_CTX_free(cctx); | |
365 | return testresult; | |
71587f2b MC |
366 | } |
367 | ||
adef87a2 MC |
368 | OPT_TEST_DECLARE_USAGE("certsdir\n") |
369 | ||
370 | int setup_tests(void) | |
371 | { | |
372 | char *certsdir = NULL; | |
373 | ||
374 | if (!test_skip_common_options()) { | |
375 | TEST_error("Error parsing test options\n"); | |
376 | return 0; | |
377 | } | |
378 | ||
379 | if (!TEST_ptr(certsdir = test_get_argument(0))) | |
380 | return 0; | |
381 | ||
adef87a2 MC |
382 | cert = test_mk_file_path(certsdir, "servercert.pem"); |
383 | if (cert == NULL) | |
384 | goto err; | |
385 | ||
386 | privkey = test_mk_file_path(certsdir, "serverkey.pem"); | |
387 | if (privkey == NULL) | |
388 | goto err; | |
389 | ||
390 | ADD_TEST(test_basic); | |
71587f2b | 391 | ADD_TEST(test_unknown_frame); |
f10e5885 | 392 | ADD_TEST(test_no_transport_params); |
6a9ab9bc | 393 | ADD_ALL_TESTS(test_corrupted_data, 2); |
adef87a2 MC |
394 | |
395 | return 1; | |
396 | ||
397 | err: | |
398 | OPENSSL_free(cert); | |
399 | OPENSSL_free(privkey); | |
400 | return 0; | |
401 | } | |
402 | ||
403 | void cleanup_tests(void) | |
404 | { | |
405 | OPENSSL_free(cert); | |
406 | OPENSSL_free(privkey); | |
407 | } |