1 # Copyright 2016-2018 The OpenSSL Project Authors. All Rights Reserved.
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
9 use POSIX ":sys_wait_h";
11 package TLSProxy::Proxy;
17 use TLSProxy::Message;
18 use TLSProxy::ClientHello;
19 use TLSProxy::ServerHello;
20 use TLSProxy::EncryptedExtensions;
21 use TLSProxy::Certificate;
22 use TLSProxy::CertificateVerify;
23 use TLSProxy::ServerKeyExchange;
24 use TLSProxy::NewSessionTicket;
30 my $ciphersuite = undef;
42 proxy_addr => "localhost",
43 server_addr => "localhost",
61 ciphers => "AES128-SHA",
62 ciphersuitess => "TLS_AES_128_GCM_SHA256",
70 # IO::Socket::IP is on the core module list, IO::Socket::INET6 isn't.
71 # However, IO::Socket::INET6 is older and is said to be more widely
72 # deployed for the moment, and may have less bugs, so we try the latter
73 # first, then fall back on the code modules. Worst case scenario, we
74 # fall back to IO::Socket::INET, only supports IPv4.
76 require IO::Socket::INET6;
77 my $s = IO::Socket::INET6->new(
86 $IP_factory = sub { IO::Socket::INET6->new(@_); };
90 require IO::Socket::IP;
91 my $s = IO::Socket::IP->new(
100 $IP_factory = sub { IO::Socket::IP->new(@_); };
103 $IP_factory = sub { IO::Socket::INET->new(@_); };
107 # Create the Proxy socket
108 my $proxaddr = $self->{proxy_addr};
109 $proxaddr =~ s/[\[\]]//g; # Remove [ and ]
111 LocalHost => $proxaddr,
116 $self->{proxy_sock} = $IP_factory->(@proxyargs);
118 if ($self->{proxy_sock}) {
119 $self->{proxy_port} = $self->{proxy_sock}->sockport();
120 print "Proxy started on port ".$self->{proxy_port}."\n";
122 warn "Failed creating proxy socket (".$proxaddr.",0): $!\n";
125 return bless $self, $class;
132 $self->{proxy_sock}->close() if $self->{proxy_sock};
139 $self->{cipherc} = "";
140 $self->{ciphersuitec} = "";
141 $self->{flight} = -1;
142 $self->{direction} = -1;
143 $self->{partial} = ["", ""];
144 $self->{record_list} = [];
145 $self->{message_list} = [];
146 $self->{clientflags} = "";
147 $self->{sessionfile} = undef;
148 $self->{clientpid} = 0;
150 $ciphersuite = undef;
152 TLSProxy::Message->clear();
153 TLSProxy::Record->clear();
161 $self->{ciphers} = "AES128-SHA";
162 $self->{ciphersuitess} = "TLS_AES_128_GCM_SHA256";
163 $self->{serverflags} = "";
164 $self->{serverconnects} = 1;
165 $self->{serverpid} = 0;
185 sub connect_to_server
188 my $servaddr = $self->{server_addr};
190 $servaddr =~ s/[\[\]]//g; # Remove [ and ]
192 $self->{server_sock} = $IP_factory->(PeerAddr => $servaddr,
193 PeerPort => $self->{server_port},
195 or die "unable to connect: $!\n";
203 if ($self->{proxy_sock} == 0) {
207 my $execcmd = $self->execute
208 ." s_server -max_protocol TLSv1.3 -no_comp -rev -engine ossltest"
209 ." -accept 0 -cert ".$self->cert." -cert2 ".$self->cert
210 ." -naccept ".$self->serverconnects;
211 unless ($self->supports_IPv6) {
214 if ($self->ciphers ne "") {
215 $execcmd .= " -cipher ".$self->ciphers;
217 if ($self->ciphersuitess ne "") {
218 $execcmd .= " -ciphersuites ".$self->ciphersuitess;
220 if ($self->serverflags ne "") {
221 $execcmd .= " ".$self->serverflags;
224 print STDERR "Server command: $execcmd\n";
227 open(my $savedin, "<&STDIN");
229 # Temporarily replace STDIN so that sink process can inherit it...
230 $pid = open(STDIN, "$execcmd |") or die "Failed to $execcmd: $!\n";
231 $self->{real_serverpid} = $pid;
233 # Process the output from s_server until we find the ACCEPT line, which
234 # tells us what the accepting address and port are.
237 s/\R$//; # Better chomp
238 next unless (/^ACCEPT\s.*:(\d+)$/);
239 $self->{server_port} = $1;
243 if ($self->{server_port} == 0) {
244 # This actually means that s_server exited, because otherwise
245 # we would still searching for ACCEPT...
247 die "no ACCEPT detected in '$execcmd' output: $?\n";
250 # Just make sure everything else is simply printed [as separate lines].
251 # The sub process simply inherits our STD* and will keep consuming
252 # server's output and printing it as long as there is anything there,
256 if (eval { require Win32::Process; 1; }) {
257 if (Win32::Process::Create(my $h, $^X, "perl -ne print", 0, 0, ".")) {
258 $pid = $h->GetProcessID();
259 $self->{proc_handle} = $h; # hold handle till next round [or exit]
261 $error = Win32::FormatMessage(Win32::GetLastError());
264 if (defined($pid = fork)) {
265 $pid or exec("$^X -ne print") or exit($!);
271 # Change back to original stdin
272 open(STDIN, "<&", $savedin);
275 if (!defined($pid)) {
276 kill(3, $self->{real_serverpid});
277 die "Failed to capture s_server's output: $error\n";
280 $self->{serverpid} = $pid;
282 print STDERR "Server responds on ",
283 $self->{server_addr}, ":", $self->{server_port}, "\n";
285 # Connect right away...
286 $self->connect_to_server();
288 return $self->clientstart;
295 if ($self->execute) {
297 my $execcmd = $self->execute
298 ." s_client -max_protocol TLSv1.3 -engine ossltest -connect "
299 .($self->proxy_addr).":".($self->proxy_port);
300 unless ($self->supports_IPv6) {
303 if ($self->cipherc ne "") {
304 $execcmd .= " -cipher ".$self->cipherc;
306 if ($self->ciphersuitesc ne "") {
307 $execcmd .= " -ciphersuites ".$self->ciphersuitesc;
309 if ($self->clientflags ne "") {
310 $execcmd .= " ".$self->clientflags;
312 if (defined $self->sessionfile) {
313 $execcmd .= " -ign_eof";
316 print STDERR "Client command: $execcmd\n";
319 open(my $savedout, ">&STDOUT");
320 # If we open pipe with new descriptor, attempt to close it,
321 # explicitly or implicitly, would incur waitpid and effectively
323 if (!($pid = open(STDOUT, "| $execcmd"))) {
325 kill(3, $self->{real_serverpid});
326 die "Failed to $execcmd: $err\n";
328 $self->{clientpid} = $pid;
330 # queue [magic] input
331 print $self->reneg ? "R" : "test";
333 # this closes client's stdin without waiting for its pid
334 open(STDOUT, ">&", $savedout);
338 # Wait for incoming connection from client
339 my $fdset = IO::Select->new($self->{proxy_sock});
340 if (!$fdset->can_read(1)) {
341 kill(3, $self->{real_serverpid});
342 die "s_client didn't try to connect\n";
346 if(!($client_sock = $self->{proxy_sock}->accept())) {
347 warn "Failed accepting incoming connection: $!\n";
351 print "Connection opened\n";
353 my $server_sock = $self->{server_sock};
356 #Wait for either the server socket or the client socket to become readable
357 $fdset = IO::Select->new($server_sock, $client_sock);
360 local $SIG{PIPE} = "IGNORE";
362 && (!(TLSProxy::Message->end)
363 || (defined $self->sessionfile()
364 && (-s $self->sessionfile()) == 0))
366 if (!(@ready = $fdset->can_read(1))) {
370 foreach my $hand (@ready) {
371 if ($hand == $server_sock) {
372 if ($server_sock->sysread($indata, 16384)) {
373 if ($indata = $self->process_packet(1, $indata)) {
374 $client_sock->syswrite($indata) or goto END;
378 $fdset->remove($server_sock);
379 $client_sock->shutdown(SHUT_WR);
381 } elsif ($hand == $client_sock) {
382 if ($client_sock->sysread($indata, 16384)) {
383 if ($indata = $self->process_packet(0, $indata)) {
384 $server_sock->syswrite($indata) or goto END;
388 $fdset->remove($client_sock);
389 $server_sock->shutdown(SHUT_WR);
392 kill(3, $self->{real_serverpid});
393 die "Unexpected handle";
399 kill(3, $self->{real_serverpid});
400 die "No progress made";
404 print "Connection closed\n";
406 $server_sock->close();
407 $self->{server_sock} = undef;
410 #Closing this also kills the child process
411 $client_sock->close();
415 if (--$self->{serverconnects} == 0) {
416 $pid = $self->{serverpid};
417 print "Waiting for 'perl -ne print' process to close: $pid...\n";
418 $pid = waitpid($pid, 0);
420 die "exit code $? from 'perl -ne print' process\n" if $? != 0;
421 } elsif ($pid == 0) {
422 kill(3, $self->{real_serverpid});
423 die "lost control over $self->{serverpid}?";
425 $pid = $self->{real_serverpid};
426 print "Waiting for s_server process to close: $pid...\n";
427 # it's done already, just collect the exit code [and reap]...
429 die "exit code $? from s_server process\n" if $? != 0;
431 # It's a bit counter-intuitive spot to make next connection to
432 # the s_server. Rationale is that established connection works
433 # as syncronization point, in sense that this way we know that
434 # s_server is actually done with current session...
435 $self->connect_to_server();
437 $pid = $self->{clientpid};
438 print "Waiting for client process to close: $pid...\n";
446 my ($self, $server, $packet) = @_;
453 print "Received server packet\n";
455 print "Received client packet\n";
458 if ($self->{direction} != $server) {
459 $self->{flight} = $self->{flight} + 1;
460 $self->{direction} = $server;
463 print "Packet length = ".length($packet)."\n";
464 print "Processing flight ".$self->flight."\n";
466 #Return contains the list of record found in the packet followed by the
467 #list of messages in those records and any partial message
468 my @ret = TLSProxy::Record->get_records($server, $self->flight,
469 $self->{partial}[$server].$packet);
470 $self->{partial}[$server] = $ret[2];
471 push @{$self->{record_list}}, @{$ret[0]};
472 push @{$self->{message_list}}, @{$ret[1]};
476 if (scalar(@{$ret[0]}) == 0) {
480 #Finished parsing. Call user provided filter here
481 if (defined $self->filter) {
482 $self->filter->($self);
485 #Reconstruct the packet
487 foreach my $record (@{$self->record_list}) {
488 $packet .= $record->reconstruct_record($server);
491 print "Forwarded packet length = ".length($packet)."\n\n";
500 return $self->{execute};
505 return $self->{cert};
510 return $self->{debug};
515 return $self->{flight};
520 return $self->{record_list};
525 return $self->{success};
540 return $self->{proxy_addr};
545 return $self->{proxy_port};
550 return $self->{server_addr};
555 return $self->{server_port};
560 return $self->{serverpid};
565 return $self->{clientpid};
568 #Read/write accessors
573 $self->{filter} = shift;
575 return $self->{filter};
581 $self->{cipherc} = shift;
583 return $self->{cipherc};
589 $self->{ciphersuitesc} = shift;
591 return $self->{ciphersuitesc};
597 $self->{ciphers} = shift;
599 return $self->{ciphers};
605 $self->{ciphersuitess} = shift;
607 return $self->{ciphersuitess};
613 $self->{serverflags} = shift;
615 return $self->{serverflags};
621 $self->{clientflags} = shift;
623 return $self->{clientflags};
629 $self->{serverconnects} = shift;
631 return $self->{serverconnects};
633 # This is a bit ugly because the caller is responsible for keeping the records
634 # in sync with the updated message list; simply updating the message list isn't
635 # sufficient to get the proxy to forward the new message.
636 # But it does the trick for the one test (test_sslsessiontick) that needs it.
641 $self->{message_list} = shift;
643 return $self->{message_list};
650 for (my $i = 0; $i < $length; $i++) {
669 $self->{reneg} = shift;
671 return $self->{reneg};
674 #Setting a sessionfile means that the client will not close until the given
675 #file exists. This is useful in TLSv1.3 where otherwise s_client will close
676 #immediately at the end of the handshake, but before the session has been
677 #received from the server. A side effect of this is that s_client never sends
678 #a close_notify, so instead we consider success to be when it sends application
679 #data over the connection.
684 $self->{sessionfile} = shift;
685 TLSProxy::Message->successondata(1);
687 return $self->{sessionfile};
694 $ciphersuite = shift;