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");
plan skip_all => "$test_name needs the sock feature enabled"
if disabled("sock");
-plan skip_all => "$test_name needs TLS enabled"
- if alldisabled(available_protocols("tls"));
+plan skip_all => "$test_name needs TLSv1.2 enabled"
+ if disabled("tls1_2");
$ENV{OPENSSL_ia32cap} = '~0x200000200000000';
my $proxy = TLSProxy::Proxy->new(
(!$ENV{HARNESS_ACTIVE} || $ENV{HARNESS_VERBOSE})
);
+my $boundary_test_type;
+
#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";
-plan tests => 4;
+plan tests => 18;
ok(TLSProxy::Message->fail(), "Out of context empty records test");
#Test 2: Injecting in context empty records should succeed
$proxy->clear();
$content_type = TLSProxy::Record::RT_HANDSHAKE;
+$proxy->serverflags("-tls1_2");
$proxy->start();
ok(TLSProxy::Message->success(), "In context empty records test");
$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");
-#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
$proxy->clear();
$proxy->filter(\&add_frag_alert_filter);
+$proxy->serverflags("-tls1_2");
+$proxy->start();
+ok(TLSProxy::Message->fail(), "Fragmented alert records test");
+
+#Run some SSLv2 ClientHello tests
+
+use constant {
+ TLSV1_2_IN_SSLV2 => 0,
+ SSLV2_IN_SSLV2 => 1,
+ FRAGMENTED_IN_TLSV1_2 => 2,
+ FRAGMENTED_IN_SSLV2 => 3,
+ ALERT_BEFORE_SSLV2 => 4
+};
+#Test 5: Inject an SSLv2 style record format for a TLSv1.2 ClientHello
+my $sslv2testtype = TLSV1_2_IN_SSLV2;
+$proxy->clear();
+$proxy->filter(\&add_sslv2_filter);
+$proxy->serverflags("-tls1_2");
+$proxy->start();
+ok(TLSProxy::Message->success(), "TLSv1.2 in SSLv2 ClientHello test");
+
+#Test 6: Inject an SSLv2 style record format for an SSLv2 ClientHello. We don't
+# support this so it should fail. We actually treat it as an unknown
+# protocol so we don't even send an alert in this case.
+$sslv2testtype = SSLV2_IN_SSLV2;
+$proxy->clear();
+$proxy->serverflags("-tls1_2");
+$proxy->start();
+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
+# reasons
+$sslv2testtype = FRAGMENTED_IN_TLSV1_2;
+$proxy->clear();
+$proxy->serverflags("-tls1_2");
+$proxy->start();
+ok(TLSProxy::Message->success(), "Fragmented ClientHello in TLSv1.2 test");
+
+#Test 8: Fragment a TLSv1.2 ClientHello across a TLS1.2 record; an SSLv2
+# record; and another TLS1.2 record. This isn't allowed so should fail
+$sslv2testtype = FRAGMENTED_IN_SSLV2;
+$proxy->clear();
+$proxy->serverflags("-tls1_2");
+$proxy->start();
+ok(TLSProxy::Message->fail(), "Fragmented ClientHello in TLSv1.2/SSLv2 test");
+
+#Test 9: Send a TLS warning alert before an SSLv2 ClientHello. This should
+# fail because an SSLv2 ClientHello must be the first record.
+$sslv2testtype = ALERT_BEFORE_SSLV2;
+$proxy->clear();
+$proxy->serverflags("-tls1_2");
+$proxy->start();
+ok(TLSProxy::Message->fail(), "Alert before SSLv2 ClientHello test");
+
+#Unrecognised record type tests
+
+#Test 10: Sending an unrecognised record type in TLS1.2 should fail
+$proxy->clear();
+$proxy->serverflags("-tls1_2");
+$proxy->filter(\&add_unknown_record_type);
+$proxy->start();
+ok(TLSProxy::Message->fail(), "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
+ $proxy->clear();
+ $proxy->clientflags("-tls1_1");
+ $proxy->start();
+ ok(TLSProxy::Message->fail(), "Unrecognised record type in TLS1.1");
+}
+
+#Test 12: Sending a different record version in TLS1.2 should fail
+$proxy->clear();
+$proxy->clientflags("-tls1_2");
+$proxy->filter(\&change_version);
$proxy->start();
-ok(!TLSProxy::Message->end(), "Fragmented alert records test");
+ok(TLSProxy::Message->fail(), "Changed record version in TLS1.2");
+
+#TLS1.3 specific tests
+SKIP: {
+ skip "TLSv1.3 disabled", 6 if disabled("tls1_3");
+
+ #Test 13: Sending a different record version in TLS1.3 should succeed
+ $proxy->clear();
+ $proxy->filter(\&change_version);
+ $proxy->start();
+ ok(TLSProxy::Message->success(), "Changed record version in TLS1.3");
+
+ #Test 14: Sending an unrecognised record type in TLS1.3 should fail
+ $proxy->clear();
+ $proxy->filter(\&add_unknown_record_type);
+ $proxy->start();
+ ok(TLSProxy::Message->fail(), "Unrecognised record type in TLS1.3");
+
+ #Test 15: Sending an outer record type other than app data once encrypted
+ #should fail
+ $proxy->clear();
+ $proxy->filter(\&change_outer_record_type);
+ $proxy->start();
+ ok(TLSProxy::Message->fail(), "Wrong outer record type in TLS1.3");
+
+ use constant {
+ DATA_AFTER_SERVER_HELLO => 0,
+ DATA_AFTER_FINISHED => 1,
+ DATA_AFTER_KEY_UPDATE => 2
+ };
+
+ #Test 16: Sending a ServerHello which doesn't end on a record boundary
+ # should fail
+ $proxy->clear();
+ $boundary_test_type = DATA_AFTER_SERVER_HELLO;
+ $proxy->filter(\¬_on_record_boundary);
+ $proxy->start();
+ ok(TLSProxy::Message->fail(), "Record not on boundary in TLS1.3 (ServerHello)");
+
+ #Test 17: Sending a Finished which doesn't end on a record boundary
+ # should fail
+ $proxy->clear();
+ $boundary_test_type = DATA_AFTER_FINISHED;
+ $proxy->filter(\¬_on_record_boundary);
+ $proxy->start();
+ ok(TLSProxy::Message->fail(), "Record not on boundary in TLS1.3 (Finished)");
+
+ #Test 18: Sending a KeyUpdate which doesn't end on a record boundary
+ # should fail
+ $proxy->clear();
+ $boundary_test_type = DATA_AFTER_KEY_UPDATE;
+ $proxy->filter(\¬_on_record_boundary);
+ $proxy->start();
+ ok(TLSProxy::Message->fail(), "Record not on boundary in TLS1.3 (KeyUpdate)");
+ }
+
sub add_empty_recs_filter
{
0,
0,
0,
+ 0,
"",
""
);
#);
#push @{$proxy->record_list}, $record;
- # Now add the alert level (Fatal) as a seperate record
+ # Now add the alert level (Fatal) as a separate record
$byte = pack('C', TLSProxy::Message::AL_LEVEL_FATAL);
my $record = TLSProxy::Record->new(
0,
TLSProxy::Record::RT_ALERT,
TLSProxy::Record::VERS_TLS_1_2,
1,
+ 0,
1,
1,
$byte,
TLSProxy::Record::RT_ALERT,
TLSProxy::Record::VERS_TLS_1_2,
1,
+ 0,
1,
1,
$byte,
);
push @{$proxy->record_list}, $record;
}
+
+sub add_sslv2_filter
+{
+ my $proxy = shift;
+ my $clienthello;
+ my $record;
+
+ # We're only interested in the initial ClientHello
+ if ($proxy->flight != 0) {
+ return;
+ }
+
+ # Ditch the real ClientHello - we're going to replace it with our own
+ shift @{$proxy->record_list};
+
+ if ($sslv2testtype == ALERT_BEFORE_SSLV2) {
+ my $alert = pack('CC', TLSProxy::Message::AL_LEVEL_FATAL,
+ TLSProxy::Message::AL_DESC_NO_RENEGOTIATION);
+ my $alertlen = length $alert;
+ $record = TLSProxy::Record->new(
+ 0,
+ TLSProxy::Record::RT_ALERT,
+ TLSProxy::Record::VERS_TLS_1_2,
+ $alertlen,
+ 0,
+ $alertlen,
+ $alertlen,
+ $alert,
+ $alert
+ );
+
+ push @{$proxy->record_list}, $record;
+ }
+
+ if ($sslv2testtype == ALERT_BEFORE_SSLV2
+ || $sslv2testtype == TLSV1_2_IN_SSLV2
+ || $sslv2testtype == SSLV2_IN_SSLV2) {
+ # This is an SSLv2 format ClientHello
+ $clienthello =
+ pack "C44",
+ 0x01, # ClientHello
+ 0x03, 0x03, #TLSv1.2
+ 0x00, 0x03, # Ciphersuites len
+ 0x00, 0x00, # Session id len
+ 0x00, 0x20, # Challenge len
+ 0x00, 0x00, 0x2f, #AES128-SHA
+ 0x01, 0x18, 0x9F, 0x76, 0xEC, 0x57, 0xCE, 0xE5, 0xB3, 0xAB, 0x79, 0x90,
+ 0xAD, 0xAC, 0x6E, 0xD1, 0x58, 0x35, 0x03, 0x97, 0x16, 0x10, 0x82, 0x56,
+ 0xD8, 0x55, 0xFF, 0xE1, 0x8A, 0xA3, 0x2E, 0xF6; # Challenge
+
+ if ($sslv2testtype == SSLV2_IN_SSLV2) {
+ # Set the version to "real" SSLv2
+ vec($clienthello, 1, 8) = 0x00;
+ vec($clienthello, 2, 8) = 0x02;
+ }
+
+ my $chlen = length $clienthello;
+
+ $record = TLSProxy::Record->new(
+ 0,
+ TLSProxy::Record::RT_HANDSHAKE,
+ TLSProxy::Record::VERS_TLS_1_2,
+ $chlen,
+ 1, #SSLv2
+ $chlen,
+ $chlen,
+ $clienthello,
+ $clienthello
+ );
+
+ push @{$proxy->record_list}, $record;
+ } else {
+ # For this test we're using a real TLS ClientHello
+ $clienthello =
+ pack "C49",
+ 0x01, # ClientHello
+ 0x00, 0x00, 0x2D, # Message length
+ 0x03, 0x03, # TLSv1.2
+ 0x01, 0x18, 0x9F, 0x76, 0xEC, 0x57, 0xCE, 0xE5, 0xB3, 0xAB, 0x79, 0x90,
+ 0xAD, 0xAC, 0x6E, 0xD1, 0x58, 0x35, 0x03, 0x97, 0x16, 0x10, 0x82, 0x56,
+ 0xD8, 0x55, 0xFF, 0xE1, 0x8A, 0xA3, 0x2E, 0xF6, # Random
+ 0x00, # Session id len
+ 0x00, 0x04, # Ciphersuites len
+ 0x00, 0x2f, # AES128-SHA
+ 0x00, 0xff, # Empty reneg info SCSV
+ 0x01, # Compression methods len
+ 0x00, # Null compression
+ 0x00, 0x00; # Extensions len
+
+ # Split this into 3: A TLS record; a SSLv2 record and a TLS record.
+ # We deliberately split the second record prior to the Challenge/Random
+ # and set the first byte of the random to 1. This makes the second SSLv2
+ # record look like an SSLv2 ClientHello
+ my $frag1 = substr $clienthello, 0, 6;
+ my $frag2 = substr $clienthello, 6, 32;
+ my $frag3 = substr $clienthello, 38;
+
+ my $fraglen = length $frag1;
+ $record = TLSProxy::Record->new(
+ 0,
+ TLSProxy::Record::RT_HANDSHAKE,
+ TLSProxy::Record::VERS_TLS_1_2,
+ $fraglen,
+ 0,
+ $fraglen,
+ $fraglen,
+ $frag1,
+ $frag1
+ );
+ push @{$proxy->record_list}, $record;
+
+ $fraglen = length $frag2;
+ my $recvers;
+ if ($sslv2testtype == FRAGMENTED_IN_SSLV2) {
+ $recvers = 1;
+ } else {
+ $recvers = 0;
+ }
+ $record = TLSProxy::Record->new(
+ 0,
+ TLSProxy::Record::RT_HANDSHAKE,
+ TLSProxy::Record::VERS_TLS_1_2,
+ $fraglen,
+ $recvers,
+ $fraglen,
+ $fraglen,
+ $frag2,
+ $frag2
+ );
+ push @{$proxy->record_list}, $record;
+
+ $fraglen = length $frag3;
+ $record = TLSProxy::Record->new(
+ 0,
+ TLSProxy::Record::RT_HANDSHAKE,
+ TLSProxy::Record::VERS_TLS_1_2,
+ $fraglen,
+ 0,
+ $fraglen,
+ $fraglen,
+ $frag3,
+ $frag3
+ );
+ push @{$proxy->record_list}, $record;
+ }
+
+}
+
+sub add_unknown_record_type
+{
+ my $proxy = shift;
+
+ # We'll change a record after the initial version neg has taken place
+ if ($proxy->flight != 1) {
+ return;
+ }
+
+ my $lastrec = ${$proxy->record_list}[-1];
+ my $record = TLSProxy::Record->new(
+ 1,
+ TLSProxy::Record::RT_UNKNOWN,
+ $lastrec->version(),
+ 1,
+ 0,
+ 1,
+ 1,
+ "X",
+ "X"
+ );
+
+ #Find ServerHello record and insert after that
+ my $i;
+ for ($i = 0; ${$proxy->record_list}[$i]->flight() < 1; $i++) {
+ next;
+ }
+ $i++;
+
+ splice @{$proxy->record_list}, $i, 0, $record;
+}
+
+sub change_version
+{
+ my $proxy = shift;
+
+ # We'll change a version after the initial version neg has taken place
+ if ($proxy->flight != 2) {
+ return;
+ }
+
+ (${$proxy->record_list}[-1])->version(TLSProxy::Record::VERS_TLS_1_1);
+}
+
+sub change_outer_record_type
+{
+ my $proxy = shift;
+
+ # We'll change a record after the initial version neg has taken place
+ if ($proxy->flight != 1) {
+ return;
+ }
+
+ #Find ServerHello record and change record after that
+ my $i;
+ for ($i = 0; ${$proxy->record_list}[$i]->flight() < 1; $i++) {
+ next;
+ }
+ #Skip CCS and ServerHello
+ $i += 2;
+ ${$proxy->record_list}[$i]->outer_content_type(TLSProxy::Record::RT_HANDSHAKE);
+}
+
+sub not_on_record_boundary
+{
+ my $proxy = shift;
+ my $data;
+
+ #Find server's first flight
+ if ($proxy->flight != 1) {
+ return;
+ }
+
+ if ($boundary_test_type == DATA_AFTER_SERVER_HELLO) {
+ #Merge the ServerHello and EncryptedExtensions records into one
+ my $i;
+ for ($i = 0; ${$proxy->record_list}[$i]->flight() < 1; $i++) {
+ next;
+ }
+ $data = ${$proxy->record_list}[$i]->data();
+ $data .= ${$proxy->record_list}[$i + 1]->decrypt_data();
+ ${$proxy->record_list}[$i]->data($data);
+ ${$proxy->record_list}[$i]->len(length $data);
+
+ #Delete the old EncryptedExtensions record
+ splice @{$proxy->record_list}, $i + 1, 1;
+ } elsif ($boundary_test_type == DATA_AFTER_FINISHED) {
+ $data = ${$proxy->record_list}[-1]->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
+ ${$proxy->record_list}[-1]->data($data);
+ ${$proxy->record_list}[-1]->len(length $data);
+ } else {
+ #KeyUpdates must end on a record boundary
+
+ my $record = TLSProxy::Record->new(
+ 1,
+ TLSProxy::Record::RT_APPLICATION_DATA,
+ TLSProxy::Record::VERS_TLS_1_0,
+ 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 @{$proxy->record_list}, $record;
+ }
+}