VirtualBox

source: vbox/trunk/src/libs/openssl-3.0.1/util/perl/TLSProxy/Proxy.pm@ 94082

Last change on this file since 94082 was 94082, checked in by vboxsync, 3 years ago

libs/openssl-3.0.1: started applying and adjusting our OpenSSL changes to 3.0.1. bugref:10128

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

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette