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