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