File Coverage

blib/lib/Net/SSLGlue/LWP.pm
Criterion Covered Total %
statement 16 29 55.1
branch 0 10 0.0
condition 0 9 0.0
subroutine 6 6 100.0
pod n/a
total 22 54 40.7


line stmt bran cond sub pod time code
1 1     1   29197 use strict;
  1         1  
  1         24  
2 1     1   3 use warnings;
  1         2  
  1         41  
3             package Net::SSLGlue::LWP;
4             our $VERSION = 0.501;
5 1     1   3 use LWP::UserAgent '5.822';
  1         1  
  1         18  
6 1     1   3 use IO::Socket::SSL 1.19;
  1         26  
  1         7  
7 1     1   134 use URI;
  1         1  
  1         141  
8              
9             # force Net::SSLGlue::LWP::Socket as superclass of Net::HTTPS, because
10             # only it can verify certificates
11             my $use_existent;
12             BEGIN {
13 1     1   193 require LWP::Protocol::https;
14 0   0       $use_existent = $LWP::Protocol::https::VERSION
15             && $LWP::Protocol::https::VERSION >= 6.06
16             && $LWP::UserAgent::VERSION >= 6.06;
17 0 0         if ($use_existent) {
18             my $oc = $Net::HTTPS::SSL_SOCKET_CLASS ||
19 0   0       $ENV{PERL_NET_HTTPS_SSL_SOCKET_CLASS};
20 0 0 0       $use_existent = 0 if $oc && $oc ne 'IO::Socket::SSL';
21             }
22 0 0         if ($use_existent) {
23 0           warn "Your LWP::UserAgent/LWP::Protocol::https looks fine.\n".
24             "Will use it instead of Net::SSLGLue::LWP\n";
25             } else {
26 0           my $oc = $Net::HTTPS::SSL_SOCKET_CLASS;
27 0           $Net::HTTPS::SSL_SOCKET_CLASS = my $need = 'Net::SSLGlue::LWP::Socket';
28 0           require Net::HTTPS;
29              
30 0 0         if ( ( my $oc = $Net::HTTPS::SSL_SOCKET_CLASS ) ne $need ) {
31             # was probably loaded before, change ISA
32 0           grep { s{^\Q$oc\E$}{$need} } @Net::HTTPS::ISA
  0            
33             }
34 0 0         die "cannot force $need into Net::HTTPS"
35             if $Net::HTTPS::SSL_SOCKET_CLASS ne $need;
36             }
37             }
38              
39              
40             our %SSLopts; # set by local and import
41             sub import {
42             shift;
43             %SSLopts = @_;
44             }
45              
46             if (!$use_existent) {
47             # add SSL options
48             my $old_eso = UNIVERSAL::can( 'LWP::Protocol::https','_extra_sock_opts' );
49             no warnings 'redefine';
50             *LWP::Protocol::https::_extra_sock_opts = sub {
51             return (
52             $old_eso ? ( $old_eso->(@_) ):(),
53             SSL_verify_mode => 1,
54             SSL_verifycn_scheme => 'http',
55             HTTPS_proxy => $_[0]->{ua}{https_proxy},
56             %SSLopts,
57             );
58             };
59              
60             # fix https_proxy handling - forward it to a variable handled by me
61             my $old_proxy = defined &LWP::UserAgent::proxy && \&LWP::UserAgent::proxy
62             or die "cannot find LWP::UserAgent::proxy";
63             *LWP::UserAgent::proxy = sub {
64             my ($self,$key,$val) = @_;
65             goto &$old_proxy if ref($key) || $key ne 'https';
66             if (@_>2) {
67             my $rv = &$old_proxy;
68             $self->{https_proxy} = delete $self->{proxy}{https}
69             || die "https proxy not set?";
70             }
71             return $self->{https_proxy};
72             };
73              
74             } else {
75             # wrapper around LWP::Protocol::https::_extra_sock_opts to support %SSLopts
76             my $old_eso = UNIVERSAL::can( 'LWP::Protocol::https','_extra_sock_opts' )
77             or die "no LWP::Protocol::https::_extra_sock_opts found";
78             no warnings 'redefine';
79             *LWP::Protocol::https::_extra_sock_opts = sub {
80             return (
81             $old_eso->(@_),
82             %SSLopts,
83             );
84             };
85             }
86              
87             {
88              
89             package Net::SSLGlue::LWP::Socket;
90             use IO::Socket::SSL;
91             use base 'IO::Socket::SSL';
92             my $sockclass = 'IO::Socket::INET';
93             use URI::Escape 'uri_unescape';
94             use MIME::Base64 'encode_base64';
95             $sockclass .= '6' if eval "require IO::Socket::INET6";
96              
97             sub configure {
98             my ($self,$args) = @_;
99             my $phost = delete $args->{HTTPS_proxy}
100             or return $self->SUPER::configure($args);
101             $phost = URI->new($phost) if ! ref $phost;
102              
103             my $port = $args->{PeerPort};
104             my $host = $args->{PeerHost} || $args->{PeerAddr};
105             if ( ! $port ) {
106             $host =~s{:(\w+)$}{};
107             $port = $args->{PeerPort} = $1;
108             $args->{PeerHost} = $host;
109             }
110             if ( $phost->scheme ne 'http' ) {
111             $@ = "scheme ".$phost->scheme." not supported for https_proxy";
112             return;
113             }
114             my $auth = '';
115             if ( my ($user,$pass) = split( ':', $phost->userinfo || '' ) ) {
116             $auth = "Proxy-authorization: Basic ".
117             encode_base64( uri_unescape($user).':'.uri_unescape($pass),'' ).
118             "\r\n";
119             }
120              
121             my $pport = $phost->port;
122             $phost = $phost->host;
123              
124             # temporally downgrade $self so that the right connect chain
125             # gets called w/o doing SSL stuff. If we don't do it it will
126             # try to call IO::Socket::SSL::connect
127             my $ssl_class = ref($self);
128             bless $self,$sockclass;
129             $self->configure({ %$args, PeerAddr => $phost, PeerPort => $pport }) or do {
130             $@ = "connect to proxy $phost port $pport failed";
131             return;
132             };
133             print $self "CONNECT $host:$port HTTP/1.0\r\n$auth\r\n";
134             my $hdr = '';
135             while (<$self>) {
136             $hdr .= $_;
137             last if $_ eq "\n" or $_ eq "\r\n";
138             }
139             if ( $hdr !~m{\AHTTP/1.\d 2\d\d} ) {
140             # error
141             $@ = "non 2xx response to CONNECT: $hdr";
142             return;
143             }
144              
145             # and upgrade self by calling start_SSL
146             $ssl_class->start_SSL( $self,
147             SSL_verifycn_name => $host,
148             %$args
149             ) or do {
150             $@ = "start SSL failed: $SSL_ERROR";
151             return;
152             };
153             return $self;
154             };
155             }
156              
157             1;
158              
159             =head1 NAME
160              
161             Net::SSLGlue::LWP - proper certificate checking for https in LWP
162              
163             =head1 SYNOPSIS
164             u
165             use Net::SSLGlue::LWP SSL_ca_path => ...;
166             use LWP::Simple;
167             get( 'https://www....' );
168              
169             {
170             local %Net::SSLGlue::LWP::SSLopts = %Net::SSLGlue::LWP::SSLopts;
171              
172             # switch off verification
173             $Net::SSLGlue::LWP::SSLopts{SSL_verify_mode} = 0;
174              
175             # or: set different verification policy, because cert does
176             # not conform to RFC (wildcards in CN are not allowed for https,
177             # but some servers do it anyway)
178             $Net::SSLGlue::LWP::SSLopts{SSL_verifycn_scheme} = {
179             wildcards_in_cn => 'anywhere',
180             check_cn => 'always',
181             };
182             }
183              
184              
185             =head1 DESCRIPTION
186              
187             L modifies L and L so that
188             L is forced to use L instead of L,
189             and that L does proper certificate checking using the
190             C SSL_verify_scheme from L.
191              
192             This module should only be used for older LWP version, see B
193             versions> below.
194              
195             Because L does not have a mechanism to forward arbitrary parameters for
196             the construction of the underlying socket these parameters can be set globally
197             when including the package, or with local settings of the
198             C<%Net::SSLGlue::LWP::SSLopts> variable.
199              
200             All of the C parameter from L can be used; the
201             following parameters are especially useful:
202              
203             =over 4
204              
205             =item SSL_ca_path, SSL_ca_file
206              
207             Specifies the path or a file where the CAs used for checking the certificates
208             are located. This is typically L on UNIX systems.
209              
210             =item SSL_verify_mode
211              
212             If set to 0, verification of the certificate will be disabled. By default
213             it is set to 1 which means that the peer certificate is checked.
214              
215             =item SSL_verifycn_name
216              
217             Usually the name given as the hostname in the constructor is used to verify the
218             identity of the certificate. If you want to check the certificate against
219             another name you can specify it with this parameter.
220              
221             =back
222              
223             =head1 Supported LWP versions
224              
225             This module should be used for older LWP version only. Starting with version
226             6.06 it is recommended to use LWP directly. If a recent version is found
227             Net::SSLGlue::LWP will print out a warning and not monkey patch too much into
228             LWP (only as much as necessary to still support C<%Net::SSLGlue::LWP::SSLopts>).
229              
230             =head1 SEE ALSO
231              
232             IO::Socket::SSL, LWP, Net::HTTPS, LWP::Protocol::https
233              
234             =head1 COPYRIGHT
235              
236             This module is copyright (c) 2008..2015, Steffen Ullrich.
237             All Rights Reserved.
238             This module is free software. It may be used, redistributed and/or modified
239             under the same terms as Perl itself.
240