From a2a0c86bb0d602253d02ded2a848ed69e8cc425a Mon Sep 17 00:00:00 2001 From: Matt Caswell Date: Tue, 2 Aug 2016 17:24:54 +0100 Subject: [PATCH] Add some SSLv2 ClientHello tests Test that we handle a TLS ClientHello in an SSLv2 record correctly. Reviewed-by: Tim Hudson --- test/recipes/70-test_sslrecords.t | 198 +++++++++++++++++++++++++++++- util/TLSProxy/Message.pm | 3 +- util/TLSProxy/Record.pm | 14 ++- util/TLSProxy/ServerHello.pm | 20 ++- 4 files changed, 228 insertions(+), 7 deletions(-) diff --git a/test/recipes/70-test_sslrecords.t b/test/recipes/70-test_sslrecords.t index 0ae018a07d..8af236fa32 100644 --- a/test/recipes/70-test_sslrecords.t +++ b/test/recipes/70-test_sslrecords.t @@ -38,7 +38,7 @@ my $proxy = TLSProxy::Proxy->new( my $content_type = TLSProxy::Record::RT_APPLICATION_DATA; my $inject_recs_num = 1; $proxy->start() or plan skip_all => "Unable to start up Proxy for tests"; -plan tests => 4; +plan tests => 9; ok(TLSProxy::Message->fail(), "Out of context empty records test"); #Test 2: Injecting in context empty records should succeed @@ -62,6 +62,52 @@ $proxy->filter(\&add_frag_alert_filter); $proxy->start(); ok(!TLSProxy::Message->end(), "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; +my $sslv2testtype = TLSV1_2_IN_SSLV2; +$proxy->clear(); +$proxy->filter(\&add_sslv2_filter); +$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->start(); +ok(!TLSProxy::Message->end(), "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->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->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->start(); +ok(TLSProxy::Message->fail(), "Alert before SSLv2 ClientHello test"); sub add_empty_recs_filter { my $proxy = shift; @@ -79,6 +125,7 @@ sub add_empty_recs_filter 0, 0, 0, + 0, "", "" ); @@ -117,6 +164,7 @@ sub add_frag_alert_filter TLSProxy::Record::RT_ALERT, TLSProxy::Record::VERS_TLS_1_2, 1, + 0, 1, 1, $byte, @@ -131,6 +179,7 @@ sub add_frag_alert_filter TLSProxy::Record::RT_ALERT, TLSProxy::Record::VERS_TLS_1_2, 1, + 0, 1, 1, $byte, @@ -138,3 +187,150 @@ sub add_frag_alert_filter ); 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; + } + +} diff --git a/util/TLSProxy/Message.pm b/util/TLSProxy/Message.pm index b8db22fb85..321e080ea3 100644 --- a/util/TLSProxy/Message.pm +++ b/util/TLSProxy/Message.pm @@ -37,7 +37,8 @@ use constant { #Alert descriptions use constant { AL_DESC_CLOSE_NOTIFY => 0, - AL_DESC_UNEXPECTED_MESSAGE => 10 + AL_DESC_UNEXPECTED_MESSAGE => 10, + AL_DESC_NO_RENEGOTIATION => 100 }; my %message_type = ( diff --git a/util/TLSProxy/Record.pm b/util/TLSProxy/Record.pm index 2a605e33a7..423bad3bf1 100644 --- a/util/TLSProxy/Record.pm +++ b/util/TLSProxy/Record.pm @@ -98,6 +98,7 @@ sub get_records $content_type, $version, $len, + 0, $len_real, $decrypt_len, substr($packet, TLS_RECORD_HEADER_LENGTH, $len_real), @@ -167,6 +168,7 @@ sub new $content_type, $version, $len, + $sslv2, $len_real, $decrypt_len, $data, @@ -177,6 +179,7 @@ sub new content_type => $content_type, version => $version, len => $len, + sslv2 => $sslv2, len_real => $len_real, decrypt_len => $decrypt_len, data => $data, @@ -247,7 +250,11 @@ sub reconstruct_record my $self = shift; my $data; - $data = pack('Cnn', $self->content_type, $self->version, $self->len); + if ($self->sslv2) { + $data = pack('n', $self->len | 0x8000); + } else { + $data = pack('Cnn', $self->content_type, $self->version, $self->len); + } $data .= $self->data; return $data; @@ -269,6 +276,11 @@ sub version my $self = shift; return $self->{version}; } +sub sslv2 +{ + my $self = shift; + return $self->{sslv2}; +} sub len_real { my $self = shift; diff --git a/util/TLSProxy/ServerHello.pm b/util/TLSProxy/ServerHello.pm index ee2fd727d2..79a8be9a89 100644 --- a/util/TLSProxy/ServerHello.pm +++ b/util/TLSProxy/ServerHello.pm @@ -56,13 +56,25 @@ sub parse my $comp_meth = unpack('C', substr($self->data, $ptr)); $ptr++; my $extensions_len = unpack('n', substr($self->data, $ptr)); - $ptr += 2; + if (!defined $extensions_len) { + $extensions_len = 0; + } else { + $ptr += 2; + } #For now we just deal with this as a block of data. In the future we will #want to parse this - my $extension_data = substr($self->data, $ptr); + my $extension_data; + if ($extensions_len != 0) { + $extension_data = substr($self->data, $ptr); - if (length($extension_data) != $extensions_len) { - die "Invalid extension length\n"; + if (length($extension_data) != $extensions_len) { + die "Invalid extension length\n"; + } + } else { + if (length($self->data) != $ptr) { + die "Invalid extension length\n"; + } + $extension_data = ""; } my %extensions = (); while (length($extension_data) >= 4) { -- 2.25.1