File Coverage

blib/lib/Data/Transform/SSL.pm
Criterion Covered Total %
statement 38 130 29.2
branch 9 68 13.2
condition 3 19 15.7
subroutine 9 15 60.0
pod 2 2 100.0
total 61 234 26.0


line stmt bran cond sub pod time code
1             package Data::Transform::SSL;
2 2     2   4035 use strict;
  2         5  
  2         70  
3 2     2   8 use warnings;
  2         4  
  2         63  
4              
5             =head1 NAME
6              
7             Data::Transform::SSL - SSL in a filter
8              
9             =head1 DESCRIPTION
10              
11             =head1 PUBLIC API
12              
13             Data::Transform::SSL implements the L API. Only
14             differences and additions are documented here.
15              
16             =cut
17              
18 2     2   18 use base qw(Data::Transform);
  2         4  
  2         1907  
19              
20             our $VERSION = '0.03';
21              
22 2     2   4515 use Carp qw(croak);
  2         3  
  2         81  
23 2     2   8 use Scalar::Util qw(blessed);
  2         2  
  2         68  
24 2     2   1872 use Net::SSLeay qw(die_now);
  2         53824  
  2         5935  
25             Net::SSLeay::load_error_strings();
26             Net::SSLeay::ERR_load_crypto_strings;
27             Net::SSLeay::SSLeay_add_ssl_algorithms();
28             Net::SSLeay::randomize();
29              
30             sub BUF () { 0 }
31             sub CTX () { 1 }
32             sub SSL () { 2 }
33             sub RB () { 3 }
34             sub WB () { 4 }
35             sub STATE () { 5 }
36             sub KEY () { 6 }
37             sub CERT () { 7 }
38             sub TYPE () { 8 }
39             sub OUTBUF () { 9 }
40             sub FLAGS () { 10 }
41              
42             sub STATE_DISC () { 0 }
43             sub STATE_CONN () { 1 }
44             sub STATE_SHUTDOWN () { 2 }
45              
46             sub TYPE_SERVER () { 0 }
47             sub TYPE_CLIENT () { 1 }
48              
49             # from IO::Socket::SSL
50             # from openssl/ssl.h, should be better in Net::SSLeay
51             sub SSL_SENT_SHUTDOWN () { 1 }
52             sub SSL_RECEIVED_SHUTDOWN () { 2 }
53              
54             # from openssl/x509_vfy.h
55             sub X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT () { 18 }
56              
57             sub FLAGS_ALLOW_SELFSIGNED () { 0x00000001 }
58              
59             sub _init {
60 2     2   4 my ($self) = @_;
61              
62 2         4 my %args = ();
63 2 50       9 if ($self->[TYPE] == TYPE_CLIENT) {
64             # don't reference $self, so there isn't an extra reference keeping
65             # it alive too long
66 2         3 my $flags = $self->[FLAGS];
67             $args{SSL_verify_callback} = sub {
68 0     0   0 my ($ok, $ctx_store) = @_;
69 0         0 my $cert = Net::SSLeay::X509_STORE_CTX_get_current_cert($ctx_store);
70 0         0 my $error = Net::SSLeay::X509_STORE_CTX_get_error($ctx_store);
71 0         0 warn Net::SSLeay::X509_verify_cert_error_string($error);
72 0         0 my $issuer = Net::SSLeay::X509_NAME_oneline(Net::SSLeay::X509_get_issuer_name($cert));
73 0         0 my $subject = Net::SSLeay::X509_NAME_oneline(Net::SSLeay::X509_get_subject_name($cert));
74 0 0 0     0 return 1
75             if ($error == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT and $flags & FLAGS_ALLOW_SELFSIGNED);
76 0         0 return $ok;
77 2         22 };
78             }
79 2 50       513 my $ctx = Net::SSLeay::CTX_new
80             or die_now("Failed to create SSL_CTX $!");
81 2 50       18 Net::SSLeay::CTX_set_options($ctx, Net::SSLeay::OP_ALL())
82             and die_if_ssl_error("Failed to set compatibility options");
83              
84 0 0       0 if ($self->[TYPE] == TYPE_SERVER) {
85 0         0 Net::SSLeay::CTX_set_cipher_list($ctx, 'ALL');
86 0 0       0 Net::SSLeay::set_cert_and_key($ctx,
87             $self->[CERT],
88             $self->[KEY],
89             ) or die "key $!";
90             } else {
91 0         0 Net::SSLeay::CTX_load_verify_locations($ctx, '', '/etc/ssl/certs/');
92 0         0 Net::SSLeay::CTX_set_verify($ctx, Net::SSLeay::VERIFY_PEER(), $args{SSL_verify_callback});
93             }
94             # enable revocation checking
95             # FIXME figure out how to do this only when we have a CRL because
96             # certificate verifying returns an error if there isn't one.
97             # my $store = Net::SSLeay::CTX_get_cert_store($ctx);
98             # my $flag = Net::SSLeay::X509_V_FLAG_CRL_CHECK();
99             # Net::SSLeay::X509_STORE_set_flags(
100             # Net::SSLeay::CTX_get_cert_store($ctx),
101             # Net::SSLeay::X509_V_FLAG_CRL_CHECK(),
102             # );
103 0 0       0 my $ssl = Net::SSLeay::new($ctx)
104             or die_now("Failed to create SSL $!");
105 0 0       0 if ($self->[TYPE] == TYPE_SERVER) {
106 0 0       0 Net::SSLeay::set_cipher_list($ssl, 'ALL')
107             or die_now("Failed to set cipher list $!");
108             }
109 0 0       0 my $rb = Net::SSLeay::BIO_new(Net::SSLeay::BIO_s_mem())
110             or die_now("Could not create memory BIO $!");
111 0 0       0 my $wb = Net::SSLeay::BIO_new(Net::SSLeay::BIO_s_mem())
112             or die_now("Could not create memory BIO $!");
113 0         0 Net::SSLeay::set_bio($ssl, $rb, $wb);
114              
115 0         0 @{$self}[CTX..STATE] = ($ctx, $ssl, $rb, $wb, STATE_DISC);
  0         0  
116 0         0 return $self;
117             }
118              
119             =head1 new
120              
121             Accepts the following parameters:
122              
123             =over 2
124              
125             =item type
126              
127             If set to 'Server', the filter will act like a server-side ssl filter,
128             otherwise it will act like a client-side one. If the filter is a
129             server-side one, the 'cert' and 'key' parameters are required.
130              
131             =item cert
132              
133             The filename of the cert to use.
134              
135             =item key
136              
137             The filename of the key to use.
138              
139             =back
140              
141             =cut
142              
143             sub new {
144 2     2 1 737 my $class = shift;
145 2         7 my %opts = @_;
146              
147 2         8 my $self = bless [], $class;
148              
149 2 50 25     21 croak "You must either supply both key and cert, or neither"
150             if (defined ($opts{key}) xor defined ($opts{cert}));
151 2 50       8 if (defined $opts{key}) {
152 0         0 $self->[KEY] = $opts{key};
153 0         0 $self->[CERT] = $opts{cert};
154             }
155              
156 2 50 33     22 $self->[TYPE] = (defined $opts{type} and $opts{type} eq 'Server') ? TYPE_SERVER : TYPE_CLIENT;
157 2 50 33     9 croak "A server-side filter requires a cert and key"
158             if ($self->[TYPE] == TYPE_SERVER and not defined $self->[KEY]);
159              
160 2         6 $self->[BUF] = [];
161 2 100       8 $self->[FLAGS] = $opts{flags} ? $opts{flags} : 0;
162              
163 2         9 return $self->_init;
164             }
165              
166             sub clone {
167 0     0 1 0 my $self = shift;
168              
169 0         0 my $new_self = bless [], ref($self);
170 0         0 $new_self->[TYPE] = $self->[TYPE];
171 0         0 $new_self->[BUF] = [ ];
172 0         0 $new_self->[CERT] = $self->[CERT];
173 0         0 $new_self->[KEY] = $self->[KEY];
174 0         0 $new_self->[FLAGS] = $self->[FLAGS];
175 0         0 return $new_self->_init;
176             }
177              
178             sub _try_connection {
179 0     0   0 my $self = shift;
180              
181 0         0 my $rv;
182 0 0       0 if ($self->[TYPE] == TYPE_SERVER) {
183 0         0 $rv = Net::SSLeay::accept($self->[SSL]);
184             } else {
185 0         0 $rv = Net::SSLeay::connect($self->[SSL]);
186             }
187              
188 0 0       0 if ($rv < 0) {
    0          
189 0         0 my $err = Net::SSLeay::get_error($self->[SSL], $rv);
190 0 0       0 if ($err == Net::SSLeay::ERROR_WANT_READ()) {
191 0         0 my $data = Net::SSLeay::BIO_read($self->[WB]);
192 0         0 return $data;
193             } else {
194             # uh oh, something went wrong
195             # theoretically, this could be ERROR_WANT_WRITE but
196             # I think that will not happen since we write to a
197             # memory buffer, which should always work. So assume
198             # it is an actual error and return its description
199             # FIXME probably check for ERROR_WANT_WRITE anyway
200 0         0 my $str;
201 0         0 while (my $e = Net::SSLeay::ERR_get_error) {
202 0         0 $str .= Net::SSLeay::ERR_error_string($e) . "\n";
203             }
204 0         0 my $ret = Data::Transform::Meta::Error->new($str);
205 0         0 return $ret;
206             }
207             } elsif ($rv == 1) {
208 0         0 $self->[STATE] = STATE_CONN;
209              
210             # SSL handshake done. send out any data already
211             # received from the client.
212 0 0       0 if (defined $self->[OUTBUF]) {
213 0         0 my $data = join ('', @{delete $self->[OUTBUF]});
  0         0  
214 0         0 Net::SSLeay::write($self->[SSL], $data);
215             }
216 0         0 return Net::SSLeay::BIO_read($self->[WB]);
217             }
218 0         0 return;
219             }
220              
221             sub _handle_get_data {
222 0     0   0 my ($self, $newdata) = @_;
223              
224 0 0       0 if (defined $newdata) {
225 0         0 Net::SSLeay::BIO_write($self->[RB], $newdata);
226             }
227              
228 0 0 0     0 return unless (Net::SSLeay::BIO_pending($self->[RB]) or $self->[STATE] == STATE_DISC);
229              
230 0 0       0 if ($self->[STATE] == STATE_DISC) {
    0          
    0          
231 0 0       0 if (my $data = $self->_try_connection) {
232 0 0 0     0 if (blessed $data and $data->isa('Data::Transform::Meta::Error')) {
233 0         0 return $data;
234             } else {
235 0         0 my $ret = Data::Transform::Meta::SENDBACK->new($data);
236 0         0 return $ret;
237             }
238             }
239             } elsif ($self->[STATE] == STATE_CONN) {
240 0         0 my $got = Net::SSLeay::read($self->[SSL]);
241 0         0 my $shutdown = Net::SSLeay::get_shutdown($self->[SSL]);
242 0 0       0 if ($shutdown == SSL_RECEIVED_SHUTDOWN()) {
243 0         0 Net::SSLeay::shutdown($self->[SSL]);
244 0         0 my $notify = Net::SSLeay::BIO_read($self->[WB]);
245 0         0 my $ret = Data::Transform::Meta::SENDBACK->new($notify);
246 0         0 $self->[STATE] = STATE_SHUTDOWN;
247 0         0 return $ret;
248             }
249 0 0       0 return $got if (defined $got);
250             } elsif ($self->[STATE] == STATE_SHUTDOWN) {
251             #my $ret Data::Transform::Meta::EOF->new;
252             #return $ret;
253             }
254 0         0 return;
255             }
256              
257             sub _handle_put_meta {
258 0     0   0 my ($self, $meta) = @_;
259              
260 0 0       0 if ($meta->isa('Data::Transform::Meta::EOF')) {
261 0         0 my $rv = Net::SSLeay::shutdown($self->[SSL]);
262 0         0 my $shutdown = Net::SSLeay::get_shutdown($self->[SSL]);
263 0 0       0 if ($shutdown == SSL_SENT_SHUTDOWN()) {
264             }
265 0         0 my $notify = Net::SSLeay::BIO_read($self->[WB]);
266 0         0 $self->[STATE] = STATE_SHUTDOWN;
267 0         0 return $notify, $meta;
268             }
269 0         0 return $meta;
270             }
271              
272             sub _handle_put_data {
273 0     0   0 my ($self, $stream) = @_;
274              
275 0 0       0 if ($self->[STATE] == STATE_DISC) {
276             # In SSL, the client starts the handshake. Since this is a
277             # filter, there's no way to trigger on some on_connect event
278             # so we do it once we receive the first data from the user.
279             # Store that data until the handshake is done.
280 0         0 push (@{$self->[OUTBUF]}, $stream);
  0         0  
281              
282 0         0 return $self->_try_connection;
283             } else {
284 0         0 Net::SSLeay::write($self->[SSL], $stream);
285 0         0 my $ret = Net::SSLeay::BIO_read($self->[WB]);
286 0 0       0 return $ret if $ret;
287             }
288 0         0 return;
289             }
290              
291             sub DESTROY {
292 2     2   307 my $self = shift;
293              
294 2         45 Net::SSLeay::free ($self->[SSL]);
295 2         239 Net::SSLeay::CTX_free ($self->[CTX]);
296             }
297              
298             1;
299              
300             __END__