2 # Copyright 2016-2022 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 my $proxy = TLSProxy::Proxy->new(
32 \&add_empty_recs_filter,
33 cmdstr(app(["openssl"]), display => 1),
34 srctop_file("apps", "server.pem"),
35 (!$ENV{HARNESS_ACTIVE} || $ENV{HARNESS_VERBOSE})
38 my $boundary_test_type;
39 my $fatal_alert = 0; # set by filters at expected fatal alerts
41 #Test 1: Injecting out of context empty records should fail
42 my $content_type = TLSProxy::Record::RT_APPLICATION_DATA;
43 my $inject_recs_num = 1;
44 $proxy->serverflags("-tls1_2");
45 $proxy->clientflags("-no_tls1_3");
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");
54 $proxy->clientflags("-no_tls1_3");
56 ok(TLSProxy::Message->success(), "In context empty records test");
58 #Test 3: Injecting too many in context empty records should fail
61 #We allow 32 consecutive in context empty records
62 $inject_recs_num = 33;
63 $proxy->serverflags("-tls1_2");
64 $proxy->clientflags("-no_tls1_3");
66 ok($fatal_alert, "Too many in context empty records test");
68 #Test 4: Injecting a fragmented fatal alert should fail. We expect the server to
69 # send back an alert of its own because it cannot handle fragmented
73 $proxy->filter(\&add_frag_alert_filter);
74 $proxy->serverflags("-tls1_2");
75 $proxy->clientflags("-no_tls1_3");
77 ok($fatal_alert, "Fragmented alert records test");
79 #Run some SSLv2 ClientHello tests
82 TLSV1_2_IN_SSLV2 => 0,
84 FRAGMENTED_IN_TLSV1_2 => 2,
85 FRAGMENTED_IN_SSLV2 => 3,
86 ALERT_BEFORE_SSLV2 => 4
89 # The TLSv1.2 in SSLv2 ClientHello need to run at security level 0
90 # because in a SSLv2 ClientHello we can't send extensions to indicate
91 # which signature algorithm we want to use, and the default is SHA1.
93 #Test 5: Inject an SSLv2 style record format for a TLSv1.2 ClientHello
94 my $sslv2testtype = TLSV1_2_IN_SSLV2;
96 $proxy->filter(\&add_sslv2_filter);
97 $proxy->serverflags("-tls1_2");
98 $proxy->clientflags("-no_tls1_3 -legacy_renegotiation");
99 $proxy->ciphers("AES128-SHA:\@SECLEVEL=0");
101 ok(TLSProxy::Message->success(), "TLSv1.2 in SSLv2 ClientHello test");
103 #Test 6: Inject an SSLv2 style record format for an SSLv2 ClientHello. We don't
104 # support this so it should fail. We actually treat it as an unknown
105 # protocol so we don't even send an alert in this case.
106 $sslv2testtype = SSLV2_IN_SSLV2;
108 $proxy->serverflags("-tls1_2");
109 $proxy->clientflags("-no_tls1_3");
110 $proxy->ciphers("AES128-SHA:\@SECLEVEL=0");
112 ok(TLSProxy::Message->fail(), "SSLv2 in SSLv2 ClientHello test");
114 #Test 7: Sanity check ClientHello fragmentation. This isn't really an SSLv2 test
115 # at all, but it gives us confidence that Test 8 fails for the right
117 $sslv2testtype = FRAGMENTED_IN_TLSV1_2;
119 $proxy->serverflags("-tls1_2");
120 $proxy->clientflags("-no_tls1_3");
121 $proxy->ciphers("AES128-SHA:\@SECLEVEL=0");
123 ok(TLSProxy::Message->success(), "Fragmented ClientHello in TLSv1.2 test");
125 #Test 8: Fragment a TLSv1.2 ClientHello across a TLS1.2 record; an SSLv2
126 # record; and another TLS1.2 record. This isn't allowed so should fail
127 $sslv2testtype = FRAGMENTED_IN_SSLV2;
129 $proxy->serverflags("-tls1_2");
130 $proxy->clientflags("-no_tls1_3");
131 $proxy->ciphers("AES128-SHA:\@SECLEVEL=0");
133 ok(TLSProxy::Message->fail(), "Fragmented ClientHello in TLSv1.2/SSLv2 test");
135 #Test 9: Send a TLS warning alert before an SSLv2 ClientHello. This should
136 # fail because an SSLv2 ClientHello must be the first record.
137 $sslv2testtype = ALERT_BEFORE_SSLV2;
139 $proxy->serverflags("-tls1_2");
140 $proxy->clientflags("-no_tls1_3");
141 $proxy->ciphers("AES128-SHA:\@SECLEVEL=0");
143 ok(TLSProxy::Message->fail(), "Alert before SSLv2 ClientHello test");
145 #Unrecognised record type tests
147 #Test 10: Sending an unrecognised record type in TLS1.2 should fail
150 $proxy->serverflags("-tls1_2");
151 $proxy->clientflags("-no_tls1_3");
152 $proxy->filter(\&add_unknown_record_type);
154 ok($fatal_alert, "Unrecognised record type in TLS1.2");
157 skip "TLSv1.1 disabled", 1 if disabled("tls1_1");
159 #Test 11: Sending an unrecognised record type in TLS1.1 should fail
162 $proxy->clientflags("-tls1_1 -cipher DEFAULT:\@SECLEVEL=0");
163 $proxy->ciphers("AES128-SHA:\@SECLEVEL=0");
165 ok($fatal_alert, "Unrecognised record type in TLS1.1");
168 #Test 12: Sending a different record version in TLS1.2 should fail
171 $proxy->clientflags("-tls1_2");
172 $proxy->filter(\&change_version);
174 ok($fatal_alert, "Changed record version in TLS1.2");
176 #TLS1.3 specific tests
178 skip "TLSv1.3 disabled", 9
179 if disabled("tls1_3") || (disabled("ec") && disabled("dh"));
181 #Test 13: Sending a different record version in TLS1.3 should fail
183 $proxy->filter(\&change_version);
185 ok(TLSProxy::Message->fail(), "Changed record version in TLS1.3");
187 #Test 14: Sending an unrecognised record type in TLS1.3 should fail
190 $proxy->filter(\&add_unknown_record_type);
192 ok($fatal_alert, "Unrecognised record type in TLS1.3");
194 #Test 15: Sending an outer record type other than app data once encrypted
198 $proxy->filter(\&change_outer_record_type);
200 ok($fatal_alert, "Wrong outer record type in TLS1.3");
203 DATA_AFTER_SERVER_HELLO => 0,
204 DATA_AFTER_FINISHED => 1,
205 DATA_AFTER_KEY_UPDATE => 2,
206 DATA_BETWEEN_KEY_UPDATE => 3,
207 NO_DATA_BETWEEN_KEY_UPDATE => 4,
210 #Test 16: Sending a ServerHello which doesn't end on a record boundary
214 $boundary_test_type = DATA_AFTER_SERVER_HELLO;
215 $proxy->filter(\¬_on_record_boundary);
217 ok($fatal_alert, "Record not on boundary in TLS1.3 (ServerHello)");
219 #Test 17: Sending a Finished which doesn't end on a record boundary
223 $boundary_test_type = DATA_AFTER_FINISHED;
225 ok($fatal_alert, "Record not on boundary in TLS1.3 (Finished)");
227 #Test 18: Sending a KeyUpdate which doesn't end on a record boundary
231 $boundary_test_type = DATA_AFTER_KEY_UPDATE;
233 ok($fatal_alert, "Record not on boundary in TLS1.3 (KeyUpdate)");
235 #Test 19: Sending application data in the middle of a fragmented KeyUpdate
236 # should fail. Strictly speaking this is not a record boundary test
237 # but we use the same filter.
240 $boundary_test_type = DATA_BETWEEN_KEY_UPDATE;
242 ok($fatal_alert, "Data between KeyUpdate");
244 #Test 20: Fragmented KeyUpdate. This should succeed. Strictly speaking this
245 # is not a record boundary test but we use the same filter.
247 $boundary_test_type = NO_DATA_BETWEEN_KEY_UPDATE;
249 ok(TLSProxy::Message->success(), "No data between KeyUpdate");
252 skip "EC disabled", 1 if disabled("ec");
254 #Test 21: Force an HRR and change the "real" ServerHello to have a protocol
255 # record version of 0x0301 (TLSv1.0). At this point we have already
256 # decided that we are doing TLSv1.3 but are still using plaintext
257 # records. The server should be sending a record version of 0x303
258 # (TLSv1.2), but the RFC requires us to ignore this field so we
259 # should tolerate the incorrect version.
261 $proxy->filter(\&change_server_hello_version);
262 $proxy->serverflags("-groups P-256"); # Force an HRR
264 ok(TLSProxy::Message->success(), "Bad ServerHello record version after HRR");
269 sub add_empty_recs_filter
272 my $records = $proxy->record_list;
274 # We're only interested in the initial ClientHello
275 if ($proxy->flight != 0) {
276 $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(1) == 10;
280 for (my $i = 0; $i < $inject_recs_num; $i++) {
281 my $record = TLSProxy::Record->new(
284 TLSProxy::Record::VERS_TLS_1_2,
292 push @{$records}, $record;
296 sub add_frag_alert_filter
299 my $records = $proxy->record_list;
302 # We're only interested in the initial ClientHello
303 if ($proxy->flight != 0) {
304 $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(1) == 10;
308 # Add a zero length fragment first
309 #my $record = TLSProxy::Record->new(
311 # TLSProxy::Record::RT_ALERT,
312 # TLSProxy::Record::VERS_TLS_1_2,
319 #push @{$proxy->record_list}, $record;
321 # Now add the alert level (Fatal) as a separate record
322 $byte = pack('C', TLSProxy::Message::AL_LEVEL_FATAL);
323 my $record = TLSProxy::Record->new(
325 TLSProxy::Record::RT_ALERT,
326 TLSProxy::Record::VERS_TLS_1_2,
334 push @{$records}, $record;
336 # And finally the description (Unexpected message) in a third record
337 $byte = pack('C', TLSProxy::Message::AL_DESC_UNEXPECTED_MESSAGE);
338 $record = TLSProxy::Record->new(
340 TLSProxy::Record::RT_ALERT,
341 TLSProxy::Record::VERS_TLS_1_2,
349 push @{$records}, $record;
358 # We're only interested in the initial ClientHello
359 if ($proxy->flight != 0) {
363 # Ditch the real ClientHello - we're going to replace it with our own
364 shift @{$proxy->record_list};
366 if ($sslv2testtype == ALERT_BEFORE_SSLV2) {
367 my $alert = pack('CC', TLSProxy::Message::AL_LEVEL_FATAL,
368 TLSProxy::Message::AL_DESC_NO_RENEGOTIATION);
369 my $alertlen = length $alert;
370 $record = TLSProxy::Record->new(
372 TLSProxy::Record::RT_ALERT,
373 TLSProxy::Record::VERS_TLS_1_2,
382 push @{$proxy->record_list}, $record;
385 if ($sslv2testtype == ALERT_BEFORE_SSLV2
386 || $sslv2testtype == TLSV1_2_IN_SSLV2
387 || $sslv2testtype == SSLV2_IN_SSLV2) {
388 # This is an SSLv2 format ClientHello
393 0x00, 0x03, # Ciphersuites len
394 0x00, 0x00, # Session id len
395 0x00, 0x20, # Challenge len
396 0x00, 0x00, 0x2f, #AES128-SHA
397 0x01, 0x18, 0x9F, 0x76, 0xEC, 0x57, 0xCE, 0xE5, 0xB3, 0xAB, 0x79, 0x90,
398 0xAD, 0xAC, 0x6E, 0xD1, 0x58, 0x35, 0x03, 0x97, 0x16, 0x10, 0x82, 0x56,
399 0xD8, 0x55, 0xFF, 0xE1, 0x8A, 0xA3, 0x2E, 0xF6; # Challenge
401 if ($sslv2testtype == SSLV2_IN_SSLV2) {
402 # Set the version to "real" SSLv2
403 vec($clienthello, 1, 8) = 0x00;
404 vec($clienthello, 2, 8) = 0x02;
407 my $chlen = length $clienthello;
409 $record = TLSProxy::Record->new(
411 TLSProxy::Record::RT_HANDSHAKE,
412 TLSProxy::Record::VERS_TLS_1_2,
421 push @{$proxy->record_list}, $record;
423 # For this test we're using a real TLS ClientHello
427 0x00, 0x00, 0x2D, # Message length
428 0x03, 0x03, # TLSv1.2
429 0x01, 0x18, 0x9F, 0x76, 0xEC, 0x57, 0xCE, 0xE5, 0xB3, 0xAB, 0x79, 0x90,
430 0xAD, 0xAC, 0x6E, 0xD1, 0x58, 0x35, 0x03, 0x97, 0x16, 0x10, 0x82, 0x56,
431 0xD8, 0x55, 0xFF, 0xE1, 0x8A, 0xA3, 0x2E, 0xF6, # Random
432 0x00, # Session id len
433 0x00, 0x04, # Ciphersuites len
434 0x00, 0x2f, # AES128-SHA
435 0x00, 0xff, # Empty reneg info SCSV
436 0x01, # Compression methods len
437 0x00, # Null compression
438 0x00, 0x00; # Extensions len
440 # Split this into 3: A TLS record; a SSLv2 record and a TLS record.
441 # We deliberately split the second record prior to the Challenge/Random
442 # and set the first byte of the random to 1. This makes the second SSLv2
443 # record look like an SSLv2 ClientHello
444 my $frag1 = substr $clienthello, 0, 6;
445 my $frag2 = substr $clienthello, 6, 32;
446 my $frag3 = substr $clienthello, 38;
448 my $fraglen = length $frag1;
449 $record = TLSProxy::Record->new(
451 TLSProxy::Record::RT_HANDSHAKE,
452 TLSProxy::Record::VERS_TLS_1_2,
460 push @{$proxy->record_list}, $record;
462 $fraglen = length $frag2;
464 if ($sslv2testtype == FRAGMENTED_IN_SSLV2) {
469 $record = TLSProxy::Record->new(
471 TLSProxy::Record::RT_HANDSHAKE,
472 TLSProxy::Record::VERS_TLS_1_2,
480 push @{$proxy->record_list}, $record;
482 $fraglen = length $frag3;
483 $record = TLSProxy::Record->new(
485 TLSProxy::Record::RT_HANDSHAKE,
486 TLSProxy::Record::VERS_TLS_1_2,
494 push @{$proxy->record_list}, $record;
499 sub add_unknown_record_type
502 my $records = $proxy->record_list;
505 # We'll change a record after the initial version neg has taken place
506 if ($proxy->flight == 0) {
509 } elsif ($proxy->flight != 1 || $added_record) {
510 $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(0) == 10;
514 my $record = TLSProxy::Record->new(
516 TLSProxy::Record::RT_UNKNOWN,
517 @{$records}[-1]->version(),
526 #Find ServerHello record and insert after that
528 for ($i = 0; ${$proxy->record_list}[$i]->flight() < 1; $i++) {
533 splice @{$proxy->record_list}, $i, 0, $record;
540 my $records = $proxy->record_list;
542 # We'll change a version after the initial version neg has taken place
543 if ($proxy->flight != 1) {
544 $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(0) == 70;
548 if ($#{$records} > 1) {
549 # ... typically in ServerHelloDone
550 @{$records}[-1]->version(TLSProxy::Record::VERS_TLS_1_1);
554 sub change_server_hello_version
557 my $records = $proxy->record_list;
559 # We're only interested in changing the ServerHello after an HRR
560 if ($proxy->flight != 3) {
564 # The ServerHello has index 5
571 @{$records}[5]->version(TLSProxy::Record::VERS_TLS_1_0);
574 sub change_outer_record_type
577 my $records = $proxy->record_list;
579 # We'll change a record after the initial version neg has taken place
580 if ($proxy->flight != 1) {
581 $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(0) == 10;
585 # Find CCS record and change record after that
587 foreach my $record (@{$records}) {
588 last if $record->content_type == TLSProxy::Record::RT_CCS;
591 if (defined(${$records}[++$i])) {
592 ${$records}[$i]->outer_content_type(TLSProxy::Record::RT_HANDSHAKE);
596 sub not_on_record_boundary
599 my $records = $proxy->record_list;
602 #Find server's first flight
603 if ($proxy->flight != 1) {
604 $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(0) == 10;
608 if ($boundary_test_type == DATA_AFTER_SERVER_HELLO) {
609 #Merge the ServerHello and EncryptedExtensions records into one
611 foreach my $record (@{$records}) {
612 if ($record->content_type == TLSProxy::Record::RT_HANDSHAKE) {
613 $record->{sent} = 1; # pretend it's sent already
619 if (defined(${$records}[$i+1])) {
620 $data = ${$records}[$i]->data();
621 $data .= ${$records}[$i+1]->decrypt_data();
622 ${$records}[$i+1]->data($data);
623 ${$records}[$i+1]->len(length $data);
625 #Delete the old ServerHello record
626 splice @{$records}, $i, 1;
628 } elsif ($boundary_test_type == DATA_AFTER_FINISHED) {
629 return if @{$proxy->{message_list}}[-1]->{mt}
630 != TLSProxy::Message::MT_FINISHED;
632 my $last_record = @{$records}[-1];
633 $data = $last_record->decrypt_data;
635 #Add a KeyUpdate message onto the end of the Finished record
636 my $keyupdate = pack "C5",
638 0x00, 0x00, 0x01, # Message length
639 0x00; # Update not requested
643 #Add content type and tag
644 $data .= pack("C", TLSProxy::Record::RT_HANDSHAKE).("\0"x16);
647 $last_record->data($data);
648 $last_record->len(length $data);
649 } elsif ($boundary_test_type == DATA_AFTER_KEY_UPDATE) {
650 return if @{$proxy->{message_list}}[-1]->{mt}
651 != TLSProxy::Message::MT_FINISHED;
653 #KeyUpdates must end on a record boundary
655 my $record = TLSProxy::Record->new(
657 TLSProxy::Record::RT_APPLICATION_DATA,
658 TLSProxy::Record::VERS_TLS_1_2,
667 #Add two KeyUpdate messages into a single record
668 my $keyupdate = pack "C5",
670 0x00, 0x00, 0x01, # Message length
671 0x00; # Update not requested
673 $data = $keyupdate.$keyupdate;
675 #Add content type and tag
676 $data .= pack("C", TLSProxy::Record::RT_HANDSHAKE).("\0"x16);
678 $record->data($data);
679 $record->len(length $data);
680 push @{$records}, $record;
682 return if @{$proxy->{message_list}}[-1]->{mt}
683 != TLSProxy::Message::MT_FINISHED;
685 my $record = TLSProxy::Record->new(
687 TLSProxy::Record::RT_APPLICATION_DATA,
688 TLSProxy::Record::VERS_TLS_1_2,
697 #Add a partial KeyUpdate message into the record
699 0x18; # KeyUpdate message type. Omit the rest of the message header
701 #Add content type and tag
702 $data .= pack("C", TLSProxy::Record::RT_HANDSHAKE).("\0"x16);
704 $record->data($data);
705 $record->len(length $data);
706 push @{$records}, $record;
708 if ($boundary_test_type == DATA_BETWEEN_KEY_UPDATE) {
709 #Now add an app data record
710 $record = TLSProxy::Record->new(
712 TLSProxy::Record::RT_APPLICATION_DATA,
713 TLSProxy::Record::VERS_TLS_1_2,
722 #Add an empty app data record (just content type and tag)
723 $data = pack("C", TLSProxy::Record::RT_APPLICATION_DATA).("\0"x16);
725 $record->data($data);
726 $record->len(length $data);
727 push @{$records}, $record;
730 #Now add the rest of the KeyUpdate message
731 $record = TLSProxy::Record->new(
733 TLSProxy::Record::RT_APPLICATION_DATA,
734 TLSProxy::Record::VERS_TLS_1_2,
743 #Add the last 4 bytes of the KeyUpdate record
745 0x00, 0x00, 0x01, # Message length
746 0x00; # Update not requested
748 #Add content type and tag
749 $data .= pack("C", TLSProxy::Record::RT_HANDSHAKE).("\0"x16);
751 $record->data($data);
752 $record->len(length $data);
753 push @{$records}, $record;