Fix a Proxy race condition
[openssl.git] / util / TLSProxy / Proxy.pm
index aafd902bca4ac112668dfa54c71ee0da22b7b24d..5c363e76eda5398f2f8d6d79ad6f6070ec0694a7 100644 (file)
@@ -16,13 +16,20 @@ use IO::Select;
 use TLSProxy::Record;
 use TLSProxy::Message;
 use TLSProxy::ClientHello;
+use TLSProxy::HelloRetryRequest;
 use TLSProxy::ServerHello;
+use TLSProxy::EncryptedExtensions;
+use TLSProxy::Certificate;
+use TLSProxy::CertificateVerify;
 use TLSProxy::ServerKeyExchange;
 use TLSProxy::NewSessionTicket;
 
 my $have_IPv6 = 0;
 my $IP_factory;
 
+my $is_tls13 = 0;
+my $ciphersuite = undef;
+
 sub new
 {
     my $class = shift;
@@ -42,13 +49,16 @@ sub new
         clientflags => "",
         serverconnects => 1,
         serverpid => 0,
+        clientpid => 0,
+        reneg => 0,
+        sessionfile => undef,
 
         #Public read
         execute => $execute,
         cert => $cert,
         debug => $debug,
         cipherc => "",
-        ciphers => "AES128-SHA",
+        ciphers => "AES128-SHA:TLS13-AES-128-GCM-SHA256",
         flight => 0,
         record_list => [],
         message_list => [],
@@ -103,6 +113,10 @@ sub clearClient
     $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();
@@ -113,10 +127,11 @@ sub clear
     my $self = shift;
 
     $self->clearClient;
-    $self->{ciphers} = "AES128-SHA";
+    $self->{ciphers} = "AES128-SHA:TLS13-AES-128-GCM-SHA256";
     $self->{serverflags} = "";
     $self->{serverconnects} = 1;
     $self->{serverpid} = 0;
+    $self->{reneg} = 0;
 }
 
 sub restart
@@ -150,18 +165,22 @@ sub start
         my $execcmd = $self->execute
             ." s_server -no_comp -rev -engine ossltest -accept "
             .($self->server_port)
-            ." -cert ".$self->cert." -naccept ".$self->serverconnects;
+            ." -cert ".$self->cert." -cert2 ".$self->cert
+            ." -naccept ".$self->serverconnects;
         if ($self->ciphers ne "") {
             $execcmd .= " -cipher ".$self->ciphers;
         }
         if ($self->serverflags ne "") {
             $execcmd .= " ".$self->serverflags;
         }
+        if ($self->debug) {
+            print STDERR "Server command: $execcmd\n";
+        }
         exec($execcmd);
     }
     $self->serverpid($pid);
 
-    $self->clientstart;
+    return $self->clientstart;
 }
 
 sub clientstart
@@ -188,7 +207,8 @@ sub clientstart
     if ($proxy_sock) {
         print "Proxy started on port ".$self->proxy_port."\n";
     } else {
-        die "Failed creating proxy socket (".$proxaddr.",".$self->proxy_port."): $!\n";
+        warn "Failed creating proxy socket (".$proxaddr.",".$self->proxy_port."): $!\n";
+        return 0;
     }
 
     if ($self->execute) {
@@ -199,7 +219,13 @@ sub clientstart
                     or die "Failed to redirect stdout: $!";
                 open(STDERR, ">&STDOUT");
             }
-            my $execcmd = "echo test | ".$self->execute
+            my $echostr;
+            if ($self->reneg()) {
+                $echostr = "R";
+            } else {
+                $echostr = "test";
+            }
+            my $execcmd = "echo ".$echostr." | ".$self->execute
                  ." s_client -engine ossltest -connect "
                  .($self->proxy_addr).":".($self->proxy_port);
             if ($self->cipherc ne "") {
@@ -208,13 +234,23 @@ sub clientstart
             if ($self->clientflags ne "") {
                 $execcmd .= " ".$self->clientflags;
             }
+            if (defined $self->sessionfile) {
+                $execcmd .= " -ign_eof";
+            }
+            if ($self->debug) {
+                print STDERR "Client command: $execcmd\n";
+            }
             exec($execcmd);
         }
+        $self->clientpid($pid);
     }
 
     # Wait for incoming connection from client
-    my $client_sock = $proxy_sock->accept()
-        or die "Failed accepting incoming connection: $!\n";
+    my $client_sock;
+    if(!($client_sock = $proxy_sock->accept())) {
+        warn "Failed accepting incoming connection: $!\n";
+        return 0;
+    }
 
     print "Connection opened\n";
 
