changeset 1917:24fea64f233f

Tests: TLS early data tests with HTTP/3.
author Sergey Kandaurov <pluknet@nginx.com>
date Thu, 22 Jun 2023 15:35:19 +0400
parents 6ab08c255dd3
children 22f45bf99a9e
files h3_ssl_early_data.t lib/Test/Nginx/HTTP3.pm
diffstat 2 files changed, 121 insertions(+), 18 deletions(-) [+]
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/h3_ssl_early_data.t
@@ -0,0 +1,108 @@
+#!/usr/bin/perl
+
+# (C) Sergey Kandaurov
+# (C) Nginx, Inc.
+
+# Tests for TLS early data with HTTP/3.
+
+###############################################################################
+
+use warnings;
+use strict;
+
+use Test::More;
+
+BEGIN { use FindBin; chdir($FindBin::Bin); }
+
+use lib 'lib';
+use Test::Nginx;
+use Test::Nginx::HTTP3;
+
+###############################################################################
+
+select STDERR; $| = 1;
+select STDOUT; $| = 1;
+
+my $t = Test::Nginx->new()->has(qw/http http_v3 cryptx/)
+	->has_daemon('openssl')->plan(5)
+	->write_file_expand('nginx.conf', <<'EOF');
+
+%%TEST_GLOBALS%%
+
+daemon off;
+
+events {
+}
+
+http {
+    %%TEST_GLOBALS_HTTP%%
+
+    ssl_certificate_key localhost.key;
+    ssl_certificate localhost.crt;
+    ssl_early_data on;
+
+    add_header X-Session $ssl_session_reused always;
+    add_header X-Early   $ssl_early_data     always;
+
+    server {
+        listen       127.0.0.1:%%PORT_8980_UDP%% quic;
+        server_name  localhost;
+    }
+}
+
+EOF
+
+$t->write_file('openssl.conf', <<EOF);
+[ req ]
+default_bits = 2048
+encrypt_key = no
+distinguished_name = req_distinguished_name
+[ req_distinguished_name ]
+EOF
+
+my $d = $t->testdir();
+
+foreach my $name ('localhost') {
+	system('openssl req -x509 -new '
+		. "-config $d/openssl.conf -subj /CN=$name/ "
+		. "-out $d/$name.crt -keyout $d/$name.key "
+		. ">>$d/openssl.out 2>&1") == 0
+		or die "Can't create certificate for $name: $!\n";
+}
+
+$t->run();
+
+###############################################################################
+
+my $s = Test::Nginx::HTTP3->new(8980);
+my $frames = $s->read(all => [{ sid => $s->new_stream(), fin => 1 }]);
+
+my ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{'x-session'}, '.', 'new session');
+
+local $TODO = 'no TLSv1.3 sessions in LibreSSL' if $t->has_module('LibreSSL');
+
+my $psk_list = $s->{psk_list};
+my $ed = $s->build_new_stream();
+
+$s = Test::Nginx::HTTP3->new(8980, psk_list => $psk_list, early_data => {});
+
+TODO: {
+local $TODO = 'no 0-RTT in OpenSSL compat layer'
+	unless $t->has_module('OpenSSL [.0-9]+\+quic')
+	or $t->has_module('BoringSSL')
+	or $t->has_module('LibreSSL');
+
+$frames = $s->read(all => [{ sid => 0, fin => 1 }]);
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{'x-session'}, 'r', 'reused session 0rtt');
+is($frame->{headers}->{'x-early'}, '1', 'reused session is early');
+
+}
+
+$frames = $s->read(all => [{ sid => $s->new_stream(), fin => 1 }]);
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{'x-session'}, 'r', 'reused session 1rtt');
+is($frame->{headers}->{'x-early'}, undef, 'reused session not early');
+
+###############################################################################
--- a/lib/Test/Nginx/HTTP3.pm
+++ b/lib/Test/Nginx/HTTP3.pm
@@ -41,6 +41,7 @@ sub new {
 	$self->{repeat} = 0;
 	$self->{token} = $extra{token} || '';
 	$self->{psk_list} = $extra{psk_list} || [];
+	$self->{early_data} = $extra{early_data};
 
 	$self->{sni} = exists $extra{sni} ? $extra{sni} : 'localhost';
 	$self->{cipher} = 0x1301;
@@ -62,7 +63,7 @@ sub new {
 }
 
 sub init {
-	my ($self, $early_data) = @_;
+	my ($self) = @_;
 	$self->{keys} = [];
 	$self->{pn} = [[-1, -1, -1, -1], [-1, -1, -1, -1]];
 	$self->{crypto_in} = [[],[],[],[]];
@@ -82,7 +83,6 @@ sub init {
 	$self->{salt} = "\x38\x76\x2c\xf7\xf5\x59\x34\xb3\x4d\x17"
 			.  "\x9a\xe6\xa4\xc8\x0c\xad\xcc\xbb\x7f\x0a";
 	$self->{ncid} = [];
-	$self->{early_data} = $early_data;
 }
 
 sub retry {
@@ -129,26 +129,24 @@ sub init_key_schedule {
 }
 
 sub initial {
-	my ($self, $ed) = @_;
+	my ($self) = @_;
 	$self->{tlsm}{ch} = $self->build_tls_client_hello();
 	my $ch = $self->{tlsm}{ch};
 	my $crypto = build_crypto($ch);
 	my $padding = 1200 - length($crypto);
-	$padding = 0 if $padding < 0 || $self->{psk}->{ed};
+	$padding = 0 if $padding < 0;
+	$padding = 0 if $self->{psk}{ed} && $self->{early_data};
 	my $payload = $crypto . pack("x$padding");
 	my $initial = $self->encrypt_aead($payload, 0);
 
-	if ($ed && $self->{psk}->{ed}) {
+	if ($self->{early_data} && $self->{psk}->{ed}) {
 		my ($hash, $hlen) = $self->{psk}{cipher} == 0x1302 ?
 			('SHA384', 48) : ('SHA256', 32);
 		$self->set_traffic_keys('tls13 c e traffic', $hash, $hlen, 1,
 			'w', $self->{es_prk}, Crypt::Digest::digest_data($hash,
 			$self->{tlsm}{ch}));
 
-#		my $ed = "\x0a\x02\x08\x00\x04\x02\x06\x1f\x0d\x00\x0a"
-#			. $self->build_stream("\x01\x06\x00\x00\xc0");
-		$payload = $ed;
-#		$payload = $self->build_stream("GET /\n");
+		$payload = $self->build_new_stream($self->{early_data});
 		$padding = 1200 - length($crypto) - length($payload);
 		$payload .= pack("x$padding") if $padding > 0;
 		$initial .= $self->encrypt_aead($payload, 1);
@@ -248,13 +246,6 @@ sub handshake {
 	$self->{socket}->syswrite($self->encrypt_aead($crypto, 2));
 }
 
-#if (!$psk->{ed}) {
-#	my $r = "\x0a\x02\x08\x00\x04\x02\x06\x1f\x0d\x00\x0a";
-#	$s->syswrite(encrypt_aead($r, 3));
-#	$r = "\x01\x06\x00\x00\xc0";
-#	$s->syswrite(encrypt_aead($self->build_stream($r), 3));
-#}
-
 sub DESTROY {
 	my ($self) = @_;
 
@@ -408,7 +399,7 @@ sub cancel_push {
 		. build_int($offset) . build_int($length) . $buf);
 }
 
-sub new_stream {
+sub build_new_stream {
 	my ($self, $uri, $stream) = @_;
 	my ($input, $buf);
 
@@ -459,8 +450,12 @@ sub new_stream {
 	$buf .= pack_body($self, $body) if defined $body;
 
 	$self->{streams}{$self->{last_stream}}{sent} = length($buf);
-	$self->raw_write($self->build_stream($buf, start => $uri->{body_more}));
+	$self->build_stream($buf, start => $uri->{body_more});
+}
 
+sub new_stream {
+	my ($self, $uri, $stream) = @_;
+	$self->raw_write($self->build_new_stream($uri, $stream));
 	return $self->{last_stream};
 }