Blame lib/LWP/Protocol/https.pm

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.