File Coverage

blib/lib/IO/Socket/SSL/Intercept.pm
Criterion Covered Total %
statement 15 103 14.5
branch 0 54 0.0
condition 0 11 0.0
subroutine 5 14 35.7
pod 4 6 66.6
total 24 188 12.7


line stmt bran cond sub pod time code
1              
2             package IO::Socket::SSL::Intercept;
3 1     1   486 use strict;
  1         1  
  1         29  
4 1     1   4 use warnings;
  1         1  
  1         75  
5 1     1   4 use Carp 'croak';
  1         2  
  1         43  
6 1     1   408 use IO::Socket::SSL::Utils;
  1         4  
  1         90  
7 1     1   5 use Net::SSLeay;
  1         1  
  1         1011  
8              
9             our $VERSION = '2.056';
10              
11              
12             sub new {
13 0     0 1   my ($class,%args) = @_;
14              
15 0           my $cacert = delete $args{proxy_cert};
16 0 0         if ( ! $cacert ) {
17 0 0         if ( my $f = delete $args{proxy_cert_file} ) {
18 0           $cacert = PEM_file2cert($f);
19             } else {
20 0           croak "no proxy_cert or proxy_cert_file given";
21             }
22             }
23              
24 0           my $cakey = delete $args{proxy_key};
25 0 0         if ( ! $cakey ) {
26 0 0         if ( my $f = delete $args{proxy_key_file} ) {
27 0           $cakey = PEM_file2key($f);
28             } else {
29 0           croak "no proxy_cert or proxy_cert_file given";
30             }
31             }
32              
33 0           my $certkey = delete $args{cert_key};
34 0 0         if ( ! $certkey ) {
35 0 0         if ( my $f = delete $args{cert_key_file} ) {
36 0           $certkey = PEM_file2key($f);
37             }
38             }
39              
40 0   0       my $cache = delete $args{cache} || {};
41 0 0         if (ref($cache) eq 'CODE') {
42             # check cache type
43 0           my $type = $cache->('type');
44 0 0         if (!$type) {
    0          
45             # old cache interface - change into new interface
46             # get: $cache->(fp)
47             # set: $cache->(fp,cert,key)
48 0           my $oc = $cache;
49             $cache = sub {
50 0     0     my ($fp,$create_cb) = @_;
51 0           my @ck = $oc->($fp);
52 0 0         $oc->($fp, @ck = &$create_cb) if !@ck;
53 0           return @ck;
54 0           };
55             } elsif ($type == 1) {
56             # current interface:
57             # get/set: $cache->(fp,cb_create)
58             } else {
59 0           die "invalid type of cache: $type";
60             }
61             }
62              
63             my $self = bless {
64             cacert => $cacert,
65             cakey => $cakey,
66             certkey => $certkey,
67             cache => $cache,
68             serial => delete $args{serial},
69 0           };
70 0           return $self;
71             }
72              
73             sub DESTROY {
74             # call various ssl _free routines
75 0 0   0     my $self = shift or return;
76 0           my @cert = (\$self->{cacert});
77 0           my @key = (\$self->{cakey}, \$self->{certkey});
78 0 0         if (ref($self->{cache}) ne 'HASH') {
79 0           my @v = values %{$self->{cache}};
  0            
80 0           push @cert, map { \$_->{cert} } @v;
  0            
81 0 0         push @key, map { \$_->{key} } @v if !$self->{certkey};
  0            
82             }
83 0           for (@cert) {
84 0 0         $$_ or next;
85 0           CERT_free($$_);
86 0           $$_ = undef;
87             }
88 0           for (@key) {
89 0 0         $$_ or next;
90 0           KEY_free($$_);
91 0           $$_ = undef;
92             }
93             }
94              
95             sub clone_cert {
96 0     0 1   my ($self,$old_cert,$clone_key) = @_;
97              
98 0           my $hash = CERT_asHash($old_cert);
99             my $create_cb = sub {
100             # if not in cache create new certificate based on original
101             # copy most but not all extensions
102 0 0   0     if (my $ext = $hash->{ext}) {
103             @$ext = grep {
104 0 0         defined($_->{sn}) && $_->{sn} !~m{^(?:
  0            
105             authorityInfoAccess |
106             subjectKeyIdentifier |
107             authorityKeyIdentifier |
108             certificatePolicies |
109             crlDistributionPoints
110             )$}x
111             } @$ext;
112             }
113             my ($clone,$key) = CERT_create(
114             %$hash,
115             ignore_invalid_args => 1,
116             issuer_cert => $self->{cacert},
117             issuer_key => $self->{cakey},
118             key => $self->{certkey},
119             serial =>
120             ! defined($self->{serial}) ? (unpack('L',$hash->{x509_digest_sha256}))[0] :
121             ref($self->{serial}) eq 'CODE' ? $self->{serial}($old_cert,$hash) :
122             ++$self->{serial},
123 0 0         );
    0          
124 0           return ($clone,$key);
125 0           };
126              
127 0   0       $clone_key ||= substr(unpack("H*", $hash->{x509_digest_sha256}),0,32);
128 0           my $c = $self->{cache};
129 0 0         return $c->($clone_key,$create_cb) if ref($c) eq 'CODE';
130              
131 0   0       my $e = $c->{$clone_key} ||= do {
132 0           my ($cert,$key) = &$create_cb;
133 0           { cert => $cert, key => $key };
134             };
135 0           $e->{atime} = time();
136 0           return ($e->{cert},$e->{key});
137             }
138              
139              
140 0     0 0   sub STORABLE_freeze { my $self = shift; $self->serialize() }
  0            
