File Coverage

blib/lib/LWP/Protocol/https.pm
Criterion Covered Total %
statement 57 71 80.2
branch 20 38 52.6
condition 3 13 23.0
subroutine 10 10 100.0
pod 0 1 0.0
total 90 133 67.6


line stmt bran cond sub pod time code
1             package LWP::Protocol::https;
2              
3 4     4   392208 use strict;
  4         20  
  4         192  
4             our $VERSION = '6.09';
5              
6 4     4   26 use base qw(LWP::Protocol::http);
  4         7  
  4         2166  
7             require Net::HTTPS;
8              
9             sub socket_type
10             {
11 12     12 0 355419 return "https";
12             }
13              
14             sub _extra_sock_opts
15             {
16 10     10   138591 my $self = shift;
17 10 50       25 my %ssl_opts = %{$self->{ua}{ssl_opts} || {}};
  10         89  
18 10 100       52 if (delete $ssl_opts{verify_hostname}) {
19 6   50     86 $ssl_opts{SSL_verify_mode} ||= 1;
20 6         17 $ssl_opts{SSL_verifycn_scheme} = 'www';
21             }
22             else {
23 4         13 $ssl_opts{SSL_verify_mode} = 0;
24             }
25 10 100       36 if ($ssl_opts{SSL_verify_mode}) {
26 6 0 33     17 unless (exists $ssl_opts{SSL_ca_file} || exists $ssl_opts{SSL_ca_path}) {
27 0         0 eval {
28 0         0 require Mozilla::CA;
29             };
30 0 0       0 if ($@) {
31 0 0       0 if ($@ =~ /^Can't locate Mozilla\/CA\.pm/) {
32 0         0 $@ = <<'EOT';
33             Can't verify SSL peers without knowing which Certificate Authorities to trust
34              
35             This problem can be fixed by either setting the PERL_LWP_SSL_CA_FILE
36             environment variable or by installing the Mozilla::CA module.
37              
38             To disable verification of SSL peers set the PERL_LWP_SSL_VERIFY_HOSTNAME
39             environment variable to 0. If you do this you can't be sure that you
40             communicate with the expected peer.
41             EOT
42             }
43 0         0 die $@;
44             }
45 0         0 $ssl_opts{SSL_ca_file} = Mozilla::CA::SSL_ca_file();
46             }
47             }
48 10         26 $self->{ssl_opts} = \%ssl_opts;
49 10         96 return (%ssl_opts, $self->SUPER::_extra_sock_opts);
50             }
51              
52             #------------------------------------------------------------
53             # _cn_match($common_name, $san_name)
54             # common_name: an IA5String
55             # san_name: subjectAltName
56             # initially we were only concerned with the dNSName
57             # and the 'left-most' only wildcard as noted in
58             # https://tools.ietf.org/html/rfc6125#section-6.4.3
59             # this method does not match any wildcarding in the
60             # domain name as listed in section-6.4.3.3
61             #
62             sub _cn_match {
63 14     14   6964 my( $me, $common_name, $san_name ) = @_;
64              
65             # /CN has a '*.' prefix
66             # MUST be an FQDN -- fishing?
67 14 100       65 return 0 if( $common_name =~ /^\*\./ );
68              
69 13         22 my $re = q{}; # empty string
70              
71             # turn a leading "*." into a regex
72 13 100       40 if( $san_name =~ /^\*\./ ) {
73 6         22 $san_name =~ s/\*//;
74 6         13 $re = "[^.]+";
75             }
76              
77             # quotemeta the rest and match anchored
78 13 100       250 if( $common_name =~ /^$re\Q$san_name\E$/ ) {
79 6         28 return 1;
80             }
81 7         24 return 0;
82             }
83              
84             #-------------------------------------------------------
85             # _in_san( cn, cert )
86             # 'cn' of the form /CN=host_to_check ( "Common Name" form )
87             # 'cert' any object that implements a peer_certificate('subjectAltNames') method
88             # which will return an array of ( type-id, value ) pairings per
89             # http://tools.ietf.org/html/rfc5280#section-4.2.1.6
90             # if there is no subjectAltNames there is nothing more to do.
91             # currently we have a _cn_match() that will allow for simple compare.
92             sub _in_san
93             {
94 3     3   1702 my($me, $cn, $cert) = @_;
95              
96             # we can return early if there are no SAN options.
97 3         9 my @sans = $cert->peer_certificate('subjectAltNames');
98 3 100       16 return unless scalar @sans;
99              
100 2         15 (my $common_name = $cn) =~ s/.*=//; # strip off the prefix.
101              
102             # get the ( type-id, value ) pairwise
103             # currently only the basic CN to san_name check
104 2         10 while( my ( $type_id, $value ) = splice( @sans, 0, 2 ) ) {
105 2 100       8 return 'ok' if $me->_cn_match($common_name,$value);
106             }
107 1         3 return;
108             }
109              
110             sub _check_sock
111             {
112 8     8   498638 my($self, $req, $sock) = @_;
113 8         56 my $check = $req->header("If-SSL-Cert-Subject");
114 8 50       799 if (defined $check) {
115 0   0     0 my $cert = $sock->get_peer_certificate ||
116             die "Missing SSL certificate";
117 0         0 my $subject = $cert->subject_name;
118 0 0 0     0 unless ( defined $subject && ( $subject =~ /$check/ ) ) {
119 0         0 my $ok = $self->_in_san( $check, $cert);
120 0 0       0 die "Bad SSL certificate subject: '$subject' !~ /$check/"
121             unless $ok;
122             }
123 0         0 $req->remove_header("If-SSL-Cert-Subject"); # don't pass it on
124             }
125             }
126              
127             sub _get_sock_info
128             {
129 8     8   193908 my $self = shift;
130 8         72 $self->SUPER::_get_sock_info(@_);
131 8         1607 my($res, $sock) = @_;
132 8 50 33     140 if ($sock->can('get_sslversion') and my $sslversion = $sock->get_sslversion) {
133 8         196 $res->header("Client-SSL-Version" => $sslversion);
134             }
135 8         584 $res->header("Client-SSL-Cipher" => $sock->get_cipher);
136 8         700 my $cert = $sock->get_peer_certificate;
137 8 50       53 if ($cert) {
138 8         44 $res->header("Client-SSL-Cert-Subject" => $cert->subject_name);
139 8         1042 $res->header("Client-SSL-Cert-Issuer" => $cert->issuer_name);
140             }
141 8 50       831 if (!$self->{ssl_opts}{SSL_verify_mode}) {
    0          
142 8         44 $res->push_header("Client-SSL-Warning" => "Peer certificate not verified");
143             }
144             elsif (!$self->{ssl_opts}{SSL_verifycn_scheme}) {
145 0         0 $res->push_header("Client-SSL-Warning" => "Peer hostname match with certificate not verified");
146             }
147 8         372 $res->header("Client-SSL-Socket-Class" => $Net::HTTPS::SSL_SOCKET_CLASS);
148             }
149              
150             # upgrade plain socket to SSL, used for CONNECT tunnel when proxying https
151             # will only work if the underlying socket class of Net::HTTPS is
152             # IO::Socket::SSL, but code will only be called in this case
153             if ( $Net::HTTPS::SSL_SOCKET_CLASS->can('start_SSL')) {
154             *_upgrade_sock = sub {
155 6     6   94490 my ($self,$sock,$url) = @_;
156 6         33 $sock = LWP::Protocol::https::Socket->start_SSL( $sock,
157             SSL_verifycn_name => $url->host,
158             SSL_hostname => $url->host,
159             $self->_extra_sock_opts,
160             );
161 6 50       77538 $@ = LWP::Protocol::https::Socket->errstr if ! $sock;
162 6         52 return $sock;
163             }
164             }
165              
166             #-----------------------------------------------------------
167             package LWP::Protocol::https::Socket;
168              
169 4     4   173605 use base qw(Net::HTTPS LWP::Protocol::http::SocketMethods);
  4         12  
  4         2080  
170              
171             1;
172              
173             __END__