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