141 0     0 0   sub STORABLE_thaw { my ($class,undef,$data) = @_; $class->unserialize($data) }
  0            
142              
143             sub serialize {
144 0     0 1   my $self = shift;
145 0           my $data = pack("N",2); # version
146 0           $data .= pack("N/a", PEM_cert2string($self->{cacert}));
147 0           $data .= pack("N/a", PEM_key2string($self->{cakey}));
148 0 0         if ( $self->{certkey} ) {
149 0           $data .= pack("N/a", PEM_key2string($self->{certkey}));
150             } else {
151 0           $data .= pack("N/a", '');
152             }
153 0           $data .= pack("N",$self->{serial});
154 0 0         if ( ref($self->{cache}) eq 'HASH' ) {
155 0           while ( my($k,$v) = each %{ $self->{cache}} ) {
  0            
156             $data .= pack("N/aN/aN/aN", $k,
157             PEM_cert2string($v->{cert}),
158             $v->{key} ? PEM_key2string($v->{key}) : '',
159 0 0         $v->{atime});
160             }
161             }
162 0           return $data;
163             }
164              
165             sub unserialize {
166 0     0 1   my ($class,$data) = @_;
167 0 0         unpack("N",substr($data,0,4,'')) == 2 or
168             croak("serialized with wrong version");
169 0           ( my $cacert,my $cakey,my $certkey,my $serial,$data)
170             = unpack("N/aN/aN/aNa*",$data);
171 0 0 0       my $self = bless {
172             serial => $serial,
173             cacert => PEM_string2cert($cacert),
174             cakey => PEM_string2key($cakey),
175             $certkey ? ( certkey => PEM_string2key($certkey)):(),
176             }, ref($class)||$class;
177              
178 0 0         $self->{cache} = {} if $data ne '';
179 0           while ( $data ne '' ) {
180 0           (my $key,my $cert,my $certkey, my $atime,$data) = unpack("N/aN/aNa*",$data);
181 0 0         $self->{cache}{$key} = {
182             cert => PEM_string2cert($cert),
183             $key ? ( key => PEM_string2key($certkey)):(),
184             atime => $atime
185             };
186             }
187 0           return $self;
188             }
189              
190             1;
191              
192             __END__