File Coverage

blib/lib/IO/Socket/SSL/Intercept.pm
Criterion Covered Total %
statement 15 96 15.6
branch 0 52 0.0
condition 0 11 0.0
subroutine 5 14 35.7
pod 4 6 66.6
total 24 179 13.4


line stmt bran cond sub pod time code
1              
2             package IO::Socket::SSL::Intercept;
3 1     1   517 use strict;
  1         3  
  1         30  
4 1     1   5 use warnings;
  1         2  
  1         32  
5 1     1   5 use Carp 'croak';
  1         3  
  1         47  
6 1     1   490 use IO::Socket::SSL::Utils;
  1         3  
  1         98  
7 1     1   7 use Net::SSLeay;
  1         5  
  1         1171  
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 0         for ( \$self->{cacert},
77 0           map { \$_->{cert} } ref($self->{cache}) ne 'CODE' ? values %{$self->{cache}} :()) {
  0            
78 0 0         $$_ or next;
79 0           CERT_free($$_);
80 0           $$_ = undef;
81             }
82 0           for ( \$self->{cakey}, \$self->{pubkey} ) {
83 0 0         $$_ or next;
84 0           KEY_free($$_);
85 0           $$_ = undef;
86             }
87             }
88              
89             sub clone_cert {
90 0     0 1   my ($self,$old_cert,$clone_key) = @_;
91              
92 0           my $hash = CERT_asHash($old_cert);
93             my $create_cb = sub {
94             # if not in cache create new certificate based on original
95             # copy most but not all extensions
96 0 0   0     if (my $ext = $hash->{ext}) {
97             @$ext = grep {
98 0 0         defined($_->{sn}) && $_->{sn} !~m{^(?:
  0            
99             authorityInfoAccess |
100             subjectKeyIdentifier |
101             authorityKeyIdentifier |
102             certificatePolicies |
103             crlDistributionPoints
104             )$}x
105             } @$ext;
106             }
107             my ($clone,$key) = CERT_create(
108             %$hash,
109             ignore_invalid_args => 1,
110             issuer_cert => $self->{cacert},
111             issuer_key => $self->{cakey},
112             key => $self->{certkey},
113             serial =>
114             ! defined($self->{serial}) ? (unpack('L',$hash->{x509_digest_sha256}))[0] :
115             ref($self->{serial}) eq 'CODE' ? $self->{serial}($old_cert,$hash) :
116             ++$self->{serial},
117 0 0         );
    0          
118 0           return ($clone,$key);
119 0           };
120              
121 0   0       $clone_key ||= substr(unpack("H*", $hash->{x509_digest_sha256}),0,32);
122 0           my $c = $self->{cache};
123 0 0         return $c->($clone_key,$create_cb) if ref($c) eq 'CODE';
124              
125 0   0       my $e = $c->{$clone_key} ||= do {
126 0           my ($cert,$key) = &$create_cb;
127 0           { cert => $cert, key => $key };
128             };
129 0           $e->{atime} = time();
130 0           return ($e->{cert},$e->{key});
131             }
132              
133              
134 0     0 0   sub STORABLE_freeze { my $self = shift; $self->serialize() }
  0            
135 0     0 0   sub STORABLE_thaw { my ($class,undef,$data) = @_; $class->unserialize($data) }
  0            
136              
137             sub serialize {
138 0     0 1   my $self = shift;
139 0           my $data = pack("N",2); # version
140 0           $data .= pack("N/a", PEM_cert2string($self->{cacert}));
141 0           $data .= pack("N/a", PEM_key2string($self->{cakey}));
142 0 0         if ( $self->{certkey} ) {
143 0           $data .= pack("N/a", PEM_key2string($self->{certkey}));
144             } else {
145 0           $data .= pack("N/a", '');
146             }
147 0           $data .= pack("N",$self->{serial});
148 0 0         if ( ref($self->{cache}) eq 'HASH' ) {
149 0           while ( my($k,$v) = each %{ $self->{cache}} ) {
  0            
150             $data .= pack("N/aN/aN/aN", $k,
151             PEM_cert2string($k->{cert}),
152             $k->{key} ? PEM_key2string($k->{key}) : '',
153 0 0         $k->{atime});
154             }
155             }
156 0           return $data;
157             }
158              
159             sub unserialize {
160 0     0 1   my ($class,$data) = @_;
161 0 0         unpack("N",substr($data,0,4,'')) == 2 or
162             croak("serialized with wrong version");
163 0           ( my $cacert,my $cakey,my $certkey,my $serial,$data)
164             = unpack("N/aN/aN/aNa*",$data);
165 0 0 0       my $self = bless {
166             serial => $serial,
167             cacert => PEM_string2cert($cacert),
168             cakey => PEM_string2key($cakey),
169             $certkey ? ( certkey => PEM_string2key($certkey)):(),
170             }, ref($class)||$class;
171              
172 0 0         $self->{cache} = {} if $data ne '';
173 0           while ( $data ne '' ) {
174 0           (my $key,my $cert,my $certkey, my $atime,$data) = unpack("N/aN/aNa*",$data);
175 0 0         $self->{cache}{$key} = {
176             cert => PEM_string2cert($cert),
177             $key ? ( key => PEM_string2key($certkey)):(),
178             atime => $atime
179             };
180             }
181 0           return $self;
182             }
183              
184             1;
185              
186             __END__