123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609 |
- #! /usr/bin/env perl
- # Copyright 2016-2018 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;
- use feature 'state';
- use OpenSSL::Test qw/:DEFAULT cmdstr srctop_file bldtop_dir/;
- use OpenSSL::Test::Utils;
- use TLSProxy::Proxy;
- my $test_name = "test_sslrecords";
- setup($test_name);
- plan skip_all => "TLSProxy isn't usable on $^O"
- 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 TLSv1.2 enabled"
- if disabled("tls1_2");
- $ENV{OPENSSL_ia32cap} = '~0x200000200000000';
- my $proxy = TLSProxy::Proxy->new(
- \&add_empty_recs_filter,
- cmdstr(app(["openssl"]), display => 1),
- srctop_file("apps", "server.pem"),
- (!$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";
- plan tests => 18;
- ok($fatal_alert, "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");
- #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($fatal_alert, "Too many in context empty records test");
- #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($fatal_alert, "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
- $fatal_alert = 0;
- $proxy->clear();
- $proxy->serverflags("-tls1_2");
- $proxy->filter(\&add_unknown_record_type);
- $proxy->start();
- 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
- $fatal_alert = 0;
- $proxy->clear();
- $proxy->clientflags("-tls1_1");
- $proxy->start();
- 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($fatal_alert, "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 fail
- $proxy->clear();
- $proxy->filter(\&change_version);
- $proxy->start();
- 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($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($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
- };
- #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->filter(\¬_on_record_boundary);
- $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->filter(\¬_on_record_boundary);
- $proxy->start();
- ok($fatal_alert, "Record not on boundary in TLS1.3 (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;
- }
- for (my $i = 0; $i < $inject_recs_num; $i++) {
- my $record = TLSProxy::Record->new(
- 0,
- $content_type,
- TLSProxy::Record::VERS_TLS_1_2,
- 0,
- 0,
- 0,
- 0,
- "",
- ""
- );
- 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;
- }
- # Add a zero length fragment first
- #my $record = TLSProxy::Record->new(
- # 0,
- # TLSProxy::Record::RT_ALERT,
- # TLSProxy::Record::VERS_TLS_1_2,
- # 0,
- # 0,
- # 0,
- # "",
- # ""
- #);
- #push @{$proxy->record_list}, $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,
- $byte
- );
- push @{$records}, $record;
- # And finally the description (Unexpected message) in a third record
- $byte = pack('C', TLSProxy::Message::AL_DESC_UNEXPECTED_MESSAGE);
- $record = TLSProxy::Record->new(
- 0,
- TLSProxy::Record::RT_ALERT,
- TLSProxy::Record::VERS_TLS_1_2,
- 1,
- 0,
- 1,
- 1,
- $byte,
- $byte
- );
- push @{$records}, $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;
- my $records = $proxy->record_list;
- state $added_record;
- # We'll change a record after the initial version neg has taken place
- 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 $record = TLSProxy::Record->new(
- 1,
- TLSProxy::Record::RT_UNKNOWN,
- @{$records}[-1]->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;
- $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 != 1) {
- $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(0) == 70;
- return;
- }
- 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 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);
- } else {
- 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;
- }
- }
|