1#! /usr/bin/env perl 2# Copyright 2016-2024 The OpenSSL Project Authors. All Rights Reserved. 3# 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 8 9use strict; 10use feature 'state'; 11 12use OpenSSL::Test qw/:DEFAULT cmdstr srctop_file bldtop_dir/; 13use OpenSSL::Test::Utils; 14use TLSProxy::Proxy; 15use TLSProxy::Message; 16 17my $test_name = "test_sslrecords"; 18setup($test_name); 19 20plan skip_all => "TLSProxy isn't usable on $^O" 21 if $^O =~ /^(VMS)$/; 22 23plan skip_all => "$test_name needs the dynamic engine feature enabled" 24 if disabled("engine") || disabled("dynamic-engine"); 25 26plan skip_all => "$test_name needs the sock feature enabled" 27 if disabled("sock"); 28 29my $inject_recs_num = undef; 30my $content_type = undef; 31my $boundary_test_type = undef; 32my $fatal_alert = undef; # set by filters at expected fatal alerts 33my $sslv2testtype = undef; 34my $proxy_start_success = 0; 35 36plan tests => 42; 37 38SKIP: { 39 skip "TLS 1.2 is disabled", 21 if disabled("tls1_2"); 40 # Run tests with TLS 41 run_tests(0); 42} 43 44SKIP: { 45 skip "DTLS 1.2 is disabled", 21 if disabled("dtls1_2"); 46 skip "DTLSProxy does not work on Windows", 21 if $^O =~ /^(MSWin32)$/; 47 run_tests(1); 48} 49 50sub run_tests 51{ 52 my $run_test_as_dtls = shift; 53 54 my $proxy; 55 if ($run_test_as_dtls == 1) { 56 $proxy = TLSProxy::Proxy->new_dtls( 57 \&add_empty_recs_filter, 58 cmdstr(app([ "openssl" ]), display => 1), 59 srctop_file("apps", "server.pem"), 60 (!$ENV{HARNESS_ACTIVE} || $ENV{HARNESS_VERBOSE}) 61 ); 62 } else { 63 $proxy = TLSProxy::Proxy->new( 64 \&add_empty_recs_filter, 65 cmdstr(app([ "openssl" ]), display => 1), 66 srctop_file("apps", "server.pem"), 67 (!$ENV{HARNESS_ACTIVE} || $ENV{HARNESS_VERBOSE}) 68 ); 69 } 70 71 $fatal_alert = 0; # set by filters at expected fatal alerts 72 SKIP: { 73 skip "Record tests not intended for dtls", 1 if $run_test_as_dtls == 1; 74 #Test 1: Injecting out of context empty records should fail 75 $proxy->clear(); 76 $content_type = TLSProxy::Record::RT_APPLICATION_DATA; 77 $inject_recs_num = 1; 78 $fatal_alert = 0; 79 $proxy->serverflags("-tls1_2"); 80 $proxy->clientflags("-no_tls1_3"); 81 $proxy_start_success = $proxy->start(); 82 ok($fatal_alert, "Out of context empty records test"); 83 } 84 85 skip "TLSProxy did not start correctly", 21 if $proxy_start_success == 0 86 && $run_test_as_dtls == 0; 87 88 #Test 2: Injecting in context empty records should succeed 89 $proxy->clear(); 90 $content_type = TLSProxy::Record::RT_HANDSHAKE; 91 if ($run_test_as_dtls == 1) { 92 $proxy->serverflags("-min_protocol DTLSv1.2 -max_protocol DTLSv1.2"); 93 $proxy->clientflags("-max_protocol DTLSv1.2"); 94 } else { 95 $proxy->serverflags("-tls1_2"); 96 $proxy->clientflags("-no_tls1_3"); 97 } 98 $proxy_start_success = $proxy->start(); 99 100 skip "TLSProxy did not start correctly", 20 if $proxy_start_success == 0 101 && $run_test_as_dtls == 1; 102 103 ok($proxy_start_success && TLSProxy::Message->success(), 104 "In context empty records test".($run_test_as_dtls == 1) ? " for DTLS" : " for TLS"); 105 106 SKIP: { 107 skip "Record tests not intended for dtls", 7 if $run_test_as_dtls == 1; 108 #Test 3: Injecting too many in context empty records should fail 109 $fatal_alert = 0; 110 $proxy->clear(); 111 #We allow 32 consecutive in context empty records 112 $inject_recs_num = 33; 113 $proxy->serverflags("-tls1_2"); 114 $proxy->clientflags("-no_tls1_3"); 115 $proxy->start(); 116 ok($fatal_alert, "Too many in context empty records test"); 117 118 #Test 4: Injecting a fragmented fatal alert should fail. We expect the server to 119 # send back an alert of its own because it cannot handle fragmented 120 # alerts 121 $fatal_alert = 0; 122 $proxy->clear(); 123 $proxy->filter(\&add_frag_alert_filter); 124 $proxy->serverflags("-tls1_2"); 125 $proxy->clientflags("-no_tls1_3"); 126 $proxy->start(); 127 ok($fatal_alert, "Fragmented alert records test"); 128 129 #Run some SSLv2 ClientHello tests 130 131 use constant { 132 TLSV1_2_IN_SSLV2 => 0, 133 SSLV2_IN_SSLV2 => 1, 134 FRAGMENTED_IN_TLSV1_2 => 2, 135 FRAGMENTED_IN_SSLV2 => 3, 136 ALERT_BEFORE_SSLV2 => 4 137 }; 138 139 # The TLSv1.2 in SSLv2 ClientHello need to run at security level 0 140 # because in a SSLv2 ClientHello we can't send extensions to indicate 141 # which signature algorithm we want to use, and the default is SHA1. 142 143 #Test 5: Inject an SSLv2 style record format for a TLSv1.2 ClientHello 144 $sslv2testtype = TLSV1_2_IN_SSLV2; 145 $proxy->clear(); 146 $proxy->filter(\&add_sslv2_filter); 147 $proxy->serverflags("-tls1_2"); 148 $proxy->clientflags("-no_tls1_3 -legacy_renegotiation"); 149 $proxy->ciphers("AES128-SHA:\@SECLEVEL=0"); 150 $proxy->start(); 151 ok(TLSProxy::Message->success(), "TLSv1.2 in SSLv2 ClientHello test"); 152 153 #Test 6: Inject an SSLv2 style record format for an SSLv2 ClientHello. We don't 154 # support this so it should fail. We actually treat it as an unknown 155 # protocol so we don't even send an alert in this case. 156 $sslv2testtype = SSLV2_IN_SSLV2; 157 $proxy->clear(); 158 $proxy->serverflags("-tls1_2"); 159 $proxy->clientflags("-no_tls1_3"); 160 $proxy->ciphers("AES128-SHA:\@SECLEVEL=0"); 161 $proxy->start(); 162 ok(TLSProxy::Message->fail(), "SSLv2 in SSLv2 ClientHello test"); 163 164 #Test 7: Sanity check ClientHello fragmentation. This isn't really an SSLv2 test 165 # at all, but it gives us confidence that Test 8 fails for the right 166 # reasons 167 $sslv2testtype = FRAGMENTED_IN_TLSV1_2; 168 $proxy->clear(); 169 $proxy->serverflags("-tls1_2"); 170 $proxy->clientflags("-no_tls1_3"); 171 $proxy->ciphers("AES128-SHA:\@SECLEVEL=0"); 172 $proxy->start(); 173 ok(TLSProxy::Message->success(), "Fragmented ClientHello in TLSv1.2 test"); 174 175 #Test 8: Fragment a TLSv1.2 ClientHello across a TLS1.2 record; an SSLv2 176 # record; and another TLS1.2 record. This isn't allowed so should fail 177 $sslv2testtype = FRAGMENTED_IN_SSLV2; 178 $proxy->clear(); 179 $proxy->serverflags("-tls1_2"); 180 $proxy->clientflags("-no_tls1_3"); 181 $proxy->ciphers("AES128-SHA:\@SECLEVEL=0"); 182 $proxy->start(); 183 ok(TLSProxy::Message->fail(), "Fragmented ClientHello in TLSv1.2/SSLv2 test"); 184 185 #Test 9: Send a TLS warning alert before an SSLv2 ClientHello. This should 186 # fail because an SSLv2 ClientHello must be the first record. 187 $sslv2testtype = ALERT_BEFORE_SSLV2; 188 $proxy->clear(); 189 $proxy->serverflags("-tls1_2"); 190 $proxy->clientflags("-no_tls1_3"); 191 $proxy->ciphers("AES128-SHA:\@SECLEVEL=0"); 192 $proxy->start(); 193 ok(TLSProxy::Message->fail(), "Alert before SSLv2 ClientHello test"); 194 } 195 #Unrecognised record type tests 196 197 #Test 10: Sending an unrecognised record type in TLS1.2 should fail 198 $fatal_alert = 0; 199 $proxy->clear(); 200 if ($run_test_as_dtls == 1) { 201 $proxy->serverflags("-min_protocol DTLSv1.2 -max_protocol DTLSv1.2"); 202 $proxy->clientflags("-max_protocol DTLSv1.2"); 203 } else { 204 $proxy->serverflags("-tls1_2"); 205 $proxy->clientflags("-no_tls1_3"); 206 } 207 $proxy->filter(\&add_unknown_record_type); 208 $proxy_start_success = $proxy->start(); 209 210 if ($run_test_as_dtls == 1) { 211 ok($proxy_start_success == 0, "Unrecognised record type in DTLS1.2"); 212 } else { 213 ok($fatal_alert, "Unrecognised record type in TLS1.2"); 214 } 215 216 SKIP: { 217 skip "TLSv1.1 or DTLSv1 disabled", 1 if ($run_test_as_dtls == 0 && disabled("tls1_1")) 218 || ($run_test_as_dtls == 1 && disabled("dtls1")); 219 220 #Test 11: Sending an unrecognised record type in TLS1.1 should fail 221 $fatal_alert = 0; 222 $proxy->clear(); 223 if ($run_test_as_dtls == 1) { 224 $proxy->clientflags("-min_protocol DTLSv1 -max_protocol DTLSv1 -cipher DEFAULT:\@SECLEVEL=0"); 225 } else { 226 $proxy->clientflags("-tls1_1 -cipher DEFAULT:\@SECLEVEL=0"); 227 } 228 $proxy->ciphers("AES128-SHA:\@SECLEVEL=0"); 229 $proxy_start_success = $proxy->start(); 230 if ($run_test_as_dtls == 1) { 231 ok($proxy_start_success == 0, "Unrecognised record type in DTLSv1"); 232 } else { 233 ok($fatal_alert, "Unrecognised record type in TLSv1.1"); 234 } 235 } 236 237 SKIP: { 238 skip "Record tests not intended for dtls", 10 if $run_test_as_dtls == 1; 239 #Test 12: Sending a different record version in TLS1.2 should fail 240 $fatal_alert = 0; 241 $proxy->clear(); 242 $proxy->clientflags("-tls1_2"); 243 $proxy->filter(\&change_version); 244 $proxy->start(); 245 ok($fatal_alert, "Changed record version in TLS1.2"); 246 247 #TLS1.3 specific tests 248 SKIP: { 249 skip "TLSv1.3 disabled", 9 250 if disabled("tls1_3") || (disabled("ec") && disabled("dh")); 251 252 #Test 13: Sending a different record version in TLS1.3 should fail 253 $proxy->clear(); 254 $proxy->filter(\&change_version); 255 $proxy->start(); 256 ok(TLSProxy::Message->fail(), "Changed record version in TLS1.3"); 257 258 #Test 14: Sending an unrecognised record type in TLS1.3 should fail 259 $fatal_alert = 0; 260 $proxy->clear(); 261 $proxy->filter(\&add_unknown_record_type); 262 $proxy->start(); 263 ok($fatal_alert, "Unrecognised record type in TLS1.3"); 264 265 #Test 15: Sending an outer record type other than app data once encrypted 266 #should fail 267 $fatal_alert = 0; 268 $proxy->clear(); 269 $proxy->filter(\&change_outer_record_type); 270 $proxy->start(); 271 ok($fatal_alert, "Wrong outer record type in TLS1.3"); 272 273 use constant { 274 DATA_AFTER_SERVER_HELLO => 0, 275 DATA_AFTER_FINISHED => 1, 276 DATA_AFTER_KEY_UPDATE => 2, 277 DATA_BETWEEN_KEY_UPDATE => 3, 278 NO_DATA_BETWEEN_KEY_UPDATE => 4, 279 }; 280 281 #Test 16: Sending a ServerHello which doesn't end on a record boundary 282 # should fail 283 $fatal_alert = 0; 284 $proxy->clear(); 285 $boundary_test_type = DATA_AFTER_SERVER_HELLO; 286 $proxy->filter(\¬_on_record_boundary); 287 $proxy->start(); 288 ok($fatal_alert, "Record not on boundary in TLS1.3 (ServerHello)"); 289 290 #Test 17: Sending a Finished which doesn't end on a record boundary 291 # should fail 292 $fatal_alert = 0; 293 $proxy->clear(); 294 $boundary_test_type = DATA_AFTER_FINISHED; 295 $proxy->start(); 296 ok($fatal_alert, "Record not on boundary in TLS1.3 (Finished)"); 297 298 #Test 18: Sending a KeyUpdate which doesn't end on a record boundary 299 # should fail 300 $fatal_alert = 0; 301 $proxy->clear(); 302 $boundary_test_type = DATA_AFTER_KEY_UPDATE; 303 $proxy->start(); 304 ok($fatal_alert, "Record not on boundary in TLS1.3 (KeyUpdate)"); 305 306 #Test 19: Sending application data in the middle of a fragmented KeyUpdate 307 # should fail. Strictly speaking this is not a record boundary test 308 # but we use the same filter. 309 $fatal_alert = 0; 310 $proxy->clear(); 311 $boundary_test_type = DATA_BETWEEN_KEY_UPDATE; 312 $proxy->start(); 313 ok($fatal_alert, "Data between KeyUpdate"); 314 315 #Test 20: Fragmented KeyUpdate. This should succeed. Strictly speaking this 316 # is not a record boundary test but we use the same filter. 317 $proxy->clear(); 318 $boundary_test_type = NO_DATA_BETWEEN_KEY_UPDATE; 319 $proxy->start(); 320 ok(TLSProxy::Message->success(), "No data between KeyUpdate"); 321 322 SKIP: { 323 skip "EC disabled", 1 if disabled("ec"); 324 325 #Test 21: Force an HRR and change the "real" ServerHello to have a protocol 326 # record version of 0x0301 (TLSv1.0). At this point we have already 327 # decided that we are doing TLSv1.3 but are still using plaintext 328 # records. The server should be sending a record version of 0x303 329 # (TLSv1.2), but the RFC requires us to ignore this field so we 330 # should tolerate the incorrect version. 331 $proxy->clear(); 332 $proxy->filter(\&change_server_hello_version); 333 $proxy->serverflags("-groups P-256"); # Force an HRR 334 $proxy->start(); 335 ok(TLSProxy::Message->success(), "Bad ServerHello record version after HRR"); 336 } 337 } 338 } 339} 340 341 342sub add_empty_recs_filter 343{ 344 my $proxy = shift; 345 my $records = $proxy->record_list; 346 my $isdtls = $proxy->isdtls(); 347 348 # We're only interested in the initial ClientHello 349 if ($proxy->flight != 0) { 350 $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(1) == TLSProxy::Message::AL_DESC_UNEXPECTED_MESSAGE; 351 return; 352 } 353 354 for (my $i = 0; $i < $inject_recs_num; $i++) { 355 my $record; 356 if ($isdtls == 1) { 357 $record = TLSProxy::Record->new_dtls( 358 0, 359 $content_type, 360 TLSProxy::Record::VERS_DTLS_1_2, 361 0, 362 0, 363 0, 364 0, 365 0, 366 0, 367 "", 368 "" 369 ); 370 } else { 371 $record = TLSProxy::Record->new( 372 0, 373 $content_type, 374 TLSProxy::Record::VERS_TLS_1_2, 375 0, 376 0, 377 0, 378 0, 379 "", 380 "" 381 ); 382 } 383 push @{$records}, $record; 384 } 385} 386 387sub add_frag_alert_filter 388{ 389 my $proxy = shift; 390 my $records = $proxy->record_list; 391 my $byte; 392 393 # We're only interested in the initial ClientHello 394 if ($proxy->flight != 0) { 395 $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(1) == TLSProxy::Message::AL_DESC_UNEXPECTED_MESSAGE; 396 return; 397 } 398 399 # Add a zero length fragment first 400 #my $record = TLSProxy::Record->new( 401 # 0, 402 # TLSProxy::Record::RT_ALERT, 403 # TLSProxy::Record::VERS_TLS_1_2, 404 # 0, 405 # 0, 406 # 0, 407 # "", 408 # "" 409 #); 410 #push @{$proxy->record_list}, $record; 411 412 # Now add the alert level (Fatal) as a separate record 413 $byte = pack('C', TLSProxy::Message::AL_LEVEL_FATAL); 414 my $record = TLSProxy::Record->new( 415 0, 416 TLSProxy::Record::RT_ALERT, 417 TLSProxy::Record::VERS_TLS_1_2, 418 1, 419 0, 420 1, 421 1, 422 $byte, 423 $byte 424 ); 425 push @{$records}, $record; 426 427 # And finally the description (Unexpected message) in a third record 428 $byte = pack('C', TLSProxy::Message::AL_DESC_UNEXPECTED_MESSAGE); 429 $record = TLSProxy::Record->new( 430 0, 431 TLSProxy::Record::RT_ALERT, 432 TLSProxy::Record::VERS_TLS_1_2, 433 1, 434 0, 435 1, 436 1, 437 $byte, 438 $byte 439 ); 440 push @{$records}, $record; 441} 442 443sub add_sslv2_filter 444{ 445 my $proxy = shift; 446 my $clienthello; 447 my $record; 448 449 # We're only interested in the initial ClientHello 450 if ($proxy->flight != 0) { 451 return; 452 } 453 454 # Ditch the real ClientHello - we're going to replace it with our own 455 shift @{$proxy->record_list}; 456 457 if ($sslv2testtype == ALERT_BEFORE_SSLV2) { 458 my $alert = pack('CC', TLSProxy::Message::AL_LEVEL_FATAL, 459 TLSProxy::Message::AL_DESC_NO_RENEGOTIATION); 460 my $alertlen = length $alert; 461 $record = TLSProxy::Record->new( 462 0, 463 TLSProxy::Record::RT_ALERT, 464 TLSProxy::Record::VERS_TLS_1_2, 465 $alertlen, 466 0, 467 $alertlen, 468 $alertlen, 469 $alert, 470 $alert 471 ); 472 473 push @{$proxy->record_list}, $record; 474 } 475 476 if ($sslv2testtype == ALERT_BEFORE_SSLV2 477 || $sslv2testtype == TLSV1_2_IN_SSLV2 478 || $sslv2testtype == SSLV2_IN_SSLV2) { 479 # This is an SSLv2 format ClientHello 480 $clienthello = 481 pack "C44", 482 0x01, # ClientHello 483 0x03, 0x03, #TLSv1.2 484 0x00, 0x03, # Ciphersuites len 485 0x00, 0x00, # Session id len 486 0x00, 0x20, # Challenge len 487 0x00, 0x00, 0x2f, #AES128-SHA 488 0x01, 0x18, 0x9F, 0x76, 0xEC, 0x57, 0xCE, 0xE5, 0xB3, 0xAB, 0x79, 0x90, 489 0xAD, 0xAC, 0x6E, 0xD1, 0x58, 0x35, 0x03, 0x97, 0x16, 0x10, 0x82, 0x56, 490 0xD8, 0x55, 0xFF, 0xE1, 0x8A, 0xA3, 0x2E, 0xF6; # Challenge 491 492 if ($sslv2testtype == SSLV2_IN_SSLV2) { 493 # Set the version to "real" SSLv2 494 vec($clienthello, 1, 8) = 0x00; 495 vec($clienthello, 2, 8) = 0x02; 496 } 497 498 my $chlen = length $clienthello; 499 500 $record = TLSProxy::Record->new( 501 0, 502 TLSProxy::Record::RT_HANDSHAKE, 503 TLSProxy::Record::VERS_TLS_1_2, 504 $chlen, 505 1, #SSLv2 506 $chlen, 507 $chlen, 508 $clienthello, 509 $clienthello 510 ); 511 512 push @{$proxy->record_list}, $record; 513 } else { 514 # For this test we're using a real TLS ClientHello 515 $clienthello = 516 pack "C49", 517 0x01, # ClientHello 518 0x00, 0x00, 0x2D, # Message length 519 0x03, 0x03, # TLSv1.2 520 0x01, 0x18, 0x9F, 0x76, 0xEC, 0x57, 0xCE, 0xE5, 0xB3, 0xAB, 0x79, 0x90, 521 0xAD, 0xAC, 0x6E, 0xD1, 0x58, 0x35, 0x03, 0x97, 0x16, 0x10, 0x82, 0x56, 522 0xD8, 0x55, 0xFF, 0xE1, 0x8A, 0xA3, 0x2E, 0xF6, # Random 523 0x00, # Session id len 524 0x00, 0x04, # Ciphersuites len 525 0x00, 0x2f, # AES128-SHA 526 0x00, 0xff, # Empty reneg info SCSV 527 0x01, # Compression methods len 528 0x00, # Null compression 529 0x00, 0x00; # Extensions len 530 531 # Split this into 3: A TLS record; a SSLv2 record and a TLS record. 532 # We deliberately split the second record prior to the Challenge/Random 533 # and set the first byte of the random to 1. This makes the second SSLv2 534 # record look like an SSLv2 ClientHello 535 my $frag1 = substr $clienthello, 0, 6; 536 my $frag2 = substr $clienthello, 6, 32; 537 my $frag3 = substr $clienthello, 38; 538 539 my $fraglen = length $frag1; 540 $record = TLSProxy::Record->new( 541 0, 542 TLSProxy::Record::RT_HANDSHAKE, 543 TLSProxy::Record::VERS_TLS_1_2, 544 $fraglen, 545 0, 546 $fraglen, 547 $fraglen, 548 $frag1, 549 $frag1 550 ); 551 push @{$proxy->record_list}, $record; 552 553 $fraglen = length $frag2; 554 my $recvers; 555 if ($sslv2testtype == FRAGMENTED_IN_SSLV2) { 556 $recvers = 1; 557 } else { 558 $recvers = 0; 559 } 560 $record = TLSProxy::Record->new( 561 0, 562 TLSProxy::Record::RT_HANDSHAKE, 563 TLSProxy::Record::VERS_TLS_1_2, 564 $fraglen, 565 $recvers, 566 $fraglen, 567 $fraglen, 568 $frag2, 569 $frag2 570 ); 571 push @{$proxy->record_list}, $record; 572 573 $fraglen = length $frag3; 574 $record = TLSProxy::Record->new( 575 0, 576 TLSProxy::Record::RT_HANDSHAKE, 577 TLSProxy::Record::VERS_TLS_1_2, 578 $fraglen, 579 0, 580 $fraglen, 581 $fraglen, 582 $frag3, 583 $frag3 584 ); 585 push @{$proxy->record_list}, $record; 586 } 587 588} 589 590sub add_unknown_record_type 591{ 592 my $proxy = shift; 593 my $records = $proxy->record_list; 594 my $isdtls = $proxy->isdtls; 595 state $added_record; 596 597 # We'll change a record after the initial version neg has taken place 598 if ($proxy->flight == 0) { 599 $added_record = 0; 600 return; 601 } elsif ($proxy->flight != 1 || $added_record) { 602 $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(0) == TLSProxy::Message::AL_DESC_UNEXPECTED_MESSAGE; 603 return; 604 } 605 606 my $record; 607 608 if ($isdtls) { 609 $record = TLSProxy::Record->new_dtls( 610 1, 611 TLSProxy::Record::RT_UNKNOWN, 612 @{$records}[-1]->version(), 613 @{$records}[-1]->epoch(), 614 @{$records}[-1]->seq() +1, 615 1, 616 0, 617 1, 618 1, 619 "X", 620 "X" 621 ); 622 } else { 623 $record = TLSProxy::Record->new( 624 1, 625 TLSProxy::Record::RT_UNKNOWN, 626 @{$records}[-1]->version(), 627 1, 628 0, 629 1, 630 1, 631 "X", 632 "X" 633 ); 634 } 635 636 #Find ServerHello record and insert after that 637 my $i; 638 for ($i = 0; ${$proxy->record_list}[$i]->flight() < 1; $i++) { 639 next; 640 } 641 $i++; 642 643 splice @{$proxy->record_list}, $i, 0, $record; 644 $added_record = 1; 645} 646 647sub change_version 648{ 649 my $proxy = shift; 650 my $records = $proxy->record_list; 651 652 # We'll change a version after the initial version neg has taken place 653 if ($proxy->flight != 1) { 654 $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(0) == TLSProxy::Message::AL_DESC_PROTOCOL_VERSION; 655 return; 656 } 657 658 if ($#{$records} > 1) { 659 # ... typically in ServerHelloDone 660 @{$records}[-1]->version(TLSProxy::Record::VERS_TLS_1_1); 661 } 662} 663 664sub change_server_hello_version 665{ 666 my $proxy = shift; 667 my $records = $proxy->record_list; 668 669 # We're only interested in changing the ServerHello after an HRR 670 if ($proxy->flight != 3) { 671 return; 672 } 673 674 # The ServerHello has index 5 675 # 0 - ClientHello 676 # 1 - HRR 677 # 2 - CCS 678 # 3 - ClientHello(2) 679 # 4 - CCS 680 # 5 - ServerHello 681 @{$records}[5]->version(TLSProxy::Record::VERS_TLS_1_0); 682} 683 684sub change_outer_record_type 685{ 686 my $proxy = shift; 687 my $records = $proxy->record_list; 688 689 # We'll change a record after the initial version neg has taken place 690 if ($proxy->flight != 1) { 691 $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(0) == TLSProxy::Message::AL_DESC_UNEXPECTED_MESSAGE; 692 return; 693 } 694 695 # Find CCS record and change record after that 696 my $i = 0; 697 foreach my $record (@{$records}) { 698 last if $record->content_type == TLSProxy::Record::RT_CCS; 699 $i++; 700 } 701 if (defined(${$records}[++$i])) { 702 ${$records}[$i]->outer_content_type(TLSProxy::Record::RT_HANDSHAKE); 703 } 704} 705 706sub not_on_record_boundary 707{ 708 my $proxy = shift; 709 my $records = $proxy->record_list; 710 my $data; 711 712 #Find server's first flight 713 if ($proxy->flight != 1) { 714 $fatal_alert = 1 if @{$records}[-1]->is_fatal_alert(0) == TLSProxy::Message::AL_DESC_UNEXPECTED_MESSAGE; 715 return; 716 } 717 718 if ($boundary_test_type == DATA_AFTER_SERVER_HELLO) { 719 #Merge the ServerHello and EncryptedExtensions records into one 720 my $i = 0; 721 foreach my $record (@{$records}) { 722 if ($record->content_type == TLSProxy::Record::RT_HANDSHAKE) { 723 $record->{sent} = 1; # pretend it's sent already 724 last; 725 } 726 $i++; 727 } 728 729 if (defined(${$records}[$i+1])) { 730 $data = ${$records}[$i]->data(); 731 $data .= ${$records}[$i+1]->decrypt_data(); 732 ${$records}[$i+1]->data($data); 733 ${$records}[$i+1]->len(length $data); 734 735 #Delete the old ServerHello record 736 splice @{$records}, $i, 1; 737 } 738 } elsif ($boundary_test_type == DATA_AFTER_FINISHED) { 739 return if @{$proxy->{message_list}}[-1]->{mt} 740 != TLSProxy::Message::MT_FINISHED; 741 742 my $last_record = @{$records}[-1]; 743 $data = $last_record->decrypt_data; 744 745 #Add a KeyUpdate message onto the end of the Finished record 746 my $keyupdate = pack "C5", 747 0x18, # KeyUpdate 748 0x00, 0x00, 0x01, # Message length 749 0x00; # Update not requested 750 751 $data .= $keyupdate; 752 753 #Add content type and tag 754 $data .= pack("C", TLSProxy::Record::RT_HANDSHAKE).("\0"x16); 755 756 #Update the record 757 $last_record->data($data); 758 $last_record->len(length $data); 759 } elsif ($boundary_test_type == DATA_AFTER_KEY_UPDATE) { 760 return if @{$proxy->{message_list}}[-1]->{mt} 761 != TLSProxy::Message::MT_FINISHED; 762 763 #KeyUpdates must end on a record boundary 764 765 my $record = TLSProxy::Record->new( 766 1, 767 TLSProxy::Record::RT_APPLICATION_DATA, 768 TLSProxy::Record::VERS_TLS_1_2, 769 0, 770 0, 771 0, 772 0, 773 "", 774 "" 775 ); 776 777 #Add two KeyUpdate messages into a single record 778 my $keyupdate = pack "C5", 779 0x18, # KeyUpdate 780 0x00, 0x00, 0x01, # Message length 781 0x00; # Update not requested 782 783 $data = $keyupdate.$keyupdate; 784 785 #Add content type and tag 786 $data .= pack("C", TLSProxy::Record::RT_HANDSHAKE).("\0"x16); 787 788 $record->data($data); 789 $record->len(length $data); 790 push @{$records}, $record; 791 } else { 792 return if @{$proxy->{message_list}}[-1]->{mt} 793 != TLSProxy::Message::MT_FINISHED; 794 795 my $record = TLSProxy::Record->new( 796 1, 797 TLSProxy::Record::RT_APPLICATION_DATA, 798 TLSProxy::Record::VERS_TLS_1_2, 799 0, 800 0, 801 0, 802 0, 803 "", 804 "" 805 ); 806 807 #Add a partial KeyUpdate message into the record 808 $data = pack "C1", 809 0x18; # KeyUpdate message type. Omit the rest of the message header 810 811 #Add content type and tag 812 $data .= pack("C", TLSProxy::Record::RT_HANDSHAKE).("\0"x16); 813 814 $record->data($data); 815 $record->len(length $data); 816 push @{$records}, $record; 817 818 if ($boundary_test_type == DATA_BETWEEN_KEY_UPDATE) { 819 #Now add an app data record 820 $record = TLSProxy::Record->new( 821 1, 822 TLSProxy::Record::RT_APPLICATION_DATA, 823 TLSProxy::Record::VERS_TLS_1_2, 824 0, 825 0, 826 0, 827 0, 828 "", 829 "" 830 ); 831 832 #Add an empty app data record (just content type and tag) 833 $data = pack("C", TLSProxy::Record::RT_APPLICATION_DATA).("\0"x16); 834 835 $record->data($data); 836 $record->len(length $data); 837 push @{$records}, $record; 838 } 839 840 #Now add the rest of the KeyUpdate message 841 $record = TLSProxy::Record->new( 842 1, 843 TLSProxy::Record::RT_APPLICATION_DATA, 844 TLSProxy::Record::VERS_TLS_1_2, 845 0, 846 0, 847 0, 848 0, 849 "", 850 "" 851 ); 852 853 #Add the last 4 bytes of the KeyUpdate record 854 $data = pack "C4", 855 0x00, 0x00, 0x01, # Message length 856 0x00; # Update not requested 857 858 #Add content type and tag 859 $data .= pack("C", TLSProxy::Record::RT_HANDSHAKE).("\0"x16); 860 861 $record->data($data); 862 $record->len(length $data); 863 push @{$records}, $record; 864 865 } 866} 867