@@ -226,22 +262,27 @@ sub clientstart
     do {
         my $servaddr = $self->server_addr;
         $servaddr =~ s/[\[\]]//g; # Remove [ and ]
-        $server_sock = $IP_factory->(
-            PeerAddr => $servaddr,
-            PeerPort => $self->server_port,
-            MultiHomed => 1,
-            Proto => 'tcp'
-        );
+        eval {
+            $server_sock = $IP_factory->(
+                PeerAddr => $servaddr,
+                PeerPort => $self->server_port,
+                MultiHomed => 1,
+                Proto => 'tcp'
+            );
+        };
 
         $retry--;
-        if ($@ || !defined($server_sock)) {
+        #Some buggy IP factories can return a defined server_sock that hasn't
+        #actually connected, so we check peerport too
+        if ($@ || !defined($server_sock) || !defined($server_sock->peerport)) {
             $server_sock->close() if defined($server_sock);
             undef $server_sock;
             if ($retry) {
                 #Sleep for a short while
                 select(undef, undef, undef, 0.1);
             } else {
-                die "Failed to start up server (".$servaddr.",".$self->server_port."): $!\n";
+                warn "Failed to start up server (".$servaddr.",".$self->server_port."): $!\n";
+                return 0;
             }
         }
     } while (!$server_sock);
@@ -252,23 +293,31 @@ sub clientstart
 
     #Wait for either the server socket or the client socket to become readable
     my @ready;
-    while(!(TLSProxy::Message->end) && (@ready = $sel->can_read)) {
+    my $ctr = 0;
+    while(     (!(TLSProxy::Message->end)
+                || (defined $self->sessionfile()
+                    && (-s $self->sessionfile()) == 0))
+            && $ctr < 10
+            && (@ready = $sel->can_read(1))) {
         foreach my $hand (@ready) {
             if ($hand == $server_sock) {
                 $server_sock->sysread($indata, 16384) or goto END;
                 $indata = $self->process_packet(1, $indata);
                 $client_sock->syswrite($indata);
+                $ctr = 0;
             } elsif ($hand == $client_sock) {
                 $client_sock->sysread($indata, 16384) or goto END;
                 $indata = $self->process_packet(0, $indata);
                 $server_sock->syswrite($indata);
+                $ctr = 0;
             } else {
-                print "Err\n";
-                goto END;
+                $ctr++
             }
         }
     }
 
+    die "No progress made" if $ctr >= 10;
+
     END:
     print "Connection closed\n";
     if($server_sock) {
@@ -290,7 +339,13 @@ sub clientstart
         print "Waiting for server process to close: "
               .$self->serverpid."\n";
         waitpid( $self->serverpid, 0);
+        die "exit code $? from server process\n" if $? != 0;
     }
+    die "clientpid is zero\n" if $self->clientpid == 0;
+    print "Waiting for client process to close: ".$self->clientpid."\n";
+    waitpid($self->clientpid, 0);
+
+    return 1;
 }
 
 sub process_packet
@@ -330,7 +385,7 @@ sub process_packet
         if ($record->flight != $self->flight) {
             next;
         }
-        $packet .= $record->reconstruct_record();
+        $packet .= $record->reconstruct_record($server);
     }
 
     $self->{flight} = $self->{flight} + 1;
@@ -387,7 +442,7 @@ sub proxy_addr
 {
     my $self = shift;
     if (@_) {
-      $self->{proxy_addr} = shift;
+        $self->{proxy_addr} = shift;
     }
     return $self->{proxy_addr};
 }
@@ -395,7 +450,7 @@ sub proxy_port
 {
     my $self = shift;
     if (@_) {
-      $self->{proxy_port} = shift;
+        $self->{proxy_port} = shift;
     }
     return $self->{proxy_port};
 }
@@ -403,7 +458,7 @@ sub server_addr
 {
     my $self = shift;
     if (@_) {
-      $self->{server_addr} = shift;
+        $self->{server_addr} = shift;
     }
     return $self->{server_addr};
 }
@@ -411,7 +466,7 @@ sub server_port
 {
     my $self = shift;
     if (@_) {
-      $self->{server_port} = shift;
+        $self->{server_port} = shift;
     }
     return $self->{server_port};
 }
@@ -419,7 +474,7 @@ sub filter
 {
     my $self = shift;
     if (@_) {
-      $self->{filter} = shift;
+        $self->{filter} = shift;
     }
     return $self->{filter};
 }
@@ -427,7 +482,7 @@ sub cipherc
 {
     my $self = shift;
     if (@_) {
-      $self->{cipherc} = shift;
+        $self->{cipherc} = shift;
     }
     return $self->{cipherc};
 }
@@ -435,7 +490,7 @@ sub ciphers
 {
     my $self = shift;
     if (@_) {
-      $self->{ciphers} = shift;
+        $self->{ciphers} = shift;
     }
     return $self->{ciphers};
 }
@@ -443,7 +498,7 @@ sub serverflags
 {
     my $self = shift;
     if (@_) {
-      $self->{serverflags} = shift;
+        $self->{serverflags} = shift;
     }
     return $self->{serverflags};
 }
@@ -451,7 +506,7 @@ sub clientflags
 {
     my $self = shift;
     if (@_) {
-      $self->{clientflags} = shift;
+        $self->{clientflags} = shift;
     }
     return $self->{clientflags};
 }
@@ -459,7 +514,7 @@ sub serverconnects
 {
     my $self = shift;
     if (@_) {
-      $self->{serverconnects} = shift;
+        $self->{serverconnects} = shift;
     }
     return $self->{serverconnects};
 }
@@ -479,8 +534,70 @@ sub serverpid
 {
     my $self = shift;
     if (@_) {
-      $self->{serverpid} = shift;
+        $self->{serverpid} = shift;
     }
     return $self->{serverpid};
 }
+sub clientpid
+{
+    my $self = shift;
+    if (@_) {
+        $self->{clientpid} = shift;
+    }
+    return $self->{clientpid};
+}
+
+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;