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