Mercurial > hg > nginx-tests
comparison ssl_ocsp.t @ 1865:0e1865aa9b33
Tests: reworked http SSL tests to use IO::Socket::SSL.
Relevant infrastructure is provided in Test::Nginx http() functions.
This also ensures that SSL handshake and various read and write operations
are guarded with timeouts.
The ssl_sni_reneg.t test uses IO::Socket::SSL::_get_ssl_object() to access
the Net::SSLeay object directly and trigger renegotation. While
not exactly correct, this seems to be good enough for tests.
Similarly, IO::Socket::SSL::_get_ssl_object() is used in ssl_stapling.t,
since SSL_ocsp_staple_callback is called with the socket instead of the
Net::SSLeay object.
Similarly, IO::Socket::SSL::_get_ssl_object() is used in ssl_verify_client.t,
since there seems to be no way to obtain CA list with IO::Socket::SSL.
Notable change to http() request interface is that http_end() now closes
the socket. This is to make sure that SSL connections are properly
closed and SSL sessions are not removed from the IO::Socket::SSL session
cache. This affected access_log.t, which was modified accordingly.
author | Maxim Dounin <mdounin@mdounin.ru> |
---|---|
date | Thu, 18 May 2023 18:07:17 +0300 |
parents | 727741cdff74 |
children | 0b5ec15c62ed |
comparison
equal
deleted
inserted
replaced
1864:46351d990aee | 1865:0e1865aa9b33 |
---|---|
15 use MIME::Base64 qw/ decode_base64 /; | 15 use MIME::Base64 qw/ decode_base64 /; |
16 | 16 |
17 BEGIN { use FindBin; chdir($FindBin::Bin); } | 17 BEGIN { use FindBin; chdir($FindBin::Bin); } |
18 | 18 |
19 use lib 'lib'; | 19 use lib 'lib'; |
20 use Test::Nginx; | 20 use Test::Nginx qw/ :DEFAULT http_end /; |
21 | 21 |
22 ############################################################################### | 22 ############################################################################### |
23 | 23 |
24 select STDERR; $| = 1; | 24 select STDERR; $| = 1; |
25 select STDOUT; $| = 1; | 25 select STDOUT; $| = 1; |
26 | 26 |
27 eval { | 27 my $t = Test::Nginx->new()->has(qw/http http_ssl sni socket_ssl_sni/) |
28 require Net::SSLeay; | 28 ->has_daemon('openssl'); |
29 Net::SSLeay::load_error_strings(); | |
30 Net::SSLeay::SSLeay_add_ssl_algorithms(); | |
31 Net::SSLeay::randomize(); | |
32 Net::SSLeay::SSLeay(); | |
33 defined &Net::SSLeay::set_tlsext_status_type or die; | |
34 }; | |
35 plan(skip_all => 'Net::SSLeay not installed or too old') if $@; | |
36 | |
37 eval { | |
38 my $ctx = Net::SSLeay::CTX_new() or die; | |
39 my $ssl = Net::SSLeay::new($ctx) or die; | |
40 Net::SSLeay::set_tlsext_host_name($ssl, 'example.org') == 1 or die; | |
41 }; | |
42 plan(skip_all => 'Net::SSLeay with OpenSSL SNI support required') if $@; | |
43 | |
44 my $t = Test::Nginx->new()->has(qw/http http_ssl sni/)->has_daemon('openssl'); | |
45 | 29 |
46 plan(skip_all => 'no OCSP support in BoringSSL') | 30 plan(skip_all => 'no OCSP support in BoringSSL') |
47 if $t->has_module('BoringSSL'); | 31 if $t->has_module('BoringSSL'); |
48 | 32 |
49 $t->write_file_expand('nginx.conf', <<'EOF'); | 33 $t->write_file_expand('nginx.conf', <<'EOF'); |
68 | 52 |
69 ssl_session_cache shared:SSL:1m; | 53 ssl_session_cache shared:SSL:1m; |
70 ssl_session_tickets off; | 54 ssl_session_tickets off; |
71 | 55 |
72 add_header X-Verify x${ssl_client_verify}:${ssl_session_reused}x always; | 56 add_header X-Verify x${ssl_client_verify}:${ssl_session_reused}x always; |
57 add_header X-SSL-Protocol $ssl_protocol always; | |
73 | 58 |
74 server { | 59 server { |
75 listen 127.0.0.1:8443 ssl; | 60 listen 127.0.0.1:8443 ssl; |
76 server_name localhost; | 61 server_name localhost; |
77 } | 62 } |
281 $t->run()->plan(15); | 266 $t->run()->plan(15); |
282 | 267 |
283 $t->waitforsocket("127.0.0.1:" . port(8081)); | 268 $t->waitforsocket("127.0.0.1:" . port(8081)); |
284 $t->waitforsocket("127.0.0.1:" . port(8082)); | 269 $t->waitforsocket("127.0.0.1:" . port(8082)); |
285 | 270 |
286 my $version = get_version(); | |
287 | |
288 ############################################################################### | 271 ############################################################################### |
289 | 272 |
290 like(get('end'), qr/200 OK.*SUCCESS/s, 'ocsp leaf'); | 273 like(get('end'), qr/200 OK.*SUCCESS/s, 'ocsp leaf'); |
291 | 274 |
292 # demonstrate that ocsp int request is failed due to missing resolver | 275 # demonstrate that ocsp int request is failed due to missing resolver |
364 . ">>$d/openssl.out 2>&1") == 0 | 347 . ">>$d/openssl.out 2>&1") == 0 |
365 or die "Can't create EC OCSP response: $!\n"; | 348 or die "Can't create EC OCSP response: $!\n"; |
366 | 349 |
367 like(get('ec-end'), qr/200 OK.*SUCCESS/s, 'ocsp ecdsa'); | 350 like(get('ec-end'), qr/200 OK.*SUCCESS/s, 'ocsp ecdsa'); |
368 | 351 |
369 my ($s, $ssl) = get('ec-end'); | 352 my $s = session('ec-end'); |
370 my $ses = Net::SSLeay::get_session($ssl); | |
371 | 353 |
372 TODO: { | 354 TODO: { |
355 local $TODO = 'no TLSv1.3 sessions, old Net::SSLeay' | |
356 if $Net::SSLeay::VERSION < 1.88 && test_tls13(); | |
357 local $TODO = 'no TLSv1.3 sessions, old IO::Socket::SSL' | |
358 if $IO::Socket::SSL::VERSION < 2.061 && test_tls13(); | |
373 local $TODO = 'no TLSv1.3 sessions in LibreSSL' | 359 local $TODO = 'no TLSv1.3 sessions in LibreSSL' |
374 if $t->has_module('LibreSSL') and $version > 0x303; | 360 if $t->has_module('LibreSSL') && test_tls13(); |
375 | 361 |
376 like(get('ec-end', ses => $ses), | 362 like(get('ec-end', ses => $s), |
377 qr/200 OK.*SUCCESS:r/s, 'session reused'); | 363 qr/200 OK.*SUCCESS:r/s, 'session reused'); |
378 | 364 |
379 } | 365 } |
380 | 366 |
381 # revoke with saved session | 367 # revoke with saved session |
396 or die "Can't create OCSP response: $!\n"; | 382 or die "Can't create OCSP response: $!\n"; |
397 | 383 |
398 # reusing session with revoked certificate | 384 # reusing session with revoked certificate |
399 | 385 |
400 TODO: { | 386 TODO: { |
387 local $TODO = 'no TLSv1.3 sessions, old Net::SSLeay' | |
388 if $Net::SSLeay::VERSION < 1.88 && test_tls13(); | |
389 local $TODO = 'no TLSv1.3 sessions, old IO::Socket::SSL' | |
390 if $IO::Socket::SSL::VERSION < 2.061 && test_tls13(); | |
401 local $TODO = 'no TLSv1.3 sessions in LibreSSL' | 391 local $TODO = 'no TLSv1.3 sessions in LibreSSL' |
402 if $t->has_module('LibreSSL') and $version > 0x303; | 392 if $t->has_module('LibreSSL') && test_tls13(); |
403 | 393 |
404 like(get('ec-end', ses => $ses), | 394 like(get('ec-end', ses => $s), |
405 qr/400 Bad.*FAILED:certificate revoked:r/s, 'session reused - revoked'); | 395 qr/400 Bad.*FAILED:certificate revoked:r/s, 'session reused - revoked'); |
406 | 396 |
407 } | 397 } |
408 | 398 |
409 # regression test for self-signed | 399 # regression test for self-signed |
415 like(`grep -F '[crit]' ${\($t->testdir())}/error.log`, qr/^$/s, 'no crit'); | 405 like(`grep -F '[crit]' ${\($t->testdir())}/error.log`, qr/^$/s, 'no crit'); |
416 | 406 |
417 ############################################################################### | 407 ############################################################################### |
418 | 408 |
419 sub get { | 409 sub get { |
420 my ($cert, %extra) = @_; | 410 my $s = get_socket(@_) || return; |
421 my ($s, $ssl) = get_ssl_socket($cert, %extra); | 411 return http_end($s); |
422 my $cipher = Net::SSLeay::get_cipher($ssl); | 412 } |
423 Test::Nginx::log_core('||', "cipher: $cipher"); | 413 |
424 my $host = $extra{sni} ? $extra{sni} : 'localhost'; | 414 sub session { |
425 local $SIG{PIPE} = 'IGNORE'; | 415 my $s = get_socket(@_) || return; |
426 log_out("GET /serial HTTP/1.0\nHost: $host\n\n"); | 416 http_end($s); |
427 Net::SSLeay::write($ssl, "GET /serial HTTP/1.0\nHost: $host\n\n"); | 417 return $s; |
428 my $r = Net::SSLeay::read($ssl); | 418 } |
429 log_in($r); | 419 |
430 $s->close(); | 420 sub get_socket { |
431 return $r unless wantarray(); | |
432 return ($s, $ssl); | |
433 } | |
434 | |
435 sub get_ssl_socket { | |
436 my ($cert, %extra) = @_; | 421 my ($cert, %extra) = @_; |
437 my $ses = $extra{ses}; | 422 my $ses = $extra{ses}; |
438 my $sni = $extra{sni}; | 423 my $sni = $extra{sni} || 'localhost'; |
439 my $port = $extra{port} || 8443; | 424 my $port = $extra{port} || 8443; |
440 my $s; | 425 |
441 | 426 return http( |
442 eval { | 427 "GET /serial HTTP/1.0\nHost: $sni\n\n", |
443 local $SIG{ALRM} = sub { die "timeout\n" }; | 428 start => 1, PeerAddr => '127.0.0.1:' . port($port), |
444 local $SIG{PIPE} = sub { die "sigpipe\n" }; | 429 SSL => 1, |
445 alarm(8); | 430 SSL_hostname => $sni, |
446 $s = IO::Socket::INET->new('127.0.0.1:' . port($port)); | 431 SSL_session_cache_size => 100, |
447 alarm(0); | 432 SSL_reuse_ctx => $ses, |
448 }; | 433 $cert ? ( |
449 alarm(0); | 434 SSL_cert_file => "$d/$cert.crt", |
450 | 435 SSL_key_file => "$d/$cert.key" |
451 if ($@) { | 436 ) : () |
452 log_in("died: $@"); | 437 ); |
453 return undef; | 438 } |
454 } | 439 |
455 | 440 sub test_tls13 { |
456 my $ctx = Net::SSLeay::CTX_new() or die("Failed to create SSL_CTX $!"); | 441 return http_get('/', SSL => 1) =~ /TLSv1.3/; |
457 | |
458 Net::SSLeay::set_cert_and_key($ctx, "$d/$cert.crt", "$d/$cert.key") | |
459 or die if $cert; | |
460 my $ssl = Net::SSLeay::new($ctx) or die("Failed to create SSL $!"); | |
461 Net::SSLeay::set_session($ssl, $ses) if defined $ses; | |
462 Net::SSLeay::set_tlsext_host_name($ssl, $sni) if $sni; | |
463 Net::SSLeay::set_fd($ssl, fileno($s)); | |
464 Net::SSLeay::connect($ssl) or die("ssl connect"); | |
465 return ($s, $ssl); | |
466 } | |
467 | |
468 sub get_version { | |
469 my ($s, $ssl) = get_ssl_socket(); | |
470 return Net::SSLeay::version($ssl); | |
471 } | 442 } |
472 | 443 |
473 ############################################################################### | 444 ############################################################################### |
474 | 445 |
475 sub http_daemon { | 446 sub http_daemon { |