diff options
author | jsing <> | 2020-10-14 15:49:14 +0000 |
---|---|---|
committer | jsing <> | 2020-10-14 15:49:14 +0000 |
commit | 2241235f2d550c606dd4e8ea6cf0f3bb8ee92356 (patch) | |
tree | da9c5d7d607475d9f9ec58197ebbd66b2b4203dd | |
parent | f7868ddf3ad814ba5bda3b57799654f9938281be (diff) | |
download | openbsd-2241235f2d550c606dd4e8ea6cf0f3bb8ee92356.tar.gz openbsd-2241235f2d550c606dd4e8ea6cf0f3bb8ee92356.tar.bz2 openbsd-2241235f2d550c606dd4e8ea6cf0f3bb8ee92356.zip |
Add regress tests for DTLS.
Test the operation of a DTLS client and server, with and without cookies,
using the default MTU and a specifically lowered MTU.
Further regress tests will be built on this to exercise other parts of the
DTLS code base (such as retransmission, fragmentation and reassembly).
-rw-r--r-- | src/regress/lib/libssl/dtls/Makefile | 18 | ||||
-rw-r--r-- | src/regress/lib/libssl/dtls/dtlstest.c | 393 |
2 files changed, 411 insertions, 0 deletions
diff --git a/src/regress/lib/libssl/dtls/Makefile b/src/regress/lib/libssl/dtls/Makefile new file mode 100644 index 0000000000..5d25cde2ee --- /dev/null +++ b/src/regress/lib/libssl/dtls/Makefile | |||
@@ -0,0 +1,18 @@ | |||
1 | # $OpenBSD: Makefile,v 1.1 2020/10/14 15:49:14 jsing Exp $ | ||
2 | |||
3 | PROG= dtlstest | ||
4 | LDADD= -lssl -lcrypto | ||
5 | DPADD= ${LIBSSL} ${LIBCRYPTO} | ||
6 | WARNINGS= Yes | ||
7 | CFLAGS+= -DLIBRESSL_INTERNAL -Werror | ||
8 | |||
9 | REGRESS_TARGETS= \ | ||
10 | regress-dtlstest | ||
11 | |||
12 | regress-dtlstest: ${PROG} | ||
13 | ./dtlstest \ | ||
14 | ${.CURDIR}/../../libssl/certs/server.pem \ | ||
15 | ${.CURDIR}/../../libssl/certs/server.pem \ | ||
16 | ${.CURDIR}/../../libssl/certs/ca.pem | ||
17 | |||
18 | .include <bsd.regress.mk> | ||
diff --git a/src/regress/lib/libssl/dtls/dtlstest.c b/src/regress/lib/libssl/dtls/dtlstest.c new file mode 100644 index 0000000000..8cb4822b33 --- /dev/null +++ b/src/regress/lib/libssl/dtls/dtlstest.c | |||
@@ -0,0 +1,393 @@ | |||
1 | /* $OpenBSD: dtlstest.c,v 1.1 2020/10/14 15:49:14 jsing Exp $ */ | ||
2 | /* | ||
3 | * Copyright (c) 2020 Joel Sing <jsing@openbsd.org> | ||
4 | * | ||
5 | * Permission to use, copy, modify, and distribute this software for any | ||
6 | * purpose with or without fee is hereby granted, provided that the above | ||
7 | * copyright notice and this permission notice appear in all copies. | ||
8 | * | ||
9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | ||
10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | ||
11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | ||
12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||
13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | ||
14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||
15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||
16 | */ | ||
17 | |||
18 | #include <netinet/in.h> | ||
19 | #include <sys/limits.h> | ||
20 | #include <sys/socket.h> | ||
21 | |||
22 | #include <err.h> | ||
23 | #include <poll.h> | ||
24 | #include <unistd.h> | ||
25 | |||
26 | #include <openssl/bio.h> | ||
27 | #include <openssl/err.h> | ||
28 | #include <openssl/ssl.h> | ||
29 | |||
30 | const char *server_ca_file; | ||
31 | const char *server_cert_file; | ||
32 | const char *server_key_file; | ||
33 | |||
34 | char dtls_cookie[32]; | ||
35 | |||
36 | static int | ||
37 | datagram_pair(int *client_sock, int *server_sock, | ||
38 | struct sockaddr_in *server_sin) | ||
39 | { | ||
40 | struct sockaddr_in sin; | ||
41 | socklen_t sock_len; | ||
42 | int cs = -1, ss = -1; | ||
43 | |||
44 | memset(&sin, 0, sizeof(sin)); | ||
45 | sin.sin_family = AF_INET; | ||
46 | sin.sin_port = 0; | ||
47 | sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); | ||
48 | |||
49 | if ((ss = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) | ||
50 | err(1, "server socket"); | ||
51 | if (bind(ss, (struct sockaddr *)&sin, sizeof(sin)) == -1) | ||
52 | err(1, "server bind"); | ||
53 | sock_len = sizeof(sin); | ||
54 | if (getsockname(ss, (struct sockaddr *)&sin, &sock_len) == -1) | ||
55 | err(1, "server getsockname"); | ||
56 | |||
57 | if ((cs = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) | ||
58 | err(1, "client socket"); | ||
59 | if (connect(cs, (struct sockaddr *)&sin, sizeof(sin)) == -1) | ||
60 | err(1, "client connect"); | ||
61 | |||
62 | *client_sock = cs; | ||
63 | *server_sock = ss; | ||
64 | memcpy(server_sin, &sin, sizeof(sin)); | ||
65 | |||
66 | return 1; | ||
67 | } | ||
68 | |||
69 | static int | ||
70 | poll_timeout(SSL *client, SSL *server) | ||
71 | { | ||
72 | int client_timeout = 0, server_timeout = 0; | ||
73 | struct timeval timeout; | ||
74 | |||
75 | if (DTLSv1_get_timeout(client, &timeout)) | ||
76 | client_timeout = timeout.tv_sec * 1000 + timeout.tv_usec / 1000; | ||
77 | |||
78 | if (DTLSv1_get_timeout(server, &timeout)) | ||
79 | server_timeout = timeout.tv_sec * 1000 + timeout.tv_usec / 1000; | ||
80 | |||
81 | if (client_timeout > 0 && client_timeout < server_timeout) | ||
82 | return client_timeout; | ||
83 | |||
84 | return server_timeout; | ||
85 | } | ||
86 | |||
87 | static int | ||
88 | dtls_cookie_generate(SSL *ssl, unsigned char *cookie, | ||
89 | unsigned int *cookie_len) | ||
90 | { | ||
91 | arc4random_buf(dtls_cookie, sizeof(dtls_cookie)); | ||
92 | memcpy(cookie, dtls_cookie, sizeof(dtls_cookie)); | ||
93 | *cookie_len = sizeof(dtls_cookie); | ||
94 | |||
95 | return 1; | ||
96 | } | ||
97 | |||
98 | static int | ||
99 | dtls_cookie_verify(SSL *ssl, const unsigned char *cookie, | ||
100 | unsigned int cookie_len) | ||
101 | { | ||
102 | return cookie_len == sizeof(dtls_cookie) && | ||
103 | memcmp(cookie, dtls_cookie, sizeof(dtls_cookie)) == 0; | ||
104 | } | ||
105 | |||
106 | static SSL * | ||
107 | dtls_client(int sock, struct sockaddr_in *server_sin, long mtu) | ||
108 | { | ||
109 | SSL_CTX *ssl_ctx = NULL; | ||
110 | SSL *ssl = NULL; | ||
111 | BIO *bio = NULL; | ||
112 | |||
113 | if ((bio = BIO_new_dgram(sock, BIO_NOCLOSE)) == NULL) | ||
114 | errx(1, "client bio"); | ||
115 | if (!BIO_socket_nbio(sock, 1)) | ||
116 | errx(1, "client nbio"); | ||
117 | if (!BIO_ctrl_set_connected(bio, 1, server_sin)) | ||
118 | errx(1, "client set connected"); | ||
119 | |||
120 | if ((ssl_ctx = SSL_CTX_new(DTLS_method())) == NULL) | ||
121 | errx(1, "client context"); | ||
122 | SSL_CTX_set_read_ahead(ssl_ctx, 1); | ||
123 | |||
124 | if ((ssl = SSL_new(ssl_ctx)) == NULL) | ||
125 | errx(1, "client ssl"); | ||
126 | |||
127 | SSL_set_bio(ssl, bio, bio); | ||
128 | bio = NULL; | ||
129 | |||
130 | if (mtu > 0) { | ||
131 | SSL_set_options(ssl, SSL_OP_NO_QUERY_MTU); | ||
132 | SSL_set_mtu(ssl, mtu); | ||
133 | } | ||
134 | |||
135 | SSL_CTX_free(ssl_ctx); | ||
136 | BIO_free(bio); | ||
137 | |||
138 | return ssl; | ||
139 | } | ||
140 | |||
141 | static SSL * | ||
142 | dtls_server(int sock, long options, long mtu) | ||
143 | { | ||
144 | SSL_CTX *ssl_ctx = NULL; | ||
145 | SSL *ssl = NULL; | ||
146 | BIO *bio = NULL; | ||
147 | |||
148 | if ((bio = BIO_new_dgram(sock, BIO_NOCLOSE)) == NULL) | ||
149 | errx(1, "server bio"); | ||
150 | if (!BIO_socket_nbio(sock, 1)) | ||
151 | errx(1, "server nbio"); | ||
152 | |||
153 | if ((ssl_ctx = SSL_CTX_new(DTLS_method())) == NULL) | ||
154 | errx(1, "server context"); | ||
155 | |||
156 | SSL_CTX_set_cookie_generate_cb(ssl_ctx, dtls_cookie_generate); | ||
157 | SSL_CTX_set_cookie_verify_cb(ssl_ctx, dtls_cookie_verify); | ||
158 | SSL_CTX_set_options(ssl_ctx, options); | ||
159 | SSL_CTX_set_read_ahead(ssl_ctx, 1); | ||
160 | |||
161 | if (SSL_CTX_use_certificate_file(ssl_ctx, server_cert_file, | ||
162 | SSL_FILETYPE_PEM) != 1) { | ||
163 | fprintf(stderr, "FAIL: Failed to load server certificate"); | ||
164 | goto failure; | ||
165 | } | ||
166 | if (SSL_CTX_use_PrivateKey_file(ssl_ctx, server_key_file, | ||
167 | SSL_FILETYPE_PEM) != 1) { | ||
168 | fprintf(stderr, "FAIL: Failed to load server private key"); | ||
169 | goto failure; | ||
170 | } | ||
171 | |||
172 | if ((ssl = SSL_new(ssl_ctx)) == NULL) | ||
173 | errx(1, "server ssl"); | ||
174 | |||
175 | SSL_set_bio(ssl, bio, bio); | ||
176 | bio = NULL; | ||
177 | |||
178 | if (mtu > 0) { | ||
179 | SSL_set_options(ssl, SSL_OP_NO_QUERY_MTU); | ||
180 | SSL_set_mtu(ssl, mtu); | ||
181 | } | ||
182 | |||
183 | failure: | ||
184 | SSL_CTX_free(ssl_ctx); | ||
185 | BIO_free(bio); | ||
186 | |||
187 | return ssl; | ||
188 | } | ||
189 | |||
190 | static int | ||
191 | ssl_error(SSL *ssl, const char *name, const char *desc, int ssl_ret, | ||
192 | short *events) | ||
193 | { | ||
194 | int ssl_err; | ||
195 | |||
196 | ssl_err = SSL_get_error(ssl, ssl_ret); | ||
197 | |||
198 | if (ssl_err == SSL_ERROR_WANT_READ) { | ||
199 | *events = POLLIN; | ||
200 | } else if (ssl_err == SSL_ERROR_WANT_WRITE) { | ||
201 | *events = POLLOUT; | ||
202 | } else if (ssl_err == SSL_ERROR_SYSCALL && errno == 0) { | ||
203 | /* Yup, this is apparently a thing... */ | ||
204 | *events = 0; | ||
205 | } else { | ||
206 | fprintf(stderr, "FAIL: %s %s failed - ssl err = %d, errno = %d\n", | ||
207 | name, desc, ssl_err, errno); | ||
208 | ERR_print_errors_fp(stderr); | ||
209 | return 0; | ||
210 | } | ||
211 | |||
212 | return 1; | ||
213 | } | ||
214 | |||
215 | static int | ||
216 | do_connect(SSL *ssl, const char *name, int *done, short *events) | ||
217 | { | ||
218 | int ssl_ret; | ||
219 | |||
220 | if ((ssl_ret = SSL_connect(ssl)) == 1) { | ||
221 | fprintf(stderr, "INFO: %s connect done\n", name); | ||
222 | *done = 1; | ||
223 | return 1; | ||
224 | } | ||
225 | |||
226 | return ssl_error(ssl, name, "connect", ssl_ret, events); | ||
227 | } | ||
228 | |||
229 | static int | ||
230 | do_accept(SSL *ssl, const char *name, int *done, short *events) | ||
231 | { | ||
232 | int ssl_ret; | ||
233 | |||
234 | if ((ssl_ret = SSL_accept(ssl)) == 1) { | ||
235 | fprintf(stderr, "INFO: %s accept done\n", name); | ||
236 | *done = 1; | ||
237 | return 1; | ||
238 | } | ||
239 | |||
240 | return ssl_error(ssl, name, "accept", ssl_ret, events); | ||
241 | } | ||
242 | |||
243 | static int | ||
244 | do_shutdown(SSL *ssl, const char *name, int *done, short *events) | ||
245 | { | ||
246 | int ssl_ret; | ||
247 | |||
248 | ssl_ret = SSL_shutdown(ssl); | ||
249 | if (ssl_ret == 1) { | ||
250 | fprintf(stderr, "INFO: %s shutdown done\n", name); | ||
251 | *done = 1; | ||
252 | return 1; | ||
253 | } | ||
254 | return ssl_error(ssl, name, "shutdown", ssl_ret, events); | ||
255 | } | ||
256 | |||
257 | typedef int (*ssl_func)(SSL *ssl, const char *name, int *done, short *events); | ||
258 | |||
259 | static int | ||
260 | do_client_server_loop(SSL *client, ssl_func client_func, SSL *server, | ||
261 | ssl_func server_func, struct pollfd pfd[2]) | ||
262 | { | ||
263 | int client_done = 0, server_done = 0; | ||
264 | int i = 0; | ||
265 | |||
266 | do { | ||
267 | if (poll(pfd, 2, poll_timeout(client, server)) == -1) | ||
268 | err(1, "poll"); | ||
269 | |||
270 | if (!client_done) { | ||
271 | if (DTLSv1_handle_timeout(client) > 0) | ||
272 | fprintf(stderr, "INFO: client timeout\n"); | ||
273 | if (!client_func(client, "client", &client_done, | ||
274 | &pfd[0].events)) | ||
275 | return 0; | ||
276 | } | ||
277 | if (!server_done) { | ||
278 | if (DTLSv1_handle_timeout(server) > 0) | ||
279 | fprintf(stderr, "INFO: server timeout\n"); | ||
280 | if (!server_func(server, "server", &server_done, | ||
281 | &pfd[1].events)) | ||
282 | return 0; | ||
283 | } | ||
284 | } while (i++ < 1000 && (!client_done || !server_done)); | ||
285 | |||
286 | if (!client_done || !server_done) | ||
287 | fprintf(stderr, "FAIL: gave up\n"); | ||
288 | |||
289 | return client_done && server_done; | ||
290 | } | ||
291 | |||
292 | struct dtls_test { | ||
293 | const unsigned char *desc; | ||
294 | const long mtu; | ||
295 | const long ssl_options; | ||
296 | }; | ||
297 | |||
298 | static struct dtls_test dtls_tests[] = { | ||
299 | { | ||
300 | .desc = "DTLS without cookies", | ||
301 | .ssl_options = 0, | ||
302 | }, | ||
303 | { | ||
304 | .desc = "DTLS with cookies", | ||
305 | .ssl_options = SSL_OP_COOKIE_EXCHANGE, | ||
306 | }, | ||
307 | { | ||
308 | .desc = "DTLS with low MTU", | ||
309 | .mtu = 256, | ||
310 | }, | ||
311 | { | ||
312 | .desc = "DTLS with low MTU and cookies", | ||
313 | .mtu = 256, | ||
314 | .ssl_options = SSL_OP_COOKIE_EXCHANGE, | ||
315 | }, | ||
316 | }; | ||
317 | |||
318 | #define N_DTLS_TESTS (sizeof(dtls_tests) / sizeof(*dtls_tests)) | ||
319 | |||
320 | static int | ||
321 | dtlstest(struct dtls_test *dt) | ||
322 | { | ||
323 | SSL *client = NULL, *server = NULL; | ||
324 | struct sockaddr_in server_sin; | ||
325 | struct pollfd pfd[2]; | ||
326 | int client_sock = -1; | ||
327 | int server_sock = -1; | ||
328 | int failed = 1; | ||
329 | |||
330 | fprintf(stderr, "\n== Testing %s... ==\n", dt->desc); | ||
331 | |||
332 | if (!datagram_pair(&client_sock, &server_sock, &server_sin)) | ||
333 | goto failure; | ||
334 | |||
335 | if ((client = dtls_client(client_sock, &server_sin, dt->mtu)) == NULL) | ||
336 | goto failure; | ||
337 | if ((server = dtls_server(server_sock, dt->ssl_options, dt->mtu)) == NULL) | ||
338 | goto failure; | ||
339 | |||
340 | pfd[0].fd = client_sock; | ||
341 | pfd[0].events = POLLOUT; | ||
342 | pfd[1].fd = server_sock; | ||
343 | pfd[1].events = 0; | ||
344 | |||
345 | if (!do_client_server_loop(client, do_connect, server, do_accept, pfd)) { | ||
346 | fprintf(stderr, "FAIL: client and server handshake failed\n"); | ||
347 | goto failure; | ||
348 | } | ||
349 | |||
350 | /* XXX - do reads and writes. */ | ||
351 | |||
352 | if (!do_client_server_loop(client, do_shutdown, server, do_shutdown, pfd)) { | ||
353 | fprintf(stderr, "FAIL: client and server shutdown failed\n"); | ||
354 | goto failure; | ||
355 | } | ||
356 | |||
357 | fprintf(stderr, "INFO: Done!\n"); | ||
358 | |||
359 | failed = 0; | ||
360 | |||
361 | failure: | ||
362 | if (client_sock != -1) | ||
363 | close(client_sock); | ||
364 | if (server_sock != -1) | ||
365 | close(server_sock); | ||
366 | |||
367 | SSL_free(client); | ||
368 | SSL_free(server); | ||
369 | |||
370 | return failed; | ||
371 | } | ||
372 | |||
373 | int | ||
374 | main(int argc, char **argv) | ||
375 | { | ||
376 | int failed = 0; | ||
377 | size_t i; | ||
378 | |||
379 | if (argc != 4) { | ||
380 | fprintf(stderr, "usage: %s keyfile certfile cafile\n", | ||
381 | argv[0]); | ||
382 | exit(1); | ||
383 | } | ||
384 | |||
385 | server_key_file = argv[1]; | ||
386 | server_cert_file = argv[2]; | ||
387 | server_ca_file = argv[3]; | ||
388 | |||
389 | for (i = 0; i < N_DTLS_TESTS; i++) | ||
390 | failed |= dtlstest(&dtls_tests[i]); | ||
391 | |||
392 | return failed; | ||
393 | } | ||