1# Copyright 2016-2018 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::ServerKeyExchange; 21use TLSProxy::NewSessionTicket; 22use Time::HiRes qw/usleep/; 23 24my $have_IPv6 = 0; 25my $IP_factory; 26 27sub new 28{ 29 my $class = shift; 30 my ($filter, 31 $execute, 32 $cert, 33 $debug) = @_; 34 35 my $self = { 36 #Public read/write 37 proxy_addr => "localhost", 38 proxy_port => 4453, 39 server_addr => "localhost", 40 server_port => 4443, 41 filter => $filter, 42 serverflags => "", 43 clientflags => "", 44 serverconnects => 1, 45 serverpid => 0, 46 clientpid => 0, 47 reneg => 0, 48 49 #Public read 50 execute => $execute, 51 cert => $cert, 52 debug => $debug, 53 cipherc => "", 54 ciphers => "AES128-SHA", 55 flight => -1, 56 direction => -1, 57 partial => ["", ""], 58 record_list => [], 59 message_list => [], 60 }; 61 62 # IO::Socket::IP is on the core module list, IO::Socket::INET6 isn't. 63 # However, IO::Socket::INET6 is older and is said to be more widely 64 # deployed for the moment, and may have less bugs, so we try the latter 65 # first, then fall back on the code modules. Worst case scenario, we 66 # fall back to IO::Socket::INET, only supports IPv4. 67 eval { 68 require IO::Socket::INET6; 69 my $s = IO::Socket::INET6->new( 70 LocalAddr => "::1", 71 LocalPort => 0, 72 Listen=>1, 73 ); 74 $s or die "\n"; 75 $s->close(); 76 }; 77 if ($@ eq "") { 78 $IP_factory = sub { IO::Socket::INET6->new(@_); }; 79 $have_IPv6 = 1; 80 } else { 81 eval { 82 require IO::Socket::IP; 83 my $s = IO::Socket::IP->new( 84 LocalAddr => "::1", 85 LocalPort => 0, 86 Listen=>1, 87 ); 88 $s or die "\n"; 89 $s->close(); 90 }; 91 if ($@ eq "") { 92 $IP_factory = sub { IO::Socket::IP->new(@_); }; 93 $have_IPv6 = 1; 94 } else { 95 $IP_factory = sub { IO::Socket::INET->new(@_); }; 96 } 97 } 98 99 # Create the Proxy socket 100 my $proxaddr = $self->{proxy_addr}; 101 $proxaddr =~ s/[\[\]]//g; # Remove [ and ] 102 my @proxyargs = ( 103 LocalHost => $proxaddr, 104 LocalPort => $self->{proxy_port}, 105 Proto => "tcp", 106 Listen => SOMAXCONN, 107 ); 108 push @proxyargs, ReuseAddr => 1 109 unless $^O eq "MSWin32"; 110 $self->{proxy_sock} = $IP_factory->(@proxyargs); 111 112 if ($self->{proxy_sock}) { 113 print "Proxy started on port ".$self->{proxy_port}."\n"; 114 } else { 115 warn "Failed creating proxy socket (".$proxaddr.",".$self->{proxy_port}."): $!\n"; 116 } 117 118 return bless $self, $class; 119} 120 121sub DESTROY 122{ 123 my $self = shift; 124 125 $self->{proxy_sock}->close() if $self->{proxy_sock}; 126} 127 128sub clearClient 129{ 130 my $self = shift; 131 132 $self->{cipherc} = ""; 133 $self->{flight} = -1; 134 $self->{direction} = -1; 135 $self->{partial} = ["", ""]; 136 $self->{record_list} = []; 137 $self->{message_list} = []; 138 $self->{clientflags} = ""; 139 $self->{clientpid} = 0; 140 141 TLSProxy::Message->clear(); 142 TLSProxy::Record->clear(); 143} 144 145sub clear 146{ 147 my $self = shift; 148 149 $self->clearClient; 150 $self->{ciphers} = "AES128-SHA"; 151 $self->{serverflags} = ""; 152 $self->{serverconnects} = 1; 153 $self->{serverpid} = 0; 154 $self->{reneg} = 0; 155} 156 157sub restart 158{ 159 my $self = shift; 160 161 $self->clear; 162 $self->start; 163} 164 165sub clientrestart 166{ 167 my $self = shift; 168 169 $self->clear; 170 $self->clientstart; 171} 172 173sub start 174{ 175 my ($self) = shift; 176 my $pid; 177 178 if ($self->{proxy_sock} == 0) { 179 return 0; 180 } 181 182 $pid = fork(); 183 if ($pid == 0) { 184 my $execcmd = $self->execute 185 ." s_server -max_protocol TLSv1.2 -no_comp -rev -engine ossltest -accept " 186 .($self->server_port) 187 ." -cert ".$self->cert." -naccept ".$self->serverconnects; 188 unless ($self->supports_IPv6) { 189 $execcmd .= " -4"; 190 } 191 if ($self->ciphers ne "") { 192 $execcmd .= " -cipher ".$self->ciphers; 193 } 194 if ($self->serverflags ne "") { 195 $execcmd .= " ".$self->serverflags; 196 } 197 if ($self->debug) { 198 print STDERR "Server command: $execcmd\n"; 199 } 200 exec($execcmd); 201 } 202 $self->serverpid($pid); 203 204 return $self->clientstart; 205} 206 207sub clientstart 208{ 209 my ($self) = shift; 210 my $oldstdout; 211 212 if ($self->execute) { 213 my $pid = fork(); 214 if ($pid == 0) { 215 my $echostr; 216 if ($self->reneg()) { 217 $echostr = "R"; 218 } else { 219 $echostr = "test"; 220 } 221 my $execcmd = "echo ".$echostr." | ".$self->execute 222 ." s_client -max_protocol TLSv1.2 -engine ossltest -connect " 223 .($self->proxy_addr).":".($self->proxy_port); 224 unless ($self->supports_IPv6) { 225 $execcmd .= " -4"; 226 } 227 if ($self->cipherc ne "") { 228 $execcmd .= " -cipher ".$self->cipherc; 229 } 230 if ($self->clientflags ne "") { 231 $execcmd .= " ".$self->clientflags; 232 } 233 if ($self->debug) { 234 print STDERR "Client command: $execcmd\n"; 235 } 236 exec($execcmd); 237 } 238 $self->clientpid($pid); 239 } 240 241 # Wait for incoming connection from client 242 my $client_sock; 243 if(!($client_sock = $self->{proxy_sock}->accept())) { 244 warn "Failed accepting incoming connection: $!\n"; 245 return 0; 246 } 247 248 print "Connection opened\n"; 249 250 # Now connect to the server 251 my $retry = 50; 252 my $server_sock; 253 #We loop over this a few times because sometimes s_server can take a while 254 #to start up 255 do { 256 my $servaddr = $self->server_addr; 257 $servaddr =~ s/[\[\]]//g; # Remove [ and ] 258 eval { 259 $server_sock = $IP_factory->( 260 PeerAddr => $servaddr, 261 PeerPort => $self->server_port, 262 MultiHomed => 1, 263 Proto => 'tcp' 264 ); 265 }; 266 267 $retry--; 268 #Some buggy IP factories can return a defined server_sock that hasn't 269 #actually connected, so we check peerport too 270 if ($@ || !defined($server_sock) || !defined($server_sock->peerport)) { 271 $server_sock->close() if defined($server_sock); 272 undef $server_sock; 273 if ($retry) { 274 #Sleep for a short while 275 select(undef, undef, undef, 0.1); 276 } else { 277 warn "Failed to start up server (".$servaddr.",".$self->server_port."): $!\n"; 278 return 0; 279 } 280 } 281 } while (!$server_sock); 282 283 my $sel = IO::Select->new($server_sock, $client_sock); 284 my $indata; 285 my @handles = ($server_sock, $client_sock); 286 287 #Wait for either the server socket or the client socket to become readable 288 my @ready; 289 local $SIG{PIPE} = "IGNORE"; 290 while(!(TLSProxy::Message->end) && (@ready = $sel->can_read)) { 291 foreach my $hand (@ready) { 292 if ($hand == $server_sock) { 293 $server_sock->sysread($indata, 16384) or goto END; 294 $indata = $self->process_packet(1, $indata); 295 $client_sock->syswrite($indata); 296 } elsif ($hand == $client_sock) { 297 $client_sock->sysread($indata, 16384) or goto END; 298 $indata = $self->process_packet(0, $indata); 299 $server_sock->syswrite($indata); 300 } else { 301 print "Err\n"; 302 goto END; 303 } 304 } 305 } 306 307 END: 308 print "Connection closed\n"; 309 if($server_sock) { 310 $server_sock->close(); 311 } 312 if($client_sock) { 313 #Closing this also kills the child process 314 $client_sock->close(); 315 } 316 if(!$self->debug) { 317 select($oldstdout); 318 } 319 $self->serverconnects($self->serverconnects - 1); 320 if ($self->serverconnects == 0) { 321 die "serverpid is zero\n" if $self->serverpid == 0; 322 print "Waiting for server process to close: " 323 .$self->serverpid."\n"; 324 waitpid( $self->serverpid, 0); 325 die "exit code $? from server process\n" if $? != 0; 326 } else { 327 # Give s_server sufficient time to finish what it was doing 328 usleep(250000); 329 } 330 die "clientpid is zero\n" if $self->clientpid == 0; 331 print "Waiting for client process to close: ".$self->clientpid."\n"; 332 waitpid($self->clientpid, 0); 333 334 return 1; 335} 336 337sub process_packet 338{ 339 my ($self, $server, $packet) = @_; 340 my $len_real; 341 my $decrypt_len; 342 my $data; 343 my $recnum; 344 345 if ($server) { 346 print "Received server packet\n"; 347 } else { 348 print "Received client packet\n"; 349 } 350 351 if ($self->{direction} != $server) { 352 $self->{flight} = $self->{flight} + 1; 353 $self->{direction} = $server; 354 } 355 356 print "Packet length = ".length($packet)."\n"; 357 print "Processing flight ".$self->flight."\n"; 358 359 #Return contains the list of record found in the packet followed by the 360 #list of messages in those records and any partial message 361 my @ret = TLSProxy::Record->get_records($server, $self->flight, $self->{partial}[$server].$packet); 362 $self->{partial}[$server] = $ret[2]; 363 push @{$self->record_list}, @{$ret[0]}; 364 push @{$self->{message_list}}, @{$ret[1]}; 365 366 print "\n"; 367 368 if (scalar(@{$ret[0]}) == 0 or length($ret[2]) != 0) { 369 return ""; 370 } 371 372 #Finished parsing. Call user provided filter here 373 if (defined $self->filter) { 374 $self->filter->($self); 375 } 376 377 #Reconstruct the packet 378 $packet = ""; 379 foreach my $record (@{$self->record_list}) { 380 $packet .= $record->reconstruct_record(); 381 } 382 383 print "Forwarded packet length = ".length($packet)."\n\n"; 384 385 return $packet; 386} 387 388#Read accessors 389sub execute 390{ 391 my $self = shift; 392 return $self->{execute}; 393} 394sub cert 395{ 396 my $self = shift; 397 return $self->{cert}; 398} 399sub debug 400{ 401 my $self = shift; 402 return $self->{debug}; 403} 404sub flight 405{ 406 my $self = shift; 407 return $self->{flight}; 408} 409sub record_list 410{ 411 my $self = shift; 412 return $self->{record_list}; 413} 414sub success 415{ 416 my $self = shift; 417 return $self->{success}; 418} 419sub end 420{ 421 my $self = shift; 422 return $self->{end}; 423} 424sub supports_IPv6 425{ 426 my $self = shift; 427 return $have_IPv6; 428} 429sub proxy_addr 430{ 431 my $self = shift; 432 return $self->{proxy_addr}; 433} 434sub proxy_port 435{ 436 my $self = shift; 437 return $self->{proxy_port}; 438} 439 440#Read/write accessors 441sub server_addr 442{ 443 my $self = shift; 444 if (@_) { 445 $self->{server_addr} = shift; 446 } 447 return $self->{server_addr}; 448} 449sub server_port 450{ 451 my $self = shift; 452 if (@_) { 453 $self->{server_port} = shift; 454 } 455 return $self->{server_port}; 456} 457sub filter 458{ 459 my $self = shift; 460 if (@_) { 461 $self->{filter} = shift; 462 } 463 return $self->{filter}; 464} 465sub cipherc 466{ 467 my $self = shift; 468 if (@_) { 469 $self->{cipherc} = shift; 470 } 471 return $self->{cipherc}; 472} 473sub ciphers 474{ 475 my $self = shift; 476 if (@_) { 477 $self->{ciphers} = shift; 478 } 479 return $self->{ciphers}; 480} 481sub serverflags 482{ 483 my $self = shift; 484 if (@_) { 485 $self->{serverflags} = shift; 486 } 487 return $self->{serverflags}; 488} 489sub clientflags 490{ 491 my $self = shift; 492 if (@_) { 493 $self->{clientflags} = shift; 494 } 495 return $self->{clientflags}; 496} 497sub serverconnects 498{ 499 my $self = shift; 500 if (@_) { 501 $self->{serverconnects} = shift; 502 } 503 return $self->{serverconnects}; 504} 505# This is a bit ugly because the caller is responsible for keeping the records 506# in sync with the updated message list; simply updating the message list isn't 507# sufficient to get the proxy to forward the new message. 508# But it does the trick for the one test (test_sslsessiontick) that needs it. 509sub message_list 510{ 511 my $self = shift; 512 if (@_) { 513 $self->{message_list} = shift; 514 } 515 return $self->{message_list}; 516} 517sub serverpid 518{ 519 my $self = shift; 520 if (@_) { 521 $self->{serverpid} = shift; 522 } 523 return $self->{serverpid}; 524} 525sub clientpid 526{ 527 my $self = shift; 528 if (@_) { 529 $self->{clientpid} = shift; 530 } 531 return $self->{clientpid}; 532} 533 534sub fill_known_data 535{ 536 my $length = shift; 537 my $ret = ""; 538 for (my $i = 0; $i < $length; $i++) { 539 $ret .= chr($i); 540 } 541 return $ret; 542} 543 544sub reneg 545{ 546 my $self = shift; 547 if (@_) { 548 $self->{reneg} = shift; 549 } 550 return $self->{reneg}; 551} 552 5531; 554