2 # Copyright 2016-2020 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
86 # The TLSv1.2 in SSLv2 ClientHello need to run at security level 0
87 # because in a SSLv2 ClientHello we can't send extentions to indicate
88 # which signature algorithm we want to use, and the default is SHA1.
90 #Test 5: Inject an SSLv2 style record format for a TLSv1.2 ClientHello
91 my $sslv2testtype = TLSV1_2_IN_SSLV2;
93 $proxy->filter(\&add_sslv2_filter);
94 $proxy->serverflags("-tls1_2");
95 $proxy->ciphers("AES128-SHA:\@SECLEVEL=0");
97 ok(TLSProxy::Message->success(), "TLSv1.2 in SSLv2 ClientHello test");
99 #Test 6: Inject an SSLv2 style record format for an SSLv2 ClientHello. We don't
100 # support this so it should fail. We actually treat it as an unknown
101 # protocol so we don't even send an alert in this case.
102 $sslv2testtype = SSLV2_IN_SSLV2;
104 $proxy->serverflags("-tls1_2");
105 $proxy->ciphers("AES128-SHA:\@SECLEVEL=0");
107 ok(TLSProxy::Message->fail(), "SSLv2 in SSLv2 ClientHello test");
109 #Test 7: Sanity check ClientHello fragmentation. This isn't really an SSLv2 test
110 # at all, but it gives us confidence that Test 8 fails for the right
112 $sslv2testtype = FRAGMENTED_IN_TLSV1_2;
114 $proxy->serverflags("-tls1_2");
115 $proxy->ciphers("AES128-SHA:\@SECLEVEL=0");
117 ok(TLSProxy::Message->success(), "Fragmented ClientHello in TLSv1.2 test");
119 #Test 8: Fragment a TLSv1.2 ClientHello across a TLS1.2 record; an SSLv2
120 # record; and another TLS1.2 record. This isn't allowed so should fail
121 $sslv2testtype = FRAGMENTED_IN_SSLV2;
123 $proxy->serverflags("-tls1_2");
124 $proxy->ciphers("AES128-SHA:\@SECLEVEL=0");
126 ok(TLSProxy::Message->fail(), "Fragmented ClientHello in TLSv1.2/SSLv2 test");
128 #Test 9: Send a TLS warning alert before an SSLv2 ClientHello. This should
129 # fail because an SSLv2 ClientHello must be the first record.
130 $sslv2testtype = ALERT_BEFORE_SSLV2;
132 $proxy->serverflags("-tls1_2");
133 $proxy->ciphers("AES128-SHA:\@SECLEVEL=0");
135 ok(TLSProxy::Message->fail(), "Alert before SSLv2 ClientHello test");
137 #Unrecognised record type tests
139 #Test 10: Sending an unrecognised record type in TLS1.2 should fail
142 $proxy->serverflags("-tls1_2");
143 $proxy->filter(\&add_unknown_record_type);
145 ok($fatal_alert, "Unrecognised record type in TLS1.2");
148 skip "TLSv1.1 disabled", 1 if disabled("tls1_1");
150 #Test 11: Sending an unrecognised record type in TLS1.1 should fail
153 $proxy->clientflags("-tls1_1 -cipher DEFAULT:\@SECLEVEL=0");
154 $proxy->ciphers("AES128-SHA:\@SECLEVEL=0");
156 ok($fatal_alert, "Unrecognised record type in TLS1.1");
159 #Test 12: Sending a different record version in TLS1.2 should fail
162 $proxy->clientflags("-tls1_2");
163 $proxy->filter(\&change_version);
165 ok($fatal_alert, "Changed record version in TLS1.2");
167 #TLS1.3 specific tests
169 skip "TLSv1.3 disabled", 8 if disabled("tls1_3");
171 #Test 13: Sending a different record version in TLS1.3 should fail
173 $proxy->filter(\&change_version);
175 ok(TLSProxy::Message->fail(), "Changed record version in TLS1.3");
177 #Test 14: Sending an unrecognised record type in TLS1.3 should fail
180 $proxy->filter(\&add_unknown_record_type);
182 ok($fatal_alert, "Unrecognised record type in TLS1.3");
184 #Test 15: Sending an outer record type other than app data once encrypted
188 $proxy->filter(\&change_outer_record_type);
190 ok($fatal_alert, "Wrong outer record type in TLS1.3");
193 DATA_AFTER_SERVER_HELLO => 0,
194 DATA_AFTER_FINISHED => 1,
195 DATA_AFTER_KEY_UPDATE => 2,
196 DATA_BETWEEN_KEY_UPDATE => 3,
197 NO_DATA_BETWEEN_KEY_UPDATE => 4,
200 #Test 16: Sending a ServerHello which doesn't end on a record boundary
204 $boundary_test_type = DATA_AFTER_SERVER_HELLO;
205 $proxy->filter(\¬_on_record_boundary);
207 ok($fatal_alert, "Record not on boundary in TLS1.3 (ServerHello)");
209 #Test 17: Sending a Finished which doesn't end on a record boundary
213 $boundary_test_type = DATA_AFTER_FINISHED;
215 ok($fatal_alert, "Record not on boundary in TLS1.3 (Finished)");
217 #Test 18: Sending a KeyUpdate which doesn't end on a record boundary
221 $boundary_test_type = DATA_AFTER_KEY_UPDATE;
223 ok($fatal_alert, "Record not on boundary in TLS1.3 (KeyUpdate)");
225 #Test 19: Sending application data in the middle of a fragmented KeyUpdate
226 # should fail. Strictly speaking this is not a record boundary test
227 # but we use the same filter.
230 $boundary_test_type = DATA_BETWEEN_KEY_UPDATE;
232 ok($fatal_alert, "Data between KeyUpdate");
234 #Test 20: Fragmented KeyUpdate. This should succeed. Strictly speaking this
235 # is not a record boundary test but we use the same filter.
237 $boundary_test_type = NO_DATA_BETWEEN_KEY_UPDATE;
239 ok(TLSProxy::Message->success(), "No data between KeyUpdate");
243 sub add_empty_recs_filter
246 my $records = $proxy->record_list;
248 # We're only interested in the initial ClientHello
249 if ($proxy->flight != 0) {
250 $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(1) == 10;
254 for (my $i = 0; $i < $inject_recs_num; $i++) {
255 my $record = TLSProxy::Record->new(
258 TLSProxy::Record::VERS_TLS_1_2,
266 push @{$records}, $record;
270 sub add_frag_alert_filter
273 my $records = $proxy->record_list;
276 # We're only interested in the initial ClientHello
277 if ($proxy->flight != 0) {
278 $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(1) == 10;
282 # Add a zero length fragment first
283 #my $record = TLSProxy::Record->new(
285 # TLSProxy::Record::RT_ALERT,
286 # TLSProxy::Record::VERS_TLS_1_2,
293 #push @{$proxy->record_list}, $record;
295 # Now add the alert level (Fatal) as a separate record
296 $byte = pack('C', TLSProxy::Message::AL_LEVEL_FATAL);
297 my $record = TLSProxy::Record->new(
299 TLSProxy::Record::RT_ALERT,
300 TLSProxy::Record::VERS_TLS_1_2,
308 push @{$records}, $record;
310 # And finally the description (Unexpected message) in a third record
311 $byte = pack('C', TLSProxy::Message::AL_DESC_UNEXPECTED_MESSAGE);
312 $record = TLSProxy::Record->new(
314 TLSProxy::Record::RT_ALERT,
315 TLSProxy::Record::VERS_TLS_1_2,
323 push @{$records}, $record;
332 # We're only interested in the initial ClientHello
333 if ($proxy->flight != 0) {
337 # Ditch the real ClientHello - we're going to replace it with our own
338 shift @{$proxy->record_list};
340 if ($sslv2testtype == ALERT_BEFORE_SSLV2) {
341 my $alert = pack('CC', TLSProxy::Message::AL_LEVEL_FATAL,
342 TLSProxy::Message::AL_DESC_NO_RENEGOTIATION);
343 my $alertlen = length $alert;
344 $record = TLSProxy::Record->new(
346 TLSProxy::Record::RT_ALERT,
347 TLSProxy::Record::VERS_TLS_1_2,
356 push @{$proxy->record_list}, $record;
359 if ($sslv2testtype == ALERT_BEFORE_SSLV2
360 || $sslv2testtype == TLSV1_2_IN_SSLV2
361 || $sslv2testtype == SSLV2_IN_SSLV2) {
362 # This is an SSLv2 format ClientHello
367 0x00, 0x03, # Ciphersuites len
368 0x00, 0x00, # Session id len
369 0x00, 0x20, # Challenge len
370 0x00, 0x00, 0x2f, #AES128-SHA
371 0x01, 0x18, 0x9F, 0x76, 0xEC, 0x57, 0xCE, 0xE5, 0xB3, 0xAB, 0x79, 0x90,
372 0xAD, 0xAC, 0x6E, 0xD1, 0x58, 0x35, 0x03, 0x97, 0x16, 0x10, 0x82, 0x56,
373 0xD8, 0x55, 0xFF, 0xE1, 0x8A, 0xA3, 0x2E, 0xF6; # Challenge
375 if ($sslv2testtype == SSLV2_IN_SSLV2) {
376 # Set the version to "real" SSLv2
377 vec($clienthello, 1, 8) = 0x00;
378 vec($clienthello, 2, 8) = 0x02;
381 my $chlen = length $clienthello;
383 $record = TLSProxy::Record->new(
385 TLSProxy::Record::RT_HANDSHAKE,
386 TLSProxy::Record::VERS_TLS_1_2,
395 push @{$proxy->record_list}, $record;
397 # For this test we're using a real TLS ClientHello
401 0x00, 0x00, 0x2D, # Message length
402 0x03, 0x03, # TLSv1.2
403 0x01, 0x18, 0x9F, 0x76, 0xEC, 0x57, 0xCE, 0xE5, 0xB3, 0xAB, 0x79, 0x90,
404 0xAD, 0xAC, 0x6E, 0xD1, 0x58, 0x35, 0x03, 0x97, 0x16, 0x10, 0x82, 0x56,
405 0xD8, 0x55, 0xFF, 0xE1, 0x8A, 0xA3, 0x2E, 0xF6, # Random
406 0x00, # Session id len
407 0x00, 0x04, # Ciphersuites len
408 0x00, 0x2f, # AES128-SHA
409 0x00, 0xff, # Empty reneg info SCSV
410 0x01, # Compression methods len
411 0x00, # Null compression
412 0x00, 0x00; # Extensions len
414 # Split this into 3: A TLS record; a SSLv2 record and a TLS record.
415 # We deliberately split the second record prior to the Challenge/Random
416 # and set the first byte of the random to 1. This makes the second SSLv2
417 # record look like an SSLv2 ClientHello
418 my $frag1 = substr $clienthello, 0, 6;
419 my $frag2 = substr $clienthello, 6, 32;
420 my $frag3 = substr $clienthello, 38;
422 my $fraglen = length $frag1;
423 $record = TLSProxy::Record->new(
425 TLSProxy::Record::RT_HANDSHAKE,
426 TLSProxy::Record::VERS_TLS_1_2,
434 push @{$proxy->record_list}, $record;
436 $fraglen = length $frag2;
438 if ($sslv2testtype == FRAGMENTED_IN_SSLV2) {
443 $record = TLSProxy::Record->new(
445 TLSProxy::Record::RT_HANDSHAKE,
446 TLSProxy::Record::VERS_TLS_1_2,
454 push @{$proxy->record_list}, $record;
456 $fraglen = length $frag3;
457 $record = TLSProxy::Record->new(
459 TLSProxy::Record::RT_HANDSHAKE,
460 TLSProxy::Record::VERS_TLS_1_2,
468 push @{$proxy->record_list}, $record;
473 sub add_unknown_record_type
476 my $records = $proxy->record_list;
479 # We'll change a record after the initial version neg has taken place
480 if ($proxy->flight == 0) {
483 } elsif ($proxy->flight != 1 || $added_record) {
484 $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(0) == 10;
488 my $record = TLSProxy::Record->new(
490 TLSProxy::Record::RT_UNKNOWN,
491 @{$records}[-1]->version(),
500 #Find ServerHello record and insert after that
502 for ($i = 0; ${$proxy->record_list}[$i]->flight() < 1; $i++) {
507 splice @{$proxy->record_list}, $i, 0, $record;
514 my $records = $proxy->record_list;
516 # We'll change a version after the initial version neg has taken place
517 if ($proxy->flight != 1) {
518 $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(0) == 70;
522 if ($#{$records} > 1) {
523 # ... typically in ServerHelloDone
524 @{$records}[-1]->version(TLSProxy::Record::VERS_TLS_1_1);
528 sub change_outer_record_type
531 my $records = $proxy->record_list;
533 # We'll change a record after the initial version neg has taken place
534 if ($proxy->flight != 1) {
535 $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(0) == 10;
539 # Find CCS record and change record after that
541 foreach my $record (@{$records}) {
542 last if $record->content_type == TLSProxy::Record::RT_CCS;
545 if (defined(${$records}[++$i])) {
546 ${$records}[$i]->outer_content_type(TLSProxy::Record::RT_HANDSHAKE);
550 sub not_on_record_boundary
553 my $records = $proxy->record_list;
556 #Find server's first flight
557 if ($proxy->flight != 1) {
558 $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(0) == 10;
562 if ($boundary_test_type == DATA_AFTER_SERVER_HELLO) {
563 #Merge the ServerHello and EncryptedExtensions records into one
565 foreach my $record (@{$records}) {
566 if ($record->content_type == TLSProxy::Record::RT_HANDSHAKE) {
567 $record->{sent} = 1; # pretend it's sent already
573 if (defined(${$records}[$i+1])) {
574 $data = ${$records}[$i]->data();
575 $data .= ${$records}[$i+1]->decrypt_data();
576 ${$records}[$i+1]->data($data);
577 ${$records}[$i+1]->len(length $data);
579 #Delete the old ServerHello record
580 splice @{$records}, $i, 1;
582 } elsif ($boundary_test_type == DATA_AFTER_FINISHED) {
583 return if @{$proxy->{message_list}}[-1]->{mt}
584 != TLSProxy::Message::MT_FINISHED;
586 my $last_record = @{$records}[-1];
587 $data = $last_record->decrypt_data;
589 #Add a KeyUpdate message onto the end of the Finished record
590 my $keyupdate = pack "C5",
592 0x00, 0x00, 0x01, # Message length
593 0x00; # Update not requested
597 #Add content type and tag
598 $data .= pack("C", TLSProxy::Record::RT_HANDSHAKE).("\0"x16);
601 $last_record->data($data);
602 $last_record->len(length $data);
603 } elsif ($boundary_test_type == DATA_AFTER_KEY_UPDATE) {
604 return if @{$proxy->{message_list}}[-1]->{mt}
605 != TLSProxy::Message::MT_FINISHED;
607 #KeyUpdates must end on a record boundary
609 my $record = TLSProxy::Record->new(
611 TLSProxy::Record::RT_APPLICATION_DATA,
612 TLSProxy::Record::VERS_TLS_1_2,
621 #Add two KeyUpdate messages into a single record
622 my $keyupdate = pack "C5",
624 0x00, 0x00, 0x01, # Message length
625 0x00; # Update not requested
627 $data = $keyupdate.$keyupdate;
629 #Add content type and tag
630 $data .= pack("C", TLSProxy::Record::RT_HANDSHAKE).("\0"x16);
632 $record->data($data);
633 $record->len(length $data);
634 push @{$records}, $record;
636 return if @{$proxy->{message_list}}[-1]->{mt}
637 != TLSProxy::Message::MT_FINISHED;
639 my $record = TLSProxy::Record->new(
641 TLSProxy::Record::RT_APPLICATION_DATA,
642 TLSProxy::Record::VERS_TLS_1_2,
651 #Add a partial KeyUpdate message into the record
653 0x18; # KeyUpdate message type. Omit the rest of the message header
655 #Add content type and tag
656 $data .= pack("C", TLSProxy::Record::RT_HANDSHAKE).("\0"x16);
658 $record->data($data);
659 $record->len(length $data);
660 push @{$records}, $record;
662 if ($boundary_test_type == DATA_BETWEEN_KEY_UPDATE) {
663 #Now add an app data record
664 $record = TLSProxy::Record->new(
666 TLSProxy::Record::RT_APPLICATION_DATA,
667 TLSProxy::Record::VERS_TLS_1_2,
676 #Add an empty app data record (just content type and tag)
677 $data = pack("C", TLSProxy::Record::RT_APPLICATION_DATA).("\0"x16);
679 $record->data($data);
680 $record->len(length $data);
681 push @{$records}, $record;
684 #Now add the rest of the KeyUpdate message
685 $record = TLSProxy::Record->new(
687 TLSProxy::Record::RT_APPLICATION_DATA,
688 TLSProxy::Record::VERS_TLS_1_2,
697 #Add the last 4 bytes of the KeyUpdate record
699 0x00, 0x00, 0x01, # Message length
700 0x00; # Update not requested
702 #Add content type and tag
703 $data .= pack("C", TLSProxy::Record::RT_HANDSHAKE).("\0"x16);
705 $record->data($data);
706 $record->len(length $data);
707 push @{$records}, $record;