#! /usr/bin/env perl
-# Copyright 2016 The OpenSSL Project Authors. All Rights Reserved.
+# Copyright 2016-2019 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
# https://www.openssl.org/source/license.html
use strict;
+use feature 'state';
+
use OpenSSL::Test qw/:DEFAULT cmdstr srctop_file bldtop_dir/;
use OpenSSL::Test::Utils;
use TLSProxy::Proxy;
setup($test_name);
plan skip_all => "TLSProxy isn't usable on $^O"
- if $^O =~ /^(VMS|MSWin32)$/;
+ if $^O =~ /^(VMS)$/;
plan skip_all => "$test_name needs the dynamic engine feature enabled"
if disabled("engine") || disabled("dynamic-engine");
(!$ENV{HARNESS_ACTIVE} || $ENV{HARNESS_VERBOSE})
);
+my $boundary_test_type;
+my $fatal_alert = 0; # set by filters at expected fatal alerts
+
#Test 1: Injecting out of context empty records should fail
my $content_type = TLSProxy::Record::RT_APPLICATION_DATA;
my $inject_recs_num = 1;
$proxy->serverflags("-tls1_2");
$proxy->start() or plan skip_all => "Unable to start up Proxy for tests";
-my $num_tests = 11;
-if (!disabled("tls1_1")) {
- $num_tests++;
-}
-if (!disabled("tls1_3")) {
- $num_tests += 3;
-}
-plan tests => $num_tests;
-ok(TLSProxy::Message->fail(), "Out of context empty records test");
+plan tests => 20;
+ok($fatal_alert, "Out of context empty records test");
#Test 2: Injecting in context empty records should succeed
$proxy->clear();
ok(TLSProxy::Message->success(), "In context empty records test");
#Test 3: Injecting too many in context empty records should fail
+$fatal_alert = 0;
$proxy->clear();
#We allow 32 consecutive in context empty records
$inject_recs_num = 33;
$proxy->serverflags("-tls1_2");
$proxy->start();
-ok(TLSProxy::Message->fail(), "Too many in context empty records test");
+ok($fatal_alert, "Too many in context empty records test");
-#Test 4: Injecting a fragmented fatal alert should fail. We actually expect no
-# alerts to be sent from either side because *we* injected the fatal
-# alert, i.e. this will look like a disorderly close
+#Test 4: Injecting a fragmented fatal alert should fail. We expect the server to
+# send back an alert of its own because it cannot handle fragmented
+# alerts
+$fatal_alert = 0;
$proxy->clear();
$proxy->filter(\&add_frag_alert_filter);
$proxy->serverflags("-tls1_2");
$proxy->start();
-ok(!TLSProxy::Message->end(), "Fragmented alert records test");
+ok($fatal_alert, "Fragmented alert records test");
#Run some SSLv2 ClientHello tests
$proxy->clear();
$proxy->serverflags("-tls1_2");
$proxy->start();
-ok(!TLSProxy::Message->end(), "SSLv2 in SSLv2 ClientHello test");
+ok(TLSProxy::Message->fail(), "SSLv2 in SSLv2 ClientHello test");
#Test 7: Sanity check ClientHello fragmentation. This isn't really an SSLv2 test
# at all, but it gives us confidence that Test 8 fails for the right
$proxy->start();
ok(TLSProxy::Message->fail(), "Alert before SSLv2 ClientHello test");
-#Unregcognised record type tests
+#Unrecognised record type tests
#Test 10: Sending an unrecognised record type in TLS1.2 should fail
+$fatal_alert = 0;
$proxy->clear();
$proxy->serverflags("-tls1_2");
$proxy->filter(\&add_unknown_record_type);
$proxy->start();
-ok(TLSProxy::Message->fail(), "Unrecognised record type in TLS1.2");
+ok($fatal_alert, "Unrecognised record type in TLS1.2");
+
+SKIP: {
+ skip "TLSv1.1 disabled", 1 if disabled("tls1_1");
-#Test 11: Sending an unrecognised record type in TLS1.1 should fail
-if (!disabled("tls1_1")) {
+ #Test 11: Sending an unrecognised record type in TLS1.1 should fail
+ $fatal_alert = 0;
$proxy->clear();
$proxy->clientflags("-tls1_1");
$proxy->start();
- ok(TLSProxy::Message->fail(), "Unrecognised record type in TLS1.1");
+ ok($fatal_alert, "Unrecognised record type in TLS1.1");
}
#Test 12: Sending a different record version in TLS1.2 should fail
+$fatal_alert = 0;
$proxy->clear();
$proxy->clientflags("-tls1_2");
$proxy->filter(\&change_version);
$proxy->start();
-ok(TLSProxy::Message->fail(), "Changed record version in TLS1.2");
+ok($fatal_alert, "Changed record version in TLS1.2");
#TLS1.3 specific tests
-if (!disabled("tls1_3")) {
- #Test 13: Sending a different record version in TLS1.3 should succeed
+SKIP: {
+ skip "TLSv1.3 disabled", 8 if disabled("tls1_3");
+
+ #Test 13: Sending a different record version in TLS1.3 should fail
$proxy->clear();
$proxy->filter(\&change_version);
$proxy->start();
- ok(TLSProxy::Message->success(), "Changed record version in TLS1.3");
+ ok(TLSProxy::Message->fail(), "Changed record version in TLS1.3");
#Test 14: Sending an unrecognised record type in TLS1.3 should fail
+ $fatal_alert = 0;
$proxy->clear();
$proxy->filter(\&add_unknown_record_type);
$proxy->start();
- ok(TLSProxy::Message->fail(), "Unrecognised record type in TLS1.3");
+ ok($fatal_alert, "Unrecognised record type in TLS1.3");
#Test 15: Sending an outer record type other than app data once encrypted
#should fail
+ $fatal_alert = 0;
$proxy->clear();
$proxy->filter(\&change_outer_record_type);
$proxy->start();
- ok(TLSProxy::Message->fail(), "Wrong outer record type in TLS1.3");
+ ok($fatal_alert, "Wrong outer record type in TLS1.3");
+
+ use constant {
+ DATA_AFTER_SERVER_HELLO => 0,
+ DATA_AFTER_FINISHED => 1,
+ DATA_AFTER_KEY_UPDATE => 2,
+ DATA_BETWEEN_KEY_UPDATE => 3,
+ NO_DATA_BETWEEN_KEY_UPDATE => 4,
+ };
+
+ #Test 16: Sending a ServerHello which doesn't end on a record boundary
+ # should fail
+ $fatal_alert = 0;
+ $proxy->clear();
+ $boundary_test_type = DATA_AFTER_SERVER_HELLO;
+ $proxy->filter(\¬_on_record_boundary);
+ $proxy->start();
+ ok($fatal_alert, "Record not on boundary in TLS1.3 (ServerHello)");
+
+ #Test 17: Sending a Finished which doesn't end on a record boundary
+ # should fail
+ $fatal_alert = 0;
+ $proxy->clear();
+ $boundary_test_type = DATA_AFTER_FINISHED;
+ $proxy->start();
+ ok($fatal_alert, "Record not on boundary in TLS1.3 (Finished)");
+
+ #Test 18: Sending a KeyUpdate which doesn't end on a record boundary
+ # should fail
+ $fatal_alert = 0;
+ $proxy->clear();
+ $boundary_test_type = DATA_AFTER_KEY_UPDATE;
+ $proxy->start();
+ ok($fatal_alert, "Record not on boundary in TLS1.3 (KeyUpdate)");
+
+ #Test 19: Sending application data in the middle of a fragmented KeyUpdate
+ # should fail. Strictly speaking this is not a record boundary test
+ # but we use the same filter.
+ $fatal_alert = 0;
+ $proxy->clear();
+ $boundary_test_type = DATA_BETWEEN_KEY_UPDATE;
+ $proxy->start();
+ ok($fatal_alert, "Data between KeyUpdate");
+
+ #Test 20: Fragmented KeyUpdate. This should succeed. Strictly speaking this
+ # is not a record boundary test but we use the same filter.
+ $proxy->clear();
+ $boundary_test_type = NO_DATA_BETWEEN_KEY_UPDATE;
+ $proxy->start();
+ ok(TLSProxy::Message->success(), "No data between KeyUpdate");
}
sub add_empty_recs_filter
{
my $proxy = shift;
+ my $records = $proxy->record_list;
# We're only interested in the initial ClientHello
if ($proxy->flight != 0) {
+ $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(1) == 10;
return;
}
"",
""
);
-
- push @{$proxy->record_list}, $record;
+ push @{$records}, $record;
}
}
sub add_frag_alert_filter
{
my $proxy = shift;
+ my $records = $proxy->record_list;
my $byte;
# We're only interested in the initial ClientHello
if ($proxy->flight != 0) {
+ $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(1) == 10;
return;
}
$byte,
$byte
);
- push @{$proxy->record_list}, $record;
+ push @{$records}, $record;
# And finally the description (Unexpected message) in a third record
$byte = pack('C', TLSProxy::Message::AL_DESC_UNEXPECTED_MESSAGE);
$byte,
$byte
);
- push @{$proxy->record_list}, $record;
+ push @{$records}, $record;
}
sub add_sslv2_filter
sub add_unknown_record_type
{
my $proxy = shift;
+ my $records = $proxy->record_list;
+ state $added_record;
# We'll change a record after the initial version neg has taken place
- if ($proxy->flight != 1) {
+ if ($proxy->flight == 0) {
+ $added_record = 0;
+ return;
+ } elsif ($proxy->flight != 1 || $added_record) {
+ $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(0) == 10;
return;
}
- my $lastrec = ${$proxy->record_list}[-1];
my $record = TLSProxy::Record->new(
1,
TLSProxy::Record::RT_UNKNOWN,
- $lastrec->version(),
+ @{$records}[-1]->version(),
1,
0,
1,
$i++;
splice @{$proxy->record_list}, $i, 0, $record;
+ $added_record = 1;
}
sub change_version
{
my $proxy = shift;
+ my $records = $proxy->record_list;
# We'll change a version after the initial version neg has taken place
- if ($proxy->flight != 2) {
+ if ($proxy->flight != 1) {
+ $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(0) == 70;
return;
}
- (${$proxy->record_list}[-1])->version(TLSProxy::Record::VERS_TLS_1_1);
+ if ($#{$records} > 1) {
+ # ... typically in ServerHelloDone
+ @{$records}[-1]->version(TLSProxy::Record::VERS_TLS_1_1);
+ }
}
sub change_outer_record_type
{
my $proxy = shift;
+ my $records = $proxy->record_list;
# We'll change a record after the initial version neg has taken place
if ($proxy->flight != 1) {
+ $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(0) == 10;
return;
}
- #Find ServerHello record and change record after that
- my $i;
- for ($i = 0; ${$proxy->record_list}[$i]->flight() < 1; $i++) {
- next;
+ # Find CCS record and change record after that
+ my $i = 0;
+ foreach my $record (@{$records}) {
+ last if $record->content_type == TLSProxy::Record::RT_CCS;
+ $i++;
+ }
+ if (defined(${$records}[++$i])) {
+ ${$records}[$i]->outer_content_type(TLSProxy::Record::RT_HANDSHAKE);
+ }
+}
+
+sub not_on_record_boundary
+{
+ my $proxy = shift;
+ my $records = $proxy->record_list;
+ my $data;
+
+ #Find server's first flight
+ if ($proxy->flight != 1) {
+ $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(0) == 10;
+ return;
+ }
+
+ if ($boundary_test_type == DATA_AFTER_SERVER_HELLO) {
+ #Merge the ServerHello and EncryptedExtensions records into one
+ my $i = 0;
+ foreach my $record (@{$records}) {
+ if ($record->content_type == TLSProxy::Record::RT_HANDSHAKE) {
+ $record->{sent} = 1; # pretend it's sent already
+ last;
+ }
+ $i++;
+ }
+
+ if (defined(${$records}[$i+1])) {
+ $data = ${$records}[$i]->data();
+ $data .= ${$records}[$i+1]->decrypt_data();
+ ${$records}[$i+1]->data($data);
+ ${$records}[$i+1]->len(length $data);
+
+ #Delete the old ServerHello record
+ splice @{$records}, $i, 1;
+ }
+ } elsif ($boundary_test_type == DATA_AFTER_FINISHED) {
+ return if @{$proxy->{message_list}}[-1]->{mt}
+ != TLSProxy::Message::MT_FINISHED;
+
+ my $last_record = @{$records}[-1];
+ $data = $last_record->decrypt_data;
+
+ #Add a KeyUpdate message onto the end of the Finished record
+ my $keyupdate = pack "C5",
+ 0x18, # KeyUpdate
+ 0x00, 0x00, 0x01, # Message length
+ 0x00; # Update not requested
+
+ $data .= $keyupdate;
+
+ #Add content type and tag
+ $data .= pack("C", TLSProxy::Record::RT_HANDSHAKE).("\0"x16);
+
+ #Update the record
+ $last_record->data($data);
+ $last_record->len(length $data);
+ } elsif ($boundary_test_type == DATA_AFTER_KEY_UPDATE) {
+ return if @{$proxy->{message_list}}[-1]->{mt}
+ != TLSProxy::Message::MT_FINISHED;
+
+ #KeyUpdates must end on a record boundary
+
+ my $record = TLSProxy::Record->new(
+ 1,
+ TLSProxy::Record::RT_APPLICATION_DATA,
+ TLSProxy::Record::VERS_TLS_1_2,
+ 0,
+ 0,
+ 0,
+ 0,
+ "",
+ ""
+ );
+
+ #Add two KeyUpdate messages into a single record
+ my $keyupdate = pack "C5",
+ 0x18, # KeyUpdate
+ 0x00, 0x00, 0x01, # Message length
+ 0x00; # Update not requested
+
+ $data = $keyupdate.$keyupdate;
+
+ #Add content type and tag
+ $data .= pack("C", TLSProxy::Record::RT_HANDSHAKE).("\0"x16);
+
+ $record->data($data);
+ $record->len(length $data);
+ push @{$records}, $record;
+ } else {
+ return if @{$proxy->{message_list}}[-1]->{mt}
+ != TLSProxy::Message::MT_FINISHED;
+
+ my $record = TLSProxy::Record->new(
+ 1,
+ TLSProxy::Record::RT_APPLICATION_DATA,
+ TLSProxy::Record::VERS_TLS_1_2,
+ 0,
+ 0,
+ 0,
+ 0,
+ "",
+ ""
+ );
+
+ #Add a partial KeyUpdate message into the record
+ $data = pack "C1",
+ 0x18; # KeyUpdate message type. Omit the rest of the message header
+
+ #Add content type and tag
+ $data .= pack("C", TLSProxy::Record::RT_HANDSHAKE).("\0"x16);
+
+ $record->data($data);
+ $record->len(length $data);
+ push @{$records}, $record;
+
+ if ($boundary_test_type == DATA_BETWEEN_KEY_UPDATE) {
+ #Now add an app data record
+ $record = TLSProxy::Record->new(
+ 1,
+ TLSProxy::Record::RT_APPLICATION_DATA,
+ TLSProxy::Record::VERS_TLS_1_2,
+ 0,
+ 0,
+ 0,
+ 0,
+ "",
+ ""
+ );
+
+ #Add an empty app data record (just content type and tag)
+ $data = pack("C", TLSProxy::Record::RT_APPLICATION_DATA).("\0"x16);
+
+ $record->data($data);
+ $record->len(length $data);
+ push @{$records}, $record;
+ }
+
+ #Now add the rest of the KeyUpdate message
+ $record = TLSProxy::Record->new(
+ 1,
+ TLSProxy::Record::RT_APPLICATION_DATA,
+ TLSProxy::Record::VERS_TLS_1_2,
+ 0,
+ 0,
+ 0,
+ 0,
+ "",
+ ""
+ );
+
+ #Add the last 4 bytes of the KeyUpdate record
+ $data = pack "C4",
+ 0x00, 0x00, 0x01, # Message length
+ 0x00; # Update not requested
+
+ #Add content type and tag
+ $data .= pack("C", TLSProxy::Record::RT_HANDSHAKE).("\0"x16);
+
+ $record->data($data);
+ $record->len(length $data);
+ push @{$records}, $record;
+
}
- $i++;
- ${$proxy->record_list}[$i]->outer_content_type(TLSProxy::Record::RT_HANDSHAKE);
}