123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729 |
- # Copyright 2016-2019 The OpenSSL Project Authors. All Rights Reserved.
- #
- # Licensed under the OpenSSL license (the "License"). You may not use
- # this file except in compliance with the License. You can obtain a copy
- # in the file LICENSE in the source distribution or at
- # https://www.openssl.org/source/license.html
- use strict;
- use POSIX ":sys_wait_h";
- package TLSProxy::Proxy;
- use File::Spec;
- use IO::Socket;
- use IO::Select;
- use TLSProxy::Record;
- use TLSProxy::Message;
- use TLSProxy::ClientHello;
- use TLSProxy::ServerHello;
- use TLSProxy::EncryptedExtensions;
- use TLSProxy::Certificate;
- use TLSProxy::CertificateRequest;
- use TLSProxy::CertificateVerify;
- use TLSProxy::ServerKeyExchange;
- use TLSProxy::NewSessionTicket;
- my $have_IPv6;
- my $IP_factory;
- BEGIN
- {
- # IO::Socket::IP is on the core module list, IO::Socket::INET6 isn't.
- # However, IO::Socket::INET6 is older and is said to be more widely
- # deployed for the moment, and may have less bugs, so we try the latter
- # first, then fall back on the core modules. Worst case scenario, we
- # fall back to IO::Socket::INET, only supports IPv4.
- eval {
- require IO::Socket::INET6;
- my $s = IO::Socket::INET6->new(
- LocalAddr => "::1",
- LocalPort => 0,
- Listen=>1,
- );
- $s or die "\n";
- $s->close();
- };
- if ($@ eq "") {
- $IP_factory = sub { IO::Socket::INET6->new(Domain => AF_INET6, @_); };
- $have_IPv6 = 1;
- } else {
- eval {
- require IO::Socket::IP;
- my $s = IO::Socket::IP->new(
- LocalAddr => "::1",
- LocalPort => 0,
- Listen=>1,
- );
- $s or die "\n";
- $s->close();
- };
- if ($@ eq "") {
- $IP_factory = sub { IO::Socket::IP->new(@_); };
- $have_IPv6 = 1;
- } else {
- $IP_factory = sub { IO::Socket::INET->new(@_); };
- $have_IPv6 = 0;
- }
- }
- }
- my $is_tls13 = 0;
- my $ciphersuite = undef;
- sub new
- {
- my $class = shift;
- my ($filter,
- $execute,
- $cert,
- $debug) = @_;
- my $self = {
- #Public read/write
- proxy_addr => $have_IPv6 ? "[::1]" : "127.0.0.1",
- filter => $filter,
- serverflags => "",
- clientflags => "",
- serverconnects => 1,
- reneg => 0,
- sessionfile => undef,
- #Public read
- proxy_port => 0,
- server_port => 0,
- serverpid => 0,
- clientpid => 0,
- execute => $execute,
- cert => $cert,
- debug => $debug,
- cipherc => "",
- ciphersuitesc => "",
- ciphers => "AES128-SHA",
- ciphersuitess => "TLS_AES_128_GCM_SHA256",
- flight => -1,
- direction => -1,
- partial => ["", ""],
- record_list => [],
- message_list => [],
- };
- # Create the Proxy socket
- my $proxaddr = $self->{proxy_addr};
- $proxaddr =~ s/[\[\]]//g; # Remove [ and ]
- my @proxyargs = (
- LocalHost => $proxaddr,
- LocalPort => 0,
- Proto => "tcp",
- Listen => SOMAXCONN,
- );
- if (my $sock = $IP_factory->(@proxyargs)) {
- $self->{proxy_sock} = $sock;
- $self->{proxy_port} = $sock->sockport();
- $self->{proxy_addr} = $sock->sockhost();
- $self->{proxy_addr} =~ s/(.*:.*)/[$1]/;
- print "Proxy started on port ",
- "$self->{proxy_addr}:$self->{proxy_port}\n";
- # use same address for s_server
- $self->{server_addr} = $self->{proxy_addr};
- } else {
- warn "Failed creating proxy socket (".$proxaddr.",0): $!\n";
- }
- return bless $self, $class;
- }
- sub DESTROY
- {
- my $self = shift;
- $self->{proxy_sock}->close() if $self->{proxy_sock};
- }
- sub clearClient
- {
- my $self = shift;
- $self->{cipherc} = "";
- $self->{ciphersuitec} = "";
- $self->{flight} = -1;
- $self->{direction} = -1;
- $self->{partial} = ["", ""];
- $self->{record_list} = [];
- $self->{message_list} = [];
- $self->{clientflags} = "";
- $self->{sessionfile} = undef;
- $self->{clientpid} = 0;
- $is_tls13 = 0;
- $ciphersuite = undef;
- TLSProxy::Message->clear();
- TLSProxy::Record->clear();
- }
- sub clear
- {
- my $self = shift;
- $self->clearClient;
- $self->{ciphers} = "AES128-SHA";
- $self->{ciphersuitess} = "TLS_AES_128_GCM_SHA256";
- $self->{serverflags} = "";
- $self->{serverconnects} = 1;
- $self->{serverpid} = 0;
- $self->{reneg} = 0;
- }
- sub restart
- {
- my $self = shift;
- $self->clear;
- $self->start;
- }
- sub clientrestart
- {
- my $self = shift;
- $self->clear;
- $self->clientstart;
- }
- sub connect_to_server
- {
- my $self = shift;
- my $servaddr = $self->{server_addr};
- $servaddr =~ s/[\[\]]//g; # Remove [ and ]
- my $sock = $IP_factory->(PeerAddr => $servaddr,
- PeerPort => $self->{server_port},
- Proto => 'tcp');
- if (!defined($sock)) {
- my $err = $!;
- kill(3, $self->{real_serverpid});
- die "unable to connect: $err\n";
- }
- $self->{server_sock} = $sock;
- }
- sub start
- {
- my ($self) = shift;
- my $pid;
- if ($self->{proxy_sock} == 0) {
- return 0;
- }
- my $execcmd = $self->execute
- ." s_server -max_protocol TLSv1.3 -no_comp -rev -engine ossltest"
- #In TLSv1.3 we issue two session tickets. The default session id
- #callback gets confused because the ossltest engine causes the same
- #session id to be created twice due to the changed random number
- #generation. Using "-ext_cache" replaces the default callback with a
- #different one that doesn't get confused.
- ." -ext_cache"
- ." -accept $self->{server_addr}:0"
- ." -cert ".$self->cert." -cert2 ".$self->cert
- ." -naccept ".$self->serverconnects;
- if ($self->ciphers ne "") {
- $execcmd .= " -cipher ".$self->ciphers;
- }
- if ($self->ciphersuitess ne "") {
- $execcmd .= " -ciphersuites ".$self->ciphersuitess;
- }
- if ($self->serverflags ne "") {
- $execcmd .= " ".$self->serverflags;
- }
- if ($self->debug) {
- print STDERR "Server command: $execcmd\n";
- }
- open(my $savedin, "<&STDIN");
- # Temporarily replace STDIN so that sink process can inherit it...
- $pid = open(STDIN, "$execcmd 2>&1 |") or die "Failed to $execcmd: $!\n";
- $self->{real_serverpid} = $pid;
- # Process the output from s_server until we find the ACCEPT line, which
- # tells us what the accepting address and port are.
- while (<>) {
- print;
- s/\R$//; # Better chomp
- next unless (/^ACCEPT\s.*:(\d+)$/);
- $self->{server_port} = $1;
- last;
- }
- if ($self->{server_port} == 0) {
- # This actually means that s_server exited, because otherwise
- # we would still searching for ACCEPT...
- waitpid($pid, 0);
- die "no ACCEPT detected in '$execcmd' output: $?\n";
- }
- # Just make sure everything else is simply printed [as separate lines].
- # The sub process simply inherits our STD* and will keep consuming
- # server's output and printing it as long as there is anything there,
- # out of our way.
- my $error;
- $pid = undef;
- if (eval { require Win32::Process; 1; }) {
- if (Win32::Process::Create(my $h, $^X, "perl -ne print", 0, 0, ".")) {
- $pid = $h->GetProcessID();
- $self->{proc_handle} = $h; # hold handle till next round [or exit]
- } else {
- $error = Win32::FormatMessage(Win32::GetLastError());
- }
- } else {
- if (defined($pid = fork)) {
- $pid or exec("$^X -ne print") or exit($!);
- } else {
- $error = $!;
- }
- }
- # Change back to original stdin
- open(STDIN, "<&", $savedin);
- close($savedin);
- if (!defined($pid)) {
- kill(3, $self->{real_serverpid});
- die "Failed to capture s_server's output: $error\n";
- }
- $self->{serverpid} = $pid;
- print STDERR "Server responds on ",
- "$self->{server_addr}:$self->{server_port}\n";
- # Connect right away...
- $self->connect_to_server();
- return $self->clientstart;
- }
- sub clientstart
- {
- my ($self) = shift;
- if ($self->execute) {
- my $pid;
- my $execcmd = $self->execute
- ." s_client -max_protocol TLSv1.3 -engine ossltest"
- ." -connect $self->{proxy_addr}:$self->{proxy_port}";
- if ($self->cipherc ne "") {
- $execcmd .= " -cipher ".$self->cipherc;
- }
- if ($self->ciphersuitesc ne "") {
- $execcmd .= " -ciphersuites ".$self->ciphersuitesc;
- }
- if ($self->clientflags ne "") {
- $execcmd .= " ".$self->clientflags;
- }
- if ($self->clientflags !~ m/-(no)?servername/) {
- $execcmd .= " -servername localhost";
- }
- if (defined $self->sessionfile) {
- $execcmd .= " -ign_eof";
- }
- if ($self->debug) {
- print STDERR "Client command: $execcmd\n";
- }
- open(my $savedout, ">&STDOUT");
- # If we open pipe with new descriptor, attempt to close it,
- # explicitly or implicitly, would incur waitpid and effectively
- # dead-lock...
- if (!($pid = open(STDOUT, "| $execcmd"))) {
- my $err = $!;
- kill(3, $self->{real_serverpid});
- die "Failed to $execcmd: $err\n";
- }
- $self->{clientpid} = $pid;
- # queue [magic] input
- print $self->reneg ? "R" : "test";
- # this closes client's stdin without waiting for its pid
- open(STDOUT, ">&", $savedout);
- close($savedout);
- }
- # Wait for incoming connection from client
- my $fdset = IO::Select->new($self->{proxy_sock});
- if (!$fdset->can_read(60)) {
- kill(3, $self->{real_serverpid});
- die "s_client didn't try to connect\n";
- }
- my $client_sock;
- if(!($client_sock = $self->{proxy_sock}->accept())) {
- warn "Failed accepting incoming connection: $!\n";
- return 0;
- }
- print "Connection opened\n";
- my $server_sock = $self->{server_sock};
- my $indata;
- #Wait for either the server socket or the client socket to become readable
- $fdset = IO::Select->new($server_sock, $client_sock);
- my @ready;
- my $ctr = 0;
- local $SIG{PIPE} = "IGNORE";
- $self->{saw_session_ticket} = undef;
- while($fdset->count && $ctr < 10) {
- if (defined($self->{sessionfile})) {
- # s_client got -ign_eof and won't be exiting voluntarily, so we
- # look for data *and* session ticket...
- last if TLSProxy::Message->success()
- && $self->{saw_session_ticket};
- }
- if (!(@ready = $fdset->can_read(1))) {
- $ctr++;
- next;
- }
- foreach my $hand (@ready) {
- if ($hand == $server_sock) {
- if ($server_sock->sysread($indata, 16384)) {
- if ($indata = $self->process_packet(1, $indata)) {
- $client_sock->syswrite($indata) or goto END;
- }
- $ctr = 0;
- } else {
- $fdset->remove($server_sock);
- $client_sock->shutdown(SHUT_WR);
- }
- } elsif ($hand == $client_sock) {
- if ($client_sock->sysread($indata, 16384)) {
- if ($indata = $self->process_packet(0, $indata)) {
- $server_sock->syswrite($indata) or goto END;
- }
- $ctr = 0;
- } else {
- $fdset->remove($client_sock);
- $server_sock->shutdown(SHUT_WR);
- }
- } else {
- kill(3, $self->{real_serverpid});
- die "Unexpected handle";
- }
- }
- }
- if ($ctr >= 10) {
- kill(3, $self->{real_serverpid});
- die "No progress made";
- }
- END:
- print "Connection closed\n";
- if($server_sock) {
- $server_sock->close();
- $self->{server_sock} = undef;
- }
- if($client_sock) {
- #Closing this also kills the child process
- $client_sock->close();
- }
- my $pid;
- if (--$self->{serverconnects} == 0) {
- $pid = $self->{serverpid};
- print "Waiting for 'perl -ne print' process to close: $pid...\n";
- $pid = waitpid($pid, 0);
- if ($pid > 0) {
- die "exit code $? from 'perl -ne print' process\n" if $? != 0;
- } elsif ($pid == 0) {
- kill(3, $self->{real_serverpid});
- die "lost control over $self->{serverpid}?";
- }
- $pid = $self->{real_serverpid};
- print "Waiting for s_server process to close: $pid...\n";
- # it's done already, just collect the exit code [and reap]...
- waitpid($pid, 0);
- die "exit code $? from s_server process\n" if $? != 0;
- } else {
- # It's a bit counter-intuitive spot to make next connection to
- # the s_server. Rationale is that established connection works
- # as synchronization point, in sense that this way we know that
- # s_server is actually done with current session...
- $self->connect_to_server();
- }
- $pid = $self->{clientpid};
- print "Waiting for s_client process to close: $pid...\n";
- waitpid($pid, 0);
- return 1;
- }
- sub process_packet
- {
- my ($self, $server, $packet) = @_;
- my $len_real;
- my $decrypt_len;
- my $data;
- my $recnum;
- if ($server) {
- print "Received server packet\n";
- } else {
- print "Received client packet\n";
- }
- if ($self->{direction} != $server) {
- $self->{flight} = $self->{flight} + 1;
- $self->{direction} = $server;
- }
- print "Packet length = ".length($packet)."\n";
- print "Processing flight ".$self->flight."\n";
- #Return contains the list of record found in the packet followed by the
- #list of messages in those records and any partial message
- my @ret = TLSProxy::Record->get_records($server, $self->flight,
- $self->{partial}[$server].$packet);
- $self->{partial}[$server] = $ret[2];
- push @{$self->{record_list}}, @{$ret[0]};
- push @{$self->{message_list}}, @{$ret[1]};
- print "\n";
- if (scalar(@{$ret[0]}) == 0 or length($ret[2]) != 0) {
- return "";
- }
- #Finished parsing. Call user provided filter here
- if (defined $self->filter) {
- $self->filter->($self);
- }
- #Take a note on NewSessionTicket
- foreach my $message (reverse @{$self->{message_list}}) {
- if ($message->{mt} == TLSProxy::Message::MT_NEW_SESSION_TICKET) {
- $self->{saw_session_ticket} = 1;
- last;
- }
- }
- #Reconstruct the packet
- $packet = "";
- foreach my $record (@{$self->record_list}) {
- $packet .= $record->reconstruct_record($server);
- }
- print "Forwarded packet length = ".length($packet)."\n\n";
- return $packet;
- }
- #Read accessors
- sub execute
- {
- my $self = shift;
- return $self->{execute};
- }
- sub cert
- {
- my $self = shift;
- return $self->{cert};
- }
- sub debug
- {
- my $self = shift;
- return $self->{debug};
- }
- sub flight
- {
- my $self = shift;
- return $self->{flight};
- }
- sub record_list
- {
- my $self = shift;
- return $self->{record_list};
- }
- sub success
- {
- my $self = shift;
- return $self->{success};
- }
- sub end
- {
- my $self = shift;
- return $self->{end};
- }
- sub supports_IPv6
- {
- my $self = shift;
- return $have_IPv6;
- }
- sub proxy_addr
- {
- my $self = shift;
- return $self->{proxy_addr};
- }
- sub proxy_port
- {
- my $self = shift;
- return $self->{proxy_port};
- }
- sub server_addr
- {
- my $self = shift;
- return $self->{server_addr};
- }
- sub server_port
- {
- my $self = shift;
- return $self->{server_port};
- }
- sub serverpid
- {
- my $self = shift;
- return $self->{serverpid};
- }
- sub clientpid
- {
- my $self = shift;
- return $self->{clientpid};
- }
- #Read/write accessors
- sub filter
- {
- my $self = shift;
- if (@_) {
- $self->{filter} = shift;
- }
- return $self->{filter};
- }
- sub cipherc
- {
- my $self = shift;
- if (@_) {
- $self->{cipherc} = shift;
- }
- return $self->{cipherc};
- }
- sub ciphersuitesc
- {
- my $self = shift;
- if (@_) {
- $self->{ciphersuitesc} = shift;
- }
- return $self->{ciphersuitesc};
- }
- sub ciphers
- {
- my $self = shift;
- if (@_) {
- $self->{ciphers} = shift;
- }
- return $self->{ciphers};
- }
- sub ciphersuitess
- {
- my $self = shift;
- if (@_) {
- $self->{ciphersuitess} = shift;
- }
- return $self->{ciphersuitess};
- }
- sub serverflags
- {
- my $self = shift;
- if (@_) {
- $self->{serverflags} = shift;
- }
- return $self->{serverflags};
- }
- sub clientflags
- {
- my $self = shift;
- if (@_) {
- $self->{clientflags} = shift;
- }
- return $self->{clientflags};
- }
- sub serverconnects
- {
- my $self = shift;
- if (@_) {
- $self->{serverconnects} = shift;
- }
- return $self->{serverconnects};
- }
- # This is a bit ugly because the caller is responsible for keeping the records
- # in sync with the updated message list; simply updating the message list isn't
- # sufficient to get the proxy to forward the new message.
- # But it does the trick for the one test (test_sslsessiontick) that needs it.
- sub message_list
- {
- my $self = shift;
- if (@_) {
- $self->{message_list} = shift;
- }
- return $self->{message_list};
- }
- sub fill_known_data
- {
- my $length = shift;
- my $ret = "";
- for (my $i = 0; $i < $length; $i++) {
- $ret .= chr($i);
- }
- return $ret;
- }
- sub is_tls13
- {
- my $class = shift;
- if (@_) {
- $is_tls13 = shift;
- }
- return $is_tls13;
- }
- sub reneg
- {
- my $self = shift;
- if (@_) {
- $self->{reneg} = shift;
- }
- return $self->{reneg};
- }
- #Setting a sessionfile means that the client will not close until the given
- #file exists. This is useful in TLSv1.3 where otherwise s_client will close
- #immediately at the end of the handshake, but before the session has been
- #received from the server. A side effect of this is that s_client never sends
- #a close_notify, so instead we consider success to be when it sends application
- #data over the connection.
- sub sessionfile
- {
- my $self = shift;
- if (@_) {
- $self->{sessionfile} = shift;
- TLSProxy::Message->successondata(1);
- }
- return $self->{sessionfile};
- }
- sub ciphersuite
- {
- my $class = shift;
- if (@_) {
- $ciphersuite = shift;
- }
- return $ciphersuite;
- }
- 1;
|