[PATCH 10 of 11] Tests: reworked http SSL tests to use IO::Socket::SSL

Sergey Kandaurov pluknet at nginx.com
Thu May 11 14:27:12 UTC 2023


> On 17 Apr 2023, at 07:31, Maxim Dounin <mdounin at mdounin.ru> wrote:
> 
> # HG changeset patch
> # User Maxim Dounin <mdounin at mdounin.ru>
> # Date 1681702264 -10800
> #      Mon Apr 17 06:31:04 2023 +0300
> # Node ID 2aaba5bbc0366bffe1f468105b1185cd48efbc93
> # Parent  90913cb36b512c45cd9a171cbb4320b12ff24b48
> 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.

This is one of the reasons why it was written in Net::SSLeay.
The intention was to avoid using undocumented _get_ssl_object.

The resulting code throughout the series is sometimes hard to read now.
Still, if you believe it is better to rewrite it in IO::Socket:SSL,
I'm ok with it.  You can treat this as a positive review.
See minor comments below and in other patches.

> 
> 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.
> 
> diff --git a/access_log.t b/access_log.t
> --- a/access_log.t
> +++ b/access_log.t
> @@ -161,11 +161,11 @@ http_get('/varlog?logname=0');
> http_get('/varlog?logname=filename');
> 
> my $s = http('', start => 1);
> -http_get('/addr', socket => $s);
> my $addr = $s->sockhost();
> my $port = $s->sockport();
> my $saddr = $s->peerhost();
> my $sport = $s->peerport();
> +http_get('/addr', socket => $s);
> 
> http_get('/binary');
> 
> diff --git a/lib/Test/Nginx.pm b/lib/Test/Nginx.pm
> --- a/lib/Test/Nginx.pm
> +++ b/lib/Test/Nginx.pm
> @@ -838,13 +838,15 @@ sub http($;%) {
> 	my $s = http_start($request, %extra);
> 
> 	return $s if $extra{start} or !defined $s;
> -	return http_end($s);
> +	return http_end($s, %extra);
> }
> 
> sub http_start($;%) {
> 	my ($request, %extra) = @_;
> 	my $s;
> 
> +	my $port = $extra{SSL} ? 8443 : 8080;
> +
> 	eval {
> 		local $SIG{ALRM} = sub { die "timeout\n" };
> 		local $SIG{PIPE} = sub { die "sigpipe\n" };
> @@ -852,10 +854,25 @@ sub http_start($;%) {
> 
> 		$s = $extra{socket} || IO::Socket::INET->new(
> 			Proto => 'tcp',
> -			PeerAddr => '127.0.0.1:' . port(8080)
> +			PeerAddr => '127.0.0.1:' . port($port),
> +			%extra
> 		)
> 			or die "Can't connect to nginx: $!\n";
> 
> +		if ($extra{SSL}) {
> +			require IO::Socket::SSL;
> +			IO::Socket::SSL->start_SSL(
> +				$s,
> +				SSL_verify_mode =>
> +					IO::Socket::SSL::SSL_VERIFY_NONE(),
> +				%extra
> +			)
> +				or die $IO::Socket::SSL::SSL_ERROR . "\n";
> +
> +			log_in("ssl cipher: " . $s->get_cipher());
> +			log_in("ssl cert: " . $s->peer_certificate('issuer'));
> +		}
> +
> 		log_out($request);
> 		$s->print($request);
> 
> @@ -879,7 +896,7 @@ sub http_start($;%) {
> }
> 
> sub http_end($;%) {
> -	my ($s) = @_;
> +	my ($s, %extra) = @_;

extra doesn't seem to be used

> 	my $reply;
> 
> 	eval {
> @@ -890,6 +907,8 @@ sub http_end($;%) {
> 		local $/;
> 		$reply = $s->getline();
> 
> +		$s->close();
> +
> 		alarm(0);
> 	};
> 	alarm(0);
> diff --git a/ssl_certificate.t b/ssl_certificate.t
> --- a/ssl_certificate.t
> +++ b/ssl_certificate.t
> @@ -17,29 +17,15 @@ use Socket qw/ CRLF /;
> BEGIN { use FindBin; chdir($FindBin::Bin); }
> 
> use lib 'lib';
> -use Test::Nginx;
> +use Test::Nginx qw/ :DEFAULT http_end /;
> 
> ###############################################################################
> 
> select STDERR; $| = 1;
> select STDOUT; $| = 1;
> 
> -eval {
> -	require Net::SSLeay;
> -	Net::SSLeay::load_error_strings();
> -	Net::SSLeay::SSLeay_add_ssl_algorithms();
> -	Net::SSLeay::randomize();
> -};
> -plan(skip_all => 'Net::SSLeay not installed') if $@;
> -
> -eval {
> -	my $ctx = Net::SSLeay::CTX_new() or die;
> -	my $ssl = Net::SSLeay::new($ctx) or die;
> -	Net::SSLeay::set_tlsext_host_name($ssl, 'example.org') == 1 or die;
> -};
> -plan(skip_all => 'Net::SSLeay with OpenSSL SNI support required') if $@;
> -
> -my $t = Test::Nginx->new()->has(qw/http http_ssl geo openssl:1.0.2/)
> +my $t = Test::Nginx->new()
> +	->has(qw/http http_ssl geo openssl:1.0.2 socket_ssl_sni/)
> 	->has_daemon('openssl');
> 
> $t->write_file_expand('nginx.conf', <<'EOF');
> @@ -67,6 +53,7 @@ http {
>     }
> 
>     add_header X-SSL $ssl_server_name:$ssl_session_reused;
> +    add_header X-SSL-Protocol $ssl_protocol;
>     ssl_session_cache shared:SSL:1m;
>     ssl_session_tickets on;
> 
> @@ -177,60 +164,63 @@ like(get('password', 8083), qr/password/
> 
> # session reuse
> 
> -my ($s, $ssl) = get('default', 8080);
> -my $ses = Net::SSLeay::get_session($ssl);
> -
> -like(get('default', 8080, $ses), qr/default:r/, 'session reused');
> +my $s = session('default', 8080);
> 
> TODO: {
> -# ticket key name mismatch prevents session resumption
> +local $TODO = 'no TLSv1.3 sessions, old Net::SSLeay'
> +	if $Net::SSLeay::VERSION < 1.88 && test_tls13();
> +local $TODO = 'no TLSv1.3 sessions, old IO::Socket::SSL'
> +	if $IO::Socket::SSL::VERSION < 2.061 && test_tls13();
> +
> +like(get('default', 8080, $s), qr/default:r/, 'session reused');
> +
> +TODO: {
> +# automatic ticket ticket key name mismatch prevents session resumption

"ticket" repetition

(also a similar comment in stream_ssl_certificate.t isn't touched)

> local $TODO = 'not yet' unless $t->has_version('1.23.2');
> 
> -like(get('default', 8081, $ses), qr/default:r/, 'session id context match');
> +like(get('default', 8081, $s), qr/default:r/, 'session id context match');
> 
> }
> +}
> 
> -like(get('default', 8082, $ses), qr/default:\./, 'session id context distinct');
> +like(get('default', 8082, $s), qr/default:\./, 'session id context distinct');
> 
> # errors
> 
> -Net::SSLeay::ERR_clear_error();
> -get_ssl_socket('nx', 8084);
> -ok(Net::SSLeay::ERR_peek_error(), 'no certificate');
> +ok(!get('nx', 8084), 'no certificate');

IIRC this was written so to ensure it is an SSL layer error
and not some abrupt termination caused by unrelated reason.
I don't object to simplify it though, if you think it is better.

[..]

-- 
Sergey Kandaurov


More information about the nginx-devel mailing list