|
Packit |
ec7ac3 |
package LWP::Protocol::https;
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
use strict;
|
|
Packit |
ec7ac3 |
our $VERSION = "6.07";
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
require LWP::Protocol::http;
|
|
Packit |
ec7ac3 |
our @ISA = qw(LWP::Protocol::http);
|
|
Packit |
ec7ac3 |
require Net::HTTPS;
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
sub socket_type
|
|
Packit |
ec7ac3 |
{
|
|
Packit |
ec7ac3 |
return "https";
|
|
Packit |
ec7ac3 |
}
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
sub _extra_sock_opts
|
|
Packit |
ec7ac3 |
{
|
|
Packit |
ec7ac3 |
my $self = shift;
|
|
Packit |
ec7ac3 |
my %ssl_opts = %{$self->{ua}{ssl_opts} || {}};
|
|
Packit |
ec7ac3 |
if (delete $ssl_opts{verify_hostname}) {
|
|
Packit |
ec7ac3 |
$ssl_opts{SSL_verify_mode} ||= 1;
|
|
Packit |
ec7ac3 |
$ssl_opts{SSL_verifycn_scheme} = 'www';
|
|
Packit |
ec7ac3 |
}
|
|
Packit |
ec7ac3 |
else {
|
|
Packit Service |
89ddb2 |
if ( $Net::HTTPS::SSL_SOCKET_CLASS eq 'Net::SSL' ) {
|
|
Packit Service |
89ddb2 |
$ssl_opts{SSL_verifycn_scheme} = '';
|
|
Packit Service |
89ddb2 |
} else {
|
|
Packit Service |
89ddb2 |
$ssl_opts{SSL_verifycn_scheme} = 'none';
|
|
Packit Service |
89ddb2 |
}
|
|
Packit |
ec7ac3 |
}
|
|
Packit |
ec7ac3 |
if ($ssl_opts{SSL_verify_mode}) {
|
|
Packit |
ec7ac3 |
unless (exists $ssl_opts{SSL_ca_file} || exists $ssl_opts{SSL_ca_path}) {
|
|
Packit |
ec7ac3 |
eval {
|
|
Packit |
ec7ac3 |
require Mozilla::CA;
|
|
Packit |
ec7ac3 |
};
|
|
Packit |
ec7ac3 |
if ($@) {
|
|
Packit |
ec7ac3 |
if ($@ =~ /^Can't locate Mozilla\/CA\.pm/) {
|
|
Packit |
ec7ac3 |
$@ = <<'EOT';
|
|
Packit |
ec7ac3 |
Can't verify SSL peers without knowing which Certificate Authorities to trust
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
This problem can be fixed by either setting the PERL_LWP_SSL_CA_FILE
|
|
Packit |
ec7ac3 |
environment variable or by installing the Mozilla::CA module.
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
To disable verification of SSL peers set the PERL_LWP_SSL_VERIFY_HOSTNAME
|
|
Packit |
ec7ac3 |
environment variable to 0. If you do this you can't be sure that you
|
|
Packit |
ec7ac3 |
communicate with the expected peer.
|
|
Packit |
ec7ac3 |
EOT
|
|
Packit |
ec7ac3 |
}
|
|
Packit |
ec7ac3 |
die $@;
|
|
Packit |
ec7ac3 |
}
|
|
Packit |
ec7ac3 |
$ssl_opts{SSL_ca_file} = Mozilla::CA::SSL_ca_file();
|
|
Packit |
ec7ac3 |
}
|
|
Packit |
ec7ac3 |
}
|
|
Packit |
ec7ac3 |
$self->{ssl_opts} = \%ssl_opts;
|
|
Packit |
ec7ac3 |
return (%ssl_opts, $self->SUPER::_extra_sock_opts);
|
|
Packit |
ec7ac3 |
}
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
#------------------------------------------------------------
|
|
Packit |
ec7ac3 |
# _cn_match($common_name, $san_name)
|
|
Packit |
ec7ac3 |
# common_name: an IA5String
|
|
Packit |
ec7ac3 |
# san_name: subjectAltName
|
|
Packit |
ec7ac3 |
# initially we were only concerned with the dNSName
|
|
Packit |
ec7ac3 |
# and the 'left-most' only wildcard as noted in
|
|
Packit |
ec7ac3 |
# https://tools.ietf.org/html/rfc6125#section-6.4.3
|
|
Packit |
ec7ac3 |
# this method does not match any wildcarding in the
|
|
Packit |
ec7ac3 |
# domain name as listed in section-6.4.3.3
|
|
Packit |
ec7ac3 |
#
|
|
Packit |
ec7ac3 |
sub _cn_match {
|
|
Packit |
ec7ac3 |
my( $me, $common_name, $san_name ) = @_;
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
# /CN has a '*.' prefix
|
|
Packit |
ec7ac3 |
# MUST be an FQDN -- fishing?
|
|
Packit |
ec7ac3 |
return 0 if( $common_name =~ /^\*\./ );
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
my $re = q{}; # empty string
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
# turn a leading "*." into a regex
|
|
Packit |
ec7ac3 |
if( $san_name =~ /^\*\./ ) {
|
|
Packit |
ec7ac3 |
$san_name =~ s/\*//;
|
|
Packit |
ec7ac3 |
$re = "[^.]+";
|
|
Packit |
ec7ac3 |
}
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
# quotemeta the rest and match anchored
|
|
Packit |
ec7ac3 |
if( $common_name =~ /^$re\Q$san_name\E$/ ) {
|
|
Packit |
ec7ac3 |
return 1;
|
|
Packit |
ec7ac3 |
}
|
|
Packit |
ec7ac3 |
return 0;
|
|
Packit |
ec7ac3 |
}
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
#-------------------------------------------------------
|
|
Packit |
ec7ac3 |
# _in_san( cn, cert )
|
|
Packit |
ec7ac3 |
# 'cn' of the form /CN=host_to_check ( "Common Name" form )
|
|
Packit |
ec7ac3 |
# 'cert' any object that implements a peer_certificate('subjectAltNames') method
|
|
Packit |
ec7ac3 |
# which will return an array of ( type-id, value ) pairings per
|
|
Packit |
ec7ac3 |
# http://tools.ietf.org/html/rfc5280#section-4.2.1.6
|
|
Packit |
ec7ac3 |
# if there is no subjectAltNames there is nothing more to do.
|
|
Packit |
ec7ac3 |
# currently we have a _cn_match() that will allow for simple compare.
|
|
Packit |
ec7ac3 |
sub _in_san
|
|
Packit |
ec7ac3 |
{
|
|
Packit |
ec7ac3 |
my($me, $cn, $cert) = @_;
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
# we can return early if there are no SAN options.
|
|
Packit |
ec7ac3 |
my @sans = $cert->peer_certificate('subjectAltNames');
|
|
Packit |
ec7ac3 |
return unless scalar @sans;
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
(my $common_name = $cn) =~ s/.*=//; # strip off the prefix.
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
# get the ( type-id, value ) pairwise
|
|
Packit |
ec7ac3 |
# currently only the basic CN to san_name check
|
|
Packit |
ec7ac3 |
while( my ( $type_id, $value ) = splice( @sans, 0, 2 ) ) {
|
|
Packit |
ec7ac3 |
return 'ok' if $me->_cn_match($common_name,$value);
|
|
Packit |
ec7ac3 |
}
|
|
Packit |
ec7ac3 |
return;
|
|
Packit |
ec7ac3 |
}
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
sub _check_sock
|
|
Packit |
ec7ac3 |
{
|
|
Packit |
ec7ac3 |
my($self, $req, $sock) = @_;
|
|
Packit |
ec7ac3 |
my $check = $req->header("If-SSL-Cert-Subject");
|
|
Packit |
ec7ac3 |
if (defined $check) {
|
|
Packit |
ec7ac3 |
my $cert = $sock->get_peer_certificate ||
|
|
Packit |
ec7ac3 |
die "Missing SSL certificate";
|
|
Packit |
ec7ac3 |
my $subject = $cert->subject_name;
|
|
Packit |
ec7ac3 |
unless ( $subject =~ /$check/ ) {
|
|
Packit |
ec7ac3 |
my $ok = $self->_in_san( $check, $cert);
|
|
Packit |
ec7ac3 |
die "Bad SSL certificate subject: '$subject' !~ /$check/"
|
|
Packit |
ec7ac3 |
unless $ok;
|
|
Packit |
ec7ac3 |
}
|
|
Packit |
ec7ac3 |
$req->remove_header("If-SSL-Cert-Subject"); # don't pass it on
|
|
Packit |
ec7ac3 |
}
|
|
Packit |
ec7ac3 |
}
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
sub _get_sock_info
|
|
Packit |
ec7ac3 |
{
|
|
Packit |
ec7ac3 |
my $self = shift;
|
|
Packit |
ec7ac3 |
$self->SUPER::_get_sock_info(@_);
|
|
Packit |
ec7ac3 |
my($res, $sock) = @_;
|
|
Packit |
ec7ac3 |
$res->header("Client-SSL-Cipher" => $sock->get_cipher);
|
|
Packit |
ec7ac3 |
my $cert = $sock->get_peer_certificate;
|
|
Packit |
ec7ac3 |
if ($cert) {
|
|
Packit |
ec7ac3 |
$res->header("Client-SSL-Cert-Subject" => $cert->subject_name);
|
|
Packit |
ec7ac3 |
$res->header("Client-SSL-Cert-Issuer" => $cert->issuer_name);
|
|
Packit |
ec7ac3 |
}
|
|
Packit |
ec7ac3 |
if (!$self->{ssl_opts}{SSL_verify_mode}) {
|
|
Packit |
ec7ac3 |
$res->push_header("Client-SSL-Warning" => "Peer certificate not verified");
|
|
Packit |
ec7ac3 |
}
|
|
Packit |
ec7ac3 |
elsif (!$self->{ssl_opts}{SSL_verifycn_scheme}) {
|
|
Packit |
ec7ac3 |
$res->push_header("Client-SSL-Warning" => "Peer hostname match with certificate not verified");
|
|
Packit |
ec7ac3 |
}
|
|
Packit |
ec7ac3 |
$res->header("Client-SSL-Socket-Class" => $Net::HTTPS::SSL_SOCKET_CLASS);
|
|
Packit |
ec7ac3 |
}
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
# upgrade plain socket to SSL, used for CONNECT tunnel when proxying https
|
|
Packit |
ec7ac3 |
# will only work if the underlying socket class of Net::HTTPS is
|
|
Packit |
ec7ac3 |
# IO::Socket::SSL, but code will only be called in this case
|
|
Packit |
ec7ac3 |
if ( $Net::HTTPS::SSL_SOCKET_CLASS->can('start_SSL')) {
|
|
Packit |
ec7ac3 |
*_upgrade_sock = sub {
|
|
Packit |
ec7ac3 |
my ($self,$sock,$url) = @_;
|
|
Packit |
ec7ac3 |
$sock = LWP::Protocol::https::Socket->start_SSL( $sock,
|
|
Packit |
ec7ac3 |
SSL_verifycn_name => $url->host,
|
|
Packit |
ec7ac3 |
SSL_hostname => $url->host,
|
|
Packit |
ec7ac3 |
$self->_extra_sock_opts,
|
|
Packit |
ec7ac3 |
);
|
|
Packit |
ec7ac3 |
$@ = LWP::Protocol::https::Socket->errstr if ! $sock;
|
|
Packit |
ec7ac3 |
return $sock;
|
|
Packit |
ec7ac3 |
}
|
|
Packit |
ec7ac3 |
}
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
#-----------------------------------------------------------
|
|
Packit |
ec7ac3 |
package LWP::Protocol::https::Socket;
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
our @ISA = qw(Net::HTTPS LWP::Protocol::http::SocketMethods);
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
1;
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
__END__
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
=head1 NAME
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
LWP::Protocol::https - Provide https support for LWP::UserAgent
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
=head1 SYNOPSIS
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
use LWP::UserAgent;
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
$ua = LWP::UserAgent->new(ssl_opts => { verify_hostname => 1 });
|
|
Packit |
ec7ac3 |
$res = $ua->get("https://www.example.com");
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
# specify a CA path
|
|
Packit |
ec7ac3 |
$ua = LWP::UserAgent->new(
|
|
Packit |
ec7ac3 |
ssl_opts => {
|
|
Packit |
ec7ac3 |
SSL_ca_path => '/etc/ssl/certs',
|
|
Packit |
ec7ac3 |
verify_hostname => 1,
|
|
Packit |
ec7ac3 |
}
|
|
Packit |
ec7ac3 |
);
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
=head1 DESCRIPTION
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
The LWP::Protocol::https module provides support for using https schemed
|
|
Packit |
ec7ac3 |
URLs with LWP. This module is a plug-in to the LWP protocol handling, so
|
|
Packit |
ec7ac3 |
you don't use it directly. Once the module is installed LWP is able
|
|
Packit |
ec7ac3 |
to access sites using HTTP over SSL/TLS.
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
If hostname verification is requested by LWP::UserAgent's C<ssl_opts>, and
|
|
Packit |
ec7ac3 |
neither C<SSL_ca_file> nor C<SSL_ca_path> is set, then C<SSL_ca_file> is
|
|
Packit |
ec7ac3 |
implied to be the one provided by Mozilla::CA. If the Mozilla::CA module
|
|
Packit |
ec7ac3 |
isn't available SSL requests will fail. Either install this module, set up an
|
|
Packit |
ec7ac3 |
alternative C<SSL_ca_file> or disable hostname verification.
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
This module used to be bundled with the libwww-perl, but it was unbundled in
|
|
Packit |
ec7ac3 |
v6.02 in order to be able to declare its dependencies properly for the CPAN
|
|
Packit |
ec7ac3 |
tool-chain. Applications that need https support can just declare their
|
|
Packit |
ec7ac3 |
dependency on LWP::Protocol::https and will no longer need to know what
|
|
Packit |
ec7ac3 |
underlying modules to install.
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
=head1 SEE ALSO
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
L<IO::Socket::SSL>, L<Crypt::SSLeay>, L<Mozilla::CA>
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
=head1 COPYRIGHT
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
Copyright 1997-2011 Gisle Aas.
|
|
Packit |
ec7ac3 |
|
|
Packit |
ec7ac3 |
This library is free software; you can redistribute it and/or
|
|
Packit |
ec7ac3 |
modify it under the same terms as Perl itself.
|