From 9ce3ed2a586032690bef6a1c4e58df8d1c18f344 Mon Sep 17 00:00:00 2001 From: Matt Caswell Date: Mon, 28 Nov 2016 22:39:23 +0000 Subject: [PATCH] Add tests for new extension code Extend test_tls13messages to additionally check the expected extensions under different options given to s_client/s_server. Perl changes reviewed by Richard Levitte. Non-perl changes reviewed by Rich Salz Reviewed-by: Rich Salz Reviewed-by: Richard Levitte --- test/recipes/70-test_tls13messages.t | 163 +++++++++++++++++++++++++-- util/TLSProxy/EncryptedExtensions.pm | 115 +++++++++++++++++++ util/TLSProxy/Message.pm | 21 +++- util/TLSProxy/Proxy.pm | 4 +- 4 files changed, 292 insertions(+), 11 deletions(-) create mode 100644 util/TLSProxy/EncryptedExtensions.pm diff --git a/test/recipes/70-test_tls13messages.t b/test/recipes/70-test_tls13messages.t index c64e54e6f8..b59bb5279b 100755 --- a/test/recipes/70-test_tls13messages.t +++ b/test/recipes/70-test_tls13messages.t @@ -27,6 +27,7 @@ plan skip_all => "$test_name needs TLSv1.3 enabled" if disabled("tls1_3"); $ENV{OPENSSL_ia32cap} = '~0x200000200000000'; +$ENV{CTLOG_FILE} = srctop_file("test", "ct", "log_list.conf"); use constant { DEFAULT_HANDSHAKE => 1, @@ -36,6 +37,17 @@ use constant { ALL_HANDSHAKES => 15 }; +use constant { + DEFAULT_EXTENSIONS => 0x00000001, + SERVER_NAME_CLI_EXTENSION => 0x00000002, + SERVER_NAME_SRV_EXTENSION => 0x00000004, + STATUS_REQUEST_CLI_EXTENSION => 0x00000008, + STATUS_REQUEST_SRV_EXTENSION => 0x00000010, + ALPN_CLI_EXTENSION => 0x00000020, + ALPN_SRV_EXTENSION => 0x00000040, + SCT_CLI_EXTENSION => 0x00000080 +}; + my @handmessages = ( [TLSProxy::Message::MT_CLIENT_HELLO, ALL_HANDSHAKES], [TLSProxy::Message::MT_SERVER_HELLO, ALL_HANDSHAKES], @@ -50,6 +62,28 @@ my @handmessages = ( [0, 0] ); +my @extensions = ( + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_SERVER_NAME, SERVER_NAME_CLI_EXTENSION], + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_STATUS_REQUEST, STATUS_REQUEST_CLI_EXTENSION], + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_SUPPORTED_GROUPS, DEFAULT_EXTENSIONS], + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_EC_POINT_FORMATS, DEFAULT_EXTENSIONS], + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_SIG_ALGS, DEFAULT_EXTENSIONS], + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_ALPN, ALPN_CLI_EXTENSION], + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_SCT, SCT_CLI_EXTENSION], + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_ENCRYPT_THEN_MAC, DEFAULT_EXTENSIONS], + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_EXTENDED_MASTER_SECRET, DEFAULT_EXTENSIONS], + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_SESSION_TICKET, DEFAULT_EXTENSIONS], + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_KEY_SHARE, DEFAULT_EXTENSIONS], + [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_SUPPORTED_VERSIONS, DEFAULT_EXTENSIONS], + + [TLSProxy::Message::MT_SERVER_HELLO, TLSProxy::Message::EXT_KEY_SHARE, DEFAULT_EXTENSIONS], + + [TLSProxy::Message::MT_ENCRYPTED_EXTENSIONS, TLSProxy::Message::EXT_SERVER_NAME, SERVER_NAME_SRV_EXTENSION], + [TLSProxy::Message::MT_ENCRYPTED_EXTENSIONS, TLSProxy::Message::EXT_STATUS_REQUEST, STATUS_REQUEST_SRV_EXTENSION], + [TLSProxy::Message::MT_ENCRYPTED_EXTENSIONS, TLSProxy::Message::EXT_ALPN, ALPN_SRV_EXTENSION], + [0,0,0] +); + my $proxy = TLSProxy::Proxy->new( undef, cmdstr(app(["openssl"]), display => 1), @@ -57,15 +91,15 @@ my $proxy = TLSProxy::Proxy->new( (!$ENV{HARNESS_ACTIVE} || $ENV{HARNESS_VERBOSE}) ); -sub checkmessages($$); +sub checkmessages($$$); #Test 1: Check we get all the right messages for a default handshake (undef, my $session) = tempfile(); #$proxy->serverconnects(2); $proxy->clientflags("-sess_out ".$session); $proxy->start() or plan skip_all => "Unable to start up Proxy for tests"; -plan tests => 3; -checkmessages(DEFAULT_HANDSHAKE, "Default handshake test"); +plan tests => 12; +checkmessages(DEFAULT_HANDSHAKE, DEFAULT_EXTENSIONS, "Default handshake test"); #TODO(TLS1.3): Test temporarily disabled until we implement TLS1.3 resumption #Test 2: Resumption handshake @@ -75,7 +109,23 @@ checkmessages(DEFAULT_HANDSHAKE, "Default handshake test"); #checkmessages(RESUME_HANDSHAKE, "Resumption handshake test"); unlink $session; -#Test 3: A default handshake, but with a CertificateStatus message +#Test 3: A status_request handshake (client request only) +$proxy->clear(); +$proxy->clientflags("-status"); +$proxy->start(); +checkmessages(DEFAULT_HANDSHAKE, + DEFAULT_EXTENSIONS | STATUS_REQUEST_CLI_EXTENSION, + "status_request handshake test (client)"); + +#Test 4: A status_request handshake (server support only) +$proxy->clear(); +$proxy->serverflags("-status_file " + .srctop_file("test", "recipes", "ocsp-response.der")); +$proxy->start(); +checkmessages(DEFAULT_HANDSHAKE, DEFAULT_EXTENSIONS, + "status_request handshake test (server)"); + +#Test 5: A status_request handshake (client and server) #TODO(TLS1.3): TLS1.3 doesn't actually have CertificateStatus messages. This is #a temporary test until such time as we do proper TLS1.3 style certificate #status @@ -84,28 +134,102 @@ $proxy->clientflags("-status"); $proxy->serverflags("-status_file " .srctop_file("test", "recipes", "ocsp-response.der")); $proxy->start(); -checkmessages(OCSP_HANDSHAKE, "OCSP handshake test"); +checkmessages(OCSP_HANDSHAKE, + DEFAULT_EXTENSIONS | STATUS_REQUEST_CLI_EXTENSION + | STATUS_REQUEST_SRV_EXTENSION, + "status_request handshake test"); -#Test 4: A client auth handshake +#Test 6: A client auth handshake $proxy->clear(); $proxy->clientflags("-cert ".srctop_file("apps", "server.pem")); $proxy->serverflags("-Verify 5"); $proxy->start(); -checkmessages(CLIENT_AUTH_HANDSHAKE, "Client auth handshake test"); +checkmessages(CLIENT_AUTH_HANDSHAKE, DEFAULT_EXTENSIONS, + "Client auth handshake test"); -sub checkmessages($$) +#Test 7: Server name handshake (client request only) +$proxy->clear(); +$proxy->clientflags("-servername testhost"); +$proxy->start(); +checkmessages(DEFAULT_HANDSHAKE, DEFAULT_EXTENSIONS | SERVER_NAME_CLI_EXTENSION, + "Server name handshake test (client)"); + +#Test 8: Server name handshake (server support only) +$proxy->clear(); +$proxy->serverflags("-servername testhost"); +$proxy->start(); +checkmessages(DEFAULT_HANDSHAKE, DEFAULT_EXTENSIONS, + "Server name handshake test (server)"); + +#Test 9: Server name handshake (client and server) +$proxy->clear(); +$proxy->clientflags("-servername testhost"); +$proxy->serverflags("-servername testhost"); +$proxy->start(); +checkmessages(DEFAULT_HANDSHAKE, + DEFAULT_EXTENSIONS | SERVER_NAME_CLI_EXTENSION + | SERVER_NAME_SRV_EXTENSION, + "Server name handshake test"); + +#Test 10: ALPN handshake (client request only) +$proxy->clear(); +$proxy->clientflags("-alpn test"); +$proxy->start(); +checkmessages(DEFAULT_HANDSHAKE, DEFAULT_EXTENSIONS | ALPN_CLI_EXTENSION, + "ALPN handshake test (client)"); + +#Test 11: ALPN handshake (server support only) +$proxy->clear(); +$proxy->serverflags("-alpn test"); +$proxy->start(); +checkmessages(DEFAULT_HANDSHAKE, DEFAULT_EXTENSIONS, + "ALPN handshake test (server)"); + +#Test 12: ALPN handshake (client and server) +$proxy->clear(); +$proxy->clientflags("-alpn test"); +$proxy->serverflags("-alpn test"); +$proxy->start(); +checkmessages(DEFAULT_HANDSHAKE, + DEFAULT_EXTENSIONS | ALPN_CLI_EXTENSION | ALPN_SRV_EXTENSION, + "ALPN handshake test"); + +#Test 13: SCT handshake (client request only) +#TODO(TLS1.3): This only checks that the client side extension appears. The +#SCT extension is unusual in that we have no built-in server side implementation +#The server side implementation can nomrally be added using the custom +#extensions framework (e.g. by using the "-serverinfo" s_server option). However +#currently we only support <= TLS1.2 for custom extensions because the existing +#framework and API has no knowledge of the TLS1.3 messages +$proxy->clear(); +#Note: -ct also sends status_request +$proxy->clientflags("-ct"); +$proxy->serverflags("-status_file " + .srctop_file("test", "recipes", "ocsp-response.der")); +$proxy->start(); +checkmessages(OCSP_HANDSHAKE, + DEFAULT_EXTENSIONS | SCT_CLI_EXTENSION + | STATUS_REQUEST_CLI_EXTENSION | STATUS_REQUEST_SRV_EXTENSION, + "SCT handshake test"); + +sub checkmessages($$$) { - my ($handtype, $testname) = @_; + my ($handtype, $exttype, $testname) = @_; subtest $testname => sub { my $loop = 0; my $numtests; + my $extcount; #First count the number of tests for ($numtests = 1; $handmessages[$loop][1] != 0; $loop++) { $numtests++ if (($handmessages[$loop][1] & $handtype) != 0); } + #Add number of extensions we check plus 3 for the number of messages + #that contain extensions + $numtests += $#extensions + 3; + plan tests => $numtests; $loop = 0; @@ -119,6 +243,27 @@ sub checkmessages($$) "Message type check. Got ".$message->mt .", expected ".$handmessages[$loop][0]); $loop++; + + + next if ($message->mt() != TLSProxy::Message::MT_CLIENT_HELLO + && $message->mt() != TLSProxy::Message::MT_SERVER_HELLO + && $message->mt() != + TLSProxy::Message::MT_ENCRYPTED_EXTENSIONS); + #Now check that we saw the extensions we expected + my $msgexts = $message->extension_data(); + for (my $extloop = 0, $extcount = 0; $extensions[$extloop][2] != 0; + $extloop++) { + next if ($message->mt() != $extensions[$extloop][0]); + ok (($extensions[$extloop][2] & $exttype) == 0 + || defined ($msgexts->{$extensions[$extloop][1]}), + "Extension presence check (Message: ".$message->mt() + ." Extension: ".($extensions[$extloop][2] & $exttype).", " + .$extloop.")"); + $extcount++ if (($extensions[$extloop][2] & $exttype) != 0); + } + ok($extcount == keys %$msgexts, "Extensions count mismatch (" + .$extcount.", ".(keys %$msgexts) + .")"); } ok($handmessages[$loop][1] == 0, "All expected messages processed"); } diff --git a/util/TLSProxy/EncryptedExtensions.pm b/util/TLSProxy/EncryptedExtensions.pm new file mode 100644 index 0000000000..d65338eaa5 --- /dev/null +++ b/util/TLSProxy/EncryptedExtensions.pm @@ -0,0 +1,115 @@ +# Copyright 2016 The OpenSSL Project Authors. All Rights Reserved. +# +# Licensed under the OpenSSL license (the "License"). You may not use +# this file except in compliance with the License. You can obtain a copy +# in the file LICENSE in the source distribution or at +# https://www.openssl.org/source/license.html + +use strict; + +package TLSProxy::EncryptedExtensions; + +use vars '@ISA'; +push @ISA, 'TLSProxy::Message'; + +sub new +{ + my $class = shift; + my ($server, + $data, + $records, + $startoffset, + $message_frag_lens) = @_; + + my $self = $class->SUPER::new( + $server, + TLSProxy::Message::MT_ENCRYPTED_EXTENSIONS, + $data, + $records, + $startoffset, + $message_frag_lens); + + $self->{extension_data} = ""; + + return $self; +} + +sub parse +{ + my $self = shift; + + my $extensions_len = unpack('n', $self->data); + if (!defined $extensions_len) { + $extensions_len = 0; + } + + my $extension_data; + if ($extensions_len != 0) { + $extension_data = substr($self->data, 2); + + if (length($extension_data) != $extensions_len) { + die "Invalid extension length\n"; + } + } else { + if (length($self->data) != 2) { + die "Invalid extension length\n"; + } + $extension_data = ""; + } + my %extensions = (); + while (length($extension_data) >= 4) { + my ($type, $size) = unpack("nn", $extension_data); + my $extdata = substr($extension_data, 4, $size); + $extension_data = substr($extension_data, 4 + $size); + $extensions{$type} = $extdata; + } + + $self->extension_data(\%extensions); + + print " Extensions Len:".$extensions_len."\n"; +} + +#Reconstruct the on-the-wire message data following changes +sub set_message_contents +{ + my $self = shift; + my $data; + my $extensions = ""; + + foreach my $key (keys %{$self->extension_data}) { + my $extdata = ${$self->extension_data}{$key}; + $extensions .= pack("n", $key); + $extensions .= pack("n", length($extdata)); + $extensions .= $extdata; + if ($key == TLSProxy::Message::EXT_DUPLICATE_EXTENSION) { + $extensions .= pack("n", $key); + $extensions .= pack("n", length($extdata)); + $extensions .= $extdata; + } + } + + $data = pack('n', length($extensions)); + $data .= $extensions; + $self->data($data); +} + +#Read/write accessors +sub extension_data +{ + my $self = shift; + if (@_) { + $self->{extension_data} = shift; + } + return $self->{extension_data}; +} +sub set_extension +{ + my ($self, $ext_type, $ext_data) = @_; + $self->{extension_data}{$ext_type} = $ext_data; +} +sub delete_extension +{ + my ($self, $ext_type) = @_; + delete $self->{extension_data}{$ext_type}; +} +1; diff --git a/util/TLSProxy/Message.pm b/util/TLSProxy/Message.pm index 4f07ee3d3f..8e743c56d3 100644 --- a/util/TLSProxy/Message.pm +++ b/util/TLSProxy/Message.pm @@ -60,13 +60,23 @@ my %message_type = ( ); use constant { + EXT_SERVER_NAME => 0, EXT_STATUS_REQUEST => 5, EXT_SUPPORTED_GROUPS => 10, + EXT_EC_POINT_FORMATS => 11, + EXT_SRP => 12, + EXT_SIG_ALGS => 13, + EXT_USE_SRTP => 14, + EXT_ALPN => 16, + EXT_SCT => 18, + EXT_PADDING => 21, EXT_ENCRYPT_THEN_MAC => 22, EXT_EXTENDED_MASTER_SECRET => 23, EXT_SESSION_TICKET => 35, - EXT_SUPPORTED_VERSIONS => 43, EXT_KEY_SHARE => 40, + EXT_SUPPORTED_VERSIONS => 43, + EXT_RENEGOTIATE => 65281, + EXT_NPN => 13172, # This extension is an unofficial extension only ever written by OpenSSL # (i.e. not read), and even then only when enabled. We use it to test # handling of duplicate extensions. @@ -245,6 +255,15 @@ sub create_message [@message_frag_lens] ); $message->parse(); + } elsif ($mt == MT_ENCRYPTED_EXTENSIONS) { + $message = TLSProxy::EncryptedExtensions->new( + $server, + $data, + [@message_rec_list], + $startoffset, + [@message_frag_lens] + ); + $message->parse(); } elsif ($mt == MT_SERVER_KEY_EXCHANGE) { $message = TLSProxy::ServerKeyExchange->new( $server, diff --git a/util/TLSProxy/Proxy.pm b/util/TLSProxy/Proxy.pm index ccfc5c9b2f..95599e50eb 100644 --- a/util/TLSProxy/Proxy.pm +++ b/util/TLSProxy/Proxy.pm @@ -17,6 +17,7 @@ use TLSProxy::Record; use TLSProxy::Message; use TLSProxy::ClientHello; use TLSProxy::ServerHello; +use TLSProxy::EncryptedExtensions; use TLSProxy::ServerKeyExchange; use TLSProxy::NewSessionTicket; @@ -153,7 +154,8 @@ sub start my $execcmd = $self->execute ." s_server -no_comp -rev -engine ossltest -accept " .($self->server_port) - ." -cert ".$self->cert." -naccept ".$self->serverconnects; + ." -cert ".$self->cert." -cert2 ".$self->cert + ." -naccept ".$self->serverconnects; if ($self->ciphers ne "") { $execcmd .= " -cipher ".$self->ciphers; } -- 2.25.1