1#! /usr/bin/env perl 2# Copyright 2016-2019 The OpenSSL Project Authors. All Rights Reserved. 3# 4# Licensed under the OpenSSL license (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 8 9use strict; 10use feature 'state'; 11 12use OpenSSL::Test qw/:DEFAULT cmdstr srctop_file bldtop_dir/; 13use OpenSSL::Test::Utils; 14use TLSProxy::Proxy; 15 16my $test_name = "test_sslrecords"; 17setup($test_name); 18 19plan skip_all => "TLSProxy isn't usable on $^O" 20 if $^O =~ /^(VMS)$/; 21 22plan skip_all => "$test_name needs the dynamic engine feature enabled" 23 if disabled("engine") || disabled("dynamic-engine"); 24 25plan skip_all => "$test_name needs the sock feature enabled" 26 if disabled("sock"); 27 28plan skip_all => "$test_name needs TLSv1.2 enabled" 29 if disabled("tls1_2"); 30 31$ENV{OPENSSL_ia32cap} = '~0x200000200000000'; 32my $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}) 37); 38 39my $boundary_test_type; 40my $fatal_alert = 0; # set by filters at expected fatal alerts 41 42#Test 1: Injecting out of context empty records should fail 43my $content_type = TLSProxy::Record::RT_APPLICATION_DATA; 44my $inject_recs_num = 1; 45$proxy->serverflags("-tls1_2"); 46$proxy->start() or plan skip_all => "Unable to start up Proxy for tests"; 47plan tests => 20; 48ok($fatal_alert, "Out of context empty records test"); 49 50#Test 2: Injecting in context empty records should succeed 51$proxy->clear(); 52$content_type = TLSProxy::Record::RT_HANDSHAKE; 53$proxy->serverflags("-tls1_2"); 54$proxy->start(); 55ok(TLSProxy::Message->success(), "In context empty records test"); 56 57#Test 3: Injecting too many in context empty records should fail 58$fatal_alert = 0; 59$proxy->clear(); 60#We allow 32 consecutive in context empty records 61$inject_recs_num = 33; 62$proxy->serverflags("-tls1_2"); 63$proxy->start(); 64ok($fatal_alert, "Too many in context empty records test"); 65 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 68# alerts 69$fatal_alert = 0; 70$proxy->clear(); 71$proxy->filter(\&add_frag_alert_filter); 72$proxy->serverflags("-tls1_2"); 73$proxy->start(); 74ok($fatal_alert, "Fragmented alert records test"); 75 76#Run some SSLv2 ClientHello tests 77 78use constant { 79 TLSV1_2_IN_SSLV2 => 0, 80 SSLV2_IN_SSLV2 => 1, 81 FRAGMENTED_IN_TLSV1_2 => 2, 82 FRAGMENTED_IN_SSLV2 => 3, 83 ALERT_BEFORE_SSLV2 => 4 84}; 85#Test 5: Inject an SSLv2 style record format for a TLSv1.2 ClientHello 86my $sslv2testtype = TLSV1_2_IN_SSLV2; 87$proxy->clear(); 88$proxy->filter(\&add_sslv2_filter); 89$proxy->serverflags("-tls1_2"); 90$proxy->start(); 91ok(TLSProxy::Message->success(), "TLSv1.2 in SSLv2 ClientHello test"); 92 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; 97$proxy->clear(); 98$proxy->serverflags("-tls1_2"); 99$proxy->start(); 100ok(TLSProxy::Message->fail(), "SSLv2 in SSLv2 ClientHello test"); 101 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 104# reasons 105$sslv2testtype = FRAGMENTED_IN_TLSV1_2; 106$proxy->clear(); 107$proxy->serverflags("-tls1_2"); 108$proxy->start(); 109ok(TLSProxy::Message->success(), "Fragmented ClientHello in TLSv1.2 test"); 110 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; 114$proxy->clear(); 115$proxy->serverflags("-tls1_2"); 116$proxy->start(); 117ok(TLSProxy::Message->fail(), "Fragmented ClientHello in TLSv1.2/SSLv2 test"); 118 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; 122$proxy->clear(); 123$proxy->serverflags("-tls1_2"); 124$proxy->start(); 125ok(TLSProxy::Message->fail(), "Alert before SSLv2 ClientHello test"); 126 127#Unrecognised record type tests 128 129#Test 10: Sending an unrecognised record type in TLS1.2 should fail 130$fatal_alert = 0; 131$proxy->clear(); 132$proxy->serverflags("-tls1_2"); 133$proxy->filter(\&add_unknown_record_type); 134$proxy->start(); 135ok($fatal_alert, "Unrecognised record type in TLS1.2"); 136 137SKIP: { 138 skip "TLSv1.1 disabled", 1 if disabled("tls1_1"); 139 140 #Test 11: Sending an unrecognised record type in TLS1.1 should fail 141 $fatal_alert = 0; 142 $proxy->clear(); 143 $proxy->clientflags("-tls1_1"); 144 $proxy->start(); 145 ok($fatal_alert, "Unrecognised record type in TLS1.1"); 146} 147 148#Test 12: Sending a different record version in TLS1.2 should fail 149$fatal_alert = 0; 150$proxy->clear(); 151$proxy->clientflags("-tls1_2"); 152$proxy->filter(\&change_version); 153$proxy->start(); 154ok($fatal_alert, "Changed record version in TLS1.2"); 155 156#TLS1.3 specific tests 157SKIP: { 158 skip "TLSv1.3 disabled", 8 if disabled("tls1_3"); 159 160 #Test 13: Sending a different record version in TLS1.3 should fail 161 $proxy->clear(); 162 $proxy->filter(\&change_version); 163 $proxy->start(); 164 ok(TLSProxy::Message->fail(), "Changed record version in TLS1.3"); 165 166 #Test 14: Sending an unrecognised record type in TLS1.3 should fail 167 $fatal_alert = 0; 168 $proxy->clear(); 169 $proxy->filter(\&add_unknown_record_type); 170 $proxy->start(); 171 ok($fatal_alert, "Unrecognised record type in TLS1.3"); 172 173 #Test 15: Sending an outer record type other than app data once encrypted 174 #should fail 175 $fatal_alert = 0; 176 $proxy->clear(); 177 $proxy->filter(\&change_outer_record_type); 178 $proxy->start(); 179 ok($fatal_alert, "Wrong outer record type in TLS1.3"); 180 181 use constant { 182 DATA_AFTER_SERVER_HELLO => 0, 183 DATA_AFTER_FINISHED => 1, 184 DATA_AFTER_KEY_UPDATE => 2, 185 DATA_BETWEEN_KEY_UPDATE => 3, 186 NO_DATA_BETWEEN_KEY_UPDATE => 4, 187 }; 188 189 #Test 16: Sending a ServerHello which doesn't end on a record boundary 190 # should fail 191 $fatal_alert = 0; 192 $proxy->clear(); 193 $boundary_test_type = DATA_AFTER_SERVER_HELLO; 194 $proxy->filter(\¬_on_record_boundary); 195 $proxy->start(); 196 ok($fatal_alert, "Record not on boundary in TLS1.3 (ServerHello)"); 197 198 #Test 17: Sending a Finished which doesn't end on a record boundary 199 # should fail 200 $fatal_alert = 0; 201 $proxy->clear(); 202 $boundary_test_type = DATA_AFTER_FINISHED; 203 $proxy->start(); 204 ok($fatal_alert, "Record not on boundary in TLS1.3 (Finished)"); 205 206 #Test 18: Sending a KeyUpdate which doesn't end on a record boundary 207 # should fail 208 $fatal_alert = 0; 209 $proxy->clear(); 210 $boundary_test_type = DATA_AFTER_KEY_UPDATE; 211 $proxy->start(); 212 ok($fatal_alert, "Record not on boundary in TLS1.3 (KeyUpdate)"); 213 214 #Test 19: Sending application data in the middle of a fragmented KeyUpdate 215 # should fail. Strictly speaking this is not a record boundary test 216 # but we use the same filter. 217 $fatal_alert = 0; 218 $proxy->clear(); 219 $boundary_test_type = DATA_BETWEEN_KEY_UPDATE; 220 $proxy->start(); 221 ok($fatal_alert, "Data between KeyUpdate"); 222 223 #Test 20: Fragmented KeyUpdate. This should succeed. Strictly speaking this 224 # is not a record boundary test but we use the same filter. 225 $proxy->clear(); 226 $boundary_test_type = NO_DATA_BETWEEN_KEY_UPDATE; 227 $proxy->start(); 228 ok(TLSProxy::Message->success(), "No data between KeyUpdate"); 229 } 230 231 232sub add_empty_recs_filter 233{ 234 my $proxy = shift; 235 my $records = $proxy->record_list; 236 237 # We're only interested in the initial ClientHello 238 if ($proxy->flight != 0) { 239 $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(1) == 10; 240 return; 241 } 242 243 for (my $i = 0; $i < $inject_recs_num; $i++) { 244 my $record = TLSProxy::Record->new( 245 0, 246 $content_type, 247 TLSProxy::Record::VERS_TLS_1_2, 248 0, 249 0, 250 0, 251 0, 252 "", 253 "" 254 ); 255 push @{$records}, $record; 256 } 257} 258 259sub add_frag_alert_filter 260{ 261 my $proxy = shift; 262 my $records = $proxy->record_list; 263 my $byte; 264 265 # We're only interested in the initial ClientHello 266 if ($proxy->flight != 0) { 267 $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(1) == 10; 268 return; 269 } 270 271 # Add a zero length fragment first 272 #my $record = TLSProxy::Record->new( 273 # 0, 274 # TLSProxy::Record::RT_ALERT, 275 # TLSProxy::Record::VERS_TLS_1_2, 276 # 0, 277 # 0, 278 # 0, 279 # "", 280 # "" 281 #); 282 #push @{$proxy->record_list}, $record; 283 284 # Now add the alert level (Fatal) as a separate record 285 $byte = pack('C', TLSProxy::Message::AL_LEVEL_FATAL); 286 my $record = TLSProxy::Record->new( 287 0, 288 TLSProxy::Record::RT_ALERT, 289 TLSProxy::Record::VERS_TLS_1_2, 290 1, 291 0, 292 1, 293 1, 294 $byte, 295 $byte 296 ); 297 push @{$records}, $record; 298 299 # And finally the description (Unexpected message) in a third record 300 $byte = pack('C', TLSProxy::Message::AL_DESC_UNEXPECTED_MESSAGE); 301 $record = TLSProxy::Record->new( 302 0, 303 TLSProxy::Record::RT_ALERT, 304 TLSProxy::Record::VERS_TLS_1_2, 305 1, 306 0, 307 1, 308 1, 309 $byte, 310 $byte 311 ); 312 push @{$records}, $record; 313} 314 315sub add_sslv2_filter 316{ 317 my $proxy = shift; 318 my $clienthello; 319 my $record; 320 321 # We're only interested in the initial ClientHello 322 if ($proxy->flight != 0) { 323 return; 324 } 325 326 # Ditch the real ClientHello - we're going to replace it with our own 327 shift @{$proxy->record_list}; 328 329 if ($sslv2testtype == ALERT_BEFORE_SSLV2) { 330 my $alert = pack('CC', TLSProxy::Message::AL_LEVEL_FATAL, 331 TLSProxy::Message::AL_DESC_NO_RENEGOTIATION); 332 my $alertlen = length $alert; 333 $record = TLSProxy::Record->new( 334 0, 335 TLSProxy::Record::RT_ALERT, 336 TLSProxy::Record::VERS_TLS_1_2, 337 $alertlen, 338 0, 339 $alertlen, 340 $alertlen, 341 $alert, 342 $alert 343 ); 344 345 push @{$proxy->record_list}, $record; 346 } 347 348 if ($sslv2testtype == ALERT_BEFORE_SSLV2 349 || $sslv2testtype == TLSV1_2_IN_SSLV2 350 || $sslv2testtype == SSLV2_IN_SSLV2) { 351 # This is an SSLv2 format ClientHello 352 $clienthello = 353 pack "C44", 354 0x01, # ClientHello 355 0x03, 0x03, #TLSv1.2 356 0x00, 0x03, # Ciphersuites len 357 0x00, 0x00, # Session id len 358 0x00, 0x20, # Challenge len 359 0x00, 0x00, 0x2f, #AES128-SHA 360 0x01, 0x18, 0x9F, 0x76, 0xEC, 0x57, 0xCE, 0xE5, 0xB3, 0xAB, 0x79, 0x90, 361 0xAD, 0xAC, 0x6E, 0xD1, 0x58, 0x35, 0x03, 0x97, 0x16, 0x10, 0x82, 0x56, 362 0xD8, 0x55, 0xFF, 0xE1, 0x8A, 0xA3, 0x2E, 0xF6; # Challenge 363 364 if ($sslv2testtype == SSLV2_IN_SSLV2) { 365 # Set the version to "real" SSLv2 366 vec($clienthello, 1, 8) = 0x00; 367 vec($clienthello, 2, 8) = 0x02; 368 } 369 370 my $chlen = length $clienthello; 371 372 $record = TLSProxy::Record->new( 373 0, 374 TLSProxy::Record::RT_HANDSHAKE, 375 TLSProxy::Record::VERS_TLS_1_2, 376 $chlen, 377 1, #SSLv2 378 $chlen, 379 $chlen, 380 $clienthello, 381 $clienthello 382 ); 383 384 push @{$proxy->record_list}, $record; 385 } else { 386 # For this test we're using a real TLS ClientHello 387 $clienthello = 388 pack "C49", 389 0x01, # ClientHello 390 0x00, 0x00, 0x2D, # Message length 391 0x03, 0x03, # TLSv1.2 392 0x01, 0x18, 0x9F, 0x76, 0xEC, 0x57, 0xCE, 0xE5, 0xB3, 0xAB, 0x79, 0x90, 393 0xAD, 0xAC, 0x6E, 0xD1, 0x58, 0x35, 0x03, 0x97, 0x16, 0x10, 0x82, 0x56, 394 0xD8, 0x55, 0xFF, 0xE1, 0x8A, 0xA3, 0x2E, 0xF6, # Random 395 0x00, # Session id len 396 0x00, 0x04, # Ciphersuites len 397 0x00, 0x2f, # AES128-SHA 398 0x00, 0xff, # Empty reneg info SCSV 399 0x01, # Compression methods len 400 0x00, # Null compression 401 0x00, 0x00; # Extensions len 402 403 # Split this into 3: A TLS record; a SSLv2 record and a TLS record. 404 # We deliberately split the second record prior to the Challenge/Random 405 # and set the first byte of the random to 1. This makes the second SSLv2 406 # record look like an SSLv2 ClientHello 407 my $frag1 = substr $clienthello, 0, 6; 408 my $frag2 = substr $clienthello, 6, 32; 409 my $frag3 = substr $clienthello, 38; 410 411 my $fraglen = length $frag1; 412 $record = TLSProxy::Record->new( 413 0, 414 TLSProxy::Record::RT_HANDSHAKE, 415 TLSProxy::Record::VERS_TLS_1_2, 416 $fraglen, 417 0, 418 $fraglen, 419 $fraglen, 420 $frag1, 421 $frag1 422 ); 423 push @{$proxy->record_list}, $record; 424 425 $fraglen = length $frag2; 426 my $recvers; 427 if ($sslv2testtype == FRAGMENTED_IN_SSLV2) { 428 $recvers = 1; 429 } else { 430 $recvers = 0; 431 } 432 $record = TLSProxy::Record->new( 433 0, 434 TLSProxy::Record::RT_HANDSHAKE, 435 TLSProxy::Record::VERS_TLS_1_2, 436 $fraglen, 437 $recvers, 438 $fraglen, 439 $fraglen, 440 $frag2, 441 $frag2 442 ); 443 push @{$proxy->record_list}, $record; 444 445 $fraglen = length $frag3; 446 $record = TLSProxy::Record->new( 447 0, 448 TLSProxy::Record::RT_HANDSHAKE, 449 TLSProxy::Record::VERS_TLS_1_2, 450 $fraglen, 451 0, 452 $fraglen, 453 $fraglen, 454 $frag3, 455 $frag3 456 ); 457 push @{$proxy->record_list}, $record; 458 } 459 460} 461 462sub add_unknown_record_type 463{ 464 my $proxy = shift; 465 my $records = $proxy->record_list; 466 state $added_record; 467 468 # We'll change a record after the initial version neg has taken place 469 if ($proxy->flight == 0) { 470 $added_record = 0; 471 return; 472 } elsif ($proxy->flight != 1 || $added_record) { 473 $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(0) == 10; 474 return; 475 } 476 477 my $record = TLSProxy::Record->new( 478 1, 479 TLSProxy::Record::RT_UNKNOWN, 480 @{$records}[-1]->version(), 481 1, 482 0, 483 1, 484 1, 485 "X", 486 "X" 487 ); 488 489 #Find ServerHello record and insert after that 490 my $i; 491 for ($i = 0; ${$proxy->record_list}[$i]->flight() < 1; $i++) { 492 next; 493 } 494 $i++; 495 496 splice @{$proxy->record_list}, $i, 0, $record; 497 $added_record = 1; 498} 499 500sub change_version 501{ 502 my $proxy = shift; 503 my $records = $proxy->record_list; 504 505 # We'll change a version after the initial version neg has taken place 506 if ($proxy->flight != 1) { 507 $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(0) == 70; 508 return; 509 } 510 511 if ($#{$records} > 1) { 512 # ... typically in ServerHelloDone 513 @{$records}[-1]->version(TLSProxy::Record::VERS_TLS_1_1); 514 } 515} 516 517sub change_outer_record_type 518{ 519 my $proxy = shift; 520 my $records = $proxy->record_list; 521 522 # We'll change a record after the initial version neg has taken place 523 if ($proxy->flight != 1) { 524 $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(0) == 10; 525 return; 526 } 527 528 # Find CCS record and change record after that 529 my $i = 0; 530 foreach my $record (@{$records}) { 531 last if $record->content_type == TLSProxy::Record::RT_CCS; 532 $i++; 533 } 534 if (defined(${$records}[++$i])) { 535 ${$records}[$i]->outer_content_type(TLSProxy::Record::RT_HANDSHAKE); 536 } 537} 538 539sub not_on_record_boundary 540{ 541 my $proxy = shift; 542 my $records = $proxy->record_list; 543 my $data; 544 545 #Find server's first flight 546 if ($proxy->flight != 1) { 547 $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(0) == 10; 548 return; 549 } 550 551 if ($boundary_test_type == DATA_AFTER_SERVER_HELLO) { 552 #Merge the ServerHello and EncryptedExtensions records into one 553 my $i = 0; 554 foreach my $record (@{$records}) { 555 if ($record->content_type == TLSProxy::Record::RT_HANDSHAKE) { 556 $record->{sent} = 1; # pretend it's sent already 557 last; 558 } 559 $i++; 560 } 561 562 if (defined(${$records}[$i+1])) { 563 $data = ${$records}[$i]->data(); 564 $data .= ${$records}[$i+1]->decrypt_data(); 565 ${$records}[$i+1]->data($data); 566 ${$records}[$i+1]->len(length $data); 567 568 #Delete the old ServerHello record 569 splice @{$records}, $i, 1; 570 } 571 } elsif ($boundary_test_type == DATA_AFTER_FINISHED) { 572 return if @{$proxy->{message_list}}[-1]->{mt} 573 != TLSProxy::Message::MT_FINISHED; 574 575 my $last_record = @{$records}[-1]; 576 $data = $last_record->decrypt_data; 577 578 #Add a KeyUpdate message onto the end of the Finished record 579 my $keyupdate = pack "C5", 580 0x18, # KeyUpdate 581 0x00, 0x00, 0x01, # Message length 582 0x00; # Update not requested 583 584 $data .= $keyupdate; 585 586 #Add content type and tag 587 $data .= pack("C", TLSProxy::Record::RT_HANDSHAKE).("\0"x16); 588 589 #Update the record 590 $last_record->data($data); 591 $last_record->len(length $data); 592 } elsif ($boundary_test_type == DATA_AFTER_KEY_UPDATE) { 593 return if @{$proxy->{message_list}}[-1]->{mt} 594 != TLSProxy::Message::MT_FINISHED; 595 596 #KeyUpdates must end on a record boundary 597 598 my $record = TLSProxy::Record->new( 599 1, 600 TLSProxy::Record::RT_APPLICATION_DATA, 601 TLSProxy::Record::VERS_TLS_1_2, 602 0, 603 0, 604 0, 605 0, 606 "", 607 "" 608 ); 609 610 #Add two KeyUpdate messages into a single record 611 my $keyupdate = pack "C5", 612 0x18, # KeyUpdate 613 0x00, 0x00, 0x01, # Message length 614 0x00; # Update not requested 615 616 $data = $keyupdate.$keyupdate; 617 618 #Add content type and tag 619 $data .= pack("C", TLSProxy::Record::RT_HANDSHAKE).("\0"x16); 620 621 $record->data($data); 622 $record->len(length $data); 623 push @{$records}, $record; 624 } else { 625 return if @{$proxy->{message_list}}[-1]->{mt} 626 != TLSProxy::Message::MT_FINISHED; 627 628 my $record = TLSProxy::Record->new( 629 1, 630 TLSProxy::Record::RT_APPLICATION_DATA, 631 TLSProxy::Record::VERS_TLS_1_2, 632 0, 633 0, 634 0, 635 0, 636 "", 637 "" 638 ); 639 640 #Add a partial KeyUpdate message into the record 641 $data = pack "C1", 642 0x18; # KeyUpdate message type. Omit the rest of the message header 643 644 #Add content type and tag 645 $data .= pack("C", TLSProxy::Record::RT_HANDSHAKE).("\0"x16); 646 647 $record->data($data); 648 $record->len(length $data); 649 push @{$records}, $record; 650 651 if ($boundary_test_type == DATA_BETWEEN_KEY_UPDATE) { 652 #Now add an app data record 653 $record = TLSProxy::Record->new( 654 1, 655 TLSProxy::Record::RT_APPLICATION_DATA, 656 TLSProxy::Record::VERS_TLS_1_2, 657 0, 658 0, 659 0, 660 0, 661 "", 662 "" 663 ); 664 665 #Add an empty app data record (just content type and tag) 666 $data = pack("C", TLSProxy::Record::RT_APPLICATION_DATA).("\0"x16); 667 668 $record->data($data); 669 $record->len(length $data); 670 push @{$records}, $record; 671 } 672 673 #Now add the rest of the KeyUpdate message 674 $record = TLSProxy::Record->new( 675 1, 676 TLSProxy::Record::RT_APPLICATION_DATA, 677 TLSProxy::Record::VERS_TLS_1_2, 678 0, 679 0, 680 0, 681 0, 682 "", 683 "" 684 ); 685 686 #Add the last 4 bytes of the KeyUpdate record 687 $data = pack "C4", 688 0x00, 0x00, 0x01, # Message length 689 0x00; # Update not requested 690 691 #Add content type and tag 692 $data .= pack("C", TLSProxy::Record::RT_HANDSHAKE).("\0"x16); 693 694 $record->data($data); 695 $record->len(length $data); 696 push @{$records}, $record; 697 698 } 699} 700