Add tests for new extension code
authorMatt Caswell <matt@openssl.org>
Mon, 28 Nov 2016 22:39:23 +0000 (22:39 +0000)
committerMatt Caswell <matt@openssl.org>
Thu, 8 Dec 2016 17:19:16 +0000 (17:19 +0000)
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 <rsalz@openssl.org>
Reviewed-by: Richard Levitte <levitte@openssl.org>
test/recipes/70-test_tls13messages.t
util/TLSProxy/EncryptedExtensions.pm [new file with mode: 0644]
util/TLSProxy/Message.pm
util/TLSProxy/Proxy.pm

index c64e54e..b59bb52 100755 (executable)
@@ -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 (file)
index 0000000..d65338e
--- /dev/null
@@ -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;
index 4f07ee3..8e743c5 100644 (file)
@@ -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,
index ccfc5c9..95599e5 100644 (file)
@@ -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;
         }