diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 299f45c..2be6847 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -30,7 +30,7 @@ jobs: curl -sL https://cpanmin.us/ | perl - -nq --with-develop --installdeps . - name: Build Module run: | - apt-get install make gcc libssl-dev; + apt-get install make gcc libssl-dev openssl; perl Makefile.PL; make - name: Run Tests diff --git a/META.yml b/META.yml index dfeb14a..71ade33 100644 --- a/META.yml +++ b/META.yml @@ -8,6 +8,8 @@ build_requires: File::Slurp: '0' Test::Exception: '0' Test::More: '0.88' + IO::Socket::SSL: '2.068' + Net::SSLeay: '1.88' configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 0 diff --git a/Makefile.PL b/Makefile.PL index 575a620..63df343 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -67,6 +67,8 @@ author_requires 'Test::Pod::Coverage' => '1.04'; test_requires 'Test::More' => '0.88'; test_requires 'File::Slurp'; test_requires 'Test::Exception'; +test_requires 'IO::Socket::SSL' => '2.068'; +test_requires 'Net::SSLeay' => '1.88'; resources license => "http://dev.perl.org/licenses", diff --git a/t/07-verify-trusted-cert.t b/t/07-verify-trusted-cert.t new file mode 100644 index 0000000..cfa6bcd --- /dev/null +++ b/t/07-verify-trusted-cert.t @@ -0,0 +1,133 @@ +use strict; +use warnings; +use Crypt::OpenSSL::Verify; +use Crypt::OpenSSL::X509; +use IO::Socket::SSL; +use Net::SSLeay; +use Data::Dumper; +use File::Slurp qw{ write_file }; +use Test::More; + +my $openssl_version = `openssl version`; +$openssl_version =~ /OpenSSL ([\d\.]+)/; +$openssl_version = $1; + +my %chain = (); +my $inter_cnt = 1; + +my $server_name = 'www.google.com'; + +sub verify_callback { + my $cert = $_[4]; + my $subject = Net::SSLeay::X509_NAME_oneline(Net::SSLeay::X509_get_subject_name($cert)); + my $issuer = Net::SSLeay::X509_NAME_oneline(Net::SSLeay::X509_get_issuer_name($cert)); + + $subject =~ /CN=(.*$)/; + $subject = $1; + $issuer =~ /CN=(.*$)/; + $issuer = $1; + if ( $subject eq $server_name ) { + $chain{'server'} = { name => $subject, x509 => Net::SSLeay::PEM_get_string_X509($cert), }; + } elsif ( $subject ne $issuer ) { + my $int = 'intermediate' . $inter_cnt; + $chain{'intermediates'} = $inter_cnt; + $chain{$int} = { 'name' => $subject, 'x509' => Net::SSLeay::PEM_get_string_X509($cert), }; + $inter_cnt++; + } elsif ( $subject eq $issuer ) { + $chain{'root'} = { 'name' => $subject, 'x509' => Net::SSLeay::PEM_get_string_X509($cert), }; + } + return 1; +} + +sub get_cert_chain { + my $peer = shift; + IO::Socket::SSL->new( + PeerHost => $peer . ":443", + SSL_verify_callback => \&verify_callback + ) or die $SSL_ERROR||$!; +} + +get_cert_chain($server_name); + +my $cert = $chain{'server'}{'x509'}; + +my $intermediate = ''; +for ( my $i = 1; $i <= $chain{intermediates}; $i++ ) { + $intermediate = $intermediate . $chain{"intermediate$i"}{'x509'} ."\n"; +} + +write_file('intermediate.pem', $intermediate); +write_file('cert.pem', $cert); + +my $ret; + +SKIP: { + skip "openSSL not installed", 1 unless `which openssl`; + + #say 'OpenSSL verification:'; + eval { + $ret = `openssl verify -CAfile intermediate.pem cert.pem`; + }; + like($ret, qr/OK/, "OpenSSL verification - OK"); +} + +#say 'Crypt::OpenSSL::Verify verification:'; +my $verifier = Crypt::OpenSSL::Verify->new('intermediate.pem',{strict_certs=>0}); +my $cert_object = Crypt::OpenSSL::X509->new_from_string($cert); +my $verify = $verifier->verify($cert_object); +ok($verify, "Crypt::OpenSSL::Verify verification - OK"); + +$verifier = Crypt::OpenSSL::Verify->new('intermediate.pem',{strict_certs=>1}); +$cert_object = Crypt::OpenSSL::X509->new_from_string($cert); +$verify = $verifier->verify($cert_object); +ok($verify, "Crypt::OpenSSL::Verify strict verification - OK"); + +SKIP: { + skip "Incorrect/missing version of openSSL", 2 unless (($openssl_version ge '1.1.1') and (`which openssl`)); + #say 'OpenSSL verification - noCApath:'; + eval { + $ret = `openssl verify -no-CApath -CAfile intermediate.pem cert.pem 2>&1`; + }; + like ($ret, qr/error 2 at 1 depth lookup: .* issuer certificate/s, "OpenSSL verification no-CApath - OK"); + + $verifier = Crypt::OpenSSL::Verify->new('intermediate.pem', {noCApath =>1, strict_certs=>1}); + $cert_object = Crypt::OpenSSL::X509->new_from_string($cert); + eval { + $ret = $verifier->verify($cert_object); + }; + like($ret, qr/error 2 at 1 depth lookup: .* issuer certificate/s, "Crypt::OpenSSL::Verify - noCApath failed to find root - OK"); +} + +SKIP: { + skip "Incorrect/missing version of openSSL", 2 unless (($openssl_version ge '1.1.1') and (`which openssl`));; + #say 'OpenSSL verification intermediate:'; + eval { + $ret = `openssl verify intermediate.pem`; + }; + like ($ret, qr/intermediate.pem: OK/s, "OpenSSL verification intermediate - OK"); +} + +$verifier = Crypt::OpenSSL::Verify->new('', { strict_certs=>1}); +$cert_object = Crypt::OpenSSL::X509->new_from_string($intermediate); +eval { + $ret = $verifier->verify($cert_object); +}; +ok($ret, "Crypt::OpenSSL::Verify intermediate - OK"); + +SKIP: { + skip "Incorrect/missing version of openSSL", 2 unless (($openssl_version ge '1.1.1') and (`which openssl`));; + #say 'OpenSSL verification intermediate - noCAfile & noCApath:'; + eval { + $ret = `openssl verify -no-CApath -no-CAfile intermediate.pem 2>&1`; + }; + like ($ret, qr/error 20 at 0 depth lookup: unable to get local issuer certificate/s, "OpenSSL verification intermediate no-CAfile & no-CApath - OK"); + + $verifier = Crypt::OpenSSL::Verify->new('', {noCAfile =>1, noCApath =>1, strict_certs=>1}); + $cert_object = Crypt::OpenSSL::X509->new_from_string($intermediate); + eval { + $ret = $verifier->verify($cert_object); + }; + like($ret, qr/error 20 at 0 depth lookup: unable to get local issuer certificate/s, "Crypt::OpenSSL::Verify intermediate - noCAfile & noCApath failed to find root - OK"); +} + +done_testing;