1# Copyright 2016-2019 The OpenSSL Project Authors. All Rights Reserved. 2# 3# Licensed under the OpenSSL license (the "License"). You may not use 4# this file except in compliance with the License. You can obtain a copy 5# in the file LICENSE in the source distribution or at 6# https://www.openssl.org/source/license.html 7 8use strict; 9use POSIX ":sys_wait_h"; 10 11package TLSProxy::Proxy; 12 13use File::Spec; 14use IO::Socket; 15use IO::Select; 16use TLSProxy::Record; 17use TLSProxy::Message; 18use TLSProxy::ClientHello; 19use TLSProxy::ServerHello; 20use TLSProxy::EncryptedExtensions; 21use TLSProxy::Certificate; 22use TLSProxy::CertificateVerify; 23use TLSProxy::ServerKeyExchange; 24use TLSProxy::NewSessionTicket; 25 26my $have_IPv6; 27my $IP_factory; 28 29BEGIN 30{ 31 # IO::Socket::IP is on the core module list, IO::Socket::INET6 isn't. 32 # However, IO::Socket::INET6 is older and is said to be more widely 33 # deployed for the moment, and may have less bugs, so we try the latter 34 # first, then fall back on the core modules. Worst case scenario, we 35 # fall back to IO::Socket::INET, only supports IPv4. 36 eval { 37 require IO::Socket::INET6; 38 my $s = IO::Socket::INET6->new( 39 LocalAddr => "::1", 40 LocalPort => 0, 41 Listen=>1, 42 ); 43 $s or die "\n"; 44 $s->close(); 45 }; 46 if ($@ eq "") { 47 $IP_factory = sub { IO::Socket::INET6->new(Domain => AF_INET6, @_); }; 48 $have_IPv6 = 1; 49 } else { 50 eval { 51 require IO::Socket::IP; 52 my $s = IO::Socket::IP->new( 53 LocalAddr => "::1", 54 LocalPort => 0, 55 Listen=>1, 56 ); 57 $s or die "\n"; 58 $s->close(); 59 }; 60 if ($@ eq "") { 61 $IP_factory = sub { IO::Socket::IP->new(@_); }; 62 $have_IPv6 = 1; 63 } else { 64 $IP_factory = sub { IO::Socket::INET->new(@_); }; 65 $have_IPv6 = 0; 66 } 67 } 68} 69 70my $is_tls13 = 0; 71my $ciphersuite = undef; 72 73sub new 74{ 75 my $class = shift; 76 my ($filter, 77 $execute, 78 $cert, 79 $debug) = @_; 80 81 my $self = { 82 #Public read/write 83 proxy_addr => $have_IPv6 ? "[::1]" : "127.0.0.1", 84 filter => $filter, 85 serverflags => "", 86 clientflags => "", 87 serverconnects => 1, 88 reneg => 0, 89 sessionfile => undef, 90 91 #Public read 92 proxy_port => 0, 93 server_port => 0, 94 serverpid => 0, 95 clientpid => 0, 96 execute => $execute, 97 cert => $cert, 98 debug => $debug, 99 cipherc => "", 100 ciphersuitesc => "", 101 ciphers => "AES128-SHA", 102 ciphersuitess => "TLS_AES_128_GCM_SHA256", 103 flight => -1, 104 direction => -1, 105 partial => ["", ""], 106 record_list => [], 107 message_list => [], 108 }; 109 110 # Create the Proxy socket 111 my $proxaddr = $self->{proxy_addr}; 112 $proxaddr =~ s/[\[\]]//g; # Remove [ and ] 113 my @proxyargs = ( 114 LocalHost => $proxaddr, 115 LocalPort => 0, 116 Proto => "tcp", 117 Listen => SOMAXCONN, 118 ); 119 120 if (my $sock = $IP_factory->(@proxyargs)) { 121 $self->{proxy_sock} = $sock; 122 $self->{proxy_port} = $sock->sockport(); 123 $self->{proxy_addr} = $sock->sockhost(); 124 $self->{proxy_addr} =~ s/(.*:.*)/[$1]/; 125 print "Proxy started on port ", 126 "$self->{proxy_addr}:$self->{proxy_port}\n"; 127 # use same address for s_server 128 $self->{server_addr} = $self->{proxy_addr}; 129 } else { 130 warn "Failed creating proxy socket (".$proxaddr.",0): $!\n"; 131 } 132 133 return bless $self, $class; 134} 135 136sub DESTROY 137{ 138 my $self = shift; 139 140 $self->{proxy_sock}->close() if $self->{proxy_sock}; 141} 142 143sub clearClient 144{ 145 my $self = shift; 146 147 $self->{cipherc} = ""; 148 $self->{ciphersuitec} = ""; 149 $self->{flight} = -1; 150 $self->{direction} = -1; 151 $self->{partial} = ["", ""]; 152 $self->{record_list} = []; 153 $self->{message_list} = []; 154 $self->{clientflags} = ""; 155 $self->{sessionfile} = undef; 156 $self->{clientpid} = 0; 157 $is_tls13 = 0; 158 $ciphersuite = undef; 159 160 TLSProxy::Message->clear(); 161 TLSProxy::Record->clear(); 162} 163 164sub clear 165{ 166 my $self = shift; 167 168 $self->clearClient; 169 $self->{ciphers} = "AES128-SHA"; 170 $self->{ciphersuitess} = "TLS_AES_128_GCM_SHA256"; 171 $self->{serverflags} = ""; 172 $self->{serverconnects} = 1; 173 $self->{serverpid} = 0; 174 $self->{reneg} = 0; 175} 176 177sub restart 178{ 179 my $self = shift; 180 181 $self->clear; 182 $self->start; 183} 184 185sub clientrestart 186{ 187 my $self = shift; 188 189 $self->clear; 190 $self->clientstart; 191} 192 193sub connect_to_server 194{ 195 my $self = shift; 196 my $servaddr = $self->{server_addr}; 197 198 $servaddr =~ s/[\[\]]//g; # Remove [ and ] 199 200 my $sock = $IP_factory->(PeerAddr => $servaddr, 201 PeerPort => $self->{server_port}, 202 Proto => 'tcp'); 203 if (!defined($sock)) { 204 my $err = $!; 205 kill(3, $self->{real_serverpid}); 206 die "unable to connect: $err\n"; 207 } 208 209 $self->{server_sock} = $sock; 210} 211 212sub start 213{ 214 my ($self) = shift; 215 my $pid; 216 217 if ($self->{proxy_sock} == 0) { 218 return 0; 219 } 220 221 my $execcmd = $self->execute 222 ." s_server -max_protocol TLSv1.3 -no_comp -rev -engine ossltest" 223 #In TLSv1.3 we issue two session tickets. The default session id 224 #callback gets confused because the ossltest engine causes the same 225 #session id to be created twice due to the changed random number 226 #generation. Using "-ext_cache" replaces the default callback with a 227 #different one that doesn't get confused. 228 ." -ext_cache" 229 ." -accept $self->{server_addr}:0" 230 ." -cert ".$self->cert." -cert2 ".$self->cert 231 ." -naccept ".$self->serverconnects; 232 if ($self->ciphers ne "") { 233 $execcmd .= " -cipher ".$self->ciphers; 234 } 235 if ($self->ciphersuitess ne "") { 236 $execcmd .= " -ciphersuites ".$self->ciphersuitess; 237 } 238 if ($self->serverflags ne "") { 239 $execcmd .= " ".$self->serverflags; 240 } 241 if ($self->debug) { 242 print STDERR "Server command: $execcmd\n"; 243 } 244 245 open(my $savedin, "<&STDIN"); 246 247 # Temporarily replace STDIN so that sink process can inherit it... 248 $pid = open(STDIN, "$execcmd 2>&1 |") or die "Failed to $execcmd: $!\n"; 249 $self->{real_serverpid} = $pid; 250 251 # Process the output from s_server until we find the ACCEPT line, which 252 # tells us what the accepting address and port are. 253 while (<>) { 254 print; 255 s/\R$//; # Better chomp 256 next unless (/^ACCEPT\s.*:(\d+)$/); 257 $self->{server_port} = $1; 258 last; 259 } 260 261 if ($self->{server_port} == 0) { 262 # This actually means that s_server exited, because otherwise 263 # we would still searching for ACCEPT... 264 waitpid($pid, 0); 265 die "no ACCEPT detected in '$execcmd' output: $?\n"; 266 } 267 268 # Just make sure everything else is simply printed [as separate lines]. 269 # The sub process simply inherits our STD* and will keep consuming 270 # server's output and printing it as long as there is anything there, 271 # out of our way. 272 my $error; 273 $pid = undef; 274 if (eval { require Win32::Process; 1; }) { 275 if (Win32::Process::Create(my $h, $^X, "perl -ne print", 0, 0, ".")) { 276 $pid = $h->GetProcessID(); 277 $self->{proc_handle} = $h; # hold handle till next round [or exit] 278 } else { 279 $error = Win32::FormatMessage(Win32::GetLastError()); 280 } 281 } else { 282 if (defined($pid = fork)) { 283 $pid or exec("$^X -ne print") or exit($!); 284 } else { 285 $error = $!; 286 } 287 } 288 289 # Change back to original stdin 290 open(STDIN, "<&", $savedin); 291 close($savedin); 292 293 if (!defined($pid)) { 294 kill(3, $self->{real_serverpid}); 295 die "Failed to capture s_server's output: $error\n"; 296 } 297 298 $self->{serverpid} = $pid; 299 300 print STDERR "Server responds on ", 301 "$self->{server_addr}:$self->{server_port}\n"; 302 303 # Connect right away... 304 $self->connect_to_server(); 305 306 return $self->clientstart; 307} 308 309sub clientstart 310{ 311 my ($self) = shift; 312 313 if ($self->execute) { 314 my $pid; 315 my $execcmd = $self->execute 316 ." s_client -max_protocol TLSv1.3 -engine ossltest" 317 ." -connect $self->{proxy_addr}:$self->{proxy_port}"; 318 if ($self->cipherc ne "") { 319 $execcmd .= " -cipher ".$self->cipherc; 320 } 321 if ($self->ciphersuitesc ne "") { 322 $execcmd .= " -ciphersuites ".$self->ciphersuitesc; 323 } 324 if ($self->clientflags ne "") { 325 $execcmd .= " ".$self->clientflags; 326 } 327 if ($self->clientflags !~ m/-(no)?servername/) { 328 $execcmd .= " -servername localhost"; 329 } 330 if (defined $self->sessionfile) { 331 $execcmd .= " -ign_eof"; 332 } 333 if ($self->debug) { 334 print STDERR "Client command: $execcmd\n"; 335 } 336 337 open(my $savedout, ">&STDOUT"); 338 # If we open pipe with new descriptor, attempt to close it, 339 # explicitly or implicitly, would incur waitpid and effectively 340 # dead-lock... 341 if (!($pid = open(STDOUT, "| $execcmd"))) { 342 my $err = $!; 343 kill(3, $self->{real_serverpid}); 344 die "Failed to $execcmd: $err\n"; 345 } 346 $self->{clientpid} = $pid; 347 348 # queue [magic] input 349 print $self->reneg ? "R" : "test"; 350 351 # this closes client's stdin without waiting for its pid 352 open(STDOUT, ">&", $savedout); 353 close($savedout); 354 } 355 356 # Wait for incoming connection from client 357 my $fdset = IO::Select->new($self->{proxy_sock}); 358 if (!$fdset->can_read(60)) { 359 kill(3, $self->{real_serverpid}); 360 die "s_client didn't try to connect\n"; 361 } 362 363 my $client_sock; 364 if(!($client_sock = $self->{proxy_sock}->accept())) { 365 warn "Failed accepting incoming connection: $!\n"; 366 return 0; 367 } 368 369 print "Connection opened\n"; 370 371 my $server_sock = $self->{server_sock}; 372 my $indata; 373 374 #Wait for either the server socket or the client socket to become readable 375 $fdset = IO::Select->new($server_sock, $client_sock); 376 my @ready; 377 my $ctr = 0; 378 local $SIG{PIPE} = "IGNORE"; 379 $self->{saw_session_ticket} = undef; 380 while($fdset->count && $ctr < 10) { 381 if (defined($self->{sessionfile})) { 382 # s_client got -ign_eof and won't be exiting voluntarily, so we 383 # look for data *and* session ticket... 384 last if TLSProxy::Message->success() 385 && $self->{saw_session_ticket}; 386 } 387 if (!(@ready = $fdset->can_read(1))) { 388 $ctr++; 389 next; 390 } 391 foreach my $hand (@ready) { 392 if ($hand == $server_sock) { 393 if ($server_sock->sysread($indata, 16384)) { 394 if ($indata = $self->process_packet(1, $indata)) { 395 $client_sock->syswrite($indata) or goto END; 396 } 397 $ctr = 0; 398 } else { 399 $fdset->remove($server_sock); 400 $client_sock->shutdown(SHUT_WR); 401 } 402 } elsif ($hand == $client_sock) { 403 if ($client_sock->sysread($indata, 16384)) { 404 if ($indata = $self->process_packet(0, $indata)) { 405 $server_sock->syswrite($indata) or goto END; 406 } 407 $ctr = 0; 408 } else { 409 $fdset->remove($client_sock); 410 $server_sock->shutdown(SHUT_WR); 411 } 412 } else { 413 kill(3, $self->{real_serverpid}); 414 die "Unexpected handle"; 415 } 416 } 417 } 418 419 if ($ctr >= 10) { 420 kill(3, $self->{real_serverpid}); 421 die "No progress made"; 422 } 423 424 END: 425 print "Connection closed\n"; 426 if($server_sock) { 427 $server_sock->close(); 428 $self->{server_sock} = undef; 429 } 430 if($client_sock) { 431 #Closing this also kills the child process 432 $client_sock->close(); 433 } 434 435 my $pid; 436 if (--$self->{serverconnects} == 0) { 437 $pid = $self->{serverpid}; 438 print "Waiting for 'perl -ne print' process to close: $pid...\n"; 439 $pid = waitpid($pid, 0); 440 if ($pid > 0) { 441 die "exit code $? from 'perl -ne print' process\n" if $? != 0; 442 } elsif ($pid == 0) { 443 kill(3, $self->{real_serverpid}); 444 die "lost control over $self->{serverpid}?"; 445 } 446 $pid = $self->{real_serverpid}; 447 print "Waiting for s_server process to close: $pid...\n"; 448 # it's done already, just collect the exit code [and reap]... 449 waitpid($pid, 0); 450 die "exit code $? from s_server process\n" if $? != 0; 451 } else { 452 # It's a bit counter-intuitive spot to make next connection to 453 # the s_server. Rationale is that established connection works 454 # as syncronization point, in sense that this way we know that 455 # s_server is actually done with current session... 456 $self->connect_to_server(); 457 } 458 $pid = $self->{clientpid}; 459 print "Waiting for s_client process to close: $pid...\n"; 460 waitpid($pid, 0); 461 462 return 1; 463} 464 465sub process_packet 466{ 467 my ($self, $server, $packet) = @_; 468 my $len_real; 469 my $decrypt_len; 470 my $data; 471 my $recnum; 472 473 if ($server) { 474 print "Received server packet\n"; 475 } else { 476 print "Received client packet\n"; 477 } 478 479 if ($self->{direction} != $server) { 480 $self->{flight} = $self->{flight} + 1; 481 $self->{direction} = $server; 482 } 483 484 print "Packet length = ".length($packet)."\n"; 485 print "Processing flight ".$self->flight."\n"; 486 487 #Return contains the list of record found in the packet followed by the 488 #list of messages in those records and any partial message 489 my @ret = TLSProxy::Record->get_records($server, $self->flight, 490 $self->{partial}[$server].$packet); 491 $self->{partial}[$server] = $ret[2]; 492 push @{$self->{record_list}}, @{$ret[0]}; 493 push @{$self->{message_list}}, @{$ret[1]}; 494 495 print "\n"; 496 497 if (scalar(@{$ret[0]}) == 0 or length($ret[2]) != 0) { 498 return ""; 499 } 500 501 #Finished parsing. Call user provided filter here 502 if (defined $self->filter) { 503 $self->filter->($self); 504 } 505 506 #Take a note on NewSessionTicket 507 foreach my $message (reverse @{$self->{message_list}}) { 508 if ($message->{mt} == TLSProxy::Message::MT_NEW_SESSION_TICKET) { 509 $self->{saw_session_ticket} = 1; 510 last; 511 } 512 } 513 514 #Reconstruct the packet 515 $packet = ""; 516 foreach my $record (@{$self->record_list}) { 517 $packet .= $record->reconstruct_record($server); 518 } 519 520 print "Forwarded packet length = ".length($packet)."\n\n"; 521 522 return $packet; 523} 524 525#Read accessors 526sub execute 527{ 528 my $self = shift; 529 return $self->{execute}; 530} 531sub cert 532{ 533 my $self = shift; 534 return $self->{cert}; 535} 536sub debug 537{ 538 my $self = shift; 539 return $self->{debug}; 540} 541sub flight 542{ 543 my $self = shift; 544 return $self->{flight}; 545} 546sub record_list 547{ 548 my $self = shift; 549 return $self->{record_list}; 550} 551sub success 552{ 553 my $self = shift; 554 return $self->{success}; 555} 556sub end 557{ 558 my $self = shift; 559 return $self->{end}; 560} 561sub supports_IPv6 562{ 563 my $self = shift; 564 return $have_IPv6; 565} 566sub proxy_addr 567{ 568 my $self = shift; 569 return $self->{proxy_addr}; 570} 571sub proxy_port 572{ 573 my $self = shift; 574 return $self->{proxy_port}; 575} 576sub server_addr 577{ 578 my $self = shift; 579 return $self->{server_addr}; 580} 581sub server_port 582{ 583 my $self = shift; 584 return $self->{server_port}; 585} 586sub serverpid 587{ 588 my $self = shift; 589 return $self->{serverpid}; 590} 591sub clientpid 592{ 593 my $self = shift; 594 return $self->{clientpid}; 595} 596 597#Read/write accessors 598sub filter 599{ 600 my $self = shift; 601 if (@_) { 602 $self->{filter} = shift; 603 } 604 return $self->{filter}; 605} 606sub cipherc 607{ 608 my $self = shift; 609 if (@_) { 610 $self->{cipherc} = shift; 611 } 612 return $self->{cipherc}; 613} 614sub ciphersuitesc 615{ 616 my $self = shift; 617 if (@_) { 618 $self->{ciphersuitesc} = shift; 619 } 620 return $self->{ciphersuitesc}; 621} 622sub ciphers 623{ 624 my $self = shift; 625 if (@_) { 626 $self->{ciphers} = shift; 627 } 628 return $self->{ciphers}; 629} 630sub ciphersuitess 631{ 632 my $self = shift; 633 if (@_) { 634 $self->{ciphersuitess} = shift; 635 } 636 return $self->{ciphersuitess}; 637} 638sub serverflags 639{ 640 my $self = shift; 641 if (@_) { 642 $self->{serverflags} = shift; 643 } 644 return $self->{serverflags}; 645} 646sub clientflags 647{ 648 my $self = shift; 649 if (@_) { 650 $self->{clientflags} = shift; 651 } 652 return $self->{clientflags}; 653} 654sub serverconnects 655{ 656 my $self = shift; 657 if (@_) { 658 $self->{serverconnects} = shift; 659 } 660 return $self->{serverconnects}; 661} 662# This is a bit ugly because the caller is responsible for keeping the records 663# in sync with the updated message list; simply updating the message list isn't 664# sufficient to get the proxy to forward the new message. 665# But it does the trick for the one test (test_sslsessiontick) that needs it. 666sub message_list 667{ 668 my $self = shift; 669 if (@_) { 670 $self->{message_list} = shift; 671 } 672 return $self->{message_list}; 673} 674 675sub fill_known_data 676{ 677 my $length = shift; 678 my $ret = ""; 679 for (my $i = 0; $i < $length; $i++) { 680 $ret .= chr($i); 681 } 682 return $ret; 683} 684 685sub is_tls13 686{ 687 my $class = shift; 688 if (@_) { 689 $is_tls13 = shift; 690 } 691 return $is_tls13; 692} 693 694sub reneg 695{ 696 my $self = shift; 697 if (@_) { 698 $self->{reneg} = shift; 699 } 700 return $self->{reneg}; 701} 702 703#Setting a sessionfile means that the client will not close until the given 704#file exists. This is useful in TLSv1.3 where otherwise s_client will close 705#immediately at the end of the handshake, but before the session has been 706#received from the server. A side effect of this is that s_client never sends 707#a close_notify, so instead we consider success to be when it sends application 708#data over the connection. 709sub sessionfile 710{ 711 my $self = shift; 712 if (@_) { 713 $self->{sessionfile} = shift; 714 TLSProxy::Message->successondata(1); 715 } 716 return $self->{sessionfile}; 717} 718 719sub ciphersuite 720{ 721 my $class = shift; 722 if (@_) { 723 $ciphersuite = shift; 724 } 725 return $ciphersuite; 726} 727 7281; 729