summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authortb <>2020-05-21 10:38:43 +0000
committertb <>2020-05-21 10:38:43 +0000
commit6ac97ed6937a4584df31ae522e3bc3739a31af2c (patch)
treef55d71473f7fac05884786b5ad37f4ed39850491 /src
parent76628e0ca0bd7284bfab6686ab3255b9f86ec5c3 (diff)
downloadopenbsd-6ac97ed6937a4584df31ae522e3bc3739a31af2c.tar.gz
openbsd-6ac97ed6937a4584df31ae522e3bc3739a31af2c.tar.bz2
openbsd-6ac97ed6937a4584df31ae522e3bc3739a31af2c.zip
Add a harness that runs tests from tlsfuzzer
This currently runs 54 tests from the tlsfuzzer suite against the TLSv1.3 server which exercise a large portion of the code. They already found a number of bugs and misbehaviors and also inspired a few diffs currently in the pipeline. This regress requires the py3-tlsfuzzer package to be installed, otherwise the tests are skipped. Many thanks to kmos for helping with the ports side and to beck for his positive feedback. ok beck
Diffstat (limited to 'src')
-rw-r--r--src/regress/lib/libssl/tlsfuzzer/Makefile45
-rw-r--r--src/regress/lib/libssl/tlsfuzzer/tlsfuzzer.py736
2 files changed, 781 insertions, 0 deletions
diff --git a/src/regress/lib/libssl/tlsfuzzer/Makefile b/src/regress/lib/libssl/tlsfuzzer/Makefile
new file mode 100644
index 0000000000..224fc96c5c
--- /dev/null
+++ b/src/regress/lib/libssl/tlsfuzzer/Makefile
@@ -0,0 +1,45 @@
1# $OpenBSD: Makefile,v 1.1 2020/05/21 10:38:43 tb Exp $
2
3.if !exists(/usr/local/share/tlsfuzzer)
4regress:
5 @echo package py3-tlsfuzzer is required for this regress
6 @echo SKIPPED
7.else
8
9localhost.key localhost.crt:
10 openssl req -x509 -newkey rsa -keyout localhost.key -out localhost.crt \
11 -subj /CN=localhost -nodes -batch
12
13certs: localhost.key localhost.crt
14
15CLEANFILES += localhost.key localhost.crt
16
17PORT ?= 4433
18SLOW = -s
19TIMING = # -t
20VERBOSE = # -v
21
22all: certs
23 python3 ${.CURDIR}/tlsfuzzer.py ${SLOW} ${TIMING} ${VERBOSE}
24
25failing: certs
26 python3 ${.CURDIR}/tlsfuzzer.py -f ${SLOW} ${TIMING} ${VERBOSE}
27
28
29port: certs
30 python3 ${.CURDIR}/tlsfuzzer.py ${SLOW} ${TIMING} ${VERBOSE} -p ${PORT}
31
32list:
33 @python3 ${.CURDIR}/tlsfuzzer.py -l
34
35list-failing:
36 @python3 ${.CURDIR}/tlsfuzzer.py -l -f
37
38missing:
39 @python3 ${.CURDIR}/tlsfuzzer.py -m
40
41.PHONY: all certs failing list list-failing missing port
42
43.endif
44
45.include <bsd.regress.mk>
diff --git a/src/regress/lib/libssl/tlsfuzzer/tlsfuzzer.py b/src/regress/lib/libssl/tlsfuzzer/tlsfuzzer.py
new file mode 100644
index 0000000000..1098e049f6
--- /dev/null
+++ b/src/regress/lib/libssl/tlsfuzzer/tlsfuzzer.py
@@ -0,0 +1,736 @@
1# $OpenBSD: tlsfuzzer.py,v 1.1 2020/05/21 10:38:43 tb Exp $
2#
3# Copyright (c) 2020 Theo Buehler <tb@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
17import getopt
18import os
19import subprocess
20import sys
21from timeit import default_timer as timer
22
23tlsfuzzer_scriptdir = "/usr/local/share/tlsfuzzer/scripts/"
24
25class Test:
26 """
27 Represents a tlsfuzzer test script.
28 name: the script's name
29 args: arguments to feed to the script
30 tls12_args: override args for a TLSv1.2 server
31 tls13_args: override args for a TLSv1.3 server
32
33 XXX Add client cert support.
34 """
35 def __init__(self, name="", args=[], tls12_args=[], tls13_args=[]):
36 self.name = name
37 self.tls12_args = args
38 self.tls13_args = args
39 if tls12_args:
40 self.tls12_args = tls12_args
41 if tls13_args:
42 self.tls13_args = tls13_args
43
44 def args(self, has_tls1_3: True):
45 if has_tls1_3:
46 return self.tls13_args
47 else:
48 return self.tls12_args
49
50 def __repr__(self):
51 return "<Test: %s tls12_args: %s tls13_args: %s>" % (
52 self.name, self.tls12_args, tls13_args
53 )
54
55class TestGroup:
56 """ A group of Test objects to be run by TestRunner."""
57 def __init__(self, title="Tests", tests=[]):
58 self.title = title
59 self.tests = tests
60
61 def __iter__(self):
62 return iter(self.tests)
63
64# argument to pass to several tests
65tls13_unsupported_ciphers = [
66 "-e", "TLS 1.3 with ffdhe2048",
67 "-e", "TLS 1.3 with ffdhe3072",
68 "-e", "TLS 1.3 with secp521r1", # XXX: why is this curve problematic?
69 "-e", "TLS 1.3 with x448",
70]
71
72tls13_tests = TestGroup("TLSv1.3 tests", [
73 Test("test-tls13-ccs.py"),
74 Test("test-tls13-conversation.py"),
75 Test("test-tls13-count-tickets.py"),
76 Test("test-tls13-empty-alert.py"),
77 Test("test-tls13-finished-plaintext.py"),
78 Test("test-tls13-hrr.py"),
79 Test("test-tls13-legacy-version.py"),
80 Test("test-tls13-nociphers.py"),
81 Test("test-tls13-record-padding.py"),
82])
83
84# Tests that take a lot of time (> ~30s on an x280)
85tls13_slow_tests = TestGroup("slow TLSv1.3 tests", [
86 # XXX: Investigate the occasional message
87 # "Got shared secret with 1 most significant bytes equal to zero."
88 Test("test-tls13-dhe-shared-secret-padding.py", tls13_unsupported_ciphers),
89
90 Test("test-tls13-invalid-ciphers.py"),
91 Test("test-tls13-serverhello-random.py", tls13_unsupported_ciphers),
92])
93
94tls13_extra_cert_tests = TestGroup("TLSv1.3 certificate tests", [
95 # need to set up client certs to run these
96 Test("test-tls13-certificate-request.py"),
97 Test("test-tls13-certificate-verify.py"),
98 Test("test-tls13-ecdsa-in-certificate-verify.py"),
99
100 # Test expects the server to have installed three certificates:
101 # with P-256, P-384 and P-521 curve. Also SHA1+ECDSA is verified
102 # to not work.
103 Test("test-tls13-ecdsa-support.py"),
104])
105
106tls13_failing_tests = TestGroup("failing TLSv1.3 tests", [
107 # Some tests fail because we fail later than the scripts expect us to.
108 # With X25519, we accept weak peer public keys and fail when we actually
109 # compute the keyshare. Other tests seem to indicate that we could be
110 # stricter about what keyshares we accept.
111 Test("test-tls13-crfg-curves.py"),
112 Test("test-tls13-ecdhe-curves.py"),
113
114 # Expects a missing_extensions alert
115 # AssertionError: Unexpected message from peer: Handshake(server_hello)
116 Test("test-tls13-keyshare-omitted.py"),
117
118 # https://github.com/openssl/openssl/issues/8369
119 Test("test-tls13-obsolete-curves.py"),
120
121 # 3 failing rsa_pss_pss tests
122 Test("test-tls13-rsa-signatures.py"),
123
124 # AssertionError: Unexpected message from peer: ChangeCipherSpec()
125 # Most failing tests expect the CCS right before finished.
126 # What's up with that?
127 Test("test-tls13-version-negotiation.py"),
128
129 # The following three tests fail due to broken pipe.
130 # AssertionError: Unexpected closure from peer:
131 # 'zero-length app data'
132 # 'zero-length app data with large padding'
133 # 'zero-length app data with padding'
134 Test("test-tls13-zero-length-data.py"),
135])
136
137tls13_slow_failing_tests = TestGroup("slow, failing TLSv1.3 tests", [
138 # After adding record overflow alert, 14 tests fail because
139 # they expect ExpectNewSessionTicket().
140 Test("test-tls13-record-layer-limits.py" ),
141
142 # Other test failures bugs in keyshare/tlsext negotiation?
143 Test("test-tls13-shuffled-extentions.py"), # should reject 2nd CH
144 Test("test-tls13-unrecognised-groups.py"), # unexpected closure
145
146 # systematic failures?
147 # TLSBadRecordMAC("Invalid tag, decryption failure")
148 Test("test-tls13-keyupdate.py"),
149 Test("test-tls13-symetric-ciphers.py"), # unexpected message from peer
150
151 # 70 fail and 644 pass. For some reason the tests expect a decode_error
152 # but we send a decrypt_error after the CBS_mem_equal() fails in
153 # tls13_server_finished_recv() (which is correct).
154 Test("test-tls13-finished.py"), # decrypt_error -> decode_error?
155
156 # The following two tests fail Test (skip empty extensions for the first one):
157 # 'empty unassigned extensions, ids in range from 2 to 4118'
158 # 'unassigned extensions with random payload, ids in range from 2 to 1046'
159 Test("test-tls13-large-number-of-extensions.py"), # 2 fail: empty/random payload
160
161 # 6 tests fail: 'rsa_pkcs1_{md5,sha{1,224,256,384,512}} signature'
162 # We send server hello, but the test expects handshake_failure
163 Test("test-tls13-pkcs-signature.py"),
164 # 8 tests fail: 'tls13 signature rsa_pss_{pss,rsae}_sha{256,384,512}
165 Test("test-tls13-rsapss-signatures.py"),
166
167 # ExpectNewSessionTicket
168 Test("test-tls13-session-resumption.py"),
169])
170
171tls13_unsupported_tests = TestGroup("TLSv1.3 tests for unsupported features", [
172 # Tests for features we don't support
173 Test("test-tls13-0rtt-garbage.py"),
174 Test("test-tls13-ffdhe-groups.py"),
175 Test("test-tls13-ffdhe-sanity.py"),
176 Test("test-tls13-psk_dhe_ke.py"),
177 Test("test-tls13-psk_ke.py"),
178
179 # need server to react to HTTP GET for /keyupdate
180 Test("test-tls13-keyupdate-from-server.py"),
181
182 # Weird test: tests servers that don't support 1.3
183 Test("test-tls13-non-support.py"),
184
185 # broken test script
186 # UnboundLocalError: local variable 'cert' referenced before assignment
187 Test("test-tls13-post-handshake-auth.py"),
188
189 # Server must be configured to support only rsa_pss_rsae_sha512
190 Test("test-tls13-signature-algorithms.py"),
191])
192
193tls12_exclude_legacy_protocols = [
194 # all these have BIO_read timeouts against TLSv1.3
195 "-e", "Protocol (3, 0)",
196 "-e", "Protocol (3, 0) in SSLv2 compatible ClientHello",
197 # the following only fail with TLSv1.3
198 "-e", "Protocol (3, 1) in SSLv2 compatible ClientHello",
199 "-e", "Protocol (3, 2) in SSLv2 compatible ClientHello",
200 "-e", "Protocol (3, 3) in SSLv2 compatible ClientHello",
201 "-e", "Protocol (3, 1) with secp521r1 group", # XXX
202 "-e", "Protocol (3, 1) with x448 group",
203 "-e", "Protocol (3, 2) with secp521r1 group", # XXX
204 "-e", "Protocol (3, 2) with x448 group",
205 "-e", "Protocol (3, 3) with secp521r1 group", # XXX
206 "-e", "Protocol (3, 3) with x448 group",
207]
208
209tls12_tests = TestGroup("TLSv1.2 tests", [
210 # Tests that pass as they are.
211 Test("test-TLSv1_2-rejected-without-TLSv1_2.py"),
212 Test("test-aes-gcm-nonces.py"),
213 Test("test-chacha20.py"),
214 Test("test-conversation.py"),
215 Test("test-cve-2016-2107.py"),
216 Test("test-dhe-rsa-key-exchange.py"),
217 Test("test-early-application-data.py"),
218 Test("test-empty-extensions.py"),
219 Test("test-fuzzed-MAC.py"),
220 Test("test-fuzzed-ciphertext.py"),
221 Test("test-fuzzed-finished.py"),
222 Test("test-fuzzed-padding.py"),
223 Test("test-hello-request-by-client.py"),
224 Test("test-invalid-cipher-suites.py"),
225 Test("test-invalid-content-type.py"),
226 Test("test-invalid-session-id.py"),
227 Test("test-invalid-version.py"),
228 Test("test-message-skipping.py"),
229 Test("test-no-heartbeat.py"),
230 Test("test-sessionID-resumption.py"),
231 Test("test-sslv2-connection.py"),
232 Test("test-truncating-of-finished.py"),
233 Test("test-truncating-of-kRSA-client-key-exchange.py"),
234 Test("test-unsupported-curve-fallback.py"),
235 Test("test-version-numbers.py"),
236 Test("test-zero-length-data.py"),
237
238 # Tests that need tweaking for unsupported features and ciphers.
239 Test(
240 "test-atypical-padding.py", [
241 "-e", "sanity - encrypt then MAC",
242 "-e", "2^14 bytes of AppData with 256 bytes of padding (SHA1 + Encrypt then MAC)",
243 ]
244 ),
245 Test(
246 "test-dhe-rsa-key-exchange-signatures.py", [
247 "-e", "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA sha224 signature",
248 "-e", "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 sha224 signature",
249 "-e", "TLS_DHE_RSA_WITH_AES_128_CBC_SHA sha224 signature",
250 "-e", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 sha224 signature",
251 "-e", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA sha224 signature",
252 ]
253 ),
254 Test("test-dhe-key-share-random.py", tls12_exclude_legacy_protocols),
255 Test("test-export-ciphers-rejected.py", ["--min-ver", "TLSv1.0"]),
256 Test(
257 "test-downgrade-protection.py",
258 tls12_args = ["--server-max-protocol", "TLSv1.2"],
259 tls13_args = ["--server-max-protocol", "TLSv1.3"],
260 ),
261 Test("test-fallback-scsv.py", tls13_args = ["--tls-1.3"] ),
262 Test("test-serverhello-random.py", args = tls12_exclude_legacy_protocols),
263])
264
265tls12_slow_tests = TestGroup("slow TLSv1.2 tests", [
266 Test("test-cve-2016-7054.py"),
267 Test("test-dhe-no-shared-secret-padding.py", tls12_exclude_legacy_protocols),
268 Test("test-ecdhe-padded-shared-secret.py", tls12_exclude_legacy_protocols),
269 Test("test-ecdhe-rsa-key-share-random.py", tls12_exclude_legacy_protocols),
270 # This test has some failures once in a while.
271 Test("test-fuzzed-plaintext.py"),
272])
273
274tls12_failing_tests = TestGroup("failing TLSv1.2 tests", [
275 # no shared cipher
276 Test("test-aesccm.py"),
277 # need server to set up alpn
278 Test("test-alpn-negotiation.py"),
279 # many tests fail due to unexpected server_name extension
280 Test("test-bleichenbacher-workaround.py"),
281
282 # need client key and cert plus extra server setup
283 Test("test-certificate-malformed.py"),
284 Test("test-certificate-request.py"),
285 Test("test-certificate-verify-malformed-sig.py"),
286 Test("test-certificate-verify-malformed.py"),
287 Test("test-certificate-verify.py"),
288 Test("test-ecdsa-in-certificate-verify.py"),
289 Test("test-renegotiation-disabled-client-cert.py"),
290 Test("test-rsa-pss-sigs-on-certificate-verify.py"),
291 Test("test-rsa-sigs-on-certificate-verify.py"),
292
293 # test doesn't expect session ticket
294 Test("test-client-compatibility.py"),
295 # abrupt closure
296 Test("test-client-hello-max-size.py"),
297 # unknown signature algorithms
298 Test("test-clienthello-md5.py"),
299 # abrupt closure
300 Test("test-cve-2016-6309.py"),
301
302 # failing tests are fixed by sending illegal_parameter alert after
303 # DH_compute_keyTest() in ssl_srvr.c
304 Test("test-dhe-rsa-key-exchange-with-bad-messages.py"),
305 # Tests expect an illegal_parameter alert
306 Test("test-ecdhe-rsa-key-exchange-with-bad-messages.py"),
307
308 # We send a handshake_failure while the test expects to succeed
309 Test("test-ecdhe-rsa-key-exchange.py"),
310
311 # unsupported?
312 Test("test-extended-master-secret-extension-with-client-cert.py"),
313
314 # no shared cipher
315 Test("test-ecdsa-sig-flexibility.py"),
316
317 # unsupported
318 Test("test-encrypt-then-mac-renegotiation.py"),
319 Test("test-encrypt-then-mac.py"),
320 Test("test-extended-master-secret-extension.py"),
321 Test("test-ffdhe-negotiation.py"),
322 # unsupported. Expects the server to send the heartbeat extension
323 Test("test-heartbeat.py"),
324
325 # 29 succeed, 263 fail:
326 # 'n extensions', 'n extensions last empty' n in 4086, 4096, 8192, 16383
327 # 'fuzz ext length to n' n in [0..255] with the exception of 41...
328 Test("test-extensions.py"),
329
330 # Tests expect SH but we send unexpected_message or handshake_failure
331 # 'Application data inside Client Hello'
332 # 'Application data inside Client Key Exchange'
333 # 'Application data inside Finished'
334 Test("test-interleaved-application-data-and-fragmented-handshakes-in-renegotiation.py"),
335 # Tests expect SH but we send handshake_failure
336 # 'Application data before Change Cipher Spec'
337 # 'Application data before Client Key Exchange'
338 # 'Application data before Finished'
339 Test("test-interleaved-application-data-in-renegotiation.py"),
340
341 # broken test script
342 # TypeError: '<' not supported between instances of 'int' and 'NoneType'
343 Test("test-invalid-client-hello-w-record-overflow.py"),
344
345 # Lots of failures. abrupt closure
346 Test("test-invalid-client-hello.py"),
347
348 # Test expects illegal_parameter, we send decode_error in ssl_srvr.c:1016
349 # Need to check that this is correct.
350 Test("test-invalid-compression-methods.py"),
351
352 # abrupt closure
353 # 'encrypted premaster set to all zero (n)' n in 256 384 512
354 Test("test-invalid-rsa-key-exchange-messages.py"),
355
356 # test expects illegal_parameter, we send unrecognized_name (which seems
357 # correct according to rfc 6066?)
358 Test("test-invalid-server-name-extension-resumption.py"),
359 # let through some server names without sending an alert
360 # again illegal_parameter vs unrecognized_name
361 Test("test-invalid-server-name-extension.py"),
362
363 Test("test-large-hello.py"),
364
365 # 14 pass
366 # 7 fail
367 # 'n extensions', n in 4095, 4096, 4097, 8191, 8192, 8193, 16383,
368 Test("test-large-number-of-extensions.py"),
369
370 # 4 failures:
371 # 'insecure (legacy) renegotiation with GET after 2nd handshake'
372 # 'insecure (legacy) renegotiation with incomplete GET'
373 # 'secure renegotiation with GET after 2nd handshake'
374 # 'secure renegotiation with incomplete GET'
375 Test("test-legacy-renegotiation.py"),
376
377 # 1 failure (timeout): we don't send the unexpected_message alert
378 # 'duplicate change cipher spec after Finished'
379 Test("test-message-duplication.py"),
380
381 # server should send status_request
382 Test("test-ocsp-stapling.py"),
383
384 # unexpected closure
385 Test("test-openssl-3712.py"),
386
387 # 3 failures:
388 # 'big, needs fragmentation: max fragment - 16336B extension'
389 # 'big, needs fragmentation: max fragment - 32768B extension'
390 # 'maximum size: max fragment - 65531B extension'
391 Test("test-record-layer-fragmentation.py"),
392
393 # wants --reply-AD-size
394 Test("test-record-size-limit.py"),
395
396 # failed: 3 (expect an alert, we send AD)
397 # 'try insecure (legacy) renegotiation with incomplete GET'
398 # 'try secure renegotiation with GET after 2nd CH'
399 # 'try secure renegotiation with incomplete GET'
400 Test("test-renegotiation-disabled.py"),
401
402 # 'resumption of safe session with NULL cipher'
403 # 'resumption with cipher from old CH but not selected by server'
404 Test("test-resumption-with-wrong-ciphers.py"),
405
406 # 5 failures:
407 # 'empty sigalgs'
408 # 'only undefined sigalgs'
409 # 'rsa_pss_pss_sha256 only'
410 # 'rsa_pss_pss_sha384 only'
411 # 'rsa_pss_pss_sha512 only'
412 Test("test-sig-algs.py"),
413
414 # 13 failures:
415 # 'duplicated n non-rsa schemes' for n in 202 2342 8119 23741 32744
416 # 'empty list of signature methods'
417 # 'tolerance n RSA or ECDSA methods' for n in 215 2355 8132 23754
418 # 'tolerance 32758 methods with sig_alg_cert'
419 # 'tolerance max 32744 number of methods with sig_alg_cert'
420 # 'tolerance max (32760) number of methods'
421 Test("test-signature-algorithms.py"),
422
423 # times out
424 Test("test-ssl-death-alert.py"),
425
426 # 17 pass, 13 fail. padding and truncation
427 Test("test-truncating-of-client-hello.py"),
428
429 # x448 tests need disabling plus x25519 corner cases need sorting out
430 Test("test-x25519.py"),
431])
432
433tls12_unsupported_tests = TestGroup("TLSv1.2 for unsupported features", [
434 # protocol_version
435 Test("test-SSLv3-padding.py"),
436])
437
438# These tests take a ton of time to fail against an 1.3 server,
439# so don't run them against 1.3 pending further investigation.
440legacy_tests = TestGroup("Legacy protocol tests", [
441 Test("test-sslv2-force-cipher-3des.py"),
442 Test("test-sslv2-force-cipher-non3des.py"),
443 Test("test-sslv2-force-cipher.py"),
444 Test("test-sslv2-force-export-cipher.py"),
445 Test("test-sslv2hello-protocol.py"),
446])
447
448all_groups = [
449 tls13_tests,
450 tls13_slow_tests,
451 tls13_extra_cert_tests,
452 tls13_failing_tests,
453 tls13_slow_failing_tests,
454 tls13_unsupported_tests,
455 tls12_tests,
456 tls12_slow_tests,
457 tls12_failing_tests,
458 tls12_unsupported_tests,
459 legacy_tests,
460]
461
462failing_groups = [
463 tls13_failing_tests,
464 tls13_slow_failing_tests,
465 tls12_failing_tests,
466]
467
468class TestRunner:
469 """ Runs the given tests troups against a server and displays stats. """
470
471 def __init__(
472 self, timing=False, verbose=False, port=4433, use_tls1_3=True,
473 dry_run=False, tests=[], scriptdir=tlsfuzzer_scriptdir,
474 ):
475 self.tests = []
476
477 self.dryrun = dry_run
478 self.use_tls1_3 = use_tls1_3
479 self.port = str(port)
480 self.scriptdir = scriptdir
481
482 self.stats = []
483 self.failed = []
484
485 self.timing = timing
486 self.verbose = verbose
487
488 def add(self, title="tests", tests=[]):
489 # tests.sort(key=lambda test: test.name)
490 self.tests.append(TestGroup(title, tests))
491
492 def add_group(self, group):
493 self.tests.append(group)
494
495 def run_script(self, test):
496 script = test.name
497 args = ["-p"] + [self.port] + test.args(self.use_tls1_3)
498
499 if self.dryrun:
500 if not self.verbose:
501 args = []
502 print(script , end=' ' if args else '')
503 print(' '.join([f"\"{arg}\"" for arg in args]))
504 return
505
506 if self.verbose:
507 print(script)
508 else:
509 print(f"{script[:68]:<72}", end=" ", flush=True)
510 start = timer()
511 test = subprocess.run(
512 ["python3", os.path.join(self.scriptdir, script)] + args,
513 capture_output=not self.verbose,
514 text=True,
515 )
516 end = timer()
517 self.stats.append((script, end - start))
518 if test.returncode == 0:
519 print("OK")
520 return
521 print("FAILED")
522 self.failed.append(script)
523
524 if self.verbose:
525 return
526
527 print('\n'.join(test.stdout.split("Test end\n", 1)[1:]), end="")
528
529 def run(self):
530 for group in self:
531 print(f"Running {group.title} ...")
532 for test in group:
533 self.run_script(test)
534 return not self.failed
535
536 def __iter__(self):
537 return iter(self.tests)
538
539 def __del__(self):
540 if self.timing and self.stats:
541 total = 0.0
542 for (script, time) in self.stats:
543 print(f"{round(time, 2):6.2f} {script}")
544 total += time
545 print(f"{round(total, 2):6.2f} total")
546
547 if self.failed:
548 print("Failed tests:")
549 print('\n'.join(self.failed))
550
551class TlsServer:
552 """ Spawns an s_server listening on localhost:port if necessary. """
553
554 def __init__(self, port=4433):
555 self.spawn = True
556 # Check whether a server is already listening on localhost:port
557 self.spawn = subprocess.run(
558 ["nc", "-c", "-z", "-T", "noverify", "localhost", str(port)],
559 stderr=subprocess.DEVNULL,
560 ).returncode != 0
561
562 if self.spawn:
563 self.server = subprocess.Popen(
564 [
565 "openssl",
566 "s_server",
567 "-accept",
568 str(port),
569 "-key",
570 "localhost.key",
571 "-cert",
572 "localhost.crt",
573 "-www",
574 ],
575 stdout=subprocess.DEVNULL,
576 stderr=subprocess.PIPE,
577 text=True,
578 )
579
580 # Check whether the server talks TLSv1.3
581 self.has_tls1_3 = subprocess.run(
582 [
583 "nc",
584 "-c",
585 "-z",
586 "-T",
587 "noverify",
588 "-T",
589 "protocols=TLSv1.3",
590 "localhost",
591 str(port),
592 ],
593 stderr=subprocess.DEVNULL,
594 ).returncode == 0
595
596 self.check()
597
598 def check(self):
599 if self.spawn and self.server.poll() is not None:
600 print(self.server.stderr.read())
601 raise RuntimeError(
602 f"openssl s_server died. Return code: {self.server.returncode}."
603 )
604 if self.spawn:
605 self.server.stderr.detach()
606
607 def __del__(self):
608 if self.spawn:
609 self.server.terminate()
610
611# Extract the arguments we pass to script
612def defaultargs(script, has_tls1_3):
613 return next(
614 (test for group in all_groups for test in group if test.name == script),
615 Test()
616 ).args(has_tls1_3)
617
618def list_or_missing(missing=True):
619 tests = [test.name for group in all_groups for test in group]
620
621 if missing:
622 scripts = {
623 f for f in os.listdir(tlsfuzzer_scriptdir) if f != "__pycache__"
624 }
625 missing = scripts - set(tests)
626 if missing:
627 print('\n'.join(sorted(missing)))
628 exit(0)
629
630 tests.sort()
631 print('\n'.join(tests))
632 exit(0)
633
634def usage():
635 print("Usage: python3 tlsfuzzer.py [-lmnstv] [-p port] [script [test...]]")
636 print(" --help help")
637 print(" -f run failing tests")
638 print(" -l list tests")
639 print(" -m list new tests after package update")
640 print(" -n do not run tests, but list the ones that would be run")
641 print(" -p port connect to this port - defaults to 4433")
642 print(" -s run slow tests")
643 print(" -t show timing stats at end")
644 print(" -v verbose output")
645 exit(0)
646
647def main():
648 failing = False
649 list = False
650 missing = False
651 dryrun = False
652 port = 4433
653 slow = False
654 timing = False
655 verbose = False
656
657 argv = sys.argv[1:]
658 opts, args = getopt.getopt(argv, "flmnp:stv", ["help"])
659 for opt, arg in opts:
660 if opt == '--help':
661 usage()
662 elif opt == '-f':
663 failing = True
664 elif opt == '-l':
665 list = True
666 elif opt == '-m':
667 missing = True
668 elif opt == '-n':
669 dryrun = True
670 elif opt == '-p':
671 port = int(arg)
672 elif opt == '-s':
673 slow = True
674 elif opt == '-t':
675 timing = True
676 elif opt == '-v':
677 verbose = True
678 else:
679 raise ValueError(f"Unknown option: {opt}")
680
681 if not os.path.exists(tlsfuzzer_scriptdir):
682 print("package py3-tlsfuzzer is required for this regress")
683 exit(1)
684
685 if list and failing:
686 failing = [test.name for group in failing_groups for test in group]
687 failing.sort()
688 print('\n'.join(failing))
689 exit(0)
690
691 if list or missing:
692 list_or_missing(missing)
693
694 tls_server = TlsServer(port)
695
696 tests = TestRunner(timing, verbose, port, tls_server.has_tls1_3, dryrun)
697
698 if args:
699 (dir, script) = os.path.split(args[0])
700 if dir and not dir == '.':
701 tests.scriptdir = dir
702
703 testargs = defaultargs(script, tls_server.has_tls1_3)
704
705 tests.verbose = True
706 tests.add("test from command line", [Test(script, testargs + args[1:])])
707
708 exit(not tests.run())
709
710 if failing:
711 if tls_server.has_tls1_3:
712 tests.add_group(tls13_failing_tests)
713 if slow:
714 tests.add_group(tls13_slow_failing_tests)
715 tests.add_group(tls12_failing_tests)
716
717 if tls_server.has_tls1_3:
718 tests.add_group(tls13_tests)
719 if slow:
720 tests.add_group(tls13_slow_tests)
721 else:
722 tests.add_group(legacy_tests)
723
724 tests.add_group(tls12_tests)
725 if slow:
726 tests.add_group(tls12_slow_tests)
727
728 success = tests.run()
729 del tests
730
731 if not success:
732 print("FAILED")
733 exit(1)
734
735if __name__ == "__main__":
736 main()