[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Re: libio-socket-ssl-perl_1.15 in lenny?



On Sun, 28 Sep 2008 16:59:15 +0200, Luk Claes wrote:

> > It's the decision of the release team, therefore I'm cc'ing them and
> > ask for their opinion instead of guessing what they may think :)
> Please provide a diff with all the whitespace changes in SSL.pm stripped.

Attached is the output of 
$ svn diff -x -b -r21743:24723 SSL.pm 

/*
r24723: releasing version 1.15-1
r21743: releasing version 1.13-1
*/

Thanks for looking into this issue!

Cheers,
gregor
-- 
 .''`.   Home: http://info.comodo.priv.at/{,blog/} / GPG Key ID: 0x00F3CFE4
 : :' :  Debian GNU/Linux user, admin, & developer - http://www.debian.org/
 `. `'   Member of VIBE!AT, SPI Inc., fellow of FSFE | http://got.to/quote/
   `-    NP: Aimee Mann: I Know There's A Word
Index: SSL.pm
===================================================================
--- SSL.pm	(revision 21743)
+++ SSL.pm	(revision 24723)
@@ -32,7 +32,6 @@
 	if $@;
 }
 
-
 use vars qw(@ISA $VERSION $DEBUG $SSL_ERROR $GLOBAL_CONTEXT_ARGS @EXPORT );
 
 {
@@ -46,13 +45,13 @@
     my $y = Net::SSLeay::ERROR_WANT_WRITE();
     use constant SSL_WANT_WRITE => dualvar( \$y, 'SSL wants a write first' );
 
-    @EXPORT = qw( SSL_WANT_READ SSL_WANT_WRITE $SSL_ERROR );
+	@EXPORT = qw( SSL_WANT_READ SSL_WANT_WRITE $SSL_ERROR GEN_DNS GEN_IPADD );
 }
 
 BEGIN {
     # Declare @ISA, $VERSION, $GLOBAL_CONTEXT_ARGS
     @ISA = qw(IO::Socket::INET);
-    $VERSION = '1.13';
+	$VERSION = '1.15';
     $GLOBAL_CONTEXT_ARGS = {};
 
     #Make $DEBUG another name for $Net::SSLeay::trace
@@ -65,18 +64,46 @@
     Net::SSLeay::load_error_strings();
     Net::SSLeay::SSLeay_add_ssl_algorithms();
     Net::SSLeay::randomize();
-
 }
 
 sub DEBUG {
-    $DEBUG or return;
+	$DEBUG>=shift or return; # check against debug level
     my (undef,$file,$line) = caller;
     my $msg = shift;
+	$file = '...'.substr( $file,-17 ) if length($file)>20;
     $msg = sprintf $msg,@_ if @_;
     print STDERR "DEBUG: $file:$line: $msg\n";
 }
 
+BEGIN {
+	# import some constants from Net::SSLeay or use hard-coded defaults
+	# if Net::SSLeay isn't recent enough to provide the constants
+	my %const = (
+		NID_CommonName => 13,
+		GEN_DNS => 2,
+		GEN_IPADD => 7,
+	);
+	while ( my ($name,$value) = each %const ) {
+		no strict 'refs';
+		*{$name} = UNIVERSAL::can( 'Net::SSLeay', $name ) || sub { $value };
+	}
 
+	# check if we have something to handle IDN
+	local $SIG{__DIE__}; local $SIG{__WARN__}; # be silent
+	if ( eval { require Net::IDN::Encode }) {
+		*{idn_to_ascii} = \&Net::IDN::Encode::domain_to_ascii;
+	} elsif ( eval { require Net::LibIDN }) {
+		*{idn_to_ascii} = \&Net::LibIDN::idn_to_ascii;
+	} else {
+		# default: croak if we really got an unencoded international domain
+		*{idn_to_ascii} = sub {
+			my $domain = shift;
+			return $domain if $domain =~m{^[a-zA-Z0-9-_\.]+$};
+			croak "cannot handle international domains, please install Net::LibIDN or Net::IDN::Encode"
+		}
+	}
+}
+
 # Export some stuff
 # inet4|inet6|debug will be handeled by myself, everything
 # else will be handeld the Exporter way
@@ -85,11 +112,20 @@
 
     my @export;
     foreach (@_) { 
-	@ISA=qw(IO::Socket::INET), next if /inet4/i;
-	@ISA=qw(IO::Socket::INET6), next if /inet6/i;
-	$DEBUG=$1, next if /debug(\d)/; 
+		if ( /^inet4$/i ) {
+			require IO::Socket::INET;
+			@ISA = 'IO::Socket::INET'
+		} elsif ( /^inet6$/i ) {
+			require IO::Socket::INET6;
+			require Socket6;
+			Socket6->import( 'inet_pton' );
+			@ISA = 'IO::Socket::INET6'
+		} elsif ( /^:?debug(\d+)/ ) {
+			$DEBUG=$1;
+		} else {
 	push @export,$_
     }
+	}
 
     @_ = ( $class,@export );
     goto &Exporter::import;
@@ -130,16 +166,18 @@
     my ($self, $arg_hash) = @_;
 
     my $is_server = $arg_hash->{'SSL_server'} || $arg_hash->{'Listen'} || 0;
