Crypt-PKCS10
view release on metacpan or search on metacpan
lib/Crypt/PKCS10.pm view on Meta::CPAN
my( $value ) = @_;
return unless( ref $value );
if( ref $value eq 'ARRAY' ) {
foreach (@$value) {
$self->_scanvalue( $_ );
}
return;
}
if( ref $value eq 'HASH' ) {
foreach my $k (keys %$value) {
if( $k eq 'bmpString' ) {
$self->_bmpstring( $value->{bmpString} );
next;
}
if( $k eq 'iPAddress' ) {
use bytes;
my $addr = $value->{iPAddress};
if( length $addr == 4 ) {
$value->{iPAddress} = sprintf( '%vd', $addr );
} else {
$addr = sprintf( '%*v02X', ':', $addr );
$addr =~ s/([[:xdigit:]]{2}):([[:xdigit:]]{2})/$1$2/g;
$value->{iPAddress} = $addr;
}
next;
}
$self->_scanvalue( $value->{$k} );
}
return;
}
return;
}
sub _convert_signatureAlgorithm {
my $self = shift;
my $signatureAlgorithm = shift;
$signatureAlgorithm->{algorithm}
= $oids{$signatureAlgorithm->{algorithm}}
if( defined $signatureAlgorithm->{algorithm}
&& exists $oids{$signatureAlgorithm->{algorithm}} );
return $signatureAlgorithm;
}
sub _convert_pkinfo {
my $self = shift;
my $pkinfo = shift;
$pkinfo->{algorithm}{algorithm}
= $oids{$pkinfo->{algorithm}{algorithm}}
if( defined $pkinfo->{algorithm}{algorithm}
&& exists $oids{$pkinfo->{algorithm}{algorithm}} );
return $pkinfo;
}
# OIDs requiring some sort of special handling
#
# Called with decoded value, returns updated value.
# Key is ASN macro name
my %special;
%special =
(
EnhancedKeyUsage => sub {
my $self = shift;
my( $value, $id ) = @_;
foreach (@{$value}) {
$_ = $oid2extkeyusage{$_} if(defined $oid2extkeyusage{$_});
}
return $value;
},
KeyUsage => sub {
my $self = shift;
my( $value, $id ) = @_;
my $bit = unpack('C*', @{$value}[0]); #get the decimal representation
my $length = int(log($bit) / log(2) + 1); #get its bit length
my @usages = reverse( $id eq 'KeyUsage'? # Following are in order from bit 0 upwards
qw(digitalSignature nonRepudiation keyEncipherment dataEncipherment
keyAgreement keyCertSign cRLSign encipherOnly decipherOnly) :
qw(client server email objsign reserved sslCA emailCA objCA) );
my $shift = ($#usages + 1) - $length; # computes the unused area in @usages
@usages = @usages[ grep { $bit & (1 << $_ - $shift) } 0 .. $#usages ]; #transfer bitmap to barewords
return [ @usages ] if( $self->{_apiVersion} >= 1 );
return join( ', ', @usages );
},
netscapeCertType => sub {
goto &{$special{KeyUsage}};
},
SubjectKeyIdentifier => sub {
my $self = shift;
my( $value, $id ) = @_;
return unpack( "H*", $value );
},
ApplicationCertPolicies => sub {
goto &{$special{certificatePolicies}} if( $_[0]->{_apiVersion} > 0 );
my $self = shift;
my( $value, $id ) = @_;
foreach my $entry (@{$value}) {
$entry->{policyIdentifier} = $self->_oid2name( $entry->{policyIdentifier} );
}
return $value;
},
certificateTemplate => sub {
my $self = shift;
my( $value, $id ) = @_;
$value->{templateID} = $self->_oid2name( $value->{templateID} ) if( $self->{_apiVersion} > 0 );
return $value;
},
lib/Crypt/PKCS10.pm view on Meta::CPAN
return $self->_hash2string( $value );
},
challengePassword => sub {
my $self = shift;
my( $value, $id ) = @_;
return $self->_hash2string( $value );
},
); # %special
sub _convert_attributes {
my $self = shift;
my( $typeandvalues ) = @_;
foreach my $entry ( @{$typeandvalues} ) {
my $oid = $entry->{type};
my $name = $oids{$oid};
$name = $variantNames{$name} if( defined $name && exists $variantNames{$name} );
next unless( defined $name );
$entry->{type} = $name;
if ($name eq 'extensionRequest') {
$entry->{values} = $self->_convert_extensionRequest($entry->{values}[0]);
} elsif ($name eq 'ENROLLMENT_NAME_VALUE_PAIR') {
my $parser = $self->_init( $name );
my @values;
foreach my $der (@{$entry->{values}}) {
my $pair = $parser->decode( $der ) or
confess( "Looks like damaged input parsing attribute $name" );
$self->_bmpstring( $pair->{name}, $pair->{value} );
push @values, $pair;
};
$entry->{values} = \@values;
} else {
my $parser = $self->_init( $name, 1 ) or next; # Skip unknown attributes
if($entry->{values}[1]) {
confess( "Incomplete parsing of attribute type: $name" );
}
my $value = $entry->{values} = $parser->decode( $entry->{values}[0] ) or
confess( "Looks like damaged input parsing attribute $name" );
if( exists $special{$name} ) {
my $action = $special{$name};
$entry->{values} = $action->( $self, $value, $name, $entry );
}
}
}
return $typeandvalues;
}
sub _convert_extensionRequest {
my $self = shift;
my( $extensionRequest ) = @_;
my $parser = $self->_init('extensionRequest');
my $decoded = $parser->decode($extensionRequest) or return [];
foreach my $entry (@{$decoded}) {
my $name = $oids{ $entry->{extnID} };
$name = $variantNames{$name} if( defined $name && exists $variantNames{$name} );
if (defined $name) {
my $asnName = $name;
$asnName =~ tr/ //d;
my $parser = $self->_init($asnName, 1);
if(!$parser) {
$entry = undef;
next;
}
$entry->{extnID} = $name;
my $dec = $parser->decode($entry->{extnValue}) or
confess( $parser->error . ".. looks like damaged input parsing extension $asnName" );
$self->_scanvalue( $dec );
if( exists $special{$asnName} ) {
my $action = $special{$asnName};
$dec = $action->( $self, $dec, $asnName, $entry );
}
$entry->{extnValue} = $dec;
}
}
@{$decoded} = grep { defined } @{$decoded};
return $decoded;
}
sub _convert_rdn {
my $self = shift;
my $typeandvalue = shift;
my %hash = ( _subject => [], );
foreach my $entry ( @$typeandvalue ) {
foreach my $item (@$entry) {
my $oid = $item->{type};
my $name = (exists $variantNames{$oid})? $variantNames{$oid}[1]: $oids{ $oid };
if( defined $name ) {
push @{$hash{$name}}, sort values %{$item->{value}};
push @{$hash{_subject}}, $name, [ sort values %{$item->{value}} ];
my @names = (exists $variantNames{$oid})? @{$variantNames{$oid}} : ( $name );
foreach my $name ( @names ) {
unless( $self->can( $name ) ) {
no strict 'refs'; ## no critic
*$name = sub {
my $self = shift;
return @{ $self->{certificationRequestInfo}{subject}{$name} } if( wantarray );
return $self->{certificationRequestInfo}{subject}{$name}[0] || '';
}
}
}
}
}
}
return \%hash;
}
sub _init {
my $self = shift;
my( $node, $optional ) = @_;
my $parsed = $self->{_asn}->find($node);
unless( defined $parsed || $optional ) {
croak( "Missing node $node in ASN.1" );
}
return $parsed;
}
###########################################################################
# interface methods
sub csrRequest {
my $self = shift;
my $format = shift;
return( "-----BEGIN CERTIFICATE REQUEST-----\n" .
_encode_PEM( $self->{_der} ) .
"-----END CERTIFICATE REQUEST-----\n" ) if( $format );
return $self->{_der};
}
# Common subject components documented to be always present:
foreach my $component (qw/commonName organizationalUnitName organizationName
lib/Crypt/PKCS10.pm view on Meta::CPAN
$rv->{keytype} = 'ECC';
eval { require Crypt::PK::ECC; };
if( $@ ) {
$rv->{keytype} = undef;
$self->{_error} =
$error = "ECC public key requires Crypt::PK::ECC\n";
croak( $error ) if( $self->{_dieOnError} );
return $rv;
}
my $key = $self->subjectPublicKey(1);
$key = Crypt::PK::ECC->new( \$key )->key2hash;
$rv->{keylen} = $key->{curve_bits};
$rv->{pub_x} = $key->{pub_x};
$rv->{pub_y} = $key->{pub_y};
$rv->{detail} = { %$key } if( $detail );
my $par = $self->_init( 'eccName' );
$rv->{curve} = $par->decode( $self->{certificationRequestInfo}{subjectPKInfo}{algorithm}{parameters} );
$rv->{curve} = $self->_oid2name( $rv->{curve} ) if ($rv->{curve});
} elsif( $at eq 'dsa' ) {
$rv->{keytype} = 'DSA';
my $par = $self->_init( 'dsaKey' );
my $dsa = $par->decode( $self->{certificationRequestInfo}{subjectPKInfo}{subjectPublicKey}[0] );
$rv->{keylen} = 4 * ( length( $dsa->as_hex ) -2 );
if( exists $self->{certificationRequestInfo}{subjectPKInfo}{algorithm}{parameters} ) {
$par = $self->_init('dsaPars');
$dsa = $par->decode($self->{certificationRequestInfo}{subjectPKInfo}{algorithm}{parameters});
$rv->{G} = substr( $dsa->{G}->as_hex, 2 );
$rv->{P} = substr( $dsa->{P}->as_hex, 2 );
$rv->{Q} = substr( $dsa->{Q}->as_hex, 2 );
}
} else {
$rv->{keytype} = undef;
$self->{_error} =
$error = "Unrecognized public key type $at\n";
croak( $error ) if( $self->{_dieOnError} );
}
return $rv;
}
sub signatureAlgorithm {
my $self = shift;
return $self->{signatureAlgorithm}{algorithm};
}
sub signatureParams {
my $self = shift;
return unless ( exists $self->{signatureAlgorithm}{parameters} );
# For RSA PSS the parameters have been parsed to a hash already
if (ref $self->{signatureAlgorithm}{parameters} eq 'HASH') {
return $self->{signatureAlgorithm}{parameters};
}
my( $tlen, undef, $tag ) = asn_decode_tag2( $self->{signatureAlgorithm}{parameters} );
if( $tlen != 0 && $tag != ASN_NULL ) {
return $self->{signatureAlgorithm}{parameters}
}
# Known algorithm's parameters MAY return a hash of decoded fields.
# For now, leaving that to the caller...
return;
}
sub signature {
my $self = shift;
my $format = shift;
if( defined $format && $format == 2 ) { # Per keytype decoding
if( $self->pkAlgorithm eq 'ecPublicKey' ) { # ECDSA
my $par = $self->_init( 'ecdsaSigValue' );
return $par->decode( $self->{signature}[0] );
}
return; # Unknown
}
return $self->{signature}[0] if( $format );
return unpack('H*', $self->{signature}[0]);
}
sub certificationRequest {
my $self = shift;
return $self->{_signed};
}
sub _attributes {
my $self = shift;
my $attributes = $self->{certificationRequestInfo}{attributes};
return unless( defined $attributes );
return { map { $_->{type} => $_->{values} } @$attributes };
}
sub attributes {
my $self = shift;
my( $name ) = @_;
if( $self->{_apiVersion} < 1 ) {
my $attributes = $self->{certificationRequestInfo}{attributes};
return () unless( defined $attributes );
my %hash = map { $_->{type} => $_->{values} }
@{$attributes};
return %hash;
}
my $attributes = $self->_attributes;
unless( defined $attributes ) {
return () if( wantarray );
return undef; ## no critic
}
unless( defined $name ) {
return grep { $_ ne 'extensionRequest' } sort keys %$attributes;
}
$name = $self->_oid2name( $name );
lib/Crypt/PKCS10.pm view on Meta::CPAN
Version 1.4 made several API changes. Most users should have a painless migration.
ALL users must call Crypt::PKCS10->setAPIversion. If not, a warning will be generated
by the first class method called. This warning will be made a fatal exception in a
future release.
Other than that requirement, the legacy mode is compatible with previous versions.
C<new> will no longer generate exceptions. C<undef> is returned on all errors. Use
the error class method to retrieve the reason.
new will accept an open file handle in addition to a request.
Users are encouraged to migrate to the version 1 API. It is much easier to use,
and does not require the application to navigate internal data structures.
Version 1.7 provides support for DSA and ECC public keys. By default, it verifies
the signature of CSRs. It also allows the caller to verify the signature of a CSR.
subjectPublicKeyParams and signatureParams provide additional information.
The readFile option to new() will open() a file containing a CSR by name.
The ignoreNonBase64 option allows PEM to contain extraneous characters.
F<Changes> describes additional improvements. Details follow.
=head1 INSTALLATION
C<Crypt::PKCS10> supports DSA, RSA and ECC public keys in CSRs.
It depends on C<Crypt::PK::*> (provided by CryptX) for some operations.
All are recommended. Some methods will return errors if
Crypt::PKCS10 is presented with a CSR containing an unsupported public key type.
To install this module type the following:
perl Makefile.PL
make
make test
make install
=head1 REQUIRES
C<Convert::ASN1>
C<Crypt::PK::DSA>
C<Crypt::PK::RSA>
C<Crypt::PK::ECC>
C<Digest::SHA>
Very old CSRs may require C<DIGEST::MD{5,4,2}>
=end :readme
=head1 SYNOPSIS
use Crypt::PKCS10;
Crypt::PKCS10->setAPIversion( 1 );
my $decoded = Crypt::PKCS10->new( $csr ) or die Crypt::PKCS10->error;
print $decoded;
@names = $decoded->extensionValue('subjectAltName' );
@names = $decoded->subject unless( @names );
%extensions = map { $_ => $decoded->extensionValue( $_ ) } $decoded->extensions
=head1 DESCRIPTION
C<Crypt::PKCS10> parses PKCS #10 certificate requests (CSRs) and provides accessor methods to extract the data in usable form.
Common object identifiers will be translated to their corresponding names.
Additionally, accessor methods allow extraction of single data fields.
The format of returned data varies by accessor.
The access methods return the value corresponding to their name. If called in scalar context, they return the first value (or an empty string). If called in array context, they return all values.
B<true> values should be specified as 1 and B<false> values as 0. Future API changes may provide different functions when other values are used.
=head1 METHODS
Access methods may exist for subject name components that are not listed here. To test for these, use code of the form:
$locality = $decoded->localityName if( $decoded->can('localityName') );
If a name component exists in a CSR, the method will be present. The converse is not (always) true.
=head2 class method setAPIversion( $version )
Selects the API version (0 or 1) expected.
Must be called before calling any other method.
The API version determines how a CSR is parsed. Changing the API version after
parsing a CSR will cause accessors to produce unpredictable results.
=over 4
=item Version 0 - B<DEPRECATED>
Some OID names have spaces and descriptions
This is the format used for C<Crypt::PKCS10> version 1.3 and lower. The attributes method returns legacy data.
Some new API functions are disabled.
=item Version 1
OID names from RFCs - or at least compatible with OpenSSL and ASN.1 notation. The attributes method conforms to version 1.
=back
If not called, a warning will be generated and the API will default to version 0.
In a future release, the warning will be changed to a fatal exception.
To ease migration, both old and new names are accepted by the API.
Every program should call C<setAPIversion(1)>.
=cut
=head2 class method getAPIversion
Returns the current API version.
Returns C<undef> if setAPIversion has never been called.
=head2 class method new( $csr, %options )
Constructor, creates a new object containing the parsed PKCS #10 certificate request.
C<$csr> may be a scalar containing the request, a file name, or a file handle from which to read it.
If a file name is specified, the C<readFile> option must be specified.
If a file handle is supplied, the caller should specify C<< acceptPEM => 0 >> if the contents are DER.
The request may be PEM or binary DER encoded. Only one request is processed.
If PEM, other data (such as mail headers) may precede or follow the CSR.
my $decoded = Crypt::PKCS10->new( $csr ) or die Crypt::PKCS10->error;
Returns C<undef> if there is an I/O error or the request can not be parsed successfully.
Call C<error()> to obtain more detail.
=head3 options
The options are specified as C<< name => value >>.
If the first option is a HASHREF, it is expanded and any remaining options are added.
=over 4
=item acceptPEM
If B<false>, the input must be in DER format. C<binmode> will be called on a file handle.
If B<true>, the input is checked for a C<CERTIFICATE REQUEST> header. If not found, the csr
is assumed to be in DER format.
Default is B<true>.
=item PEMonly
If B<true>, the input must be in PEM format. An error will be returned if the input doesn't contain a C<CERTIFICATE REQUEST> header.
If B<false>, the input is parsed according to C<acceptPEM>.
Default is B<false>.
=item binaryMode
If B<true>, an input file or file handle will be set to binary mode prior to reading.
If B<false>, an input file or file handle's C<binmode> will not be modified.
Defaults to B<false> if B<acceptPEM> is B<true>, otherwise B<true>.
=item dieOnError
If B<true>, any API function that sets an error string will also C<die>.
If B<false>, exceptions are only generated for fatal conditions.
The default is B<false>. API version 1 only..
=item escapeStrings
If B<true>, strings returned for extension and attribute values are '\'-escaped when formatted.
This is compatible with OpenSSL configuration files.
The special characters are: '\', '$', and '"'
If B<false>, these strings are not '\'-escaped. This is useful when they are being displayed
to a human.
The default is B<true>.
=item ignoreNonBase64
If B<true>, most invalid base64 characters in PEM data will be ignored. For example, this will
lib/Crypt/PKCS10.pm view on Meta::CPAN
'value' => 'ACME',
'type' => '2.5.4.10'
},
[
{
'format' => 'utf8String',
'type' => '2.5.4.3',
'value' => 'Foobar'
},
{
'format' => 'utf8String',
'type' => '0.9.2342.19200300.100.1.1',
'value' => 'foobar'
}
]
];
=head3 subjectSequence
Returns the subject as returned from the ASN1 parser.
Similar to subjectRaw this is a list with the RDNs but each item is
always a list itself, in case of a single valued RND holding only
a single item. Each item is a hash with the keys type and value where
the value part is a hash with the format as key and the item value as
value:
[
[
{
'value' => { 'ia5String' => 'Org' },
'type' => '0.9.2342.19200300.100.1.25'
}
],
[
{
'value' => { 'utf8String' => 'ACME' },
'type' => '2.5.4.10'
},
],
[
{
'type' => '2.5.4.3',
'value' => { 'utf8String' => 'Foobar' }
},
{
'type' => '0.9.2342.19200300.100.1.1',
'value' => { 'utf8String' => 'foobar' }
}
]
];
This structure can be used directly to assemble ASN1 structures with
the OpenXPKI::Crypt::* objects.
=head3 commonName
Returns the common name(s) from the subject.
my $cn = $decoded->commonName();
=head3 organizationalUnitName
Returns the organizational unit name(s) from the subject
=head3 organizationName
Returns the organization name(s) from the subject.
=head3 emailAddress
Returns the email address from the subject.
=head3 stateOrProvinceName
Returns the state or province name(s) from the subject.
=head3 countryName
Returns the country name(s) from the subject.
=head2 subjectAltName( $type )
Convenience method.
When $type is specified: returns the subject alternate name values of the specified type in list context, or the first value
of the specified type in scalar context.
Returns undefined/empty list if no values of the specified type are present, or if the B<subjectAltName>
extension is not present.
Types can be any of:
otherName
* rfc822Name
* dNSName
x400Address
directoryName
ediPartyName
* uniformResourceIdentifier
* iPAddress
* registeredID
The types marked with '*' are the most common.
If C<$type> is not specified:
In list context returns the types present in the subjectAlternate name.
In scalar context, returns the SAN as a string.
=head2 version
Returns the structure version as a string, e.g. "v1" "v2", or "v3"
=head2 pkAlgorithm
Returns the public key algorithm according to its object identifier.
=head2 subjectPublicKey( $format )
If C<$format> is B<true>, the public key will be returned in PEM format.
Otherwise, the public key will be returned in its hexadecimal representation
=head2 subjectPublicKeyParams
Returns a hash describing the public key. The contents vary depending on
the public key type.
=head3 Standard items:
C<keytype> - ECC, RSA, DSA or C<undef>
C<keytype> will be C<undef> if the key type is not supported. In
this case, C<error()> returns a diagnostic message.
C<keylen> - Approximate length of the key in bits.
Other items include:
For RSA, C<modulus> and C<publicExponent>.
For DSA, C<G, P and Q>.
For ECC, C<curve>, C<pub_x> and C<pub_y>. C<curve> is an OID name.
=head3 Additional detail
C<subjectPublicKeyParams(1)> returns the standard items, and may
also return C<detail>, which is a hashref.
For ECC, the C<detail> hash includes the curve definition constants.
=head2 signatureAlgorithm
Returns the signature algorithm according to its object identifier.
=head2 signatureParams
Returns the parameters associated with the B<signatureAlgorithm> as binary.
Returns B<undef> if none, or if B<NULL>.
Note: In the future, some B<signatureAlgorithm>s may return a hashref of decoded fields.
Callers are advised to check for a ref before decoding...
=head2 signature( $format )
The CSR's signature is returned.
If C<$format> is B<1>, in binary.
If C<$format> is B<2>, decoded as an ECDSA signature - returns hashref to C<r> and C<s>.
Otherwise, in its hexadecimal representation.
=head2 attributes( $name )
A request may contain a set of attributes. The attributes are OIDs with values.
The most common is a list of requested extensions, but other OIDs can also
occur. Of those, B<challengePassword> is typical.
For API version 0, this method returns a hash consisting of all
attributes in an internal format. This usage is B<deprecated>.
For API version 1:
If $name is not specified, a list of attribute names is returned. The list does not
include the requestedExtensions attribute. For that, use extensions();
If no attributes are present, the empty list (C<undef> in scalar context) is returned.
If $name is specified, the value of the extension is returned. $name can be specified
as a numeric OID.
In scalar context, a single string is returned, which may include lists and labels.
cspName="Microsoft Strong Cryptographic Provider",keySpec=2,signature=("",0)
Special characters are escaped as described in options.
In array context, the value(s) are returned as a list of items, which may be references.
print( " $_: ", scalar $decoded->attributes($_), "\n" )
foreach ($decoded->attributes);
=for readme stop
See the I<Table of known OID names> below for a list of names.
=for readme continue
=begin :readme
See the module documentation for a list of known OID names.
It is too long to include here.
=end :readme
=head2 extensions
Returns an array containing the names of all extensions present in the CSR. If no extensions are present,
the empty list is returned.
The names vary depending on the API version; however, the returned names are acceptable to C<extensionValue>, C<extensionPresent>, and C<name2oid>.
The values of extensions vary, however the following code fragment will dump most extensions and their value(s).
print( "$_: ", $decoded->extensionValue($_,1), "\n" ) foreach ($decoded->extensions);
The sample code fragment is not guaranteed to handle all cases.
Production code needs to select the extensions that it understands and should respect
the B<critical> boolean. B<critical> can be obtained with extensionPresent.
=head2 extensionValue( $name, $format )
Returns the value of an extension by name, e.g. C<extensionValue( 'keyUsage' )>.
The name SHOULD be an API v1 name, but API v0 names are accepted for compatibility.
The name can also be specified as a numeric OID.
If C<$format> is 1, the value is a formatted string, which may include lists and labels.
Special characters are escaped as described in options;
If C<$format> is 0 or not defined, a string, or an array reference may be returned.
The array many contain any Perl variable type.
To interpret the value(s), you need to know the structure of the OID.
=for readme stop
See the I<Table of known OID names> below for a list of names.
=for readme continue
=begin :readme
See the module documentation for a list of known OID names.
It is too long to include here.
=end :readme
=head2 extensionPresent( $name )
Returns B<true> if a named extension is present:
If the extension is B<critical>, returns 2.
Otherwise, returns 1, indicating B<not critical>, but present.
If the extension is not present, returns C<undef>.
The name can also be specified as a numeric OID.
=for readme stop
See the I<Table of known OID names> below for a list of names.
=for readme continue
=begin :readme
See the module documentation for a list of known OID names.
It is too long to include here.
=end :readme
=head2 registerOID( )
( run in 0.686 second using v1.01-cache-2.11-cpan-4e7a2411597 )