2 # Copyright 2016-2018 The OpenSSL Project Authors. All Rights Reserved.
4 # Licensed under the Apache License 2.0 (the "License"). You may not use
5 # this file except in compliance with the License. You can obtain a copy
6 # in the file LICENSE in the source distribution or at
7 # https://www.openssl.org/source/license.html
12 use OpenSSL::Test qw/:DEFAULT cmdstr srctop_file bldtop_dir/;
13 use OpenSSL::Test::Utils;
16 my $test_name = "test_sslrecords";
19 plan skip_all => "TLSProxy isn't usable on $^O"
22 plan skip_all => "$test_name needs the dynamic engine feature enabled"
23 if disabled("engine") || disabled("dynamic-engine");
25 plan skip_all => "$test_name needs the sock feature enabled"
28 plan skip_all => "$test_name needs TLSv1.2 enabled"
29 if disabled("tls1_2");
31 $ENV{OPENSSL_ia32cap} = '~0x200000200000000';
32 my $proxy = TLSProxy::Proxy->new(
33 \&add_empty_recs_filter,
34 cmdstr(app(["openssl"]), display => 1),
35 srctop_file("apps", "server.pem"),
36 (!$ENV{HARNESS_ACTIVE} || $ENV{HARNESS_VERBOSE})
39 my $boundary_test_type;
40 my $fatal_alert = 0; # set by filters at expected fatal alerts
42 #Test 1: Injecting out of context empty records should fail
43 my $content_type = TLSProxy::Record::RT_APPLICATION_DATA;
44 my $inject_recs_num = 1;
45 $proxy->serverflags("-tls1_2");
46 $proxy->start() or plan skip_all => "Unable to start up Proxy for tests";
48 ok($fatal_alert, "Out of context empty records test");
50 #Test 2: Injecting in context empty records should succeed
52 $content_type = TLSProxy::Record::RT_HANDSHAKE;
53 $proxy->serverflags("-tls1_2");
55 ok(TLSProxy::Message->success(), "In context empty records test");
57 #Test 3: Injecting too many in context empty records should fail
60 #We allow 32 consecutive in context empty records
61 $inject_recs_num = 33;
62 $proxy->serverflags("-tls1_2");
64 ok($fatal_alert, "Too many in context empty records test");
66 #Test 4: Injecting a fragmented fatal alert should fail. We expect the server to
67 # send back an alert of its own because it cannot handle fragmented
71 $proxy->filter(\&add_frag_alert_filter);
72 $proxy->serverflags("-tls1_2");
74 ok($fatal_alert, "Fragmented alert records test");
76 #Run some SSLv2 ClientHello tests
79 TLSV1_2_IN_SSLV2 => 0,
81 FRAGMENTED_IN_TLSV1_2 => 2,
82 FRAGMENTED_IN_SSLV2 => 3,
83 ALERT_BEFORE_SSLV2 => 4
85 #Test 5: Inject an SSLv2 style record format for a TLSv1.2 ClientHello
86 my $sslv2testtype = TLSV1_2_IN_SSLV2;
88 $proxy->filter(\&add_sslv2_filter);
89 $proxy->serverflags("-tls1_2");
91 ok(TLSProxy::Message->success(), "TLSv1.2 in SSLv2 ClientHello test");
93 #Test 6: Inject an SSLv2 style record format for an SSLv2 ClientHello. We don't
94 # support this so it should fail. We actually treat it as an unknown
95 # protocol so we don't even send an alert in this case.
96 $sslv2testtype = SSLV2_IN_SSLV2;
98 $proxy->serverflags("-tls1_2");
100 ok(TLSProxy::Message->fail(), "SSLv2 in SSLv2 ClientHello test");
102 #Test 7: Sanity check ClientHello fragmentation. This isn't really an SSLv2 test
103 # at all, but it gives us confidence that Test 8 fails for the right
105 $sslv2testtype = FRAGMENTED_IN_TLSV1_2;
107 $proxy->serverflags("-tls1_2");
109 ok(TLSProxy::Message->success(), "Fragmented ClientHello in TLSv1.2 test");
111 #Test 8: Fragment a TLSv1.2 ClientHello across a TLS1.2 record; an SSLv2
112 # record; and another TLS1.2 record. This isn't allowed so should fail
113 $sslv2testtype = FRAGMENTED_IN_SSLV2;
115 $proxy->serverflags("-tls1_2");
117 ok(TLSProxy::Message->fail(), "Fragmented ClientHello in TLSv1.2/SSLv2 test");
119 #Test 9: Send a TLS warning alert before an SSLv2 ClientHello. This should
120 # fail because an SSLv2 ClientHello must be the first record.
121 $sslv2testtype = ALERT_BEFORE_SSLV2;
123 $proxy->serverflags("-tls1_2");
125 ok(TLSProxy::Message->fail(), "Alert before SSLv2 ClientHello test");
127 #Unrecognised record type tests
129 #Test 10: Sending an unrecognised record type in TLS1.2 should fail
132 $proxy->serverflags("-tls1_2");
133 $proxy->filter(\&add_unknown_record_type);
135 ok($fatal_alert, "Unrecognised record type in TLS1.2");
138 skip "TLSv1.1 disabled", 1 if disabled("tls1_1");
140 #Test 11: Sending an unrecognised record type in TLS1.1 should fail
143 $proxy->clientflags("-tls1_1");
145 ok($fatal_alert, "Unrecognised record type in TLS1.1");
148 #Test 12: Sending a different record version in TLS1.2 should fail
151 $proxy->clientflags("-tls1_2");
152 $proxy->filter(\&change_version);
154 ok($fatal_alert, "Changed record version in TLS1.2");
156 #TLS1.3 specific tests
158 skip "TLSv1.3 disabled", 6 if disabled("tls1_3");
160 #Test 13: Sending a different record version in TLS1.3 should fail
162 $proxy->filter(\&change_version);
164 ok(TLSProxy::Message->fail(), "Changed record version in TLS1.3");
166 #Test 14: Sending an unrecognised record type in TLS1.3 should fail
169 $proxy->filter(\&add_unknown_record_type);
171 ok($fatal_alert, "Unrecognised record type in TLS1.3");
173 #Test 15: Sending an outer record type other than app data once encrypted
177 $proxy->filter(\&change_outer_record_type);
179 ok($fatal_alert, "Wrong outer record type in TLS1.3");
182 DATA_AFTER_SERVER_HELLO => 0,
183 DATA_AFTER_FINISHED => 1,
184 DATA_AFTER_KEY_UPDATE => 2
187 #Test 16: Sending a ServerHello which doesn't end on a record boundary
191 $boundary_test_type = DATA_AFTER_SERVER_HELLO;
192 $proxy->filter(\¬_on_record_boundary);
194 ok($fatal_alert, "Record not on boundary in TLS1.3 (ServerHello)");
196 #Test 17: Sending a Finished which doesn't end on a record boundary
200 $boundary_test_type = DATA_AFTER_FINISHED;
201 $proxy->filter(\¬_on_record_boundary);
203 ok($fatal_alert, "Record not on boundary in TLS1.3 (Finished)");
205 #Test 18: Sending a KeyUpdate which doesn't end on a record boundary
209 $boundary_test_type = DATA_AFTER_KEY_UPDATE;
210 $proxy->filter(\¬_on_record_boundary);
212 ok($fatal_alert, "Record not on boundary in TLS1.3 (KeyUpdate)");
216 sub add_empty_recs_filter
219 my $records = $proxy->record_list;
221 # We're only interested in the initial ClientHello
222 if ($proxy->flight != 0) {
223 $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(1) == 10;
227 for (my $i = 0; $i < $inject_recs_num; $i++) {
228 my $record = TLSProxy::Record->new(
231 TLSProxy::Record::VERS_TLS_1_2,
239 push @{$records}, $record;
243 sub add_frag_alert_filter
246 my $records = $proxy->record_list;
249 # We're only interested in the initial ClientHello
250 if ($proxy->flight != 0) {
251 $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(1) == 10;
255 # Add a zero length fragment first
256 #my $record = TLSProxy::Record->new(
258 # TLSProxy::Record::RT_ALERT,
259 # TLSProxy::Record::VERS_TLS_1_2,
266 #push @{$proxy->record_list}, $record;
268 # Now add the alert level (Fatal) as a separate record
269 $byte = pack('C', TLSProxy::Message::AL_LEVEL_FATAL);
270 my $record = TLSProxy::Record->new(
272 TLSProxy::Record::RT_ALERT,
273 TLSProxy::Record::VERS_TLS_1_2,
281 push @{$records}, $record;
283 # And finally the description (Unexpected message) in a third record
284 $byte = pack('C', TLSProxy::Message::AL_DESC_UNEXPECTED_MESSAGE);
285 $record = TLSProxy::Record->new(
287 TLSProxy::Record::RT_ALERT,
288 TLSProxy::Record::VERS_TLS_1_2,
296 push @{$records}, $record;
305 # We're only interested in the initial ClientHello
306 if ($proxy->flight != 0) {
310 # Ditch the real ClientHello - we're going to replace it with our own
311 shift @{$proxy->record_list};
313 if ($sslv2testtype == ALERT_BEFORE_SSLV2) {
314 my $alert = pack('CC', TLSProxy::Message::AL_LEVEL_FATAL,
315 TLSProxy::Message::AL_DESC_NO_RENEGOTIATION);
316 my $alertlen = length $alert;
317 $record = TLSProxy::Record->new(
319 TLSProxy::Record::RT_ALERT,
320 TLSProxy::Record::VERS_TLS_1_2,
329 push @{$proxy->record_list}, $record;
332 if ($sslv2testtype == ALERT_BEFORE_SSLV2
333 || $sslv2testtype == TLSV1_2_IN_SSLV2
334 || $sslv2testtype == SSLV2_IN_SSLV2) {
335 # This is an SSLv2 format ClientHello
340 0x00, 0x03, # Ciphersuites len
341 0x00, 0x00, # Session id len
342 0x00, 0x20, # Challenge len
343 0x00, 0x00, 0x2f, #AES128-SHA
344 0x01, 0x18, 0x9F, 0x76, 0xEC, 0x57, 0xCE, 0xE5, 0xB3, 0xAB, 0x79, 0x90,
345 0xAD, 0xAC, 0x6E, 0xD1, 0x58, 0x35, 0x03, 0x97, 0x16, 0x10, 0x82, 0x56,
346 0xD8, 0x55, 0xFF, 0xE1, 0x8A, 0xA3, 0x2E, 0xF6; # Challenge
348 if ($sslv2testtype == SSLV2_IN_SSLV2) {
349 # Set the version to "real" SSLv2
350 vec($clienthello, 1, 8) = 0x00;
351 vec($clienthello, 2, 8) = 0x02;
354 my $chlen = length $clienthello;
356 $record = TLSProxy::Record->new(
358 TLSProxy::Record::RT_HANDSHAKE,
359 TLSProxy::Record::VERS_TLS_1_2,
368 push @{$proxy->record_list}, $record;
370 # For this test we're using a real TLS ClientHello
374 0x00, 0x00, 0x2D, # Message length
375 0x03, 0x03, # TLSv1.2
376 0x01, 0x18, 0x9F, 0x76, 0xEC, 0x57, 0xCE, 0xE5, 0xB3, 0xAB, 0x79, 0x90,
377 0xAD, 0xAC, 0x6E, 0xD1, 0x58, 0x35, 0x03, 0x97, 0x16, 0x10, 0x82, 0x56,
378 0xD8, 0x55, 0xFF, 0xE1, 0x8A, 0xA3, 0x2E, 0xF6, # Random
379 0x00, # Session id len
380 0x00, 0x04, # Ciphersuites len
381 0x00, 0x2f, # AES128-SHA
382 0x00, 0xff, # Empty reneg info SCSV
383 0x01, # Compression methods len
384 0x00, # Null compression
385 0x00, 0x00; # Extensions len
387 # Split this into 3: A TLS record; a SSLv2 record and a TLS record.
388 # We deliberately split the second record prior to the Challenge/Random
389 # and set the first byte of the random to 1. This makes the second SSLv2
390 # record look like an SSLv2 ClientHello
391 my $frag1 = substr $clienthello, 0, 6;
392 my $frag2 = substr $clienthello, 6, 32;
393 my $frag3 = substr $clienthello, 38;
395 my $fraglen = length $frag1;
396 $record = TLSProxy::Record->new(
398 TLSProxy::Record::RT_HANDSHAKE,
399 TLSProxy::Record::VERS_TLS_1_2,
407 push @{$proxy->record_list}, $record;
409 $fraglen = length $frag2;
411 if ($sslv2testtype == FRAGMENTED_IN_SSLV2) {
416 $record = TLSProxy::Record->new(
418 TLSProxy::Record::RT_HANDSHAKE,
419 TLSProxy::Record::VERS_TLS_1_2,
427 push @{$proxy->record_list}, $record;
429 $fraglen = length $frag3;
430 $record = TLSProxy::Record->new(
432 TLSProxy::Record::RT_HANDSHAKE,
433 TLSProxy::Record::VERS_TLS_1_2,
441 push @{$proxy->record_list}, $record;
446 sub add_unknown_record_type
449 my $records = $proxy->record_list;
452 # We'll change a record after the initial version neg has taken place
453 if ($proxy->flight == 0) {
456 } elsif ($proxy->flight != 1 || $added_record) {
457 $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(0) == 10;
461 my $record = TLSProxy::Record->new(
463 TLSProxy::Record::RT_UNKNOWN,
464 @{$records}[-1]->version(),
473 #Find ServerHello record and insert after that
475 for ($i = 0; ${$proxy->record_list}[$i]->flight() < 1; $i++) {
480 splice @{$proxy->record_list}, $i, 0, $record;
487 my $records = $proxy->record_list;
489 # We'll change a version after the initial version neg has taken place
490 if ($proxy->flight != 1) {
491 $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(0) == 70;
495 if ($#{$records} > 1) {
496 # ... typically in ServerHelloDone
497 @{$records}[-1]->version(TLSProxy::Record::VERS_TLS_1_1);
501 sub change_outer_record_type
504 my $records = $proxy->record_list;
506 # We'll change a record after the initial version neg has taken place
507 if ($proxy->flight != 1) {
508 $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(0) == 10;
512 # Find CCS record and change record after that
514 foreach my $record (@{$records}) {
515 last if $record->content_type == TLSProxy::Record::RT_CCS;
518 if (defined(${$records}[++$i])) {
519 ${$records}[$i]->outer_content_type(TLSProxy::Record::RT_HANDSHAKE);
523 sub not_on_record_boundary
526 my $records = $proxy->record_list;
529 #Find server's first flight
530 if ($proxy->flight != 1) {
531 $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(0) == 10;
535 if ($boundary_test_type == DATA_AFTER_SERVER_HELLO) {
536 #Merge the ServerHello and EncryptedExtensions records into one
538 foreach my $record (@{$records}) {
539 if ($record->content_type == TLSProxy::Record::RT_HANDSHAKE) {
540 $record->{sent} = 1; # pretend it's sent already
546 if (defined(${$records}[$i+1])) {
547 $data = ${$records}[$i]->data();
548 $data .= ${$records}[$i+1]->decrypt_data();
549 ${$records}[$i+1]->data($data);
550 ${$records}[$i+1]->len(length $data);
552 #Delete the old ServerHello record
553 splice @{$records}, $i, 1;
555 } elsif ($boundary_test_type == DATA_AFTER_FINISHED) {
556 return if @{$proxy->{message_list}}[-1]->{mt}
557 != TLSProxy::Message::MT_FINISHED;
559 my $last_record = @{$records}[-1];
560 $data = $last_record->decrypt_data;
562 #Add a KeyUpdate message onto the end of the Finished record
563 my $keyupdate = pack "C5",
565 0x00, 0x00, 0x01, # Message length
566 0x00; # Update not requested
570 #Add content type and tag
571 $data .= pack("C", TLSProxy::Record::RT_HANDSHAKE).("\0"x16);
574 $last_record->data($data);
575 $last_record->len(length $data);
577 return if @{$proxy->{message_list}}[-1]->{mt}
578 != TLSProxy::Message::MT_FINISHED;
580 #KeyUpdates must end on a record boundary
582 my $record = TLSProxy::Record->new(
584 TLSProxy::Record::RT_APPLICATION_DATA,
585 TLSProxy::Record::VERS_TLS_1_2,
594 #Add two KeyUpdate messages into a single record
595 my $keyupdate = pack "C5",
597 0x00, 0x00, 0x01, # Message length
598 0x00; # Update not requested
600 $data = $keyupdate.$keyupdate;
602 #Add content type and tag
603 $data .= pack("C", TLSProxy::Record::RT_HANDSHAKE).("\0"x16);
605 $record->data($data);
606 $record->len(length $data);
607 push @{$records}, $record;