#!perl -w package Crypt::OpenSSL::CA::Test; use warnings; use strict; =head1 NAME B - Testing L =head1 SYNOPSIS =for My::Tests::Below "synopsis" begin use Crypt::OpenSSL::CA::Test qw(:default %test_der_DNs); use Test::Group; my $utf8 = Crypt::OpenSSL::CA::Test->test_simple_utf8(); run_perl_ok(<<"SCRIPT"); use Crypt::OpenSSL::CA::Test; warn "Hello world"; SCRIPT skip_next_test "Devel::Leak needed" if cannot_check_SV_leaks; test "leaky code" => sub { leaks_SVs_ok { # Do stuff }, -max => 6; }; skip_next_test "Devel::Mallinfo needed" if cannot_check_bytes_leaks; test "even leakier code" => sub { leaks_bytes_ok { # Do stuff }, -max => 51200; }; =for My::Tests::Below "synopsis" end =for My::Tests::Below "synopsis-asn1" begin use Crypt::OpenSSL::CA::Test qw(x509_decoder); my $dn_as_tree = x509_decoder('Name')->decode($dn_der); =for My::Tests::Below "synopsis-asn1" end =head1 DESCRIPTION This module provides some handy utility functions for testing L. L and L are especially handy for testing XS or Inline::C stuff. =head1 EXPORTED FUNCTIONS All functions described in this section factor some useful test tactics and are exported by default. The L may also be exported upon request. =cut use Test::Builder; use Test::More; use Test::Group; use File::Path (); use File::Spec (); use File::Slurp (); use File::Temp (); use base 'Exporter'; BEGIN { our @EXPORT = qw(openssl_path run_thru_openssl dumpasn1_available run_dumpasn1 run_perl run_perl_ok run_perl_script run_perl_script_ok errstack_empty_ok certificate_looks_ok certificate_chain_ok certificate_chain_invalid_ok cannot_check_SV_leaks leaks_SVs_ok cannot_check_bytes_leaks leaks_bytes_ok x509_schema x509_decoder); our @EXPORT_OK = (@EXPORT, qw(test_simple_utf8 test_bmp_utf8 @test_DN_CAs %test_der_DNs %test_public_keys %test_reqs_SPKAC %test_reqs_PKCS10 %test_keys_plaintext %test_keys_password %test_self_signed_certs %test_rootca_certs %test_entity_certs )); our %EXPORT_TAGS = ("default" => \@EXPORT); } =head2 openssl_path Returns the path to the C command-line tool, if it is known, or undef. Useful for skipping tests that depend on L being able to run. =cut sub openssl_path { my ($openssl_bin) = ( `which openssl 2>/dev/null` =~ m/^(.*)/ ); # Chopped, untainted return if ! ($openssl_bin && -x $openssl_bin); return $openssl_bin; } =head2 run_thru_openssl ($stdin_text, $arg1, $arg2, ...) Runs the command C, feeding it $stdin_text on its standard input. In list context, returns a ($stdout_text, $stderr_text) pair. In scalar context, returns the text of the combined standard output and error streams. Throws an exception if the C command is unavailable (that is, L returns undef). Upon return $? will be set to the exit status of C. =cut use IPC::Run (); use Carp (); sub run_thru_openssl { my ($data, @cmdline) = @_; $data = "" if (! defined($data)); Carp::croak "Bizarre first argument passed to run_thru_openssl()" if ref($data); defined(my $binary = openssl_path) or die "Cannot find openssl binary"; unshift(@cmdline, $binary); my ($out, $err); IPC::Run::run(\@cmdline, \$data, \$out, wantarray ? \$err : \$out); # Under FreeBSD-amd64 6.2's OpenSSL 0.9.7e-p1 25 Oct 2004, the # return code of "openssl crl" is unreliable (see eg # http://www.nntp.perl.org/group/perl.cpan.testers/1042233): if ($cmdline[1] eq "crl") { $? = 0 if $? == 1 << 8; } return wantarray ? ($out, $err) : $out; } =head2 dumpasn1_available ()> Returns true iff the I command can be found in $ENV{PATH}. =cut use File::Which (); sub dumpasn1_available { not(not File::Which::which("dumpasn1")) } =head2 run_dumpasn1 ($der) Runs the I command (found in $ENV{PATH}) on $der and returns its output. Throws an exception if dumpasn1 fails for some reason. See also L. =cut sub run_dumpasn1 { my ($der) = @_; my $out; IPC::Run::run(["dumpasn1", "-"], \$der, \$out, \$out); die "dumpasn1 failed with code $?" if $?; return $out; } =head2 run_perl ($scripttext) Runs $scripttext in a sub-Perl interpreter, returning the text of its combined stdout and stderr as a single string. $? is set to the exit value of same. =head2 run_perl_ok ($scripttext) =head2 run_perl_ok ($scripttext, \$stdout) =head2 run_perl_ok ($scripttext, \$stdout, $testname) Like L but simultaneously asserts (using L) that the exit value is successful. The return value of the sub is the status of the assertion; the output of $scripttext (that is, the return value of the underlying call to I) is transmitted to the caller by modifying in-place the scalar reference passed as the second argument, if any. Additionally the aforementioned output is passed to L if the script does exit with nonzero status. =head2 run_perl_script ($scriptname) =head2 run_perl_script_ok ($scriptname, \$stdout, $testname) Like L resp L except that the script is specified as a file name instead of Perl text. =cut sub run_perl { my ($scripttext) = @_; Carp::croak "Bizarre first argument passed to run_perl()" if (! defined($scripttext) || ref($scripttext)); if ($ENV{DEBUG}) { my $scriptdir = File::Spec->catdir(_tempdir(), "run_perl_ok"); File::Path::mkpath($scriptdir); my $scriptfile = File::Spec->catfile ($scriptdir, sprintf("run_perl_ok_%d_%d", $$, _unique_number())); File::Slurp::write_file($scriptfile, $scripttext); diag(<<"FOR_CONVENIENCE"); run_perl: a copy of the script to run was saved in $scriptfile to ease debugging. FOR_CONVENIENCE } my ($stdout, $stderr); IPC::Run::run([_perl_cmdline()], \$scripttext, \$stdout, \$stderr); return $stdout . $stderr; } sub run_perl_script { my ($scriptfile) = @_; my ($stdout, $stderr); IPC::Run::run([_perl_cmdline(), $scriptfile], \"", \$stdout, \$stderr); return $stdout . $stderr; } BEGIN { foreach my $functionname (qw(run_perl run_perl_script)) { my $ok_wrapper = sub { my ($code, $outref, $testname) = @_; local $Test::Builder::Level = $Test::Builder::Level + 1; $testname ||= $functionname; my $out = __PACKAGE__->can($functionname)->($code); $$outref = $out if ref($outref) eq "SCALAR"; my $retval = is($?, 0, $testname); diag($out) if ! $retval; return $retval; }; no strict "refs"; *{"${functionname}_ok"} = $ok_wrapper; }} =head2 _perl_cmdline () Computes (with cache) and returns the command line to invoke sub-Perls as on behalf of L and L while (more or less) preserving @INC. Returns the name of the Perl binary and a list of C<-I> command line switches that should be passed as part of an invocation of or similar. The C<-I> paths returned are exactly the elements in the current @INC that are B part of the Perl interpreter's compiled-in @INC. =cut { my @perlcmdline; sub _perl_cmdline { return @perlcmdline if @perlcmdline; my ($perl) = ($^X =~ m/^(.*)$/); # Untainted # There might be a more elegant way of fetching the pristine # @INC set... my ($indent, $orig_inc); { local $ENV{PERL5LIB}; ( ($indent, $orig_inc) = `$perl -V` =~ m/^( *)\@INC:\n(.*)\Z/sm ) or die <<"FAIL"; Couldn't find original \@INC in the output of $perl -V. FAIL } my %orig_inc_set; foreach (split m{$/}, $orig_inc) { last unless m/^$indent +(.*?)$/; $orig_inc_set{$1}++; } @perlcmdline = ($perl, (map { -I => $_ } (grep {! $orig_inc_set{$_} } @INC))); diag(join(" ", @perlcmdline)) if $ENV{DEBUG}; return @perlcmdline; } } =head2 errstack_empty_ok () Asserts that OpenSSL's error stack is empty, and clears it if not. To be run at the end of every test. =cut sub errstack_empty_ok { local $Test::Builder::Level = $Test::Builder::Level + 1; # Net::SSLeay provides an undocumented access to OpenSSL's ERR_ # API. Fortunately, thanks to my newly acquired XS-Fu, reading # the source was a piece of cake! require Net::SSLeay; my $errcount = 0; while(my $error = Net::SSLeay::ERR_get_error()) { diag(Net::SSLeay::ERR_error_string($error)); $errcount++; } return is($errcount, 0, "number of errors found on OpenSSL's stack"); } =head2 cannot_check_SV_leaks () Returns true iff L is unavailable. =cut sub cannot_check_SV_leaks { ! eval { require Devel::Leak } } =head2 cannot_check_bytes_leaks () Returns true iff L is unavailable or does nothing on this platform (eg MacOS). =cut sub cannot_check_bytes_leaks { return 1 if ! eval { require Devel::Mallinfo }; return (! exists Devel::Mallinfo::mallinfo()->{uordblks}); } =head2 leaks_SVs_ok ($coderef, %named_arguments) Executes $coderef and asserts (with L) that it doesn't leak Perl SVs (checked using L). As a tester, you should arrange for $coderef to manipulate about 10 SVs; smaller leaks will not be detected (see I<-max> below). Available named arguments are: =over =item I<< -name => $testname >> The name of the test, as in the second argument to L. =item I<< -max => $threshold >> The minimum number of leaked SVs to look for. The default is 6. Setting this too low will trigger false positives, as L needs a couple of SVs of its own. =back =cut sub leaks_SVs_ok (&@) { my ($coderef, %args) = @_; local $Test::Builder::Level = $Test::Builder::Level + 1; require Devel::Leak; my $handle; my $count = Devel::Leak::NoteSV($handle); $coderef->(); my $consumed_SVs = Devel::Leak::CheckSV($handle) - $count; cmp_ok($consumed_SVs, "<=", ($args{-max} || 6), ($args{-name} || "leaks_SVs_ok")); } =head2 leaks_bytes_ok ($coderef) =head2 leaks_bytes_ok ($coderef, $testname) Executes $coderef and asserts (with L) that it doesn't leak memory (checked using L). As a tester, you should arrange for $coderef to manipulate about 100k of memory; smaller leaks will not be detected (see I<-max> below). Available named arguments are: =over =item I<< -name => $testname >> The name of the test, as in the second argument to L. =item I<< -max => $threshold >> The minimum number of leaked bytes to look for. The default is 51200. Setting this too low will trigger false positives, as Perl does some funky memory management eg in hash tables and that may cause jitter in the memory consumption as measured from malloc's point of view. =back =cut sub leaks_bytes_ok (&@) { my ($coderef, %args) = @_; require Devel::Mallinfo; my $size_before = Devel::Mallinfo::mallinfo()->{uordblks} || 0; $coderef->(); my $size_after = Devel::Mallinfo::mallinfo()->{uordblks} || 0; my $consumption = $size_after - $size_before; local $Test::Builder::Level = $Test::Builder::Level + 1; cmp_ok($consumption, "<", ($args{-max} || 51200), $args{-name} || "leaks_bytes_ok"); } =head2 certificate_looks_ok ($pem_certificate) =head2 certificate_looks_ok ($pem_certificate, $test_name) Checks that a certificate passed as a PEM string looks OK to OpenSSL, meaning that the signature validates OK and OpenSSL is able to parse it. =cut sub certificate_looks_ok { my ($pem_certificate, $test_name) = @_; $test_name ||= "certificate_looks_ok"; test $test_name => sub { my ($out, $err); ($out, $err) = run_thru_openssl($pem_certificate, qw(x509 -noout -text)); unless (is($?, 0, "openssl execution failed with code $?")) { diag $err; return; } unlike($out, qr/error/, "openssl seemed to dislike the certificate"); like($out, qr/Certificate:/, "openssl seemed not to be able to parse the certificate"); }; } =head2 certificate_chain_ok ($pem_certificate, \@certchain ) =head2 certificate_chain_ok ($pem_certificate, \@certchain , $test_name) Checks that a certificate passed as a PEM string is validly signed by the certificate chain @certchain, which is a list of PEM strings passed as a reference. =cut sub certificate_chain_ok { my ($cert, $certchain, $testname) = @_; test (($testname || "certificate_chain_ok") => sub { my $out = _run_openssl_verify($cert, $certchain, $testname); return if ! defined $out; # Already failed like($out, qr/OK/, "verify successful"); unlike($out, qr/error/, "no errors"); }); } sub _run_openssl_verify { my ($cert, $certchain, $testname) = @_; # This is mostly a hack to get the test suite to # work, but CA:FALSE certificates *really* should # not be made part of a certification chain. my @certchain = grep { my $out = run_thru_openssl($_, qw(x509 -noout -text)); ( $out =~ m/CA:TRUE/ ) ? 1 : (warn(<<"WARNING"), 0); ${$testname ? \"$testname: " : \""}Ignoring a non-CA certificate that was passed as part of the chain. WARNING } @$certchain; fail("no remaining certificates in chain"), return undef if ! @certchain; my $bundlefile = File::Spec->catfile (_tempdir(), sprintf("ca-bundle-%d-%d.crt", $$, _unique_number())); File::Slurp::write_file($bundlefile, join("\n", @certchain)); return scalar run_thru_openssl($cert, qw(verify), -CAfile => $bundlefile); } =head2 certificate_chain_invalid_ok ($pem_certificate, \@certchain ) The converse of L; checks that I<$pem_certificate> is B validly signed by @certchain. Note, however, that there is a case where both I and I both fail, and that is when @certchain doesn't contain any B CA certificate. =cut sub certificate_chain_invalid_ok { my ($cert, $certchain, $testname) = @_; test (($testname || "certificate_chain_ok") => sub { my $out = _run_openssl_verify($cert, $certchain, $testname); return if ! defined $out; # Already failed like($out, qr/error/, "verify failed as expected"); }); } =head2 x509_schema () Returns the ASN.1 schema for the whole X509 specification, as a string that L will grok. =cut sub x509_schema { <<"SCHEMA" } -- Taken from examples/x509decode in Convert::ASN1 Attribute ::= SEQUENCE { type AttributeType, values SET OF AttributeValue -- at least one value is required -- } AttributeType ::= OBJECT IDENTIFIER AttributeValue ::= DirectoryString --ANY AttributeTypeAndValue ::= SEQUENCE { type AttributeType, value AttributeValue } -- naming data types -- Name ::= CHOICE { -- only one possibility for now rdnSequence RDNSequence } RDNSequence ::= SEQUENCE OF RelativeDistinguishedName DistinguishedName ::= RDNSequence RelativeDistinguishedName ::= SET OF AttributeTypeAndValue --SET SIZE (1 .. MAX) OF -- Directory string type -- DirectoryString ::= CHOICE { teletexString TeletexString, --(SIZE (1..MAX)), printableString PrintableString, --(SIZE (1..MAX)), bmpString BMPString, --(SIZE (1..MAX)), universalString UniversalString, --(SIZE (1..MAX)), utf8String UTF8String, --(SIZE (1..MAX)), ia5String IA5String --added for EmailAddress } -- certificate and CRL specific structures begin here Certificate ::= SEQUENCE { tbsCertificate TBSCertificate, signatureAlgorithm AlgorithmIdentifier, signature BIT STRING } TBSCertificate ::= SEQUENCE { version [0] EXPLICIT Version OPTIONAL, --DEFAULT v1 serialNumber CertificateSerialNumber, signature AlgorithmIdentifier, issuer Name, validity Validity, subject Name, subjectPublicKeyInfo SubjectPublicKeyInfo, issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL, -- If present, version shall be v2 or v3 subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL, -- If present, version shall be v2 or v3 extensions [3] EXPLICIT Extensions OPTIONAL -- If present, version shall be v3 } Version ::= INTEGER --{ v1(0), v2(1), v3(2) } CertificateSerialNumber ::= INTEGER Validity ::= SEQUENCE { notBefore Time, notAfter Time } Time ::= CHOICE { utcTime UTCTime, generalTime GeneralizedTime } UniqueIdentifier ::= BIT STRING SubjectPublicKeyInfo ::= SEQUENCE { algorithm AlgorithmIdentifier, subjectPublicKey BIT STRING } Extensions ::= SEQUENCE OF Extension --SIZE (1..MAX) OF Extension Extension ::= SEQUENCE { extnID OBJECT IDENTIFIER, critical BOOLEAN OPTIONAL, --DEFAULT FALSE, extnValue OCTET STRING } AlgorithmIdentifier ::= SEQUENCE { algorithm OBJECT IDENTIFIER, parameters ANY } --extensions AuthorityKeyIdentifier ::= SEQUENCE { keyIdentifier [0] KeyIdentifier OPTIONAL, authorityCertIssuer [1] GeneralNames OPTIONAL, authorityCertSerialNumber [2] CertificateSerialNumber OPTIONAL } -- authorityCertIssuer and authorityCertSerialNumber shall both -- be present or both be absent KeyIdentifier ::= OCTET STRING SubjectKeyIdentifier ::= KeyIdentifier -- key usage extension OID and syntax -- id-ce-keyUsage OBJECT IDENTIFIER ::= { id-ce 15 } KeyUsage ::= BIT STRING --{ -- digitalSignature (0), -- nonRepudiation (1), -- keyEncipherment (2), -- dataEncipherment (3), -- keyAgreement (4), -- keyCertSign (5), -- cRLSign (6), -- encipherOnly (7), -- decipherOnly (8) } -- private key usage period extension OID and syntax -- id-ce-privateKeyUsagePeriod OBJECT IDENTIFIER ::= { id-ce 16 } PrivateKeyUsagePeriod ::= SEQUENCE { notBefore [0] GeneralizedTime OPTIONAL, notAfter [1] GeneralizedTime OPTIONAL } -- either notBefore or notAfter shall be present -- certificate policies extension OID and syntax -- id-ce-certificatePolicies OBJECT IDENTIFIER ::= { id-ce 32 } CertificatePolicies ::= SEQUENCE OF PolicyInformation PolicyInformation ::= SEQUENCE { policyIdentifier CertPolicyId, policyQualifiers SEQUENCE OF PolicyQualifierInfo } --OPTIONAL } CertPolicyId ::= OBJECT IDENTIFIER PolicyQualifierInfo ::= SEQUENCE { policyQualifierId PolicyQualifierId, qualifier ANY } --DEFINED BY policyQualifierId } -- Implementations that recognize additional policy qualifiers shall -- augment the following definition for PolicyQualifierId PolicyQualifierId ::= OBJECT IDENTIFIER --( id-qt-cps | id-qt-unotice ) -- CPS pointer qualifier CPSuri ::= IA5String -- user notice qualifier UserNotice ::= SEQUENCE { noticeRef NoticeReference OPTIONAL, explicitText DisplayText OPTIONAL} NoticeReference ::= SEQUENCE { organization DisplayText, noticeNumbers SEQUENCE OF INTEGER } DisplayText ::= CHOICE { visibleString VisibleString , bmpString BMPString , utf8String UTF8String } -- policy mapping extension OID and syntax -- id-ce-policyMappings OBJECT IDENTIFIER ::= { id-ce 33 } PolicyMappings ::= SEQUENCE OF SEQUENCE { issuerDomainPolicy CertPolicyId, subjectDomainPolicy CertPolicyId } -- subject alternative name extension OID and syntax -- id-ce-subjectAltName OBJECT IDENTIFIER ::= { id-ce 17 } SubjectAltName ::= GeneralNames GeneralNames ::= SEQUENCE OF GeneralName GeneralName ::= CHOICE { otherName [0] AnotherName, rfc822Name [1] IA5String, dNSName [2] IA5String, x400Address [3] ANY, --ORAddress, directoryName [4] Name, ediPartyName [5] EDIPartyName, uniformResourceIdentifier [6] IA5String, iPAddress [7] OCTET STRING, registeredID [8] OBJECT IDENTIFIER } -- AnotherName replaces OTHER-NAME ::= TYPE-IDENTIFIER, as -- TYPE-IDENTIFIER is not supported in the '88 ASN.1 syntax AnotherName ::= SEQUENCE { type OBJECT IDENTIFIER, value [0] EXPLICIT ANY } --DEFINED BY type-id } EDIPartyName ::= SEQUENCE { nameAssigner [0] DirectoryString OPTIONAL, partyName [1] DirectoryString } -- issuer alternative name extension OID and syntax -- id-ce-issuerAltName OBJECT IDENTIFIER ::= { id-ce 18 } IssuerAltName ::= GeneralNames -- id-ce-subjectDirectoryAttributes OBJECT IDENTIFIER ::= { id-ce 9 } SubjectDirectoryAttributes ::= SEQUENCE OF Attribute -- basic constraints extension OID and syntax -- id-ce-basicConstraints OBJECT IDENTIFIER ::= { id-ce 19 } BasicConstraints ::= SEQUENCE { cA BOOLEAN OPTIONAL, --DEFAULT FALSE, pathLenConstraint INTEGER OPTIONAL } -- name constraints extension OID and syntax -- id-ce-nameConstraints OBJECT IDENTIFIER ::= { id-ce 30 } NameConstraints ::= SEQUENCE { permittedSubtrees [0] GeneralSubtrees OPTIONAL, excludedSubtrees [1] GeneralSubtrees OPTIONAL } GeneralSubtrees ::= SEQUENCE OF GeneralSubtree GeneralSubtree ::= SEQUENCE { base GeneralName, minimum [0] BaseDistance OPTIONAL, --DEFAULT 0, maximum [1] BaseDistance OPTIONAL } BaseDistance ::= INTEGER -- policy constraints extension OID and syntax -- id-ce-policyConstraints OBJECT IDENTIFIER ::= { id-ce 36 } PolicyConstraints ::= SEQUENCE { requireExplicitPolicy [0] SkipCerts OPTIONAL, inhibitPolicyMapping [1] SkipCerts OPTIONAL } SkipCerts ::= INTEGER -- CRL distribution points extension OID and syntax -- id-ce-cRLDistributionPoints OBJECT IDENTIFIER ::= {id-ce 31} cRLDistributionPoints ::= SEQUENCE OF DistributionPoint DistributionPoint ::= SEQUENCE { distributionPoint [0] DistributionPointName OPTIONAL, reasons [1] ReasonFlags OPTIONAL, cRLIssuer [2] GeneralNames OPTIONAL } DistributionPointName ::= CHOICE { fullName [0] GeneralNames, nameRelativeToCRLIssuer [1] RelativeDistinguishedName } ReasonFlags ::= BIT STRING --{ -- unused (0), -- keyCompromise (1), -- cACompromise (2), -- affiliationChanged (3), -- superseded (4), -- cessationOfOperation (5), -- certificateHold (6), -- privilegeWithdrawn (7), -- aACompromise (8) } -- extended key usage extension OID and syntax -- id-ce-extKeyUsage OBJECT IDENTIFIER ::= {id-ce 37} ExtKeyUsageSyntax ::= SEQUENCE OF KeyPurposeId KeyPurposeId ::= OBJECT IDENTIFIER -- extended key purpose OIDs -- id-kp-serverAuth OBJECT IDENTIFIER ::= { id-kp 1 } -- id-kp-clientAuth OBJECT IDENTIFIER ::= { id-kp 2 } -- id-kp-codeSigning OBJECT IDENTIFIER ::= { id-kp 3 } -- id-kp-emailProtection OBJECT IDENTIFIER ::= { id-kp 4 } -- id-kp-ipsecEndSystem OBJECT IDENTIFIER ::= { id-kp 5 } -- id-kp-ipsecTunnel OBJECT IDENTIFIER ::= { id-kp 6 } -- id-kp-ipsecUser OBJECT IDENTIFIER ::= { id-kp 7 } -- id-kp-timeStamping OBJECT IDENTIFIER ::= { id-kp 8 } SCHEMA =head2 x509_decoder ($name) Returns the same as L would when called upon an object that would previously have L fed to him. The difference is that I checks for errors and will therefore never return undef. The returned object has a C<< ->decode >> object that serves to validate the various pieces of DER produced by OpenSSL from within the tests. =cut use Convert::ASN1; sub x509_decoder { my ($name) = @_; my $asn = Convert::ASN1->new; $asn->prepare(x509_schema()); die $asn->error if $asn->error; my $retval = $asn->find($name); die "$name not found in X509 schema" if ! defined $retval; return $retval; } =head1 SAMPLE INPUTS I also provides a couple of constants and class methods to serve as inputs for tests. All such symbols are exportable, but not exported by default (see L) and they start with I, so as to be clearly identified as sample data in the test code. =head2 test_simple_utf8 () =head2 test_bmp_utf8 () Two constant functions that return test strings for testing the UTF-8 capabilities of I. Both strings are encoded internally in UTF-8 in the sense of L. I contains only characters in the Latin1 range; I contains only characters outside Latin1, but inside the Basic Multilingual Plane. =cut sub test_simple_utf8 { my $retval = "zoinxé"; die unless utf8::decode($retval); return $retval; } sub test_bmp_utf8 { my $retval = "☮☺⌨"; # Peace, joy, coding :-) die unless utf8::decode($retval); return $retval; } =head2 %test_der_DNs Contains a set of DER-encoded DNs. The keys are the DNs in L notation, and the values are strings of bytes. Available DN keys for now are C. =cut ## You can generate more using Crypt::OpenSSL::CA itself using a ## one-liner such as ## ## perl -MCrypt::OpenSSL::CA -MMIME::Base64 -e 'print ## encode_base64(Crypt::OpenSSL::CA::X509_NAME->new ## (C => "fr", CN => "Zoinx")->to_asn1)' use MIME::Base64 qw(decode_base64); our %test_der_DNs = ("CN=Zoinx,C=fr" => decode_base64(<, L and friends. Set in the same order as the parameters to the I function in L. =cut our @test_DN_CAs = (C => "AU", ST => "Some-State", O => "Internet Widgits Pty Ltd"); =head2 %test_reqs_SPKAC Certificate signing requests (CSRs) in Netscape L format, as if generated by openssl spkac -key test.key -challenge secret but without the trailing newline, and with the leading C removed. =cut our %test_reqs_SPKAC = (rsa1024 => "MIIBQDCBqjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA3xmJpTmG6RpAQ/2oE1J4sS3HYeh9VzNd8Ne0W82qAO28mQ+i/g5/DGXevT7l3GQEBFBuDnukMgHGn7Lw2+0h48iRy6D0zrAGdHsf9MyCVacPl8qaQPH2cem57hylGm6n4/Nzi5PwAn0EgV+23C+2PIcGHGSXKsozM7fQU+6ApXcCAwEAARYGc2VjcmV0MA0GCSqGSIb3DQEBBAUAA4GBAMpl9v+6SSQt0yGlmg20bZEz9jiTzbD3UX6vdCdIdYuksTnVrTarVTi6zMSAK/me+fo+54LbZxqxFVjrnz1eg7yUQkvjfrs/HGDpdBoWHvw3+iePK8DHlaipolACNF+OyoMryl5gqRPhV6FosHiiD9QQ4IY7GSMKMr5iQ/pwlAGx", rsa2048 => "MIICRjCCAS4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCxupRLykaWvgQP2aZmcEGq9/3OXtnQ1H0tnNfbJexzYYyCOiU1CP8KsMoeNMvdUun4FwGKeckjGF1eDuOgbGh0naG4+M4/5PTCbOaF2otb8zPc+oUGh3tmgiLhLnlV4zQbeTBRD6/giHnFgUWC+Ec/PjEnmDu917430GI2nnD66/OZr9NnyxFYMhSlufwWRGCtR6LLa9QqDAl+DvbSmvHGL9G7VFBGcFwLbaTYUWmkvQwEhq01yZ/bp+yAIJpygsnWMg6kJahkBI5hNFK1KWbLYyF9IDJb6TsL9mRiW8+0BAkZosD5jdm4Ra7SMtiTjzY+FyNp2IRwZ32N70iNGGPZAgMBAAEWBnNlY3JldDANBgkqhkiG9w0BAQQFAAOCAQEAd3JfT2QEo8pBHhQFlh9PDfc3OhL7z0IcebcDL7kslxB5JViuzKMce/+68RoQ9eaepmVunXxVIJEauNp5LrZatxODp8kOsJI86HD1ChMVqrr6DZi6ulBEXst2kvzkEwVN24Hm5t80hGK8jnZtN86iIXk4iA7iEiniTO7qVhq3kEIouV6fprOk2P8bZ24OlVQ0+1Lp4h5EKajRQZoacnK4IGUTNXEGdAI17ID/qf8sqKZQtiqrRXGAQqbx3bxk8aLUm8OhmyeGett75H0n956MNPJiwDy9ftcUnyiuHHYGKq6SZNNs4mKOjnSnz3D9DhUCbJkfG2FbCkRsMl8SHARoyA==", ); =head2 %test_reqs_PKCS10 Certificate signing requests (CSRs) in standard PKCS#10 PEM format, as if generated by openssl req -new -key test.key -batch but without the trailing newline, and with the leading C removed. =cut our %test_reqs_PKCS10 = (rsa1024 => < < and C<-----END RSA PRIVATE KEY----->, without encryption). The keys are well-known key handles that are re-used throughout the sample input hashes below: =over =item I =item I RSA keys of size 1024 bits and 2048 bits respectively. =back More RSA keys can be obtained using the command openssl genrsa 1024 or similar (e.g. changing the key size) =cut our %test_keys_plaintext = (rsa1024 => < <, but protected with C as the password. Keys are the same as in I<%test_keys_plaintext>; values are encrypted using 3DES-CBC, as if by the command openssl rsa -des3 -passout pass:secret -in test.key =cut our %test_keys_password = (rsa1024 => < < using the following C command: openssl rsa -pubout -in test.key =cut our %test_public_keys = (rsa1024 => <<"RSA1024", -----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDfGYmlOYbpGkBD/agTUnixLcdh 6H1XM13w17RbzaoA7byZD6L+Dn8MZd69PuXcZAQEUG4Oe6QyAcafsvDb7SHjyJHL oPTOsAZ0ex/0zIJVpw+XyppA8fZx6bnuHKUabqfj83OLk/ACfQSBX7bcL7Y8hwYc ZJcqyjMzt9BT7oCldwIDAQAB -----END PUBLIC KEY----- RSA1024 rsa2048 => <<"RSA2048", -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsbqUS8pGlr4ED9mmZnBB qvf9zl7Z0NR9LZzX2yXsc2GMgjolNQj/CrDKHjTL3VLp+BcBinnJIxhdXg7joGxo dJ2huPjOP+T0wmzmhdqLW/Mz3PqFBod7ZoIi4S55VeM0G3kwUQ+v4Ih5xYFFgvhH Pz4xJ5g7vde+N9BiNp5w+uvzma/TZ8sRWDIUpbn8FkRgrUeiy2vUKgwJfg720prx xi/Ru1RQRnBcC22k2FFppL0MBIatNcmf26fsgCCacoLJ1jIOpCWoZASOYTRStSlm y2MhfSAyW+k7C/ZkYlvPtAQJGaLA+Y3ZuEWu0jLYk482PhcjadiEcGd9je9IjRhj 2QIDAQAB -----END PUBLIC KEY----- RSA2048 ); =head2 %test_self_signed_certs Self-signed certificates obtained from the L as if using the following C command: openssl req -x509 -new -key test.key -batch -days 10958 \ -extensions usr_cert where 10958 stands for a validity period of 30 years, so that these self-signed certificates seldom actually expire. Because the default configuration is used, the world-famous yet Belgian I company is put in charge as issuer and subject of these certificates. =cut our %test_self_signed_certs = (rsa1024 => <<"RSA1024", -----BEGIN CERTIFICATE----- MIICgzCCAeygAwIBAgIJAPecvJ1g5yDDMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX aWRnaXRzIFB0eSBMdGQwHhcNMDcwMTMxMTc1NDQyWhcNMzcwMTMxMTc1NDQyWjBF MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50 ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB gQDfGYmlOYbpGkBD/agTUnixLcdh6H1XM13w17RbzaoA7byZD6L+Dn8MZd69PuXc ZAQEUG4Oe6QyAcafsvDb7SHjyJHLoPTOsAZ0ex/0zIJVpw+XyppA8fZx6bnuHKUa bqfj83OLk/ACfQSBX7bcL7Y8hwYcZJcqyjMzt9BT7oCldwIDAQABo3sweTAJBgNV HRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZp Y2F0ZTAdBgNVHQ4EFgQU7vqhl+/cXLxRPKRuc8dhXKgLleUwHwYDVR0jBBgwFoAU 7vqhl+/cXLxRPKRuc8dhXKgLleUwDQYJKoZIhvcNAQEFBQADgYEAWedOBH/dGoLv 7isX9DfsGqz337/NhdTO9dGg+l4htskmlIGitzjC2uSPi6QT/8cPpXGKEIiaaigI e9WIdiVrEIk9kvp4cgnwCF0O/K02/BIpq5MlqSXwGQhQ/o29J4/A4/LobcLDYr11 mGZJJpjA9oDx7sZF6FbTTa5E+tXZRls= -----END CERTIFICATE----- RSA1024 rsa2048 => <<"RSA2048", -----BEGIN CERTIFICATE----- MIIDiDCCAnCgAwIBAgIJAL6sAb2vcVpUMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX aWRnaXRzIFB0eSBMdGQwHhcNMDcwMTMxMTc1MzU1WhcNMzcwMTMxMTc1MzU1WjBF MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50 ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB CgKCAQEAsbqUS8pGlr4ED9mmZnBBqvf9zl7Z0NR9LZzX2yXsc2GMgjolNQj/CrDK HjTL3VLp+BcBinnJIxhdXg7joGxodJ2huPjOP+T0wmzmhdqLW/Mz3PqFBod7ZoIi 4S55VeM0G3kwUQ+v4Ih5xYFFgvhHPz4xJ5g7vde+N9BiNp5w+uvzma/TZ8sRWDIU pbn8FkRgrUeiy2vUKgwJfg720prxxi/Ru1RQRnBcC22k2FFppL0MBIatNcmf26fs gCCacoLJ1jIOpCWoZASOYTRStSlmy2MhfSAyW+k7C/ZkYlvPtAQJGaLA+Y3ZuEWu 0jLYk482PhcjadiEcGd9je9IjRhj2QIDAQABo3sweTAJBgNVHRMEAjAAMCwGCWCG SAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4E FgQUZKgIK9vXzM9vCVYnGs0hUrzx+lcwHwYDVR0jBBgwFoAUZKgIK9vXzM9vCVYn Gs0hUrzx+lcwDQYJKoZIhvcNAQEFBQADggEBABwMCRvRijVdZ2VEpkObAQyhYNxD XjyXTIL2XJek9mCf9mnwW6qiCdiDxwjsPv7ctq7Xfl6QZ0ox4Mg1zma/IQZQuFfy nyawZSrLx86bsyBu7aRbK29nCNXzTU3JT9xjPgZat3J2bVPunbXSgVoQnfceMtJG xuTL5Pz2246X3TRDzAu27ZTWIbAgzzXppXba+X4xKaC2pAGs5M0B6qWr20zqzrtS abDMwiOqndnPFfSNFTWue9PcgpMoT3V+eq6VN0Q6AyPZxkfzVg+VUISli0sXNMKB KjI6FX0+FXEYyhmsnkAq83kVYop/ietw/mvJkF1xxpkv/urU2AagNVmaxuo= -----END CERTIFICATE----- RSA2048 ); =head2 %test_rootca_certs Self-signed certificates just like L, except that these certificates are signed using C<-extensions v3_ca> in lieu of C<-extensions usr_cert>, resulting in certificates that have the C BasicConstraint set to C. Those certificates can therefore be used e.g. in the second argument to L, unlike L which, lacking a CA BasicConstraint, usually cannot be a non-leaf part of a valid certification chain as per RFC3280 section 6.1.4, item k. =cut our %test_rootca_certs = (rsa1024 => < < from L, L and the default OpenSSL configuration using the procedure described in L where the precise C commands used are openssl req -new -batch -subj "/C=fr/O=Yoyodyne/CN=John Doe" \ -key test.key | \ openssl ca -batch -days 10958 -in /dev/stdin In particular this means that entries keyed off the same identifier in %test_entity_certs and %test_rootca_certs form a valid RFC3280 certification path: that is, =for My::Tests::Below "certificate_chain_ok" begin certificate_chain_ok($test_entity_certs{$id}, [ $test_rootca_certs{$id} ]); # Works =for My::Tests::Below "certificate_chain_ok" end holds for every valid $id. But conversely, =for My::Tests::Below "certificate_chain_notok" begin certificate_chain_ok($test_entity_certs{$id}, [ $test_self_signed_certs{$id} ]); # NOT OK! =for My::Tests::Below "certificate_chain_notok" end fails, due to the lack of a C BasicConstraint extension in %test_self_signed_certs. Notice that in the sample inputs, CAs and end entities share the same set of private RSA keys L which would not be the case in a real PKI deployment. However this is of little impact, if any, on the test coverage of I as we never make use of the fact that all certificates for a given key length actually have the same private key. =cut our %test_entity_certs = (rsa1024 => < < for L. =cut { my $cached; sub _tempdir { return My::Tests::Below->tempdir if (My::Tests::Below->can("tempdir")); return $cached if defined $cached; return ($cached = File::Temp::tempdir ("perl-Crypt-OpenSSL-CA-Test-XXXXXX", TMPDIR => 1, ($ENV{DEBUG} ? () : (CLEANUP => 1)))); } } =head2 _unique_number As the name implies. Typically used to create unique filenames in L. =cut { my $unique = 0; sub _unique_number { $unique++ } } =head1 TODO Maybe L and L deserve a CPAN module of their own? =cut require My::Tests::Below unless caller; 1; __END__ =head1 TEST SUITE =cut use Test::More no_plan => 1; use Test::Group; use Crypt::OpenSSL::CA::Test; =head2 Fixture Tests =head3 Running commands =cut test "run_thru_openssl" => sub { my $version = run_thru_openssl(undef, "version"); is($?, 0); like($version, qr/openssl/i); unlike($version, qr/uninitialized/); # In case there is some barfage # going on in the forked Perls... my ($out, $err) = run_thru_openssl(undef, "version"); is($err, ""); # Yes, this is OpenSSL. Welcome onboard. my ($modulus, $error) = run_thru_openssl ($Crypt::OpenSSL::CA::Test::test_keys_plaintext{rsa1024}, qw(rsa -modulus -noout)); is($?, 0); like($modulus, qr/modulus=/i) or diag $error; run_thru_openssl(undef, "rsa"); isnt($?, 0); }; test "run_perl and run_perl_ok" => sub { my $out; run_perl_ok(<<"SCRIPT_OK", \$out); print "hello"; # STDOUT warn "coucou"; # STDERR SCRIPT_OK like($out, qr/hello/); like($out, qr/coucou/); my $tempdir = My::Tests::Below->tempdir; $out = run_perl(<<"SCRIPT_WRAPPER"); use Test::More no_plan => 1; use Crypt::OpenSSL::CA::Test qw(run_perl_ok); run_perl_ok <<'SCRIPT_OK'; warn "yipee"; exit 0; SCRIPT_OK run_perl_ok <<'SCRIPT_NOT_OK'; die "argl"; SCRIPT_NOT_OK exit(1); SCRIPT_WRAPPER isnt($?, 0, "run_perl: that script shall exit with nonzero status"); like($out, qr/not ok 2/m); unlike($out, qr/Crypt.*CA/, "errors are reported at the proper stack depth"); # Errors must be propagated: like($out, qr/argl/m); # But not successes: unlike($out, qr/yipee/m); }; test "errstack_empty_ok" => sub { errstack_empty_ok(); my $out = run_perl(<<"SCRIPT_NOT_OK"); use Test::More no_plan => 1; use Crypt::OpenSSL::CA::Test qw(errstack_empty_ok); use Net::SSLeay; is(Net::SSLeay::BIO_new_file("/no/such/file_", "r"), 0); # OK errstack_empty_ok(); # not OK SCRIPT_NOT_OK like($out, qr/^ok 1/m); like($out, qr/^not ok 2/m); like($out, qr/at.* line/, "errors are reported"); # Grr, "like" won't let $1 through: my ($filename) = $out =~ m/(.*) line/; unlike($filename, qr/Crypt.*CA/, "errors are reported at the proper stack depth"); }; test "certificate_looks_ok" => sub { my $ok_cert = <<'OK_CERT'; -----BEGIN CERTIFICATE----- MIICsDCCAhmgAwIBAgIJAPV18QziY9UvMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX aWRnaXRzIFB0eSBMdGQwHhcNMDcwMTI5MDgyODI0WhcNMDcwMjI4MDgyODI0WjBF MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50 ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB gQDfGYmlOYbpGkBD/agTUnixLcdh6H1XM13w17RbzaoA7byZD6L+Dn8MZd69PuXc ZAQEUG4Oe6QyAcafsvDb7SHjyJHLoPTOsAZ0ex/0zIJVpw+XyppA8fZx6bnuHKUa bqfj83OLk/ACfQSBX7bcL7Y8hwYcZJcqyjMzt9BT7oCldwIDAQABo4GnMIGkMB0G A1UdDgQWBBTu+qGX79xcvFE8pG5zx2FcqAuV5TB1BgNVHSMEbjBsgBTu+qGX79xc vFE8pG5zx2FcqAuV5aFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUt U3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAPV18Qzi Y9UvMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAFRkTlHJwSgOFQtxG h0HHr4UES2xR+wD9xZOeFGZk066ZEdiOuUvNLYMFEe+Vo9OxAL/SdPt4oOcWremD lTRumdcVP9vA8K4asPpXKqhpE/2EwDRmYE9g73n50yy2yazifourQmRVqSixs/ew RSQ7/6JIpIihvyCUDUzM2bvexk8= -----END CERTIFICATE----- OK_CERT certificate_looks_ok($ok_cert); certificate_looks_ok($ok_cert . "\n"); # Robustness my $out = run_perl(<<"SCRIPT"); use strict; use warnings; use Test::More no_plan => 1; use Crypt::OpenSSL::CA::Test qw(certificate_looks_ok); my \$certificate = <<'OK_CERT'; $ok_cert OK_CERT certificate_looks_ok(\$certificate, "OK certificate"); # expecting OK \$certificate =~ s/CQYDVQQGE/CQYDVQQGF/; certificate_looks_ok(\$certificate, "botched certificate"); # expecting not OK \$certificate = <<'DUD_CERT'; # Generated with an early version of # Crypt::OpenSSL::CA; a public key is missing -----BEGIN CERTIFICATE----- MIHaMEWgAwIBAgIBATANBgkqhkiG9w0BAQUFADAAMB4XDTcwMDEwMTAwMDAwMFoX DTcwMDEwMTAwMDAwMFowADAIMAMGAQADAQAwDQYJKoZIhvcNAQEFBQADgYEAsURd sgu7sYyODuo5bCzkYBLrYb8653jjVt8hecoQj1Ete0X6uHk6t+nJ8qCwURc4FayF kzapy9zWAGMy+6A/9CQz5862Phf3MkFM4OwkjJARBF7I73WfVEVX4e1PIgl4qjjJ lgiG5TCUNWQrbRGa6LVDx7DErReEJE5vRwNxvjo= -----END CERTIFICATE----- DUD_CERT certificate_looks_ok(\$certificate, "REGRESSION: dud cert"); # expecting not OK, lest REGRESSION certificate_looks_ok({}, "Should have thrown (bad input)"); # Should throw SCRIPT like($out, qr/^ok 1/m); like($out, qr/^not ok 2/m); like($out, qr/^not ok 3/m); unlike($out, qr/^ok 4/m); # Should have died in run_thru_openssl() unlike($out, qr/source for input redirection/, "REGRESSION: passing undef to certificate_looks_ok() caused a strange error"); }; =head2 Leak tests =cut begin_skipping_tests unless eval { require Devel::Leak; require Devel::Mallinfo; }; test "no leak" => sub { leaks_SVs_ok { }; leaks_bytes_ok { }; }; test "leaking scalars" => sub { my $leakyscript = <<'LEAKYSCRIPT'; use Test::More no_plan => 1; use Crypt::OpenSSL::CA::Test; sub leak { for (1..20) { my $yin = {}; my $yang = { yin => $yin }; $yin->{yang} = $yang; } } leaks_bytes_ok { leak }; leaks_SVs_ok { leak }; LEAKYSCRIPT my $out = run_perl($leakyscript); is($? & 255, 0, "we don't get signal"); like($out, qr/^ok 1/m); like($out, qr/^not ok 2/m); unlike($out, qr/Crypt.*CA/, "errors are reported at the proper stack depth"); }; skip_next_test if cannot_check_bytes_leaks; # Eg MacOS test "leaking bytes" => sub { my $leakyscript = <<'LEAKYSCRIPT'; use Test::More no_plan => 1; use Crypt::OpenSSL::CA::Test; sub leak { # Perl is getting smarter and smarter in memory mgmt, and we need # to ward off constant folding for that test to fail as expected: push(our @a, "abcde" x (10000 + scalar @a)); } leaks_bytes_ok { leak() }; leaks_SVs_ok { leak() }; LEAKYSCRIPT my $out = run_perl($leakyscript); is($? & 255, 0, "we don't get signal"); like($out, qr/^not ok 1/m); like($out, qr/^ok 2/m); unlike($out, qr/Crypt.*CA/, "errors are reported at the proper stack depth"); }; my $cert_pem = $Crypt::OpenSSL::CA::Test::test_self_signed_certs{"rsa1024"}; # REFACTORME into Crypt::OpenSSL::CA::Test::pem2der or something my $cert_der = do { use MIME::Base64 (); local $_ = $cert_pem; is(scalar(s/^-+(BEGIN|END) CERTIFICATE-+$//gm), 2, "test PEM certificate looks good") or warn $cert_pem; MIME::Base64::decode_base64($_); }; test "x509_decoder" => sub { use MIME::Base64; my $decoder = Crypt::OpenSSL::CA::Test::x509_decoder('Certificate'); ok($decoder->can("decode")); my $tree = $decoder->decode($cert_der); is($tree->{tbsCertificate}->{subjectPublicKeyInfo} ->{algorithm}->{algorithm}, "1.2.840.113549.1.1.1", "rsaEncryption"); }; =head2 Synopsis tests =cut test "synopsis" => sub { # Thank you Test::Group for being fully reflexive! eval My::Tests::Below->pod_code_snippet("synopsis"); die $@ if $@; }; test "synopsis asn1" => sub { my $synopsis = My::Tests::Below->pod_code_snippet("synopsis-asn1"); ok(defined(my $dn_der = $Crypt::OpenSSL::CA::Test::test_der_DNs{"CN=Zoinx,C=fr"}), "\$dn_der defined"); eval $synopsis; die $@ if $@; pass; }; =head2 Sample Input Validation =cut test "test_simple_utf8 and test_bmp_utf8" => sub { is(length(Crypt::OpenSSL::CA::Test->test_simple_utf8()), 6); ok(utf8::is_utf8(Crypt::OpenSSL::CA::Test->test_simple_utf8())); is(length(Crypt::OpenSSL::CA::Test->test_bmp_utf8()), 3); ok(utf8::is_utf8(Crypt::OpenSSL::CA::Test->test_bmp_utf8())); }; test "%test_keys_plaintext and %test_keys_password" => sub { is_deeply ([sort keys %Crypt::OpenSSL::CA::Test::test_keys_plaintext], [sort keys %Crypt::OpenSSL::CA::Test::test_keys_password], "same keys in both"); if (defined(my $openssl_bin = openssl_path)) { while(my ($k, $v) = each %Crypt::OpenSSL::CA::Test::test_keys_password) { my ($out, $err) = run_thru_openssl ($v, qw(rsa -passin pass:secret)); is($out, $Crypt::OpenSSL::CA::Test::test_keys_plaintext{$k}); } } }; test "certificate_chain_ok and test certificates" => sub { my @keyids = keys %Crypt::OpenSSL::CA::Test::test_rootca_certs; foreach my $id (@keyids) { certificate_chain_ok ($Crypt::OpenSSL::CA::Test::test_entity_certs{$id}, [ $Crypt::OpenSSL::CA::Test::test_rootca_certs{$id} ]); } my ($snippet_ok, $snippet_not_ok) = map { My::Tests::Below->pod_code_snippet($_) } (qw(certificate_chain_ok certificate_chain_notok)); my $out = run_perl(<<"SCRIPT"); use Test::More no_plan => 1; use Crypt::OpenSSL::CA::Test qw(certificate_chain_ok %test_rootca_certs %test_self_signed_certs %test_entity_certs); foreach my \$id (qw(${\join(" ", @keyids)})) { $snippet_ok $snippet_not_ok } SCRIPT for my $i (0..$#keyids) { my $success = 2 * $i + 1; my $failure = 2 * $i + 2; like($out, qr/^ok $success/m); like($out, qr/^not ok $failure/m); } };