Home | History | Annotate | Line # | Download | only in TLSProxy
      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 
      8 use strict;
      9 use POSIX ":sys_wait_h";
     10 
     11 package TLSProxy::Proxy;
     12 
     13 use File::Spec;
     14 use IO::Socket;
     15 use IO::Select;
     16 use TLSProxy::Record;
     17 use TLSProxy::Message;
     18 use TLSProxy::ClientHello;
     19 use TLSProxy::ServerHello;
     20 use TLSProxy::EncryptedExtensions;
     21 use TLSProxy::Certificate;
     22 use TLSProxy::CertificateRequest;
     23 use TLSProxy::CertificateVerify;
     24 use TLSProxy::ServerKeyExchange;
     25 use TLSProxy::NewSessionTicket;
     26 
     27 my $have_IPv6;
     28 my $IP_factory;
     29 
     30 BEGIN
     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 
     71 my $is_tls13 = 0;
     72 my $ciphersuite = undef;
     73 
     74 sub 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 
    137 sub DESTROY
    138 {
    139     my $self = shift;
    140 
    141     $self->{proxy_sock}->close() if $self->{proxy_sock};
    142 }
    143 
    144 sub 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 
    165 sub 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 
    178 sub restart
    179 {
    180     my $self = shift;
    181 
    182     $self->clear;
    183     $self->start;
    184 }
    185 
    186 sub clientrestart
    187 {
    188     my $self = shift;
    189 
    190     $self->clear;
    191     $self->clientstart;
    192 }
    193 
    194 sub 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 
    213 sub 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 
    310 sub 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 
    466 sub 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
    527 sub execute
    528 {
    529     my $self = shift;
    530     return $self->{execute};
    531 }
    532 sub cert
    533 {
    534     my $self = shift;
    535     return $self->{cert};
    536 }
    537 sub debug
    538 {
    539     my $self = shift;
    540     return $self->{debug};
    541 }
    542 sub flight
    543 {
    544     my $self = shift;
    545     return $self->{flight};
    546 }
    547 sub record_list
    548 {
    549     my $self = shift;
    550     return $self->{record_list};
    551 }
    552 sub success
    553 {
    554     my $self = shift;
    555     return $self->{success};
    556 }
    557 sub end
    558 {
    559     my $self = shift;
    560     return $self->{end};
    561 }
    562 sub supports_IPv6
    563 {
    564     my $self = shift;
    565     return $have_IPv6;
    566 }
    567 sub proxy_addr
    568 {
    569     my $self = shift;
    570     return $self->{proxy_addr};
    571 }
    572 sub proxy_port
    573 {
    574     my $self = shift;
    575     return $self->{proxy_port};
    576 }
    577 sub server_addr
    578 {
    579     my $self = shift;
    580     return $self->{server_addr};
    581 }
    582 sub server_port
    583 {
    584     my $self = shift;
    585     return $self->{server_port};
    586 }
    587 sub serverpid
    588 {
    589     my $self = shift;
    590     return $self->{serverpid};
    591 }
    592 sub clientpid
    593 {
    594     my $self = shift;
    595     return $self->{clientpid};
    596 }
    597 
    598 #Read/write accessors
    599 sub filter
    600 {
    601     my $self = shift;
    602     if (@_) {
    603         $self->{filter} = shift;
    604     }
    605     return $self->{filter};
    606 }
    607 sub cipherc
    608 {
    609     my $self = shift;
    610     if (@_) {
    611         $self->{cipherc} = shift;
    612     }
    613     return $self->{cipherc};
    614 }
    615 sub ciphersuitesc
    616 {
    617     my $self = shift;
    618     if (@_) {
    619         $self->{ciphersuitesc} = shift;
    620     }
    621     return $self->{ciphersuitesc};
    622 }
    623 sub ciphers
    624 {
    625     my $self = shift;
    626     if (@_) {
    627         $self->{ciphers} = shift;
    628     }
    629     return $self->{ciphers};
    630 }
    631 sub ciphersuitess
    632 {
    633     my $self = shift;
    634     if (@_) {
    635         $self->{ciphersuitess} = shift;
    636     }
    637     return $self->{ciphersuitess};
    638 }
    639 sub serverflags
    640 {
    641     my $self = shift;
    642     if (@_) {
    643         $self->{serverflags} = shift;
    644     }
    645     return $self->{serverflags};
    646 }
    647 sub clientflags
    648 {
    649     my $self = shift;
    650     if (@_) {
    651         $self->{clientflags} = shift;
    652     }
    653     return $self->{clientflags};
    654 }
    655 sub 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.
    667 sub message_list
    668 {
    669     my $self = shift;
    670     if (@_) {
    671         $self->{message_list} = shift;
    672     }
    673     return $self->{message_list};
    674 }
    675 
    676 sub 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 
    686 sub is_tls13
    687 {
    688     my $class = shift;
    689     if (@_) {
    690         $is_tls13 = shift;
    691     }
    692     return $is_tls13;
    693 }
    694 
    695 sub 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.
    710 sub sessionfile
    711 {
    712     my $self = shift;
    713     if (@_) {
    714         $self->{sessionfile} = shift;
    715         TLSProxy::Message->successondata(1);
    716     }
    717     return $self->{sessionfile};
    718 }
    719 
    720 sub ciphersuite
    721 {
    722     my $class = shift;
    723     if (@_) {
    724         $ciphersuite = shift;
    725     }
    726     return $ciphersuite;
    727 }
    728 
    729 1;
    730