-    my %default_args =
-	('Proto'         => 'tcp',
-	 'SSL_server'    => $is_server,
-	 'SSL_ca_file'   => 'certs/my-ca.pem',
-	 'SSL_ca_path'   => 'ca/',
-	 'SSL_use_cert'  => $is_server,
-	 'SSL_check_crl' => 0,
-	 'SSL_version'   => 'sslv23',
-	 'SSL_verify_mode' => Net::SSLeay::VERIFY_NONE(),
-	 'SSL_verify_callback' => 0,
+	my %default_args = (
+		Proto => 'tcp',
+		SSL_server => $is_server,
+		SSL_ca_file => 'certs/my-ca.pem',
+		SSL_ca_path => 'ca/',
+		SSL_use_cert => $is_server,
+		SSL_check_crl => 0,
+		SSL_version	=> 'sslv23',
+		SSL_verify_mode => Net::SSLeay::VERIFY_NONE(),
+		SSL_verify_callback => undef,
+		SSL_verifycn_scheme => undef,  # don't verify cn
+		SSL_verifycn_name => undef,    # use from PeerAddr/PeerHost
     );
      
     # SSL_key_file and SSL_cert_file will only be set in defaults if 
@@ -161,13 +199,44 @@
 
     #Handle CA paths properly if no CA file is specified
     if ($arg_hash->{'SSL_ca_path'} ne '' and !(-f $arg_hash->{'SSL_ca_file'})) {
-	warn "CA file $arg_hash->{'SSL_ca_file'} not found, using CA path instead.\n" if ($DEBUG);
+		DEBUG(1, "CA file $arg_hash->{SSL_ca_file} not found, using CA path instead.\n" )
+			if $arg_hash->{SSL_ca_file};
 	$arg_hash->{'SSL_ca_file'} = '';
     }
 
+	my $vcn_scheme = delete $arg_hash->{SSL_verifycn_scheme};
+	if ( $vcn_scheme && $vcn_scheme ne 'none' ) {
+		my $vcb = $arg_hash->{SSL_verify_callback};
+		$arg_hash->{SSL_verify_callback} = sub {
+			my ($ok,$ctx_store,$cert,$error) = @_;
+			$ok = $vcb->($ok,$ctx_store,$cert,$error) if $vcb;
+			$ok or return;
+			my $depth = Net::SSLeay::X509_STORE_CTX_get_error_depth($ctx_store);
+			return $ok if $depth != 0;
+
+			# use SSL_peer_hostname or determine from PeerAddr
+			my $arg_hash = ${*$self}{_SSL_arguments};
+			my $host = $arg_hash->{SSL_verifycn_name};
+			if (not defined($host)) {
+				$host = ( $arg_hash->{PeerAddr} || $arg_hash->{PeerHost} );
+				$host =~s{:\w+$}{} if ! $host;
+			}
+			$host ||= ref($vcn_scheme) && $vcn_scheme->{callback} && 'unknown';
+			$host or return $self->error( "Cannot determine peer hostname for verification" );
+
+			# verify name
+			my $x509 = Net::SSLeay::X509_STORE_CTX_get_current_cert($ctx_store);
+			my $rv = verify_hostname_of_cert( $host,$x509,$vcn_scheme );
+			# just do some code here against optimization because x509 has no
+			# increased reference and CRYPTO_add is not available from Net::SSLeay
+			DEBUG(99999,"don't to anything with $x509" );
+			return $rv;
+		};
+	}
+
     ${*$self}{'_SSL_arguments'} = $arg_hash;
     ${*$self}{'_SSL_ctx'} = IO::Socket::SSL::SSL_Context->new($arg_hash) || return;
-    ${*$self}{'_SSL_opened'} = 1 if ($is_server);
+	${*$self}{'_SSL_opened'} = 1 if $is_server;
 
     return $self;
 }
@@ -181,7 +250,7 @@
 	$err == Net::SSLeay::ERROR_WANT_WRITE() ? SSL_WANT_WRITE :
 	return;
     $! ||= EAGAIN;
-    ${*$self}{'_SSL_last_err'} = $SSL_ERROR if (ref($self));
+	${*$self}{'_SSL_last_err'} = $SSL_ERROR if ref($self);
     return 1;
 }
 
@@ -197,9 +266,9 @@
 	# if this fails this might not be an error (e.g. if $! = EINPROGRESS
 	# and socket is nonblocking this is normal), so keep any error
 	# handling to the client
-	#DEBUG( 'socket not yet connected' );
+		DEBUG(2, 'socket not yet connected' );
 	$self->SUPER::connect(@_) || return;
-	#DEBUG( 'socket connected' );
+		DEBUG(2,'socket connected' );
     }
     return $self->connect_SSL;
 }
