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->clientflags("-no_tls1_3");
47 $proxy->start() or plan skip_all
=> "Unable to start up Proxy for tests";
49 ok
($fatal_alert, "Out of context empty records test");
51 #Test 2: Injecting in context empty records should succeed
53 $content_type = TLSProxy
::Record
::RT_HANDSHAKE
;
54 $proxy->serverflags("-tls1_2");
55 $proxy->clientflags("-no_tls1_3");
57 ok
(TLSProxy
::Message
->success(), "In context empty records test");
59 #Test 3: Injecting too many in context empty records should fail
62 #We allow 32 consecutive in context empty records
63 $inject_recs_num = 33;
64 $proxy->serverflags("-tls1_2");
65 $proxy->clientflags("-no_tls1_3");
67 ok
($fatal_alert, "Too many in context empty records test");
69 #Test 4: Injecting a fragmented fatal alert should fail. We expect the server to
70 # send back an alert of its own because it cannot handle fragmented
74 $proxy->filter(\
&add_frag_alert_filter
);
75 $proxy->serverflags("-tls1_2");
76 $proxy->clientflags("-no_tls1_3");
78 ok
($fatal_alert, "Fragmented alert records test");
80 #Run some SSLv2 ClientHello tests
83 TLSV1_2_IN_SSLV2
=> 0,
85 FRAGMENTED_IN_TLSV1_2
=> 2,
86 FRAGMENTED_IN_SSLV2
=> 3,
87 ALERT_BEFORE_SSLV2
=> 4
90 # The TLSv1.2 in SSLv2 ClientHello need to run at security level 0
91 # because in a SSLv2 ClientHello we can't send extentions to indicate
92 # which signature algorithm we want to use, and the default is SHA1.
94 #Test 5: Inject an SSLv2 style record format for a TLSv1.2 ClientHello
95 my $sslv2testtype = TLSV1_2_IN_SSLV2
;
97 $proxy->filter(\
&add_sslv2_filter
);
98 $proxy->serverflags("-tls1_2");
99 $proxy->clientflags("-no_tls1_3");
100 $proxy->ciphers("AES128-SHA:\@SECLEVEL=0");
102 ok
(TLSProxy
::Message
->success(), "TLSv1.2 in SSLv2 ClientHello test");
104 #Test 6: Inject an SSLv2 style record format for an SSLv2 ClientHello. We don't
105 # support this so it should fail. We actually treat it as an unknown
106 # protocol so we don't even send an alert in this case.
107 $sslv2testtype = SSLV2_IN_SSLV2
;
109 $proxy->serverflags("-tls1_2");
110 $proxy->clientflags("-no_tls1_3");
111 $proxy->ciphers("AES128-SHA:\@SECLEVEL=0");
113 ok
(TLSProxy
::Message
->fail(), "SSLv2 in SSLv2 ClientHello test");
115 #Test 7: Sanity check ClientHello fragmentation. This isn't really an SSLv2 test
116 # at all, but it gives us confidence that Test 8 fails for the right
118 $sslv2testtype = FRAGMENTED_IN_TLSV1_2
;
120 $proxy->serverflags("-tls1_2");
121 $proxy->clientflags("-no_tls1_3");
122 $proxy->ciphers("AES128-SHA:\@SECLEVEL=0");
124 ok
(TLSProxy
::Message
->success(), "Fragmented ClientHello in TLSv1.2 test");
126 #Test 8: Fragment a TLSv1.2 ClientHello across a TLS1.2 record; an SSLv2
127 # record; and another TLS1.2 record. This isn't allowed so should fail
128 $sslv2testtype = FRAGMENTED_IN_SSLV2
;
130 $proxy->serverflags("-tls1_2");
131 $proxy->clientflags("-no_tls1_3");
132 $proxy->ciphers("AES128-SHA:\@SECLEVEL=0");
134 ok
(TLSProxy
::Message
->fail(), "Fragmented ClientHello in TLSv1.2/SSLv2 test");
136 #Test 9: Send a TLS warning alert before an SSLv2 ClientHello. This should
137 # fail because an SSLv2 ClientHello must be the first record.
138 $sslv2testtype = ALERT_BEFORE_SSLV2
;
140 $proxy->serverflags("-tls1_2");
141 $proxy->clientflags("-no_tls1_3");
142 $proxy->ciphers("AES128-SHA:\@SECLEVEL=0");
144 ok
(TLSProxy
::Message
->fail(), "Alert before SSLv2 ClientHello test");
146 #Unrecognised record type tests
148 #Test 10: Sending an unrecognised record type in TLS1.2 should fail
151 $proxy->serverflags("-tls1_2");
152 $proxy->clientflags("-no_tls1_3");
153 $proxy->filter(\
&add_unknown_record_type
);
155 ok
($fatal_alert, "Unrecognised record type in TLS1.2");
158 skip
"TLSv1.1 disabled", 1 if disabled
("tls1_1");
160 #Test 11: Sending an unrecognised record type in TLS1.1 should fail
163 $proxy->clientflags("-tls1_1 -cipher DEFAULT:\@SECLEVEL=0");
164 $proxy->ciphers("AES128-SHA:\@SECLEVEL=0");
166 ok
($fatal_alert, "Unrecognised record type in TLS1.1");
169 #Test 12: Sending a different record version in TLS1.2 should fail
172 $proxy->clientflags("-tls1_2");
173 $proxy->filter(\
&change_version
);
175 ok
($fatal_alert, "Changed record version in TLS1.2");
177 #TLS1.3 specific tests
179 skip
"TLSv1.3 disabled", 8
180 if disabled
("tls1_3") || (disabled
("ec") && disabled
("dh"));
182 #Test 13: Sending a different record version in TLS1.3 should fail
184 $proxy->filter(\
&change_version
);
186 ok
(TLSProxy
::Message
->fail(), "Changed record version in TLS1.3");
188 #Test 14: Sending an unrecognised record type in TLS1.3 should fail
191 $proxy->filter(\
&add_unknown_record_type
);
193 ok
($fatal_alert, "Unrecognised record type in TLS1.3");
195 #Test 15: Sending an outer record type other than app data once encrypted
199 $proxy->filter(\
&change_outer_record_type
);
201 ok
($fatal_alert, "Wrong outer record type in TLS1.3");
204 DATA_AFTER_SERVER_HELLO
=> 0,
205 DATA_AFTER_FINISHED
=> 1,
206 DATA_AFTER_KEY_UPDATE
=> 2,
207 DATA_BETWEEN_KEY_UPDATE
=> 3,
208 NO_DATA_BETWEEN_KEY_UPDATE
=> 4,
211 #Test 16: Sending a ServerHello which doesn't end on a record boundary
215 $boundary_test_type = DATA_AFTER_SERVER_HELLO
;
216 $proxy->filter(\
¬_on_record_boundary
);
218 ok
($fatal_alert, "Record not on boundary in TLS1.3 (ServerHello)");
220 #Test 17: Sending a Finished which doesn't end on a record boundary
224 $boundary_test_type = DATA_AFTER_FINISHED
;
226 ok
($fatal_alert, "Record not on boundary in TLS1.3 (Finished)");
228 #Test 18: Sending a KeyUpdate which doesn't end on a record boundary
232 $boundary_test_type = DATA_AFTER_KEY_UPDATE
;
234 ok
($fatal_alert, "Record not on boundary in TLS1.3 (KeyUpdate)");
236 #Test 19: Sending application data in the middle of a fragmented KeyUpdate
237 # should fail. Strictly speaking this is not a record boundary test
238 # but we use the same filter.
241 $boundary_test_type = DATA_BETWEEN_KEY_UPDATE
;
243 ok
($fatal_alert, "Data between KeyUpdate");
245 #Test 20: Fragmented KeyUpdate. This should succeed. Strictly speaking this
246 # is not a record boundary test but we use the same filter.
248 $boundary_test_type = NO_DATA_BETWEEN_KEY_UPDATE
;
250 ok
(TLSProxy
::Message
->success(), "No data between KeyUpdate");
254 sub add_empty_recs_filter
257 my $records = $proxy->record_list;
259 # We're only interested in the initial ClientHello
260 if ($proxy->flight != 0) {
261 $fatal_alert = 1 if @
{$records}[-1]->is_fatal_alert(1) == 10;
265 for (my $i = 0; $i < $inject_recs_num; $i++) {
266 my $record = TLSProxy
::Record
->new(
269 TLSProxy
::Record
::VERS_TLS_1_2
,
277 push @
{$records}, $record;
281 sub add_frag_alert_filter
284 my $records = $proxy->record_list;
287 # We're only interested in the initial ClientHello
288 if ($proxy->flight != 0) {
289 $fatal_alert = 1 if @
{$records}[-1]->is_fatal_alert(1) == 10;
293 # Add a zero length fragment first
294 #my $record = TLSProxy::Record->new(
296 # TLSProxy::Record::RT_ALERT,
297 # TLSProxy::Record::VERS_TLS_1_2,
304 #push @{$proxy->record_list}, $record;
306 # Now add the alert level (Fatal) as a separate record
307 $byte = pack('C', TLSProxy
::Message
::AL_LEVEL_FATAL
);
308 my $record = TLSProxy
::Record
->new(
310 TLSProxy
::Record
::RT_ALERT
,
311 TLSProxy
::Record
::VERS_TLS_1_2
,
319 push @
{$records}, $record;
321 # And finally the description (Unexpected message) in a third record
322 $byte = pack('C', TLSProxy
::Message
::AL_DESC_UNEXPECTED_MESSAGE
);
323 $record = TLSProxy
::Record
->new(
325 TLSProxy
::Record
::RT_ALERT
,
326 TLSProxy
::Record
::VERS_TLS_1_2
,
334 push @
{$records}, $record;
343 # We're only interested in the initial ClientHello
344 if ($proxy->flight != 0) {
348 # Ditch the real ClientHello - we're going to replace it with our own
349 shift @
{$proxy->record_list};
351 if ($sslv2testtype == ALERT_BEFORE_SSLV2
) {
352 my $alert = pack('CC', TLSProxy
::Message
::AL_LEVEL_FATAL
,
353 TLSProxy
::Message
::AL_DESC_NO_RENEGOTIATION
);
354 my $alertlen = length $alert;
355 $record = TLSProxy
::Record
->new(
357 TLSProxy
::Record
::RT_ALERT
,
358 TLSProxy
::Record
::VERS_TLS_1_2
,
367 push @
{$proxy->record_list}, $record;
370 if ($sslv2testtype == ALERT_BEFORE_SSLV2
371 || $sslv2testtype == TLSV1_2_IN_SSLV2
372 || $sslv2testtype == SSLV2_IN_SSLV2
) {
373 # This is an SSLv2 format ClientHello
378 0x00, 0x03, # Ciphersuites len
379 0x00, 0x00, # Session id len
380 0x00, 0x20, # Challenge len
381 0x00, 0x00, 0x2f, #AES128-SHA
382 0x01, 0x18, 0x9F, 0x76, 0xEC, 0x57, 0xCE, 0xE5, 0xB3, 0xAB, 0x79, 0x90,
383 0xAD, 0xAC, 0x6E, 0xD1, 0x58, 0x35, 0x03, 0x97, 0x16, 0x10, 0x82, 0x56,
384 0xD8, 0x55, 0xFF, 0xE1, 0x8A, 0xA3, 0x2E, 0xF6; # Challenge
386 if ($sslv2testtype == SSLV2_IN_SSLV2
) {
387 # Set the version to "real" SSLv2
388 vec($clienthello, 1, 8) = 0x00;
389 vec($clienthello, 2, 8) = 0x02;
392 my $chlen = length $clienthello;
394 $record = TLSProxy
::Record
->new(
396 TLSProxy
::Record
::RT_HANDSHAKE
,
397 TLSProxy
::Record
::VERS_TLS_1_2
,
406 push @
{$proxy->record_list}, $record;
408 # For this test we're using a real TLS ClientHello
412 0x00, 0x00, 0x2D, # Message length
413 0x03, 0x03, # TLSv1.2
414 0x01, 0x18, 0x9F, 0x76, 0xEC, 0x57, 0xCE, 0xE5, 0xB3, 0xAB, 0x79, 0x90,
415 0xAD, 0xAC, 0x6E, 0xD1, 0x58, 0x35, 0x03, 0x97, 0x16, 0x10, 0x82, 0x56,
416 0xD8, 0x55, 0xFF, 0xE1, 0x8A, 0xA3, 0x2E, 0xF6, # Random
417 0x00, # Session id len
418 0x00, 0x04, # Ciphersuites len
419 0x00, 0x2f, # AES128-SHA
420 0x00, 0xff, # Empty reneg info SCSV
421 0x01, # Compression methods len
422 0x00, # Null compression
423 0x00, 0x00; # Extensions len
425 # Split this into 3: A TLS record; a SSLv2 record and a TLS record.
426 # We deliberately split the second record prior to the Challenge/Random
427 # and set the first byte of the random to 1. This makes the second SSLv2
428 # record look like an SSLv2 ClientHello
429 my $frag1 = substr $clienthello, 0, 6;
430 my $frag2 = substr $clienthello, 6, 32;
431 my $frag3 = substr $clienthello, 38;
433 my $fraglen = length $frag1;
434 $record = TLSProxy
::Record
->new(
436 TLSProxy
::Record
::RT_HANDSHAKE
,
437 TLSProxy
::Record
::VERS_TLS_1_2
,
445 push @
{$proxy->record_list}, $record;
447 $fraglen = length $frag2;
449 if ($sslv2testtype == FRAGMENTED_IN_SSLV2
) {
454 $record = TLSProxy
::Record
->new(
456 TLSProxy
::Record
::RT_HANDSHAKE
,
457 TLSProxy
::Record
::VERS_TLS_1_2
,
465 push @
{$proxy->record_list}, $record;
467 $fraglen = length $frag3;
468 $record = TLSProxy
::Record
->new(
470 TLSProxy
::Record
::RT_HANDSHAKE
,
471 TLSProxy
::Record
::VERS_TLS_1_2
,
479 push @
{$proxy->record_list}, $record;
484 sub add_unknown_record_type
487 my $records = $proxy->record_list;
490 # We'll change a record after the initial version neg has taken place
491 if ($proxy->flight == 0) {
494 } elsif ($proxy->flight != 1 || $added_record) {
495 $fatal_alert = 1 if @
{$records}[-1]->is_fatal_alert(0) == 10;
499 my $record = TLSProxy
::Record
->new(
501 TLSProxy
::Record
::RT_UNKNOWN
,
502 @
{$records}[-1]->version(),
511 #Find ServerHello record and insert after that
513 for ($i = 0; ${$proxy->record_list}[$i]->flight() < 1; $i++) {
518 splice @
{$proxy->record_list}, $i, 0, $record;
525 my $records = $proxy->record_list;
527 # We'll change a version after the initial version neg has taken place
528 if ($proxy->flight != 1) {
529 $fatal_alert = 1 if @
{$records}[-1]->is_fatal_alert(0) == 70;
533 if ($#{$records} > 1) {
534 # ... typically in ServerHelloDone
535 @
{$records}[-1]->version(TLSProxy
::Record
::VERS_TLS_1_1
);
539 sub change_outer_record_type
542 my $records = $proxy->record_list;
544 # We'll change a record after the initial version neg has taken place
545 if ($proxy->flight != 1) {
546 $fatal_alert = 1 if @
{$records}[-1]->is_fatal_alert(0) == 10;
550 # Find CCS record and change record after that
552 foreach my $record (@
{$records}) {
553 last if $record->content_type == TLSProxy
::Record
::RT_CCS
;
556 if (defined(${$records}[++$i])) {
557 ${$records}[$i]->outer_content_type(TLSProxy
::Record
::RT_HANDSHAKE
);
561 sub not_on_record_boundary
564 my $records = $proxy->record_list;
567 #Find server's first flight
568 if ($proxy->flight != 1) {
569 $fatal_alert = 1 if @
{$records}[-1]->is_fatal_alert(0) == 10;
573 if ($boundary_test_type == DATA_AFTER_SERVER_HELLO
) {
574 #Merge the ServerHello and EncryptedExtensions records into one
576 foreach my $record (@
{$records}) {
577 if ($record->content_type == TLSProxy
::Record
::RT_HANDSHAKE
) {
578 $record->{sent
} = 1; # pretend it's sent already
584 if (defined(${$records}[$i+1])) {
585 $data = ${$records}[$i]->data();
586 $data .= ${$records}[$i+1]->decrypt_data();
587 ${$records}[$i+1]->data($data);
588 ${$records}[$i+1]->len(length $data);
590 #Delete the old ServerHello record
591 splice @
{$records}, $i, 1;
593 } elsif ($boundary_test_type == DATA_AFTER_FINISHED
) {
594 return if @
{$proxy->{message_list
}}[-1]->{mt
}
595 != TLSProxy
::Message
::MT_FINISHED
;
597 my $last_record = @
{$records}[-1];
598 $data = $last_record->decrypt_data;
600 #Add a KeyUpdate message onto the end of the Finished record
601 my $keyupdate = pack "C5",
603 0x00, 0x00, 0x01, # Message length
604 0x00; # Update not requested
608 #Add content type and tag
609 $data .= pack("C", TLSProxy
::Record
::RT_HANDSHAKE
).("\0"x16
);
612 $last_record->data($data);
613 $last_record->len(length $data);
614 } elsif ($boundary_test_type == DATA_AFTER_KEY_UPDATE
) {
615 return if @
{$proxy->{message_list
}}[-1]->{mt
}
616 != TLSProxy
::Message
::MT_FINISHED
;
618 #KeyUpdates must end on a record boundary
620 my $record = TLSProxy
::Record
->new(
622 TLSProxy
::Record
::RT_APPLICATION_DATA
,
623 TLSProxy
::Record
::VERS_TLS_1_2
,
632 #Add two KeyUpdate messages into a single record
633 my $keyupdate = pack "C5",
635 0x00, 0x00, 0x01, # Message length
636 0x00; # Update not requested
638 $data = $keyupdate.$keyupdate;
640 #Add content type and tag
641 $data .= pack("C", TLSProxy
::Record
::RT_HANDSHAKE
).("\0"x16
);
643 $record->data($data);
644 $record->len(length $data);
645 push @
{$records}, $record;
647 return if @
{$proxy->{message_list
}}[-1]->{mt
}
648 != TLSProxy
::Message
::MT_FINISHED
;
650 my $record = TLSProxy
::Record
->new(
652 TLSProxy
::Record
::RT_APPLICATION_DATA
,
653 TLSProxy
::Record
::VERS_TLS_1_2
,
662 #Add a partial KeyUpdate message into the record
664 0x18; # KeyUpdate message type. Omit the rest of the message header
666 #Add content type and tag
667 $data .= pack("C", TLSProxy
::Record
::RT_HANDSHAKE
).("\0"x16
);
669 $record->data($data);
670 $record->len(length $data);
671 push @
{$records}, $record;
673 if ($boundary_test_type == DATA_BETWEEN_KEY_UPDATE
) {
674 #Now add an app data record
675 $record = TLSProxy
::Record
->new(
677 TLSProxy
::Record
::RT_APPLICATION_DATA
,
678 TLSProxy
::Record
::VERS_TLS_1_2
,
687 #Add an empty app data record (just content type and tag)
688 $data = pack("C", TLSProxy
::Record
::RT_APPLICATION_DATA
).("\0"x16
);
690 $record->data($data);
691 $record->len(length $data);
692 push @
{$records}, $record;
695 #Now add the rest of the KeyUpdate message
696 $record = TLSProxy
::Record
->new(
698 TLSProxy
::Record
::RT_APPLICATION_DATA
,
699 TLSProxy
::Record
::VERS_TLS_1_2
,
708 #Add the last 4 bytes of the KeyUpdate record
710 0x00, 0x00, 0x01, # Message length
711 0x00; # Update not requested
713 #Add content type and tag
714 $data .= pack("C", TLSProxy
::Record
::RT_HANDSHAKE
).("\0"x16
);
716 $record->data($data);
717 $record->len(length $data);
718 push @
{$records}, $record;