The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl
#
#  t/04-cli-cert.t - Test client certificates - based on an example
#                    program from Net::SSLeay
#
# Copyright (C) 2009  NZ Registry Services
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the Artistic License 2.0 or later.  You should
# have received a copy of the Artistic License the file COPYING.txt.
# If not, see <http://www.perlfoundation.org/artistic_license_2_0>

use strict;
use warnings;
use FindBin qw($Bin);

our $TC;
our $DEBUG = $ENV{DEBUG_SSL};

sub ok($$) {
	if ( !$_[0] ) {
		print "not ";
	}
	print "ok " . ( ++$TC ) . ( $_[1] ? " - $_[1]" : "" ) . "\n";
	return !!$_[0];
}

sub diag {
	if ($DEBUG) {
		print map {"# $_\n"} map { split "\n", $_ } @_;
	}
}

$ENV{RND_SEED} = '1234567890123456789012345678901234567890';

use Net::SSLeay::OO;
use Net::SSLeay::OO::Constants qw(VERIFY_PEER FILETYPE_PEM);
use Net::SSLeay::OO::X509;
use Net::SSLeay::OO::X509::Context;

my $cert_dir = "$Bin/certs";

my $ctx = Net::SSLeay::OO::Context->new;
$ctx->set_default_passwd_cb( sub {"secr1t"} );
$ctx->load_verify_locations( '', $cert_dir );

pipe RS, WC or die "pipe 1 ($!)";
pipe RC, WS or die "pipe 2 ($!)";
select WC;
$| = 1;
select WS;
$| = 1;
select STDOUT;
$| = 1;
pipe R2, W2 or die "pipe 3 ($!)";

ok( $ctx, "Set up Context OK" );

my $child_pid = fork;
defined($child_pid) or die $!;
unless ($child_pid) {
	diag("child - using server cert");
	$ctx->use_certificate_chain_file("$cert_dir/server-cert.pem");
	$ctx->use_PrivateKey_file( "$cert_dir/server-key.pem", FILETYPE_PEM,);

	# we get one event for each certificate check
	my @check_certs;
	my @found_altnames;
	my $cb = (
		sub {
			my ( $ok, $x509 ) = @_;
			diag("$$ - ok = $ok");

			#my $x509 = $x509_store_ctx->get_current_cert;
			if ($x509) {
				my $name = $x509->get_subject_name;
				diag "  Verifying cert: "
					. $name->oneline . "\n";
				push @check_certs, $name->cn;
				if ($DEBUG) {
					for my $nid ( 0 .. 120 ) {
						my $val = eval {
							$name->get_text_by_NID
								($nid);
						};
						last if $@;
						if ($val) {
							diag("$nid=$val");
						}
					}
				}
				my @altnames = $x509->get_subjectAltNames;
				diag( "saltnames:", @altnames );
				push @found_altnames, @altnames;

				#diag("C=".$name->country,
				#"L=".$name->locality,
				#"S=".$name->state,
				#"O=".$name->org,
				#"OU=".$name->org_unit,
				#"S=".$name->subject_key,
				#"SA=".$name->subject_alt,
				#"IA=".$name->issuer_alt,
				#"#=".$name->serial,
				#"name=".$name->name,
				#);
			}
			return $ok;
		}
	);
	$ctx->set_verify( VERIFY_PEER, $cb );

	my $ssl = Net::SSLeay::OO::SSL->new( ctx => $ctx );

	$ssl->set_rfd( fileno(RS) );
	$ssl->set_wfd( fileno(WS) );

	ok( !$ssl->get_session, "No session before accept" );
	diag("child - accept");
	$ssl->accept;
	ok( $ssl->get_session, "Set up session OK" );
	ok( @check_certs == 2, "certificate check callback fired twice" );
	ok( ( $check_certs[0] eq "Test CA" ), "once for Root CA" )
		or diag("it is: '$check_certs[0]'");
	ok( $check_certs[1] eq "Test Client", "once for Client cert" );
	ok( @found_altnames, "Found altnames" );

	my $cipher = $ssl->get_cipher;
	ok( $cipher, "Got a cipher ($cipher)" );

	my $peer_cert = $ssl->get_peer_certificate;
	ok( $peer_cert, "Got a peer certificate OK" );

	my $got = $ssl->read;
	ok( $got, "Read some data via SSL" );
	my $wrote = $ssl->write($got);
	ok( ( $wrote == length $got ), "Wrote data back via SSL" );

	undef($ssl);    # Tear down connection
	undef($ctx);

	close WS;
	close RS;
	ok( 1, "Server shut down OK" );
	print W2 "$TC\n";
	close W2;
	exit;
}
close W2;

#sleep 1;		    # Give server time to get its act together

# set up a client certificate
diag("client - use certificate");
$ctx->use_certificate_chain_file("$cert_dir/client-cert.pem");
$ctx->use_PrivateKey_file( "$cert_dir/client-key.pem", FILETYPE_PEM );

my $ssl = Net::SSLeay::OO::SSL->new( ctx => $ctx );
$ssl->set_rfd( fileno(RC) );
$ssl->set_wfd( fileno(WC) );
diag("client - connect");
alarm(5);    # timeout if something goes wrong
$ssl->connect;

#print "$$: Cipher `" . $ssl->get_cipher . "'\n";
#print "$$: server cert: " . $ssl->dump_peer_certificate;

# Exchange data

my $secret_message = "ping from pid $$";
$ssl->write( $secret_message . "\n" );
my $got_back = $ssl->read;
chomp($got_back);

$TC = <R2>;
chomp($TC);
ok( ( $got_back eq $secret_message ), "SSL round-trip" )
	or diag( "got: $got_back", "sent: $secret_message" );

undef($ssl);    # Tear down connection
undef($ctx);
close WC;
close RC;

print "1..$TC\n";

# Local Variables:
# mode:cperl
# indent-tabs-mode: t
# cperl-continued-statement-offset: 8
# cperl-brace-offset: 0
# cperl-close-paren-offset: 0
# cperl-continued-brace-offset: 0
# cperl-continued-statement-offset: 8
# cperl-extra-newline-before-brace: nil
# cperl-indent-level: 8
# cperl-indent-parens-as-block: t
# cperl-indent-wrt-brace: nil
# cperl-label-offset: -8
# cperl-merge-trailing-else: t
# End:
# vim: filetype=perl:noexpandtab:ts=3:sw=3