@@ -212,7 +281,7 @@
     my ($ssl,$ctx);
     if ( ! ${*$self}{'_SSL_opening'} ) {
 	# start ssl connection
-	#DEBUG( 'ssl handshake not started' );
+		DEBUG(2,'ssl handshake not started' );
 	${*$self}{'_SSL_opening'} = 1;
 	my $arg_hash = ${*$self}{'_SSL_arguments'};
 
@@ -243,7 +312,7 @@
     	? $args->{Timeout} 
 	: ${*$self}{io_socket_timeout}; # from IO::Socket
     if ( defined($timeout) && $timeout>0 && $self->blocking(0) ) {
-	#DEBUG( "set socket to non-blocking to enforce timeout=$timeout" );
+		DEBUG(2, "set socket to non-blocking to enforce timeout=$timeout" );
 	# timeout was given and socket was blocking
     	# enforce timeout with now non-blocking socket
     } else {
@@ -255,17 +324,17 @@
     for my $dummy (1) {
 	#DEBUG( 'calling ssleay::connect' );
 	my $rv = Net::SSLeay::connect($ssl);
-	#DEBUG( "connect -> rv=$rv" );
+		DEBUG( 3,"Net::SSLeay::connect -> $rv" );
 	if ( $rv < 0 ) {
 	    unless ( $self->_set_rw_error( $ssl,$rv )) {
 		$self->error("SSL connect attempt failed with unknown error");
 		delete ${*$self}{'_SSL_opening'};
-		${*$self}{'_SSL_opened'} = 1;
-		#DEBUG( "fatal SSL error: $SSL_ERROR" );
+				${*$self}{'_SSL_opened'} = -1;
+				DEBUG(1, "fatal SSL error: $SSL_ERROR" );
 		return $self->fatal_ssl_error();
 	    }
 
-	    #DEBUG( 'ssl handshake in progress' );
+			DEBUG(2,'ssl handshake in progress' );
 	    # connect failed because handshake needs to be completed
 	    # if socket was non-blocking or no timeout was given return with this error
 	    return if ! defined($timeout);
@@ -275,27 +344,27 @@
 	    if ( $timeout>0 ) {
 		my $vec = '';
 		vec($vec,$self->fileno,1) = 1;
-		#DEBUG( "waiting for fd to become ready: $SSL_ERROR" );
+				DEBUG(2, "waiting for fd to become ready: $SSL_ERROR" );
 	    	$rv = 
 		    $SSL_ERROR == SSL_WANT_READ ? select( $vec,undef,undef,$timeout) :
 		    $SSL_ERROR == SSL_WANT_WRITE ? select( undef,$vec,undef,$timeout) :
 		    undef;
 	    } else {
-		#DEBUG( "handshake failed because no more time" );
+				DEBUG(2,"handshake failed because no more time" );
 	    	$! = ETIMEDOUT
 	    }
 	    if ( ! $rv ) {
-		#DEBUG( "handshake failed because socket did not became ready" );
+				DEBUG(2,"handshake failed because socket did not became ready" );
 		# failed because of timeout, return
 	    	$! ||= ETIMEDOUT;
 		delete ${*$self}{'_SSL_opening'};
-		${*$self}{'_SSL_opened'} = 1;
+				${*$self}{'_SSL_opened'} = -1;
 		$self->blocking(1); # was blocking before
 	    	return 
 	    }
 
 	    # socket is ready, try non-blocking connect again after recomputing timeout
-	    #DEBUG( "socket ready, retrying connect" );
+			DEBUG(2,"socket ready, retrying connect" );
 	    my $now = time();
 	    $timeout -= $now - $start;
 	    $start = $now;
@@ -303,14 +372,14 @@
 
 	} elsif ( $rv == 0 ) {
 	    delete ${*$self}{'_SSL_opening'};
-	    #DEBUG( "connection failed - connect returned 0" );
+			DEBUG(2,"connection failed - connect returned 0" );
 	    $self->error("SSL connect attempt failed because of handshake problems" );
-	    ${*$self}{'_SSL_opened'} = 1;
+			${*$self}{'_SSL_opened'} = -1;
 	    return $self->fatal_ssl_error();
 	}
     }
 
-    #DEBUG( 'ssl handshake done' );
+	DEBUG(2,'ssl handshake done' );
     # ssl connect successful
     delete ${*$self}{'_SSL_opening'};
     ${*$self}{'_SSL_opened'}=1;
@@ -352,13 +421,13 @@
     my $socket = ${*$self}{'_SSL_opening'};
     if ( ! $socket ) {
 	# underlying socket not done
-	#DEBUG( 'no socket yet' );
+		DEBUG(2,'no socket yet' );
 	$socket = $self->SUPER::accept($class) || return;
-	#DEBUG( 'accept created normal socket '.$socket );
+		DEBUG(2,'accept created normal socket '.$socket );
     }
 
     $self->accept_SSL($socket) || return;
-    #DEBUG( 'accept_SSL ok' );
+	DEBUG(2,'accept_SSL ok' );
 
     return wantarray ? ($socket, getpeername($socket) ) : $socket;
 }
@@ -370,7 +439,7 @@
 
     my $ssl;
     if ( ! ${*$self}{'_SSL_opening'} ) {
-	#DEBUG( 'starting sslifying' );
+		DEBUG(2,'starting sslifying' );
 	${*$self}{'_SSL_opening'} = $socket;
 	my $arg_hash = ${*$self}{'_SSL_arguments'};
 	${*$socket}{'_SSL_arguments'} = { %$arg_hash, SSL_server => 0 };
@@ -394,7 +463,7 @@
     $ssl ||= ${*$socket}{'_SSL_object'};
 
     $SSL_ERROR = undef;
-    #DEBUG( 'calling ssleay::accept' );
+	#DEBUG(2,'calling ssleay::accept' );
 
     my $timeout = exists $args->{Timeout} 
     	? $args->{Timeout} 
@@ -410,12 +479,12 @@
     my $start = defined($timeout) && time();
     for my $dummy (1) {
 	my $rv = Net::SSLeay::accept($ssl);
-	#DEBUG( 'called ssleay::accept rv='.$rv );
+		DEBUG(3, "Net::SSLeay::accept -> $rv" );
 	if ( $rv < 0 ) {
 	    unless ( $socket->_set_rw_error( $ssl,$rv )) {
 		$socket->error("SSL accept attempt failed with unknown error");
 		delete ${*$self}{'_SSL_opening'};
-		${*$socket}{'_SSL_opened'} = 1;
+				${*$socket}{'_SSL_opened'} = -1;
 		return $socket->fatal_ssl_error();
 	    }
 
@@ -439,7 +508,7 @@
 		# failed because of timeout, return
 	    	$! ||= ETIMEDOUT;
 		delete ${*$self}{'_SSL_opening'};
-		${*$socket}{'_SSL_opened'} = 1;
+				${*$socket}{'_SSL_opened'} = -1;
 		$socket->blocking(1); # was blocking before
 	    	return 
 	    }
@@ -453,12 +522,12 @@
 	} elsif ( $rv == 0 ) {
 	    $socket->error("SSL connect accept failed because of handshake problems" );
 	    delete ${*$self}{'_SSL_opening'};
-	    ${*$socket}{'_SSL_opened'} = 1;
+			${*$socket}{'_SSL_opened'} = -1;
 	    return $socket->fatal_ssl_error();
 	}
     }
 
-    #DEBUG( 'handshake done, socket ready' );
+	DEBUG(2,'handshake done, socket ready' );
     # socket opened
     delete ${*$self}{'_SSL_opening'};
     ${*$socket}{'_SSL_opened'} = 1;
@@ -674,7 +743,8 @@
 sub stop_SSL {
     my $self = shift || return _invalid_object();
     my $stop_args = (ref($_[0]) eq 'HASH') ? $_[0] : {@_};
-    return $self->error("SSL object already closed") unless (${*$self}{'_SSL_opened'});
+	return $self->error("SSL object already closed") 
+		unless (${*$self}{'_SSL_opened'} == 1);
 
     if (my $ssl = ${*$self}{'_SSL_object'}) {
 	my $shutdown_done;
@@ -748,7 +818,7 @@
 sub kill_socket {
     my $self = shift;
     shutdown($self, 2);
-    $self->close(SSL_no_shutdown => 1) if (${*$self}{'_SSL_opened'});
+	$self->close(SSL_no_shutdown => 1) if (${*$self}{'_SSL_opened'} == 1);
     delete(${*$self}{'_SSL_ctx'});
     return;
 }
@@ -799,7 +869,7 @@
     my $start_handshake = $arg_hash->{SSL_startHandshake};
     if ( ! defined($start_handshake) || $start_handshake ) {
 	# if we have no callback force blocking mode
-	#DEBUG( "start handshake" );
+		DEBUG(2, "start handshake" );
 	my $blocking = $socket->blocking(1);
 	my $result = ${*$socket}{'_SSL_arguments'}{SSL_server}
 	    ? $socket->accept_SSL(%to)
@@ -807,7 +877,7 @@
 	$socket->blocking(0) if !$blocking;
     	return $result ? $socket : (bless($socket, $original_class) && ());
     } else {
-	#DEBUG( "dont start handshake: $socket" );
+		DEBUG(2, "dont start handshake: $socket" );
     	return $socket; # just return upgraded socket 
     }
 
@@ -835,25 +905,201 @@
     return Net::SSLeay::dump_peer_certificate($ssl);
 }
 
-sub peer_certificate {
+{
+	my %dispatcher = (
+		issuer =>	  sub { Net::SSLeay::X509_NAME_oneline( Net::SSLeay::X509_get_issuer_name( shift )) },
+		subject =>	  sub { Net::SSLeay::X509_NAME_oneline( Net::SSLeay::X509_get_subject_name( shift )) },
+	);
+	if ( $Net::SSLeay::VERSION >= 1.30 ) {
+		# I think X509_NAME_get_text_by_NID got added in 1.30
+		$dispatcher{commonName} = sub { 
+			my $cn = Net::SSLeay::X509_NAME_get_text_by_NID(
+				Net::SSLeay::X509_get_subject_name( shift ), NID_CommonName);
+			$cn =~s{\0$}{}; # work around Bug in Net::SSLeay <1.33
+			$cn;
+		}
+	} else {
+		$dispatcher{commonName} = sub { 
+			croak "you need at least Net::SSLeay version 1.30 for getting commonName"
+		}
+	}
+
+	if ( $Net::SSLeay::VERSION >= 1.33 ) {
+		# X509_get_subjectAltNames did not really work before
+		$dispatcher{subjectAltNames} = sub { Net::SSLeay::X509_get_subjectAltNames( shift ) };
+	} else {
+		$dispatcher{subjectAltNames} = sub {
+			croak "you need at least Net::SSLeay version 1.33 for getting subjectAltNames"
+		};
+	}
+
+	# alternative names
+	$dispatcher{authority} = $dispatcher{issuer};
+	$dispatcher{owner}     = $dispatcher{subject};
+	$dispatcher{cn}	       = $dispatcher{commonName};
+
+	sub peer_certificate {
     my ($self, $field) = @_;
-    my $ssl = $self->_get_ssl_object || return;
+		my $ssl = $self->_get_ssl_object or return;
 
-    my $cert = ${*$self}{'_SSL_certificate'} ||= Net::SSLeay::get_peer_certificate($ssl) ||
-	return $self->error("Could not retrieve peer certificate");
+		my $cert = ${*$self}{_SSL_certificate} 
+			||= Net::SSLeay::get_peer_certificate($ssl) 
+			or return $self->error("Could not retrieve peer certificate");
 
     if ($field) {
-	my $name = ($field eq "issuer" or $field eq "authority")
-	    ? Net::SSLeay::X509_get_issuer_name($cert)
-	    : Net::SSLeay::X509_get_subject_name($cert);
+			my $sub = $dispatcher{$field} or croak 
+				"invalid argument for peer_certificate, valid are: ".join( " ",keys %dispatcher ).
+				"\nMaybe you need to upgrade your Net::SSLeay";
+			return $sub->($cert);
+		} else {
+			return $cert
+		}
+	}
 
-	return $self->error("Could not retrieve peer certificate $field") unless ($name);
-	return Net::SSLeay::X509_NAME_oneline($name);
+	# known schemes, possible attributes are:
+	#  - wildcards_in_alt (0, 'leftmost', 'anywhere')
+	#  - wildcards_in_cn (0, 'leftmost', 'anywhere')
+	#  - check_cn (0, 'always', 'when_only')
+
+	my %scheme = (
+		# rfc 4513
+		ldap => {
+			wildcards_in_cn	 => 0,
+			wildcards_in_alt => 'leftmost',
+			check_cn         => 'always',
+		},
+		# rfc 2818
+		http => {
+			wildcards_in_cn	 => 0,
+			wildcards_in_alt => 'anywhere',
+			check_cn         => 'when_only',
+		},
+		# rfc 3207
+		# This is just a dumb guess
+		# RFC3207 itself just says, that the client should expect the
+		# domain name of the server in the certificate. It doesn't say
+		# anything about wildcards, so I forbid them. It doesn't say
+		# anything about alt names, but other documents show, that alt 
+		# names should be possible. The check_cn value again is a guess.
+		# Fix the spec!
+		smtp => {
+			wildcards_in_cn	 => 0,
+			wildcards_in_alt => 0,
+			check_cn         => 'always'
+		},
+		none => {}, # do not check
+	);
+
+	$scheme{www}  = $scheme{http}; # alias
+	$scheme{xmpp} = $scheme{http}; # rfc 3920
+	$scheme{pop3} = $scheme{ldap}; # rfc 2595
+	$scheme{imap} = $scheme{ldap}; # rfc 2595
+	$scheme{acap} = $scheme{ldap}; # rfc 2595
+	$scheme{nntp} = $scheme{ldap}; # rfc 4642
+
+	# function to verify the hostname
+	#
+	# as every application protocol has its own rules to do this
+	# we provide some default rules as well as a user-defined
+	# callback
+
+	sub verify_hostname_of_cert {
+		my $identity = shift;
+		my $cert = shift;
+		my $scheme = shift || 'none';
+		if ( ! ref($scheme) ) {
+			DEBUG(3, "scheme=$scheme cert=$cert" );
+			$scheme = $scheme{$scheme} or croak "scheme $scheme not defined";
+		}
+
+		# get data from certificate
+		my $commonName = $dispatcher{cn}->($cert);
+		my @altNames = $dispatcher{subjectAltNames}->($cert);
+		DEBUG(3,"identity=$identity cn=$commonName alt=@altNames" );
+
+		if ( my $sub = $scheme->{callback} ) {
+			# use custom callback
+			return $sub->($identity,$commonName,@altNames);
+		}
+
+		# is the given hostname an IP address? Then we have to convert to network byte order [RFC791][RFC2460]
+
+		my ($ip4,$ip6);
+		if ( $identity =~m{:} ) {
+			# no IPv4 or hostname have ':'	in it, try IPv6.
+			#  make sure that Socket6 was loaded properly
+			UNIVERSAL::can( __PACKAGE__, 'inet_pton' ) or croak
+				q[Looks like IPv6 address, make sure that Socket6 is loaded or make "use IO::Socket::SSL 'inet6'];
+			$ip6 = inet_pton( $identity ) or croak "'$identity' is not IPv6, but neither IPv4 nor hostname";
+		} elsif ( $identity =~m{^\d+\.\d+\.\d+\.\d+$} ) {
+			 # definitly no hostname, try IPv4
+			$ip4 = inet_aton( $identity ) or croak "'$identity' is not IPv4, but neither IPv6 nor hostname";
     } else {
-    	return $cert
+			# assume hostname
+			if ( $identity !~m{^[a-zA-Z0-9-_\.]+$} ) {
+				$identity = idn_to_ascii($identity) or
+					croak "Warning: Given name '$identity' could not be converted to IDNA!";
+			}
+		}
+
+		# do the actual verification
+		my $check_name = sub {
+			my ($name,$identity,$wtyp) = @_;
+			$wtyp ||= '';
+			my $pattern;
+			### IMPORTANT!
+			# we accept only a single wildcard and only for a single part of the FQDN
+			# e.g *.example.org does match www.example.org but not bla.www.example.org
+			# The RFCs are in this regard unspecific but we don't want to have to
+			# deal with certificates like *.com, *.co.uk or even *
+			# see also http://nils.toedtmann.net/pub/subjectAltName.txt
+			if ( $wtyp eq 'anywhere' and $name =~m{^([\w\-]*)\*(.+)} ) {
+				$pattern = qr{^\Q$1\E[\w\-]*\Q$2\E$}i;
+			} elsif ( $wtyp eq 'leftmost' and $name =~m{^\*(\..+)$} ) {
+				$pattern = qr{^[\w\-]*\Q$1\E$}i;
+			} else {
+				$pattern = qr{^\Q$name}i;
+			}
+			return $identity =~ $pattern;
     };
+
+		my $alt_dnsNames = 0;
+		while (@altNames) {
+			my ($type, $name) = splice (@altNames, 0, 2);
+			if ( $type == GEN_IPADD ) {
+				# exakt match needed for IP
+				# $name is already packed format (inet_xton)
+				return 1 if 
+					$ip6 ? $ip6 eq $name : 
+					$ip4 ? $ip4 eq $name :
+					0;
+
+			} elsif ( $type == GEN_DNS ) {
+				$name =~s/\s+$//; $name =~s/^\s+//;
+				$alt_dnsNames++;
+				$check_name->($name,$identity,$scheme->{wildcards_in_alt})
+					and return 1;
+			}
+		}
+
+		if ( $scheme->{check_cn} eq 'always' or 
+			$scheme->{check_cn} eq 'when_only' and !$alt_dnsNames) {
+			$check_name->($commonName,$identity,$scheme->{wildcars_in_cn})
+				and return 1;
+		}
+
+		return 0; # no match
+	}
 }
 
+sub verify_hostname {
+	my $self = shift;
+	my $host = shift;
+	my $cert = $self->peer_certificate;
+	return verify_hostname_of_cert( $host,$cert,@_ );
+}
+
+
 sub get_cipher {
     my $ssl = shift()->_get_ssl_object || return;
     return Net::SSLeay::get_cipher($ssl);
@@ -870,7 +1116,9 @@
     $@ = $self->errstr;
     if (defined $error_trap and ref($error_trap) eq 'CODE') {
 	$error_trap->($self, $self->errstr()."\n".$self->get_ssleay_error());
-    } else { $self->kill_socket; }
+	} else { 
+		$self->kill_socket; 
+	}
     return;
 }
 
@@ -884,7 +1132,7 @@
 sub error {
     my ($self, $error, $destroy_socket) = @_;
     $error .= Net::SSLeay::ERR_error_string(Net::SSLeay::ERR_get_error());
-    carp $error."\n".$self->get_ssleay_error() if $DEBUG;
+	DEBUG(2, $error."\n".$self->get_ssleay_error());
     $SSL_ERROR = dualvar( -1, $error );
     ${*$self}{'_SSL_last_err'} = $SSL_ERROR if (ref($self));
     return;
@@ -893,7 +1141,8 @@
 
 sub DESTROY {
     my $self = shift || return;
-    $self->close(_SSL_in_DESTROY => 1, SSL_no_shutdown => 1) if (${*$self}{'_SSL_opened'});
+	$self->close(_SSL_in_DESTROY => 1, SSL_no_shutdown => 1) 
+		if (${*$self}{'_SSL_opened'} == 1);
     delete(${*$self}{'_SSL_ctx'});
 }
 
@@ -918,10 +1167,18 @@
     $GLOBAL_CONTEXT_ARGS->{SSL_session_cache} = shift;
 }
 
+sub set_ctx_defaults {
+	my %args = @_;
+	while ( my ($k,$v) = each %args ) {
+		$k =~s{^(SSL_)?}{SSL_};
+		$GLOBAL_CONTEXT_ARGS->{$k} = $v;
+	}
+}
 
+
 sub opened {
     my $self = shift;
-    return IO::Handle::opened($self) && ${*$self}{'_SSL_opened'};
+	return IO::Handle::opened($self) && ( ${*$self}{'_SSL_opened'} == 1 );
 }
 
 sub opening {
@@ -935,8 +1192,10 @@
 
 #Redundant IO::Handle functionality
 sub getline  { return(scalar shift->readline()) }
-sub getlines { if (wantarray()) { return(shift->readline()) }
-	       else { croak("Use of getlines() not allowed in scalar context");  }}
+sub getlines { 
+	return(shift->readline()) if wantarray();
+	croak("Use of getlines() not allowed in scalar context");
+}
 
 #Useless IO::Handle functionality
 sub truncate { croak("Use of truncate() not allowed with SSL") }
@@ -971,15 +1230,15 @@
     bless \$handle, $class;
 }
 
-sub READ     { ${shift()}->sysread  (@_) }
-sub READLINE { ${shift()}->readline (@_) }
-sub GETC     { ${shift()}->getc     (@_) }
+sub READ     { ${shift()}->sysread(@_) }
+sub READLINE { ${shift()}->readline(@_) }
+sub GETC     { ${shift()}->getc(@_) }
 
-sub PRINT    { ${shift()}->print    (@_) }
-sub PRINTF   { ${shift()}->printf   (@_) }
-sub WRITE    { ${shift()}->syswrite (@_) }
+sub PRINT    { ${shift()}->print(@_) }
+sub PRINTF   { ${shift()}->printf(@_) }
+sub WRITE    { ${shift()}->syswrite(@_) }
 
-sub FILENO   { ${shift()}->fileno   (@_) }
+sub FILENO   { ${shift()}->fileno(@_) }
 
 sub TELL     { $! = EBADF; return -1 }
 sub BINMODE  { return 0 }  # not perfect, but better than not implementing the method
@@ -1007,7 +1266,7 @@
 # it can be blessed.
 sub new {
     my $class = shift;
-    DEBUG( "$class @_" );
+	#DEBUG( "$class @_" );
     my $arg_hash = (ref($_[0]) eq 'HASH') ? $_[0] : {@_};
 
     my $ctx_object = $arg_hash->{'SSL_reuse_ctx'};
@@ -1036,24 +1295,23 @@
     # buffer was written and not block for the rest
     # SSL_MODE_ENABLE_PARTIAL_WRITE can be necessary for non-blocking because we
     # cannot guarantee, that the location of the buffer stays constant
-    Net::SSLeay::CTX_set_mode( $ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER
-    	|SSL_MODE_ENABLE_PARTIAL_WRITE);
+	Net::SSLeay::CTX_set_mode( $ctx, 
+		SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER|SSL_MODE_ENABLE_PARTIAL_WRITE);
 
 
-    my ($verify_mode, $verify_cb) = @{$arg_hash}{'SSL_verify_mode','SSL_verify_callback'};
-    unless ($verify_mode == Net::SSLeay::VERIFY_NONE())
-    {
-	&Net::SSLeay::CTX_load_verify_locations
-	    ($ctx, @{$arg_hash}{'SSL_ca_file','SSL_ca_path'}) ||
-	     return IO::Socket::SSL->error("Invalid certificate authority locations");
+	my $verify_mode = $arg_hash->{SSL_verify_mode};
+	unless ($verify_mode == Net::SSLeay::VERIFY_NONE()) {
+		Net::SSLeay::CTX_load_verify_locations(
+			$ctx, $arg_hash->{SSL_ca_file},$arg_hash->{SSL_ca_path}
+		) || return IO::Socket::SSL->error("Invalid certificate authority locations");
     }
 
     if ($arg_hash->{'SSL_check_crl'}) {
-	if (Net::SSLeay::OPENSSL_VERSION_NUMBER() >= 0x0090702f)
-	{
-	    Net::SSLeay::X509_STORE_CTX_set_flags
-		(Net::SSLeay::CTX_get_cert_store($ctx),
-		 Net::SSLeay::X509_V_FLAG_CRL_CHECK());
+		if (Net::SSLeay::OPENSSL_VERSION_NUMBER() >= 0x0090702f) {
+			Net::SSLeay::X509_STORE_CTX_set_flags(
+				Net::SSLeay::CTX_get_cert_store($ctx),
+				Net::SSLeay::X509_V_FLAG_CRL_CHECK()
+			);
 	} else {
 	    return IO::Socket::SSL->error("CRL not supported for OpenSSL < v0.9.7b");
 	}
@@ -1108,8 +1366,8 @@
 	}
     }
 
-    my $verify_callback = $verify_cb &&
-	sub {
+	my $verify_cb = $arg_hash->{SSL_verify_callback};
+	my $verify_callback = $verify_cb && sub {
 	    my ($ok, $ctx_store) = @_;
 	    my ($cert, $error);
 	    if ($ctx_store) {
@@ -1119,13 +1377,14 @@
 		    Net::SSLeay::X509_NAME_oneline(Net::SSLeay::X509_get_subject_name($cert));
 		$error &&= Net::SSLeay::ERR_error_string($error);
 	    }
+		DEBUG(3, "ok=$ok cert=$cert" );
 	    return $verify_cb->($ok, $ctx_store, $cert, $error);
 	};
 
     Net::SSLeay::CTX_set_verify($ctx, $verify_mode, $verify_callback);
 
     $ctx_object = { context => $ctx };
-    DEBUG( "new ctx $ctx" );
+	DEBUG(3, "new ctx $ctx" );
     $CTX_CREATED_IN_THIS_THREAD{$ctx} = 1;
 
     if ( my $cache = $arg_hash->{SSL_session_cache} ) {
@@ -1156,14 +1415,14 @@
 }
 
 
-sub CLONE { %CTX_CREATED_IN_THIS_THREAD = (); DEBUG( "clone!" ) }
+sub CLONE { %CTX_CREATED_IN_THIS_THREAD = (); }
 sub DESTROY {
     my $self = shift;
     if ( my $ctx = $self->{context} ) {
-	DEBUG( "free ctx $ctx open=".join( " ",keys %CTX_CREATED_IN_THIS_THREAD ));
+		DEBUG( 3,"free ctx $ctx open=".join( " ",keys %CTX_CREATED_IN_THIS_THREAD ));
 	if ( %CTX_CREATED_IN_THIS_THREAD and 
 	    delete $CTX_CREATED_IN_THIS_THREAD{$ctx} ) {
-	    DEBUG( "OK free ctx $ctx" );
+			DEBUG( 3,"OK free ctx $ctx" );
 	    Net::SSLeay::CTX_free($ctx);
 	}
     }
@@ -1237,18 +1496,16 @@
 
 =head1 SYNOPSIS
 
+	use strict;
     use IO::Socket::SSL;
 
-    my $client = IO::Socket::SSL->new("www.example.com:https");
+	my $client = IO::Socket::SSL->new("www.example.com:https") 
+		|| warn "I encountered a problem: ".IO::Socket::SSL::errstr();
+	$client->verify_hostname( 'www.example.com','http' )
+		|| die "hostname verification failed";
 
-    if ($client) {
 	print $client "GET / HTTP/1.0\r\n\r\n";
 	print <$client>;
-	close $client;
-    } else {
-	warn "I encountered a problem: ",
-	  IO::Socket::SSL::errstr();
-    }
 
 
 =head1 DESCRIPTION
@@ -1376,6 +1633,7 @@
 (0x00) does no authentication.  You may combine 0x01 (verify peer), 0x02 (fail
 verification if no peer certificate exists; ignored for clients), and 0x04
 (verify client once) to change the default.
+See OpenSSL man page for SSL_CTX_set_verify for more information.
 
 =item SSL_verify_callback
 
@@ -1388,6 +1646,22 @@
 The function should return 1 or 0, depending on whether it thinks the certificate
 is valid or invalid.  The default is to let OpenSSL do all of the busy work.
 
+=item SSL_verifycn_scheme
+
+Set the scheme used to automatically verify the hostname of the peer.
+See the information about the verification schemes in B<verify_hostname>.
+The default is undef, e.g. to not automatically verify the hostname.
+
+=item SSL_verifycn_name
+
+Set the name which is used in verification of hostname. If SSL_verifycn_scheme
+is set and no SSL_verifycn_name is given it will try to use the PeerHost and
+PeerAddr settings and fail if no name caan be determined.
+
+Using PeerHost or PeerAddr works only if you create the connection directly
+with C<< IO::Socket::SSL->new >>, if an IO::Socket::INET object is upgraded
+with B<start_SSL> the name has to be given in B<SSL_verifycn_name>.
+
 =item SSL_check_crl
 
 If you want to verify that the peer certificate has not been revoked by the
@@ -1504,13 +1778,99 @@
 
 =item B<peer_certificate($field)>
 
-If a peer certificate exists, this function can retrieve values from it.  Right now, the
-only fields it can return are "authority" and "owner" (or "issuer" and "subject" if
-you want to use OpenSSL names), corresponding to the certificate authority that signed the
-peer certificate and the owner of the peer certificate.  This function returns a string
-with all the information about the particular field in one parsable line.
-If no field is given it returns the full certificate (x509).
+If a peer certificate exists, this function can retrieve values from it. 
+If no field is given the internal representation of certificate from Net::SSLeay is
+returned.
+The following fields can be queried:
 
+=over 8
+
+=item authority (alias issuer)
+
+The certificate authority which signed the certificate.
+
+=item owner (alias subject)
+
+The owner of the certificate.
+
+=item commonName (alias cn) - only for Net::SSLeay version >=1.30
+
+The common name, usually the server name for SSL certificates.
+
+=item subjectAltNames - only for Net::SSLeay version >=1.33
+
+Alternative names for the subject, usually different names for the same
+server, like example.org, example.com, *.example.com.
+
+It returns a list of (typ,value) with typ GEN_DNS, GEN_IPADD etc (these
+constants are exported from IO::Socket::SSL). 
+See Net::SSLeay::X509_get_subjectAltNames.
+
+=back
+
+=item B<verify_hostname($hostname,$scheme)>
+
+This verifies the given hostname against the peer certificate using the
+given scheme. Hostname is usually what you specify within the PeerAddr.
+
+Verification of hostname against a certificate is different between various
+applications and RFCs. Some scheme allow wildcards for hostnames, some only
+in subjectAltNames, and even their different wildcard schemes are possible.
+
+To ease the verification the following schemes are predefined:
+
+=over 8
+
+=item ldap (rfc4513), pop3,imap,acap (rfc2995), nntp (rfc4642)
+
+Simple wildcards in subjectAltNames are possible, e.g. *.example.org matches
+www.example.org but not lala.www.example.org. If nothing from subjectAltNames
+match it checks against the common name, but there are no wildcards allowed.
+
+=item http (rfc2818), alias is www
+
+Extended wildcards in subjectAltNames are possible, e.g. *.example.org or
+even www*.example.org. Wildcards in the common name are not allowed. The common
+name will be only checked if no names are given in subjectAltNames.
+
+=item smtp (rfc3207)
+
+This RFC doesn't say much useful about the verification so it just assumes
+that subjectAltNames are possible, but no wildcards are possible anywhere.
+
+=back
+
+The scheme can be given either by specifying the name for one of the above predefined 
+schemes, by using a callback (see below) or by using a hash which can have the 
+following keys and values:
+
+=over 8
+
+=item check_cn:  0|'always'|'when_only'
+
+Determines if the common name gets checked. If 'always' it will always be checked 
+(like in ldap), if 'when_only' it will only be checked if no names are given in
+subjectAltNames (like in http), for any other values the common name will not be checked.
+
+=item wildcards_in_alt: 0|'leftmost'|'anywhere'
+
+Determines if and where wildcards in subjectAltNames are possible. If 'leftmost'
+only cases like *.example.org will be possible (like in ldap), for 'anywhere' 
+www*.example.org is possible too (like http), dangerous things like but www.*.org 
+or even '*' will not be allowed.
+
+=item wildcards_in_cn: 0|'leftmost'|'anywhere'
+
+Similar to wildcards_in_alt, but checks the common name. There is no predefined
+scheme which allows wildcards in common names.
+
+=back
+
+If you give a subroutine for verification it will be called with the arguments
+($hostname,$commonName,@subjectAltNames), where hostname is the name given for
+verification, commonName is the result from peer_certificate('cn') and
+subjectAltNames is the result from peer_certificate('subjectAltNames').
+
 =item B<errstr()>
 
 Returns the last error (in string form) that occurred.  If you do not have a real
@@ -1577,9 +1937,27 @@
 See the SSL_session_cache option of new() for more details.  Note that this sets the default
 cache globally, so use with caution.
 
+=item B<IO::Socket::SSL::set_ctx_defaults(%args)>
 
+With this function one can set defaults for all SSL_* parameter used for creation of
+the context, like the SSL_verify* parameter.
+
+=over 8
+
+=item mode - set default SSL_verify_mode
+
+=item callback - set default SSL_verify_callback
+
+=item scheme - set default SSL_verifycn_scheme
+
+=item name - set default SSL_verifycn_name
+
+If not given and scheme is hash reference with key callback it will be set to 'unknown'
+
 =back
 
+=back
+
 The following methods are unsupported (not to mention futile!) and IO::Socket::SSL
 will emit a large CROAK() if you are silly enough to use them:
 
@@ -1637,36 +2015,31 @@
 
 If you are having problems using IO::Socket::SSL despite the fact that can recite backwards
 the section of this documentation labelled 'Using SSL', you should try enabling debugging.  To
-specify the debug level, pass 'debug#' (where # is a number from 0 to 4) to IO::Socket::SSL
-when calling it:
+specify the debug level, pass 'debug#' (where # is a number from 0 to 3) to IO::Socket::SSL
+when calling it. 
+The debug level will also be propagated to Net::SSLeay::trace, see also L<Net::SSLeay>:
 
 =over 4
 
 =item use IO::Socket::SSL qw(debug0);
 
-#No debugging (default).
+No debugging (default).
 
 =item use IO::Socket::SSL qw(debug1);
 
-#Only print out errors.
+Print out errors from IO::Socket::SSL and ciphers from Net::SSLeay.
 
 =item use IO::Socket::SSL qw(debug2);
 
-#Print out errors and cipher negotiation.
+Print also information about call flow from IO::Socket::SSL and progress
+information from Net::SSLeay.
 
 =item use IO::Socket::SSL qw(debug3);
 
-#Print out progress, ciphers, and errors.
+Print also some data dumps from IO::Socket::SSL and from Net::SSLeay.
 
-=item use IO::Socket::SSL qw(debug4);
-
-#Print out everything, including data.
-
 =back
 
-You can also set $IO::Socket::SSL::DEBUG to 0-4, but that's a bit of a mouthful,
-isn't it?
-
 =head1 EXAMPLES
 
 See the 'example' directory.
@@ -1677,7 +2050,7 @@
 This is because IO::Socket::SSL is based on Net::SSLeay which 
 uses a global object to access some of the API of openssl
 and is therefore not threadsafe.
-It might probably work if you don't use SSL_verify_cb and
+It might probably work if you don't use SSL_verify_callback and
 SSL_password_cb.
 
 IO::Socket::SSL does not work together with Storable::fd_retrieve/fd_store.

Attachment: signature.asc
Description: Digital signature


Reply to: