File Coverage

blib/lib/Crypt/PKCS10.pm
Criterion Covered Total %
statement 564 711 79.3
branch 247 404 61.1
condition 73 143 51.0
subroutine 55 59 93.2
pod 24 26 92.3
total 963 1343 71.7


line stmt bran cond sub pod time code
1             #
2             # Crypt::PKCS10
3             #
4             # ABSTRACT: parse PKCS #10 certificate requests
5             #
6             # This software is copyright (c) 2014 by Gideon Knocke.
7             # Copyright (c) 2016 Gideon Knocke, Timothe Litt
8             #
9             # See LICENSE for details.
10             #
11              
12             package Crypt::PKCS10;
13              
14 2     2   116589 use strict;
  2         15  
  2         75  
15 2     2   11 use warnings;
  2         5  
  2         63  
16 2     2   8 use Carp;
  2         4  
  2         191  
17              
18 2     2   1028 use overload( q("") => 'as_string' );
  2         1182  
  2         25  
19              
20 2     2   1185 use Convert::ASN1( qw/:tag :const/ );
  2         70395  
  2         565  
21 2     2   19 use Encode ();
  2         5  
  2         43  
22 2     2   1017 use MIME::Base64;
  2         2743  
  2         165  
23 2     2   17 use Scalar::Util ();
  2         6  
  2         6419  
24              
25             our $VERSION = '2.003';
26              
27             my $apiVersion = undef; # 0 for compatibility. 1 for prefered
28             my $error;
29              
30             # N.B. Names are exposed in the API.
31             # %shortnames follows & depends on (some) values.
32             # When adding OIDs, re-generate the documentation (see "for MAINTAINER" below)
33             #
34             # New OIDs don't need the [ ] syntax, which is [ prefered name, deprecated name ]
35             # Some of the deprecated names are used in the ASN.1 definition. and in the $self
36             # structure, which unfortunately is exposed with the attributes() method.
37             # Dealing with the deprecated names causes some messy code.
38              
39             my %oids = (
40             '2.5.4.6' => 'countryName',
41             '2.5.4.8' => 'stateOrProvinceName',
42             '2.5.4.10' => 'organizationName',
43             '2.5.4.11' => 'organizationalUnitName',
44             '2.5.4.3' => 'commonName',
45             '1.2.840.113549.1.9.1' => 'emailAddress',
46             '1.2.840.113549.1.9.2' => 'unstructuredName',
47             '1.2.840.113549.1.9.7' => 'challengePassword',
48             '1.2.840.113549.1.9.8' => 'unstructuredAddress',
49             '1.2.840.113549.1.1.1' => [ 'rsaEncryption', 'RSA encryption' ],
50             '1.2.840.113549.1.1.5' => [ 'sha1WithRSAEncryption', 'SHA1 with RSA encryption' ],
51             '1.2.840.113549.1.1.4' => [ 'md5WithRSAEncryption', 'MD5 with RSA encryption' ],
52             '1.2.840.113549.1.1.10' => 'rsassaPss',
53             '1.2.840.113549.1.9.14' => 'extensionRequest',
54             '1.3.6.1.4.1.311.13.2.3' => 'OS_Version', # Microsoft
55             '1.3.6.1.4.1.311.13.2.2' => 'EnrollmentCSP', # Microsoft
56             '1.3.6.1.4.1.311.21.20' => 'ClientInformation', # Microsoft REQUEST_CLIENT_INFO
57             '1.3.6.1.4.1.311.21.7' => 'certificateTemplate', # Microsoft
58             '2.5.29.37' => [ 'extKeyUsage', 'EnhancedKeyUsage' ],
59             '2.5.29.15' => [ 'keyUsage', 'KeyUsage' ],
60             '1.3.6.1.4.1.311.21.10' => 'ApplicationCertPolicies', # Microsoft APPLICATION_CERT_POLICIES
61             '2.5.29.14' => [ 'subjectKeyIdentifier', 'SubjectKeyIdentifier' ],
62             '2.5.29.17' => 'subjectAltName',
63             '1.3.6.1.4.1.311.20.2' => 'certificateTemplateName', # Microsoft
64             '2.16.840.1.113730.1.1' => 'netscapeCertType',
65             '2.16.840.1.113730.1.2' => 'netscapeBaseUrl',
66             '2.16.840.1.113730.1.4' => 'netscapeCaRevocationUrl',
67             '2.16.840.1.113730.1.7' => 'netscapeCertRenewalUrl',
68             '2.16.840.1.113730.1.8' => 'netscapeCaPolicyUrl',
69             '2.16.840.1.113730.1.12' => 'netscapeSSLServerName',
70             '2.16.840.1.113730.1.13' => 'netscapeComment',
71              
72             #untested
73             '2.5.29.19' => [ 'basicConstraints', 'Basic Constraints' ],
74             '1.2.840.10040.4.1' => [ 'dsa', 'DSA' ],
75             '1.2.840.10040.4.3' => [ 'dsaWithSha1', 'DSA with SHA1' ],
76             '1.2.840.10045.2.1' => 'ecPublicKey',
77             '1.2.840.10045.4.3.1' => 'ecdsa-with-SHA224',
78             '1.2.840.10045.4.3.2' => 'ecdsa-with-SHA256',
79             '1.2.840.10045.4.3.3' => 'ecdsa-with-SHA384',
80             '1.2.840.10045.4.3.4' => 'ecdsa-with-SHA512',
81             '1.3.36.3.3.2.8.1.1.1' => 'brainpoolP160r1',
82             '1.3.36.3.3.2.8.1.1.2' => 'brainpoolP160t1',
83             '1.3.36.3.3.2.8.1.1.3' => 'brainpoolP192r1',
84             '1.3.36.3.3.2.8.1.1.4' => 'brainpoolP192t1',
85             '1.3.36.3.3.2.8.1.1.5' => 'brainpoolP224r1',
86             '1.3.36.3.3.2.8.1.1.6' => 'brainpoolP224t1',
87             '1.3.36.3.3.2.8.1.1.7' => 'brainpoolP256r1',
88             '1.3.36.3.3.2.8.1.1.8' => 'brainpoolP256t1',
89             '1.3.36.3.3.2.8.1.1.9' => 'brainpoolP320r1',
90             '1.3.36.3.3.2.8.1.1.10' => 'brainpoolP320t1',
91             '1.3.36.3.3.2.8.1.1.11' => 'brainpoolP384r1',
92             '1.3.36.3.3.2.8.1.1.12' => 'brainpoolP384t1',
93             '1.3.36.3.3.2.8.1.1.13' => 'brainpoolP512r1',
94             '1.3.36.3.3.2.8.1.1.14' => 'brainpoolP512t1',
95             '1.2.840.10045.3.1.1' => 'secp192r1',
96             '1.3.132.0.1' => 'sect163k1',
97             '1.3.132.0.15' => 'sect163r2',
98             '1.3.132.0.33' => 'secp224r1',
99             '1.3.132.0.26' => 'sect233k1',
100             '1.3.132.0.27' => 'sect233r1',
101             '1.3.132.0.16' => 'sect283k1',
102             '1.3.132.0.17' => 'sect283r1',
103             '1.2.840.10045.3.1.7' => 'secp256r1',
104             '1.3.132.0.34' => 'secp384r1',
105             '1.3.132.0.36' => 'sect409k1',
106             '1.3.132.0.37' => 'sect409r1',
107             '1.3.132.0.35' => 'secp521r1',
108             '1.3.132.0.38' => 'sect571k1',
109             '1.3.132.0.39' => 'sect571r1',
110             #not std yet '1.3.6.1.4.1.3029.1.5.1' => 'curve25519', # GNU TLS
111             # '1.3.6.1.4.1.11591.7' => 'curve25519', #ID josefsson-pkix-newcurves-00
112             # '1.3.6.1.4.1.11591.8' => 'curve448', #ID josefsson-pkix-newcurves-00
113             '0.9.2342.19200300.100.1.25' => 'domainComponent',
114             '0.9.2342.19200300.100.1.1' => 'userID',
115             '2.5.4.7' => 'localityName',
116             '1.2.840.113549.1.1.11' => [ 'sha256WithRSAEncryption', 'SHA-256 with RSA encryption' ],
117             '1.2.840.113549.1.1.12' => 'sha384WithRSAEncryption',
118             '1.2.840.113549.1.1.13' => [ 'sha512WithRSAEncryption', 'SHA-512 with RSA encryption' ],
119             '1.2.840.113549.1.1.14' => 'sha224WithRSAEncryption',
120             '1.2.840.113549.1.1.2' => [ 'md2WithRSAEncryption', 'MD2 with RSA encryption' ],
121             '1.2.840.113549.1.1.3' => 'md4WithRSAEncryption',
122             '1.2.840.113549.1.1.6' => 'rsaOAEPEncryptionSET',
123             '1.2.840.113549.1.1.7' => 'RSAES-OAEP',
124             '1.2.840.113549.1.9.15' => [ 'smimeCapabilities', 'SMIMECapabilities' ],
125             '1.3.14.3.2.29' => [ 'sha1WithRSAEncryption', 'SHA1 with RSA signature' ],
126             '1.3.6.1.4.1.311.13.1' => 'RENEWAL_CERTIFICATE', # Microsoft
127             '1.3.6.1.4.1.311.13.2.1' => 'ENROLLMENT_NAME_VALUE_PAIR', # Microsoft
128             '1.3.6.1.4.1.311.13.2.2' => 'ENROLLMENT_CSP_PROVIDER', # Microsoft
129             '1.3.6.1.4.1.311.2.1.14' => 'CERT_EXTENSIONS', # Microsoft
130             '1.3.6.1.5.2.3.5' => [ 'keyPurposeKdc', 'KDC Authentication' ],
131             '1.3.6.1.5.5.7.9.5' => 'countryOfResidence',
132             '2.16.840.1.101.3.4.2.1' => [ 'sha256', 'SHA-256' ],
133             '2.16.840.1.101.3.4.2.2' => [ 'sha384', 'SHA-384' ],
134             '2.16.840.1.101.3.4.2.3' => [ 'sha512', 'SHA-512' ],
135             '2.16.840.1.101.3.4.2.4' => [ 'sha224', 'SHA-224' ],
136             '2.16.840.1.101.3.4.3.1' => 'dsaWithSha224',
137             '2.16.840.1.101.3.4.3.2' => 'dsaWithSha256',
138             '2.16.840.1.101.3.4.3.3' => 'dsaWithSha384',
139             '2.16.840.1.101.3.4.3.4' => 'dsaWithSha512',
140             '2.5.4.12' => [ 'title', 'Title' ],
141             '2.5.4.13' => [ 'description', 'Description' ],
142             '2.5.4.14' => 'searchGuide',
143             '2.5.4.15' => 'businessCategory',
144             '2.5.4.16' => 'postalAddress',
145             '2.5.4.17' => 'postalCode',
146             '2.5.4.18' => 'postOfficeBox',
147             '2.5.4.19', => 'physicalDeliveryOfficeName',
148             '2.5.4.20', => 'telephoneNumber',
149             '2.5.4.23', => 'facsimileTelephoneNumber',
150             '2.5.4.4' => [ 'surname', 'Surname' ],
151             '2.5.4.41' => [ 'name', 'Name' ],
152             '2.5.4.42' => 'givenName',
153             '2.5.4.43' => 'initials',
154             '2.5.4.44' => 'generationQualifier',
155             '2.5.4.45' => 'uniqueIdentifier',
156             '2.5.4.46' => 'dnQualifier',
157             '2.5.4.51' => 'houseIdentifier',
158             '2.5.4.65' => 'pseudonym',
159             '2.5.4.5' => 'serialNumber',
160             '2.5.4.9' => 'streetAddress',
161             '2.5.29.32' => 'certificatePolicies',
162             '2.5.29.32.0' => 'anyPolicy',
163             '1.3.6.1.5.5.7.2.1' => 'CPS',
164             '1.3.6.1.5.5.7.2.2' => 'userNotice',
165             );
166              
167             my %variantNames;
168              
169             foreach (keys %oids) {
170             my $val = $oids{$_};
171             if( ref $val ) {
172             $variantNames{$_} = $val; # OID to [ new, trad ]
173             $variantNames{$val->[0]} = $val->[1]; # New name to traditional for lookups
174             $variantNames{'$' . $val->[1]} = $val->[0]; # \$Traditional to new
175             $oids{$_} = $val->[!$apiVersion || 0];
176             }
177             }
178              
179             my %oid2extkeyusage = (
180             '1.3.6.1.5.5.7.3.1' => 'serverAuth',
181             '1.3.6.1.5.5.7.3.2' => 'clientAuth',
182             '1.3.6.1.5.5.7.3.3' => 'codeSigning',
183             '1.3.6.1.5.5.7.3.4' => 'emailProtection',
184             '1.3.6.1.5.5.7.3.8' => 'timeStamping',
185             '1.3.6.1.5.5.7.3.9' => 'OCSPSigning',
186              
187             '1.3.6.1.5.5.7.3.21' => 'sshClient',
188             '1.3.6.1.5.5.7.3.22' => 'sshServer',
189              
190             # Microsoft usages
191              
192             '1.3.6.1.4.1.311.10.3.1' => 'msCTLSign',
193             '1.3.6.1.4.1.311.10.3.2' => 'msTimeStamping',
194             '1.3.6.1.4.1.311.10.3.3' => 'msSGC',
195             '1.3.6.1.4.1.311.10.3.4' => 'msEFS',
196             '1.3.6.1.4.1.311.10.3.4.1' => 'msEFSRecovery',
197             '1.3.6.1.4.1.311.10.3.5' => 'msWHQLCrypto',
198             '1.3.6.1.4.1.311.10.3.6' => 'msNT5Crypto',
199             '1.3.6.1.4.1.311.10.3.7' => 'msOEMWHQLCrypto',
200             '1.3.6.1.4.1.311.10.3.8' => 'msEmbeddedNTCrypto',
201             '1.3.6.1.4.1.311.10.3.9' => 'msRootListSigner',
202             '1.3.6.1.4.1.311.10.3.10' => 'msQualifiedSubordination',
203             '1.3.6.1.4.1.311.10.3.11' => 'msKeyRecovery',
204             '1.3.6.1.4.1.311.10.3.12' => 'msDocumentSigning',
205             '1.3.6.1.4.1.311.10.3.13' => 'msLifetimeSigning',
206             '1.3.6.1.4.1.311.10.3.14' => 'msMobileDeviceSoftware',
207              
208             '1.3.6.1.4.1.311.2.1.21' => 'msCodeInd',
209             '1.3.6.1.4.1.311.2.1.22' => 'msCodeCom',
210             '1.3.6.1.4.1.311.20.2.2' => 'msSmartCardLogon',
211              
212              
213             # Netscape
214             '2.16.840.1.113730.4.1' => 'nsSGC',
215             );
216              
217             my %shortnames = (
218             countryName => 'C',
219             stateOrProvinceName => 'ST',
220             organizationName => 'O',
221             organizationalUnitName => 'OU',
222             commonName => 'CN',
223             # emailAddress => 'E', # Deprecated & not recognized by some software
224             domainComponent => 'DC',
225             localityName => 'L',
226             userID => 'UID',
227             surname => 'SN',
228             givenName => 'GN',
229             );
230              
231             our $schema = <
232             DirectoryString ::= CHOICE {
233             teletexString TeletexString,
234             printableString PrintableString,
235             bmpString BMPString,
236             universalString UniversalString,
237             utf8String UTF8String,
238             ia5String IA5String,
239             integer INTEGER}
240              
241             Algorithms ::= ANY
242              
243             Name ::= SEQUENCE OF RelativeDistinguishedName
244             RelativeDistinguishedName ::= SET OF AttributeTypeAndValue
245             AttributeTypeAndValue ::= SEQUENCE {
246             type OBJECT IDENTIFIER,
247             value DirectoryString}
248              
249             Attributes ::= SET OF Attribute
250             Attribute ::= SEQUENCE {
251             type OBJECT IDENTIFIER,
252             values SET OF ANY}
253              
254              
255             AlgorithmIdentifier ::= SEQUENCE {
256             algorithm OBJECT IDENTIFIER,
257             parameters Algorithms OPTIONAL}
258              
259             SubjectPublicKeyInfo ::= SEQUENCE {
260             algorithm AlgorithmIdentifier,
261             subjectPublicKey BIT STRING}
262              
263             --- Certificate Request ---
264              
265             CertificationRequest ::= SEQUENCE {
266             certificationRequestInfo CertificationRequestInfo,
267             signatureAlgorithm AlgorithmIdentifier,
268             signature BIT STRING},
269              
270             CertificationRequestInfo ::= SEQUENCE {
271             version INTEGER ,
272             subject Name OPTIONAL,
273             subjectPKInfo SubjectPublicKeyInfo,
274             attributes [0] Attributes OPTIONAL}
275              
276             --- Extensions ---
277              
278             BasicConstraints ::= SEQUENCE {
279             cA BOOLEAN OPTIONAL, -- DEFAULT FALSE,
280             pathLenConstraint INTEGER OPTIONAL}
281              
282             OS_Version ::= IA5String
283             emailAddress ::= IA5String
284              
285             EnrollmentCSP ::= SEQUENCE {
286             KeySpec INTEGER,
287             Name BMPString,
288             Signature BIT STRING}
289              
290             ENROLLMENT_CSP_PROVIDER ::= SEQUENCE { -- MSDN
291             keySpec INTEGER,
292             cspName BMPString,
293             signature BIT STRING}
294              
295             ENROLLMENT_NAME_VALUE_PAIR ::= EnrollmentNameValuePair -- MSDN: SEQUENCE OF
296              
297             EnrollmentNameValuePair ::= SEQUENCE { -- MSDN
298             name BMPString,
299             value BMPString}
300              
301             ClientInformation ::= SEQUENCE { -- MSDN
302             clientId INTEGER,
303             MachineName UTF8String,
304             UserName UTF8String,
305             ProcessName UTF8String}
306              
307             extensionRequest ::= SEQUENCE OF Extension
308             Extension ::= SEQUENCE {
309             extnID OBJECT IDENTIFIER,
310             critical BOOLEAN OPTIONAL,
311             extnValue OCTET STRING}
312              
313             SubjectKeyIdentifier ::= OCTET STRING
314              
315             certificateTemplate ::= SEQUENCE {
316             templateID OBJECT IDENTIFIER,
317             templateMajorVersion INTEGER OPTIONAL, -- (0..4294967295)
318             templateMinorVersion INTEGER OPTIONAL} -- (0..4294967295)
319              
320             EnhancedKeyUsage ::= SEQUENCE OF OBJECT IDENTIFIER
321             KeyUsage ::= BIT STRING
322             netscapeCertType ::= BIT STRING
323              
324             ApplicationCertPolicies ::= SEQUENCE OF PolicyInformation -- Microsoft
325              
326             PolicyInformation ::= SEQUENCE {
327             policyIdentifier OBJECT IDENTIFIER,
328             policyQualifiers SEQUENCE OF PolicyQualifierInfo OPTIONAL}
329              
330             PolicyQualifierInfo ::= SEQUENCE {
331             policyQualifierId OBJECT IDENTIFIER,
332             qualifier ANY}
333              
334             certificatePolicies ::= SEQUENCE OF certPolicyInformation -- RFC 3280
335              
336             certPolicyInformation ::= SEQUENCE {
337             policyIdentifier CertPolicyId,
338             policyQualifier SEQUENCE OF certPolicyQualifierInfo OPTIONAL}
339              
340             CertPolicyId ::= OBJECT IDENTIFIER
341              
342             certPolicyQualifierInfo ::= SEQUENCE {
343             policyQualifierId CertPolicyQualifierId,
344             qualifier ANY DEFINED BY policyQualifierId}
345              
346             CertPolicyQualifierId ::= OBJECT IDENTIFIER
347              
348             CertPolicyQualifier ::= CHOICE {
349             cPSuri CPSuri,
350             userNotice UserNotice }
351              
352             CPSuri ::= IA5String
353              
354             UserNotice ::= SEQUENCE {
355             noticeRef NoticeReference OPTIONAL,
356             explicitText DisplayText OPTIONAL}
357              
358             NoticeReference ::= SEQUENCE {
359             organization DisplayText,
360             noticeNumbers SEQUENCE OF INTEGER }
361              
362             DisplayText ::= CHOICE {
363             ia5String IA5String,
364             visibleString VisibleString,
365             bmpString BMPString,
366             utf8String UTF8String }
367              
368             unstructuredName ::= CHOICE {
369             Ia5String IA5String,
370             directoryString DirectoryString}
371              
372             challengePassword ::= DirectoryString
373              
374             subjectAltName ::= SEQUENCE OF GeneralName
375              
376             GeneralName ::= CHOICE {
377             otherName [0] AnotherName,
378             rfc822Name [1] IA5String,
379             dNSName [2] IA5String,
380             x400Address [3] ANY, --ORAddress,
381             directoryName [4] Name,
382             ediPartyName [5] EDIPartyName,
383             uniformResourceIdentifier [6] IA5String,
384             iPAddress [7] OCTET STRING,
385             registeredID [8] OBJECT IDENTIFIER}
386              
387             AnotherName ::= SEQUENCE {
388             type OBJECT IDENTIFIER,
389             value [0] EXPLICIT ANY }
390              
391             EDIPartyName ::= SEQUENCE {
392             nameAssigner [0] DirectoryString OPTIONAL,
393             partyName [1] DirectoryString }
394              
395             certificateTemplateName ::= CHOICE {
396             octets OCTET STRING,
397             directoryString DirectoryString}
398              
399             rsaKey ::= SEQUENCE {
400             modulus INTEGER,
401             publicExponent INTEGER}
402              
403             dsaKey ::= INTEGER
404              
405             dsaPars ::= SEQUENCE {
406             P INTEGER,
407             Q INTEGER,
408             G INTEGER}
409              
410             eccName ::= OBJECT IDENTIFIER
411              
412             ecdsaSigValue ::= SEQUENCE {
413             r INTEGER,
414             s INTEGER}
415              
416             rsassaPssParam ::= SEQUENCE {
417             digestAlgorithm [0] EXPLICIT AlgorithmIdentifier,
418             maskGenAlgorithm ANY,
419             saltLength [2] EXPLICIT INTEGER OPTIONAL,
420             trailerField ANY OPTIONAL}
421             ASN1
422             ;
423              
424             my %name2oid;
425              
426             # For generating documentation, not part of API
427              
428             sub _cmpOID {
429 0     0   0 my @a = split( /\./, $a );
430 0         0 my @b = split( /\./, $b );
431              
432 0   0     0 while( @a && @b ) {
433 0         0 my $c = shift @a <=> shift @b;
434 0 0       0 return $c if( $c );
435             }
436 0         0 return @a <=> @b;
437             }
438              
439             sub __listOIDs {
440 0     0   0 my $class = shift;
441 0         0 my ( $hash ) = @_;
442              
443 0         0 my @max = (0) x 3;
444 0         0 foreach my $oid ( keys %$hash ) {
445 0         0 my $len;
446              
447 0         0 $len = length $oid;
448 0 0       0 $max[0] = $len if( $len > $max[0] );
449 0 0       0 if( exists $variantNames{$oid} ) {
450 0         0 $len = length $variantNames{$oid}[0];
451 0 0       0 $max[1] = $len if( $len > $max[1] );
452 0         0 $len = length $variantNames{$oid}[1];
453 0 0       0 $max[2] = $len if( $len > $max[2] );
454             } else {
455 0         0 $len = length $hash->{$oid};
456 0 0       0 $max[1] = $len if( $len > $max[1] );
457             }
458             }
459              
460 0         0 printf( " %-*s %-*s %s\n %s %s %s\n", $max[0], 'OID',
461             $max[1], 'Name (API v1)', 'Old Name (API v0)',
462             '-' x $max[0], '-' x $max[1], '-' x $max[2] );
463              
464 0         0 foreach my $oid ( sort _cmpOID keys %$hash ) {
465             printf( " %-*s %-*s", $max[0], $oid, $max[1], (exists $variantNames{$oid})?
466 0 0       0 $variantNames{$oid}[0]: $hash->{$oid} );
467 0 0       0 printf( " (%-s)", $variantNames{$oid}[1] ) if( exists $variantNames{$oid} );
468 0         0 print( "\n" );
469             }
470 0         0 return;
471             }
472              
473             sub _listOIDs {
474 0     0   0 my $class = shift;
475              
476 0         0 $class->setAPIversion(1);
477 0         0 $class-> __listOIDs( { %oids, %oid2extkeyusage } );
478              
479 0         0 return;
480             }
481              
482             sub setAPIversion {
483 2     2 1 2791 my( $class, $version ) = @_;
484              
485 2 50 33     30 croak( substr(($error = "Wrong number of arguments\n"), 0, -1) ) unless( @_ == 2 && defined $class && !ref $class );
      33        
486 2 50       9 $version = 0 unless( defined $version );
487 2 50 33     13 croak( substr(($error = "Unsupported API version $version\n"), 0, -1) ) unless( $version >= 0 && $version <= 1 );
488 2         4 $apiVersion = $version;
489              
490 2   100     9 $version = !$version || 0;
491              
492 2         33 foreach (keys %variantNames) {
493 136 100       263 $oids{$_} = $variantNames{$_}[$version] if( /^\d/ ); # Map OID to selected name
494             }
495 2         250 %name2oid = reverse (%oids, %oid2extkeyusage);
496              
497 2         18 return 1;
498             }
499              
500             sub getAPIversion {
501 2     2 1 2074 my( $class ) = @_;
502              
503 2 50       8 croak( "Class not specified for getAPIversion()" ) unless( defined $class );
504              
505 2 50 33     8 return $class->{_apiVersion} if( ref $class && $class->isa( __PACKAGE__ ) );
506              
507 2         13 return $apiVersion;
508             }
509              
510             sub name2oid {
511 4     4 1 2857 my $class = shift;
512 4         7 my( $oid ) = @_;
513              
514 4 50       12 croak( "Class not specifed for name2oid()" ) unless( defined $class );
515              
516 4 50 33     23 return unless( defined $oid && defined $apiVersion && $apiVersion > 0 );
      33        
517              
518 4         19 return $name2oid{$oid};
519             }
520              
521             sub oid2name {
522 4     4 0 7 my $class = shift;
523 4         7 my( $oid ) = @_;
524              
525 4 50       10 croak( "Class not specifed for oid2name()" ) unless( defined $class );
526              
527 4 50 33     16 return $oid unless( defined $apiVersion && $apiVersion > 0 );
528              
529 4         10 return $class->_oid2name( @_ );
530             }
531              
532             # Should not be exported, as overloading may break ASN lookups
533              
534             sub _oid2name {
535 70     70   89 my $class = shift;
536 70         84 my( $oid ) = @_;
537              
538 70 100       110 return unless($oid);
539              
540 69 100       165 if( exists $oids{$oid} ) {
    100          
541 22         32 $oid = $oids{$oid};
542             }elsif( exists $oid2extkeyusage{$oid} ) {
543 1         4 $oid = $oid2extkeyusage{$oid};
544             }
545 69         115 return $oid;
546             }
547              
548             # registerOID( $oid ) => true if $oid is registered, false if not
549             # registerOID( $oid, $longname ) => Register an OID with its name
550             # registerOID( $oid, $longname, $shortname ) => Register an OID with an abbreviation for RDNs.
551             # registerOID( $oid, undef, $shortname ) => Register an abbreviation for RDNs for an existing OID
552              
553             sub registerOID {
554 11     11 1 5029 my( $class, $oid, $longname, $shortname ) = @_;
555              
556 11 50       29 croak( "Class not specifed for registerOID()" ) unless( defined $class );
557              
558 11 50       20 unless( defined $apiVersion ) {
559 0         0 carp( "${class}::setAPIversion MUST be called before registerOID(). Defaulting to legacy mode" );
560 0         0 $class->setAPIversion(0);
561             }
562              
563 11 100 100     51 return exists $oids{$oid} || exists $oid2extkeyusage{$oid} if( @_ == 2 && defined $oid );
      100        
564              
565 8 50 66     117 croak( "Not enough arguments" ) unless( @_ >= 3 && defined $oid && ( defined $longname || defined $shortname ) );
      66        
      66        
566 7 100 66     132 croak( "Invalid OID $oid" ) unless( defined $oid && $oid =~ /^\d+(?:\.\d+)*$/ );
567              
568 6 100       13 if( defined $longname ) {
569 3 100 66     95 croak( "$oid already registered" ) if( exists $oids{$oid} || exists $oid2extkeyusage{$oid} );
570 2 100       234 croak( "$longname already registered" ) if( grep /^$longname$/, values %oids );
571             } else {
572 3 50 66     90 croak( "$oid not registered" ) unless( exists $oids{$oid} || exists $oid2extkeyusage{$oid} );
573             }
574 3 100 100     233 croak( "$shortname already registered" ) if( defined $shortname && grep /^\U$shortname\E$/,
575             values %shortnames );
576              
577 2 100       6 if( defined $longname ) {
578 1         4 $oids{$oid} = $longname;
579 1         4 $name2oid{$longname} = $oid;
580             } else {
581 1         3 $longname = $class->_oid2name( $oid );
582             }
583 2 100       6 $shortnames{$longname} = uc $shortname if( defined $shortname );
584 2         9 return 1;
585             }
586              
587             sub new {
588 13     13 1 16580 my $class = shift;
589              
590 13         28 undef $error;
591              
592 13 50 33     77 $class = ref $class if( defined $class && ref $class && $class->isa( __PACKAGE__ ) );
      33        
593              
594 13 50       32 unless( defined $apiVersion ) {
595 0         0 carp( "${class}::setAPIversion MUST be called before new(). Defaulting to legacy mode" );
596 0         0 $class->setAPIversion(0);
597             }
598              
599 13         36 my( $void, $die ) = ( !defined wantarray, 0 );
600 13         19 my $self = eval {
601 13 50 33     52 die( "Insufficient arguments for new\n" ) unless( defined $class && @_ >= 1 );
602 13 100       31 die( "Value of Crypt::PKCS10->new ignored\n" ) if( $void );
603 12         46 return $class->_new( \$die, @_ );
604 13 100       41 }; if( $@ ) {
605 4         8 $error = $@;
606 4 100 100     23 if( !$apiVersion || $die || !defined wantarray ) {
      100        
607 3         13 1 while( chomp $@ );
608 3         493 croak( $@ );
609             }
610 1         7 return;
611             }
612              
613 9         165 return $self;
614             }
615              
616             sub error {
617 1     1 1 658 my $class = shift;
618              
619 1 50       4 croak( "Class not specifed for error()" ) unless( defined $class );
620              
621 1 50 33     6 if( ref $class && $class->isa( __PACKAGE__ ) ) {
622 0         0 return $class->{_error};
623             }
624 1         3 return $error;
625             }
626              
627             my $pemre = qr/(?ms:^\r?-----BEGIN\s(?:NEW\s)?CERTIFICATE\sREQUEST-----\s*\r?\n\s*(.*?)\s*^\r?-----END\s(?:NEW\s)?CERTIFICATE\sREQUEST-----\r?$)/;
628              
629             sub _new {
630 12     12   38 my( $class, $die, $der ) = splice( @_, 0, 3 );
631              
632 12         116 my %options = (
633             acceptPEM => 1,
634             PEMonly => 0,
635             escapeStrings => 1,
636             readFile => 0,
637             ignoreNonBase64 => 0,
638             verifySignature => ($apiVersion >= 1),
639             dieOnError => 0,
640             );
641              
642 12 100 66     64 %options = ( %options, %{ shift @_ } ) if( @_ >= 1 && ref( $_[0] ) eq 'HASH' );
  1         7  
643              
644 12 50       36 die( "Every option to new() must have a value\n" ) unless( @_ % 2 == 0 );
645              
646 12 50       76 %options = ( %options, @_ ) if( @_ );
647              
648 12         35 my $self = { _apiVersion => $apiVersion };
649              
650 12         28 my $keys = join( '|', qw/escapeStrings acceptPEM PEMonly binaryMode readFile verifySignature ignoreNonBase64 warnings dieOnError/ );
651              
652 12         39 $self->{"_$_"} = delete $options{$_} foreach (grep { /^(?:$keys)$/ } keys %options);
  85         544  
653              
654 12   66     58 $$die = $self->{_dieOnError} &&= $apiVersion >= 1;
655              
656 12 100       40 die( "\$csr argument to new() is not defined\n" ) unless( defined $der );
657              
658 11 50       27 if( keys %options ) {
659 0         0 die( "Invalid option(s) specified: " . join( ', ', sort keys %options ) . "\n" );
660             }
661              
662 11 100       37 $self->{_binaryMode} = !$self->{_acceptPEM} unless( exists $self->{_binaryMode} );
663              
664 11         18 my $parser;
665              
666             # malformed requests can produce various warnings; don't proceed in that case.
667              
668 11     1   88 local $SIG{__WARN__} = sub { my $msg = $_[0]; $msg =~ s/\A(.*?) at .*\Z/$1/s; 1 while( chomp $msg ); die "$msg\n" };
  1         2  
  1         4  
  1         4  
  1         11  
669              
670 11 100       36 if( $self->{_readFile} ) {
671 6 50       367 open( my $fh, '<', $der ) or die( "Failed to open $der: $!\n" );
672 6         27 $der = $fh;
673             }
674              
675 11 100       44 if( Scalar::Util::openhandle( $der ) ) {
676 8         37 local $/;
677              
678 8 100       30 binmode $der if( $self->{_binaryMode} );
679              
680 8         389 $der = <$der>; # Note: this closes files opened by readFile
681 8 50       156 die( "Failed to read request: $!\n" ) unless( defined $der );
682             }
683              
684 11         21 my $isPEM;
685              
686 11 100 66     477 if( $self->{_PEMonly} ) {
    100          
687 2 100       88 if( $der =~ $pemre ) {
688 1         6 $der = $1;
689 1         2 $isPEM = 1;
690             } else {
691 1         10 die( "No certificate request found\n" );
692             }
693             } elsif( $self->{_acceptPEM} && $der =~ $pemre ) {
694 6         25 $der = $1;
695 6         11 $isPEM = 1;
696             }
697 10 100       24 if( $isPEM ) {
698             # Some versions of MIME::Base64 check the input. Some don't. Those that do
699             # seem to obey -w, but not 'use warnings'. So we'll check here.
700              
701 7         100 $der =~ s/\s+//g; # Delete whitespace, which is legal but meaningless
702 7 100       23 $der =~ tr~A-Za-z0-9+=/~~cd if( $self->{_ignoreNonBase64} );
703              
704 7 100 66     59 unless( $der =~ m{\A[A-Za-z0-9+/]+={0,2}\z} && ( length( $der ) % 4 == 0 ) ) {
705 1         7 warn( "Invalid base64 encoding\n" ); # Invalid character or length
706             }
707 6         130 $der = decode_base64( $der );
708             }
709              
710             # some requests may contain information outside of the regular ASN.1 structure.
711             # This padding must be removed.
712              
713 9         20 $der = eval { # Catch out of range errors caused by bad DER & report as format errors.
714             # SEQUENCE -- CertificationRequest
715              
716 9         33 my( $tlen, undef, $tag ) = asn_decode_tag2( $der );
717 9 50 33     158 die( "SEQUENCE not present\n" ) unless( $tlen && $tag == ASN_SEQUENCE );
718              
719 9         36 my( $llen, $len ) = asn_decode_length( substr( $der, $tlen ) );
720 9 50 33     174 die( "Invalid SEQUENCE length\n" ) unless( $llen && $len );
721              
722 9         29 $len += $tlen + $llen;
723 9         14 $tlen = length $der;
724 9 50       20 die( "DER too short to contain request\n" ) if( $tlen < $len );
725              
726 9 50 66     27 if( $tlen != $len && $self->{_warnings} ) { # Debugging
727 0         0 local $SIG{__WARN__};
728 0         0 carp( sprintf( "DER length of %u contains %u bytes of padding",
729             $tlen, $tlen - $len ) );
730             }
731 9         26 return substr( $der, 0, $len );
732 9 50       21 }; if( $@ ) {
733 0         0 1 while( chomp $@ );
734 0         0 die( "Invalid format for request: $@\n" );
735             }
736              
737 9         22 $self->{_der} = $der;
738              
739 9         27 bless( $self, $class );
740              
741 9         40 $self->{_bmpenc} = Encode::find_encoding('UCS2-BE');
742              
743 9         6575 my $asn = Convert::ASN1->new;
744 9         293 $self->{_asn} = $asn;
745 9 50       31 $asn->prepare($schema) or die( "Internal error in " . __PACKAGE__ . ": " . $asn->error );
746              
747 9         395959 $asn->registertype( 'qualifier', '1.3.6.1.5.5.7.2.1', $self->_init('CPSuri') );
748 9         90 $asn->registertype( 'qualifier', '1.3.6.1.5.5.7.2.2', $self->_init('UserNotice') );
749              
750 9         60 $parser = $self->_init( 'CertificationRequest' );
751              
752 9 50       37 my $top =
753             $parser->decode( $der ) or
754             confess( "decode: " . $parser->error .
755             "Cannot handle input or missing ASN.1 definitions" );
756              
757             $self->{certificationRequestInfo}{subject_raw}
758 9         16032 = $top->{certificationRequestInfo}{subject};
759              
760             $self->{certificationRequestInfo}{subject}
761 9         39 = $self->_convert_rdn( $top->{certificationRequestInfo}{subject} );
762              
763             $self->{certificationRequestInfo}{version}
764 9         24 = $top->{certificationRequestInfo}{version};
765              
766             $self->{certificationRequestInfo}{attributes} = $self->_convert_attributes(
767 9         32 $top->{certificationRequestInfo}{attributes} );
768              
769             $self->{_pubkey} = "-----BEGIN PUBLIC KEY-----\n" .
770             _encode_PEM( $self->_init('SubjectPublicKeyInfo')->
771 9         23 encode( $top->{certificationRequestInfo}{subjectPKInfo} ) ) .
772             "-----END PUBLIC KEY-----\n";
773              
774             $self->{certificationRequestInfo}{subjectPKInfo} = $self->_convert_pkinfo(
775 9         53 $top->{certificationRequestInfo}{subjectPKInfo} );
776              
777 9         22 $self->{signature} = $top->{signature};
778              
779             $self->{signatureAlgorithm}
780 9         28 = $self->_convert_signatureAlgorithm( $top->{signatureAlgorithm} );
781              
782             # parse parameters for RSA PSS
783 9 100       42 if ($self->{signatureAlgorithm}{algorithm} eq 'rsassaPss') {
784             my $params = $self->_init('rsassaPssParam')->decode(
785 1         3 $self->{signatureAlgorithm}{parameters});
786             $self->{signatureAlgorithm}{parameters} = {
787             'saltLength' => ($params->{saltLength} || 32),
788 1   50     287 'digestAlgorithm' => $self->_oid2name($params->{digestAlgorithm}{algorithm}),
789             };
790             }
791              
792             # Extract bundle of bits that is signed
793             # The DER is SEQUENCE -- CertificationRequest
794             # SEQUENCE -- CertificationRequestInfo [SIGNED]
795              
796 9         16 my( $CRtaglen, $CRtag, $CRllen, $CRlen );
797 9         37 ($CRtaglen, undef, $CRtag) = asn_decode_tag2( $der );
798 9 50       108 die( "Invalid CSR format: missing SEQUENCE 1\n" ) unless( $CRtag == ASN_SEQUENCE );
799 9         27 ($CRllen, $CRlen) = asn_decode_length( substr( $der, $CRtaglen ) );
800              
801 9         181 my( $CItaglen, $CItag, $CIllen, $CIlen );
802 9         26 ($CItaglen, undef, $CItag) = asn_decode_tag2( substr( $der, $CRtaglen + $CRllen ) );
803 9 50       103 die( "Invalid CSR format: missing SEQUENCE 2\n" ) unless( $CItag == ASN_SEQUENCE );
804 9         30 ($CIllen, $CIlen) = asn_decode_length( substr( $der, $CRtaglen + $CRllen + $CItaglen ) );
805              
806 9         118 $self->{_signed} = substr( $der, $CRtaglen + $CRllen, $CItaglen + $CIllen + $CIlen );
807              
808 9 50 33     32 die( $error ) if( $self->{_verifySignature} && !$self->checkSignature );
809              
810 9         115 return $self;
811             }
812              
813             # Convert::ASN1 returns BMPStrings as 16-bit fixed-width characters, e.g. UCS2-BE
814              
815             sub _bmpstring {
816 9     9   12 my $self = shift;
817              
818 9         18 my $enc = $self->{_bmpenc};
819              
820 9         67 $_ = $enc->decode( $_ ) foreach (@_);
821              
822 9         15 return;
823             }
824              
825             # Find the obvious BMPStrings in a value and convert them
826             # This doesn't catch direct values, but does find them in hashes
827             # (generally as a result of a CHOICE)
828             #
829             # Convert iPAddresses as well
830              
831             sub _scanvalue {
832 213     213   217 my $self = shift;
833              
834 213         249 my( $value ) = @_;
835              
836 213 100       340 return unless( ref $value );
837 102 100       149 if( ref $value eq 'ARRAY' ) {
838 24         36 foreach (@$value) {
839 90         111 $self->_scanvalue( $_ );
840             }
841 24         33 return;
842             }
843 78 50       118 if( ref $value eq 'HASH' ) {
844 78         144 foreach my $k (keys %$value) {
845 105 100       159 if( $k eq 'bmpString' ) {
846 3         10 $self->_bmpstring( $value->{bmpString} );
847 3         5 next;
848             }
849 102 100       131 if( $k eq 'iPAddress' ) {
850 2     2   32 use bytes;
  2         5  
  2         18  
851 6         9 my $addr = $value->{iPAddress};
852 6 100       12 if( length $addr == 4 ) {
853 3         15 $value->{iPAddress} = sprintf( '%vd', $addr );
854             } else {
855 3         13 $addr = sprintf( '%*v02X', ':', $addr );
856 3         53 $addr =~ s/([[:xdigit:]]{2}):([[:xdigit:]]{2})/$1$2/g;
857 3         8 $value->{iPAddress} = $addr;
858             }
859 6         11 next;
860             }
861 96         145 $self->_scanvalue( $value->{$k} );
862             }
863 78         99 return;
864             }
865 0         0 return;
866             }
867              
868             sub _convert_signatureAlgorithm {
869 9     9   16 my $self = shift;
870              
871 9         15 my $signatureAlgorithm = shift;
872             $signatureAlgorithm->{algorithm}
873             = $oids{$signatureAlgorithm->{algorithm}}
874             if( defined $signatureAlgorithm->{algorithm}
875 9 50 33     50 && exists $oids{$signatureAlgorithm->{algorithm}} );
876              
877 9         30 return $signatureAlgorithm;
878             }
879              
880             sub _convert_pkinfo {
881 9     9   14 my $self = shift;
882              
883 9         14 my $pkinfo = shift;
884              
885             $pkinfo->{algorithm}{algorithm}
886             = $oids{$pkinfo->{algorithm}{algorithm}}
887             if( defined $pkinfo->{algorithm}{algorithm}
888 9 50 33     58 && exists $oids{$pkinfo->{algorithm}{algorithm}} );
889 9         20 return $pkinfo;
890             }
891              
892             # OIDs requiring some sort of special handling
893             #
894             # Called with decoded value, returns updated value.
895             # Key is ASN macro name
896              
897             my %special;
898             %special =
899             (
900             EnhancedKeyUsage => sub {
901             my $self = shift;
902             my( $value, $id ) = @_;
903              
904             foreach (@{$value}) {
905             $_ = $oid2extkeyusage{$_} if(defined $oid2extkeyusage{$_});
906             }
907             return $value;
908             },
909             KeyUsage => sub {
910             my $self = shift;
911             my( $value, $id ) = @_;
912              
913             my $bit = unpack('C*', @{$value}[0]); #get the decimal representation
914             my $length = int(log($bit) / log(2) + 1); #get its bit length
915             my @usages = reverse( $id eq 'KeyUsage'? # Following are in order from bit 0 upwards
916             qw(digitalSignature nonRepudiation keyEncipherment dataEncipherment
917             keyAgreement keyCertSign cRLSign encipherOnly decipherOnly) :
918             qw(client server email objsign reserved sslCA emailCA objCA) );
919             my $shift = ($#usages + 1) - $length; # computes the unused area in @usages
920              
921             @usages = @usages[ grep { $bit & (1 << $_ - $shift) } 0 .. $#usages ]; #transfer bitmap to barewords
922              
923             return [ @usages ] if( $self->{_apiVersion} >= 1 );
924              
925             return join( ', ', @usages );
926             },
927             netscapeCertType => sub {
928             goto &{$special{KeyUsage}};
929             },
930             SubjectKeyIdentifier => sub {
931             my $self = shift;
932             my( $value, $id ) = @_;
933              
934             return unpack( "H*", $value );
935             },
936             ApplicationCertPolicies => sub {
937             goto &{$special{certificatePolicies}} if( $_[0]->{_apiVersion} > 0 );
938              
939             my $self = shift;
940             my( $value, $id ) = @_;
941              
942             foreach my $entry (@{$value}) {
943             $entry->{policyIdentifier} = $self->_oid2name( $entry->{policyIdentifier} );
944             }
945              
946             return $value;
947             },
948             certificateTemplate => sub {
949             my $self = shift;
950             my( $value, $id ) = @_;
951              
952             $value->{templateID} = $self->_oid2name( $value->{templateID} ) if( $self->{_apiVersion} > 0 );
953             return $value;
954             },
955             EnrollmentCSP => sub {
956             my $self = shift;
957             my( $value, $id ) = @_;
958              
959             $self->_bmpstring( $value->{Name} );
960              
961             return $value;
962             },
963             ENROLLMENT_CSP_PROVIDER => sub {
964             my $self = shift;
965             my( $value, $id ) = @_;
966              
967             $self->_bmpstring( $value->{cspName} );
968              
969             return $value;
970             },
971             certificatePolicies => sub {
972             my $self = shift;
973             my( $value, $id ) = @_;
974              
975             foreach my $policy (@$value) {
976             $policy->{policyIdentifier} = $self->_oid2name( $policy->{policyIdentifier} );
977             if( exists $policy->{policyQualifier} ) {
978             foreach my $qualifier (@{$policy->{policyQualifier}}) {
979             $qualifier->{policyQualifierId} = $self->_oid2name( $qualifier->{policyQualifierId} );
980             my $qv = $qualifier->{qualifier};
981             if( ref $qv eq 'HASH' ) {
982             foreach my $qt (keys %$qv) {
983             if( $qt eq 'explicitText' ) {
984             $qv->{$qt} = (values %{$qv->{$qt}})[0];
985             } elsif( $qt eq 'noticeRef' ) {
986             my $userNotice = $qv->{$qt};
987             $userNotice->{organization} = (values %{$userNotice->{organization}})[0];
988             }
989             }
990             $qv->{userNotice} = delete $qv->{noticeRef}
991             if( exists $qv->{noticeRef} );
992             }
993             }
994             }
995             }
996             return $value;
997             },
998             CERT_EXTENSIONS => sub {
999             my $self = shift;
1000             my( $value, $id, $entry ) = @_;
1001              
1002             return $self->_convert_extensionRequest( [ $value ] ) if( $self->{_apiVersion} > 0 ); # Untested
1003             },
1004             BasicConstraints => sub {
1005             my $self = shift;
1006             my( $value, $id, $entry ) = @_;
1007              
1008             my $r = {
1009             CA => $value->{cA}? 'TRUE' : 'FALSE',
1010             };
1011             my $string = "CA:$r->{CA}";
1012              
1013             if( exists $value->{pathLenConstraint} ) {
1014             $r->{pathlen} = $value->{pathLenConstraint};
1015             $string .= sprintf( ',pathlen:%u', $value->{pathLenConstraint} );
1016             }
1017             $entry->{_FMT} = [ $r, $string ]; # [ Raw, formatted ]
1018             return $value;
1019             },
1020             unstructuredName => sub {
1021             my $self = shift;
1022             my( $value, $id ) = @_;
1023              
1024             return $self->_hash2string( $value );
1025             },
1026             challengePassword => sub {
1027             my $self = shift;
1028             my( $value, $id ) = @_;
1029              
1030             return $self->_hash2string( $value );
1031             },
1032             ); # %special
1033              
1034             sub _convert_attributes {
1035 9     9   33 my $self = shift;
1036 9         22 my( $typeandvalues ) = @_;
1037              
1038 9         17 foreach my $entry ( @{$typeandvalues} ) {
  9         19  
1039 24         44 my $oid = $entry->{type};
1040 24         44 my $name = $oids{$oid};
1041 24 50 33     113 $name = $variantNames{$name} if( defined $name && exists $variantNames{$name} );
1042              
1043 24 50       42 next unless( defined $name );
1044              
1045 24         31 $entry->{type} = $name;
1046              
1047 24 100       60 if ($name eq 'extensionRequest') {
    100          
1048 6         18 $entry->{values} = $self->_convert_extensionRequest($entry->{values}[0]);
1049              
1050             } elsif ($name eq 'ENROLLMENT_NAME_VALUE_PAIR') {
1051 3         10 my $parser = $self->_init( $name );
1052 3         5 my @values;
1053 3         4 foreach my $der (@{$entry->{values}}) {
  3         8  
1054 3 50       7 my $pair = $parser->decode( $der ) or
1055             confess( "Looks like damaged input parsing attribute $name" );
1056 3         386 $self->_bmpstring( $pair->{name}, $pair->{value} );
1057 3         6 push @values, $pair;
1058             };
1059 3         9 $entry->{values} = \@values;
1060              
1061             } else {
1062 15 50       34 my $parser = $self->_init( $name, 1 ) or next; # Skip unknown attributes
1063              
1064 15 50       29 if($entry->{values}[1]) {
1065 0         0 confess( "Incomplete parsing of attribute type: $name" );
1066             }
1067 15 50       32 my $value = $entry->{values} = $parser->decode( $entry->{values}[0] ) or
1068             confess( "Looks like damaged input parsing attribute $name" );
1069              
1070 15 100       2311 if( exists $special{$name} ) {
1071 9         17 my $action = $special{$name};
1072 9         25 $entry->{values} = $action->( $self, $value, $name, $entry );
1073             }
1074             }
1075             }
1076 9         29 return $typeandvalues;
1077             }
1078              
1079             sub _convert_extensionRequest {
1080 6     6   7 my $self = shift;
1081 6         13 my( $extensionRequest ) = @_;
1082              
1083 6         11 my $parser = $self->_init('extensionRequest');
1084 6 50       15 my $decoded = $parser->decode($extensionRequest) or return [];
1085              
1086 6         3488 foreach my $entry (@{$decoded}) {
  6         16  
1087 27         62 my $name = $oids{ $entry->{extnID} };
1088 27 100 66     107 $name = $variantNames{$name} if( defined $name && exists $variantNames{$name} );
1089 27 50       46 if (defined $name) {
1090 27         35 my $asnName = $name;
1091 27         48 $asnName =~ tr/ //d;
1092 27         46 my $parser = $self->_init($asnName, 1);
1093 27 50       46 if(!$parser) {
1094 0         0 $entry = undef;
1095 0         0 next;
1096             }
1097 27         40 $entry->{extnID} = $name;
1098 27 50       52 my $dec = $parser->decode($entry->{extnValue}) or
1099             confess( $parser->error . ".. looks like damaged input parsing extension $asnName" );
1100              
1101 27         7906 $self->_scanvalue( $dec );
1102              
1103 27 100       52 if( exists $special{$asnName} ) {
1104 21         30 my $action = $special{$asnName};
1105 21         49 $dec = $action->( $self, $dec, $asnName, $entry );
1106             }
1107 27         69 $entry->{extnValue} = $dec;
1108             }
1109             }
1110 6         11 @{$decoded} = grep { defined } @{$decoded};
  6         14  
  27         38  
  6         12  
1111 6         19 return $decoded;
1112             }
1113              
1114             sub _convert_rdn {
1115 9     9   24 my $self = shift;
1116 9         14 my $typeandvalue = shift;
1117 9         28 my %hash = ( _subject => [], );
1118 9         26 foreach my $entry ( @$typeandvalue ) {
1119 43         59 foreach my $item (@$entry) {
1120 44         59 my $oid = $item->{type};
1121 44 50       116 my $name = (exists $variantNames{$oid})? $variantNames{$oid}[1]: $oids{ $oid };
1122 44 50       77 if( defined $name ) {
1123 44         50 push @{$hash{$name}}, sort values %{$item->{value}};
  44         76  
  44         176  
1124 44         62 push @{$hash{_subject}}, $name, [ sort values %{$item->{value}} ];
  44         57  
  44         106  
1125 44 50       99 my @names = (exists $variantNames{$oid})? @{$variantNames{$oid}} : ( $name );
  0         0  
1126 44         59 foreach my $name ( @names ) {
1127 44 100       206 unless( $self->can( $name ) ) {
1128 2     2   3734 no strict 'refs'; ## no critic
  2         4  
  2         620  
1129             *$name = sub {
1130 2     2   4 my $self = shift;
1131 2 100       7 return @{ $self->{certificationRequestInfo}{subject}{$name} } if( wantarray );
  1         6  
1132 1   50     8 return $self->{certificationRequestInfo}{subject}{$name}[0] || '';
1133             }
1134 2         27 }
1135             }
1136             }
1137             }
1138             }
1139              
1140 9         90 return \%hash;
1141             }
1142              
1143             sub _init {
1144 92     92   112 my $self = shift;
1145 92         150 my( $node, $optional ) = @_;
1146              
1147 92         208 my $parsed = $self->{_asn}->find($node);
1148              
1149 92 50 33     999 unless( defined $parsed || $optional ) {
1150 0         0 croak( "Missing node $node in ASN.1" );
1151             }
1152 92         202 return $parsed;
1153             }
1154              
1155             ###########################################################################
1156             # interface methods
1157              
1158             sub csrRequest {
1159 6     6 1 1606 my $self = shift;
1160 6         10 my $format = shift;
1161              
1162             return( "-----BEGIN CERTIFICATE REQUEST-----\n" .
1163 6 100       22 _encode_PEM( $self->{_der} ) .
1164             "-----END CERTIFICATE REQUEST-----\n" ) if( $format );
1165              
1166 3         26 return $self->{_der};
1167             }
1168              
1169             # Common subject components documented to be always present:
1170              
1171             foreach my $component (qw/commonName organizationalUnitName organizationName
1172             emailAddress stateOrProvinceName countryName domainComponent/ ) {
1173 2     2   14 no strict 'refs'; ## no critic
  2         3  
  2         7921  
1174              
1175             unless( defined &$component ) {
1176             *$component = sub {
1177 9     9   1324 my $self = shift;
1178 9 50       21 return @{ $self->{certificationRequestInfo}{subject}{$component} || [] } if( wantarray );
  7 100       51  
1179 2   50     11 return $self->{certificationRequestInfo}{subject}{$component}[0] || '';
1180             }
1181             }
1182             }
1183              
1184             # Complete subject
1185              
1186             sub subject {
1187 5     5 1 580 my $self = shift;
1188 5         10 my $long = shift;
1189              
1190 5 100       18 return @{ $self->{certificationRequestInfo}{subject}{_subject} } if( wantarray );
  1         14  
1191              
1192 4         7 my @subject = @{ $self->{certificationRequestInfo}{subject}{_subject} };
  4         21  
1193              
1194 4         7 my $subj = '';
1195 4         11 while( @subject ) {
1196 19         29 my( $name, $value ) = splice( @subject, 0, 2 );
1197 19 100 66     70 $name = $shortnames{$name} if( !$long && exists $shortnames{$name} );
1198 19         58 $subj .= "/$name=" . join( ',', @$value );
1199             }
1200              
1201 4         17 return $subj;
1202             }
1203              
1204              
1205             sub subjectRaw {
1206              
1207 1     1 1 3 my $self = shift;
1208 1         2 my @subject;
1209 1         2 foreach my $rdn (@{$self->{certificationRequestInfo}{subject_raw}}) {
  1         4  
1210             my @sequence = map {
1211 6         6 $_->{format} = (keys %{$_->{value}})[0];
  6         14  
1212 6         8 $_->{value} = (values %{$_->{value}})[0];
  6         12  
1213 6         11 $_;
1214 5         8 } @{$rdn};
  5         7  
1215 5 100       11 if (scalar @sequence > 1) {
1216 1         2 push @subject, \@sequence;
1217             } else {
1218 4         7 push @subject, $sequence[0];
1219             }
1220             }
1221 1         13 return \@subject;
1222              
1223             }
1224              
1225              
1226             sub subjectAltName {
1227 4     4 1 1632 my $self = shift;
1228 4         7 my( $type ) = @_;
1229              
1230 4         10 my $san = $self->extensionValue( 'subjectAltName' );
1231 4 50       8 unless( defined $san ) {
1232 0 0       0 return () if( wantarray );
1233 0         0 return undef; ## no critic
1234             }
1235              
1236 4 100       8 if( !defined $type ) {
1237 2 100       5 if( wantarray ) {
1238 1         2 my %comps;
1239 1         2 $comps{$_} = 1 foreach (map { keys %$_ } @$san);
  9         16  
1240 1         10 return sort keys %comps; ## no critic
1241             }
1242 1         2 my @string;
1243 1         2 foreach my $comp (@$san) {
1244 9         16 push @string, join( '+', map { "$_:$comp->{$_}" } sort keys %$comp );
  9         25  
1245             }
1246 1         7 return join( ',', @string );
1247             }
1248              
1249 2         4 my $result = [ map { $_->{$type} } grep { exists $_->{$type} } @$san ];
  4         9  
  18         25  
1250              
1251 2 100       10 return @$result if( wantarray );
1252 1         5 return $result->[0];
1253             }
1254              
1255             sub version {
1256 2     2 1 965 my $self = shift;
1257 2         4 my $v = $self->{certificationRequestInfo}{version};
1258 2         15 return sprintf( "v%u", $v+1 );
1259             }
1260              
1261             sub pkAlgorithm {
1262 9     9 1 14 my $self = shift;
1263 9         39 return $self->{certificationRequestInfo}{subjectPKInfo}{algorithm}{algorithm};
1264             }
1265              
1266             sub subjectPublicKey {
1267 4     4 1 6 my $self = shift;
1268 4         6 my $format = shift;
1269              
1270 4 100       12 return $self->{_pubkey} if( $format );
1271 2         13 return unpack('H*', $self->{certificationRequestInfo}{subjectPKInfo}{subjectPublicKey}[0]);
1272             }
1273              
1274             sub subjectPublicKeyParams {
1275 4     4 1 567 my $self = shift;
1276 4         8 my $detail = shift;
1277              
1278 4 100       159 croak( "Requires API version 1" ) unless( $self->{_apiVersion} >= 1 );
1279              
1280 3         6 undef $error;
1281 3         6 delete $self->{_error};
1282              
1283 3         6 my $rv = {};
1284 3         7 my $at = $self->pkAlgorithm;
1285 3 50       8 $at = 'undef' unless( defined $at );
1286              
1287 3 100       13 if( $at eq 'rsaEncryption' ) {
    50          
    50          
1288 2         5 $rv->{keytype} = 'RSA';
1289 2         5 my $par = $self->_init( 'rsaKey' );
1290 2         8 my $rsa = $par->decode( $self->{certificationRequestInfo}{subjectPKInfo}{subjectPublicKey}[0] );
1291 2         135821 $rv->{keylen} = 4 * ( length( $rsa->{modulus}->as_hex ) -2 ); # 2 == length( '0x' )
1292 2         29009 $rv->{modulus} = substr( $rsa->{modulus}->as_hex, 2 );
1293             $rv->{publicExponent} = ( ref( $rsa->{publicExponent} )?
1294             $rsa->{publicExponent}->as_hex :
1295 2 50       28889 sprintf( '%x', $rsa->{publicExponent} ) );
1296             } elsif( $at eq 'ecPublicKey' ) {
1297 0         0 $rv->{keytype} = 'ECC';
1298              
1299 0         0 eval { require Crypt::PK::ECC; };
  0         0  
1300 0 0       0 if( $@ ) {
1301 0         0 $rv->{keytype} = undef;
1302             $self->{_error} =
1303 0         0 $error = "ECC public key requires Crypt::PK::ECC\n";
1304 0 0       0 croak( $error ) if( $self->{_dieOnError} );
1305 0         0 return $rv;
1306             }
1307 0         0 my $key = $self->subjectPublicKey(1);
1308 0         0 $key = Crypt::PK::ECC->new( \$key )->key2hash;
1309 0         0 $rv->{keylen} = $key->{curve_bits};
1310 0         0 $rv->{pub_x} = $key->{pub_x};
1311 0         0 $rv->{pub_y} = $key->{pub_y};
1312 0 0       0 $rv->{detail} = { %$key } if( $detail );
1313              
1314 0         0 my $par = $self->_init( 'eccName' );
1315 0         0 $rv->{curve} = $par->decode( $self->{certificationRequestInfo}{subjectPKInfo}{algorithm}{parameters} );
1316 0 0       0 $rv->{curve} = $self->_oid2name( $rv->{curve} ) if ($rv->{curve});
1317             } elsif( $at eq 'dsa' ) {
1318 1         3 $rv->{keytype} = 'DSA';
1319 1         3 my $par = $self->_init( 'dsaKey' );
1320 1         4 my $dsa = $par->decode( $self->{certificationRequestInfo}{subjectPKInfo}{subjectPublicKey}[0] );
1321 1         26738 $rv->{keylen} = 4 * ( length( $dsa->as_hex ) -2 );
1322 1 50       7002 if( exists $self->{certificationRequestInfo}{subjectPKInfo}{algorithm}{parameters} ) {
1323 1         5 $par = $self->_init('dsaPars');
1324 1         4 $dsa = $par->decode($self->{certificationRequestInfo}{subjectPKInfo}{algorithm}{parameters});
1325 1         57821 $rv->{G} = substr( $dsa->{G}->as_hex, 2 );
1326 1         6655 $rv->{P} = substr( $dsa->{P}->as_hex, 2 );
1327 1         6614 $rv->{Q} = substr( $dsa->{Q}->as_hex, 2 );
1328             }
1329             } else {
1330 0         0 $rv->{keytype} = undef;
1331             $self->{_error} =
1332 0         0 $error = "Unrecognized public key type $at\n";
1333 0 0       0 croak( $error ) if( $self->{_dieOnError} );
1334             }
1335 3         495 return $rv;
1336             }
1337              
1338             sub signatureAlgorithm {
1339 4     4 1 1048 my $self = shift;
1340 4         21 return $self->{signatureAlgorithm}{algorithm};
1341             }
1342              
1343             sub signatureParams {
1344 1     1 1 3 my $self = shift;
1345              
1346 1 50       5 return unless ( exists $self->{signatureAlgorithm}{parameters} );
1347              
1348             # For RSA PSS the parameters have been parsed to a hash already
1349 1 50       4 if (ref $self->{signatureAlgorithm}{parameters} eq 'HASH') {
1350 0         0 return $self->{signatureAlgorithm}{parameters};
1351             }
1352              
1353 1         6 my( $tlen, undef, $tag ) = asn_decode_tag2( $self->{signatureAlgorithm}{parameters} );
1354 1 50 33     17 if( $tlen != 0 && $tag != ASN_NULL ) {
1355             return $self->{signatureAlgorithm}{parameters}
1356 0         0 }
1357             # Known algorithm's parameters MAY return a hash of decoded fields.
1358             # For now, leaving that to the caller...
1359              
1360 1         4 return;
1361             }
1362              
1363             sub signature {
1364 6     6 1 16 my $self = shift;
1365 6         11 my $format = shift;
1366              
1367 6 100 66     29 if( defined $format && $format == 2 ) { # Per keytype decoding
1368 4 50       14 if( $self->pkAlgorithm eq 'ecPublicKey' ) { # ECDSA
1369 0         0 my $par = $self->_init( 'ecdsaSigValue' );
1370 0         0 return $par->decode( $self->{signature}[0] );
1371             }
1372 4         17 return; # Unknown
1373             }
1374 2 50       6 return $self->{signature}[0] if( $format );
1375              
1376 2         13 return unpack('H*', $self->{signature}[0]);
1377             }
1378              
1379             sub certificationRequest {
1380 1     1 1 3 my $self = shift;
1381              
1382 1         8 return $self->{_signed};
1383             }
1384              
1385             sub _attributes {
1386 55     55   67 my $self = shift;
1387              
1388 55         83 my $attributes = $self->{certificationRequestInfo}{attributes};
1389 55 50       102 return unless( defined $attributes );
1390              
1391 55         131 return { map { $_->{type} => $_->{values} } @$attributes };
  213         455  
1392             }
1393              
1394             sub attributes {
1395 22     22 1 2863 my $self = shift;
1396 22         34 my( $name ) = @_;
1397              
1398 22 50       51 if( $self->{_apiVersion} < 1 ) {
1399 0         0 my $attributes = $self->{certificationRequestInfo}{attributes};
1400 0 0       0 return () unless( defined $attributes );
1401              
1402 0         0 my %hash = map { $_->{type} => $_->{values} }
1403 0         0 @{$attributes};
  0         0  
1404 0         0 return %hash;
1405             }
1406              
1407 22         33 my $attributes = $self->_attributes;
1408 22 50       38 unless( defined $attributes ) {
1409 0 0       0 return () if( wantarray );
1410 0         0 return undef; ## no critic
1411             }
1412              
1413 22 100       38 unless( defined $name ) {
1414 5         41 return grep { $_ ne 'extensionRequest' } sort keys %$attributes;
  23         61  
1415             }
1416              
1417 17         38 $name = $self->_oid2name( $name );
1418              
1419 17 50       36 if( $name eq 'extensionRequest' ) { # Meaningless, and extensions/extensionValue handle
1420 0 0       0 return () if( wantarray );
1421 0         0 return undef; ## no critic
1422             }
1423              
1424             # There can only be one matching the name.
1425             # If the match becomes wider, sort the keys.
1426              
1427              
1428 17         48 my @attrs = grep { $_ eq $name } keys %$attributes;
  73         110  
1429 17 100       36 unless( @attrs ) {
1430 1 50       3 return () if( wantarray );
1431 1         7 return undef; ## no critic
1432             }
1433              
1434 16         17 my @values;
1435 16         29 foreach my $attr (@attrs) {
1436 16         22 my $values = $attributes->{$attr};
1437 16 100       38 $values = [ $values ] unless( ref $values eq 'ARRAY' );
1438 16         25 foreach my $value (@$values) {
1439 16         31 my $value = $self->_hash2string( $value );
1440 16 100       43 push @values, (wantarray? $value : $self->_value2strings( $value ));
1441             }
1442             }
1443 16 100       64 return @values if( wantarray );
1444              
1445 11 50       18 if( @values == 1 ) {
1446 11         44 $values[0] =~ s/^\((.*)\)$/$1/;
1447 11         54 return $values[0];
1448             }
1449 0         0 return join( ',', @values );
1450             }
1451              
1452             sub certificateTemplate {
1453 1     1 1 2 my $self = shift;
1454              
1455 1         4 return $self->extensionValue( 'certificateTemplate', @_ );
1456             }
1457              
1458             # If a hash contains one string (e.g. a CHOICE containing type=>value), return the string.
1459             # If the hash is nested, try recursing.
1460             # If the string can't be identified (clutter in the hash), return the hash
1461             # Some clutter can be filtered by specifying $exclude (a regexp)
1462              
1463             sub _hash2string {
1464 43     43   58 my $self = shift;
1465 43         66 my( $hash, $exclude ) = @_;
1466              
1467 43 100       89 return $hash unless( ref $hash eq 'HASH' );
1468              
1469 20         54 my @keys = keys %$hash;
1470              
1471 20 100       36 @keys = grep { $_ !~ /$exclude/ } @keys if( defined $exclude );
  1         41  
1472              
1473 20 100       47 return $hash if( @keys != 1 );
1474              
1475 11 100       30 return $self->_hash2string( $hash->{$keys[0]} ) if( ref $hash->{$keys[0]} eq 'HASH' );
1476              
1477 7         42 return $hash->{$keys[0]};
1478             }
1479              
1480             # Convert a value to a printable string
1481              
1482             sub _value2strings {
1483 99     99   103 my $self = shift;
1484 99         125 my( $value ) = @_;
1485              
1486 99         96 my @strings;
1487 99 100       143 if( ref $value eq 'ARRAY' ) {
1488 10         14 foreach my $value (@$value) {
1489 34         58 push @strings, $self->_value2strings( $value );
1490             }
1491 10 100       46 return '(' . join( ',', @strings ) . ')' if( @strings > 1 );
1492 1         3 return join( ',', @strings );
1493             }
1494 89 100       128 if( ref $value eq 'HASH' ) {
1495 25         55 foreach my $k (sort keys %$value) {
1496 46         93 push @strings, "$k=" . $self->_value2strings( $value->{$k} );
1497             }
1498 25 100       87 return '(' . join( ',', @strings ) . ')' if( @strings > 1 );
1499 10         24 return join( ',', @strings );
1500             }
1501              
1502 64 100       160 return $value if( $value =~ /^\d+$/ );
1503              
1504             # OpenSSL and Perl-compatible string syntax
1505              
1506 54 100       103 $value =~ s/(["\\\$])/\\$1/g if( $self->{_escapeStrings} );
1507              
1508 54 100       400 return $value if( $value =~ m{\A[\w!\@$%^&*_=+\[\]\{\}:;|<>./?"'-]+\z} ); # Barewords
1509              
1510 10         37 return '"' . $value . '"'; # Must quote: whitespace, non-printable, comma, (), \, null string
1511             }
1512              
1513             sub extensions {
1514 4     4 1 2272 my $self = shift;
1515              
1516 4         8 my $attributes = $self->_attributes;
1517 4 50 33     17 return () unless( defined $attributes && exists $attributes->{extensionRequest} );
1518              
1519 4         7 my @present = map { $_->{extnID} } @{$attributes->{extensionRequest}};
  15         25  
  4         9  
1520 4 50       12 if( $self->{_apiVersion} >= 1 ) {
1521 4         6 foreach my $ext (@present) {
1522 15 100       35 $ext = $variantNames{'$' . $ext} if( exists $variantNames{'$' . $ext} );
1523             }
1524             }
1525 4         20 return @present;
1526             }
1527              
1528             sub extensionValue {
1529 23     23 1 2800 my $self = shift;
1530 23         43 my( $extensionName, $format ) = @_;
1531              
1532 23         42 my $attributes = $self->_attributes;
1533 23         35 my $value;
1534 23 50 33     82 return unless( defined $attributes && exists $attributes->{extensionRequest} );
1535              
1536 23         49 $extensionName = $self->_oid2name( $extensionName );
1537              
1538 23 100       78 $extensionName = $variantNames{$extensionName} if( exists $variantNames{$extensionName} );
1539              
1540 23         31 foreach my $entry (@{$attributes->{extensionRequest}}) {
  23         46  
1541 76 100       127 if ($entry->{extnID} eq $extensionName) {
1542 21         26 $value = $entry->{extnValue};
1543 21 100       44 if( $self->{_apiVersion} == 0 ) {
1544 1         5 while (ref $value eq 'HASH') {
1545 0         0 my @keys = sort keys %{$value};
  0         0  
1546 0         0 $value = $value->{ shift @keys } ;
1547             }
1548             } else {
1549 20 100       29 if( $entry->{_FMT} ) { # Special formatting
1550 3 100       7 $value = $entry->{_FMT}[$format? 1:0];
1551             } else {
1552 17         33 $value = $self->_hash2string( $value, '(?i:^(?:critical|.*id)$)' );
1553 17 100       37 $value = $self->_value2strings( $value ) if( $format );
1554             }
1555             }
1556 21         31 last;
1557             }
1558             }
1559 23 100       59 $value =~ s/^\((.*)\)$/$1/ if( $format );
1560              
1561 23         120 return $value;
1562             }
1563              
1564             sub extensionPresent {
1565 6     6 1 12 my $self = shift;
1566 6         12 my( $extensionName ) = @_;
1567              
1568 6         10 my $attributes = $self->_attributes;
1569 6 50 33     24 return unless( defined $attributes && exists $attributes->{extensionRequest} );
1570              
1571 6         14 $extensionName = $self->_oid2name( $extensionName );
1572              
1573 6 100       16 $extensionName = $variantNames{$extensionName} if( exists $variantNames{$extensionName} );
1574              
1575 6         8 foreach my $entry (@{$attributes->{extensionRequest}}) {
  6         11  
1576 18 100       31 if ($entry->{extnID} eq $extensionName) {
1577 5 100       16 return 2 if ($entry->{critical});
1578 3         13 return 1;
1579             }
1580             }
1581 1         6 return;
1582             }
1583              
1584             sub checkSignature {
1585 0     0 1 0 my $self = shift;
1586              
1587 0         0 undef $error;
1588 0         0 delete $self->{_error};
1589              
1590 0         0 my $ok = eval {
1591 0 0       0 die( "checkSignature requires API version 1\n" ) unless( $self->{_apiVersion} >= 1 );
1592              
1593 0         0 my $key = $self->subjectPublicKey(1); # Key as PEM
1594 0         0 my $sig = $self->signature(1); # Signature as DER
1595 0         0 my $alg = $self->signatureAlgorithm; # Algorithm name
1596              
1597             # Determine the signature hash type from the algorithm name
1598              
1599 0         0 my @params = ( $sig, $self->certificationRequest );
1600 0 0       0 if( $alg =~ /sha-?(\d+)/i ) {
    0          
    0          
1601 0         0 push @params, "SHA$1";
1602              
1603             } elsif( $alg =~ /md-?(\d)/i ) {
1604 0         0 push @params, "MD$1";
1605              
1606             } elsif ( $alg eq 'rsassaPss' ) {
1607              
1608 0         0 my $sigParam = $self->signatureParams;
1609 0         0 push @params, uc($sigParam->{digestAlgorithm});
1610 0         0 push @params, 'pss';
1611 0         0 push @params, $sigParam->{saltLength};
1612              
1613             } else {
1614              
1615 0         0 die( "Unknown hash in signature algorithm $alg\n" );
1616             }
1617              
1618 0         0 my $keyp = $self->subjectPublicKeyParams;
1619              
1620 0 0       0 die( "Unknown public key type\n" ) unless( defined $keyp->{keytype} );
1621              
1622             # Verify signature using the correct module and hash type.
1623              
1624 0 0       0 if( $keyp->{keytype} eq 'RSA' ) {
1625              
1626 0         0 eval { require Crypt::PK::RSA; };
  0         0  
1627 0 0       0 die( "Unable to load Crypt::PK::RSA\n" ) if( $@ );
1628              
1629 0         0 $key = Crypt::PK::RSA->new( \$key );
1630              
1631             # if we have NOT pss padding we need to add v1.5 padding
1632 0 0       0 push @params, 'v1.5' if (@params == 3);
1633 0         0 return $key->verify_message( @params );
1634              
1635             }
1636              
1637 0 0       0 if( $keyp->{keytype} eq 'DSA' ) {
1638              
1639 0         0 eval { require Crypt::PK::DSA; };
  0         0  
1640 0 0       0 die( "Unable to load Crypt::PK::DSA\n" ) if( $@ );
1641              
1642 0         0 $key = Crypt::PK::DSA->new( \$key );
1643 0         0 return $key->verify_message( @params );
1644             }
1645              
1646 0 0       0 if( $keyp->{keytype} eq 'ECC' ) {
1647 0         0 eval { require Crypt::PK::ECC; };
  0         0  
1648 0 0       0 die( "Unable to load Crypt::PK::ECC\n" ) if( $@ );
1649              
1650 0         0 $key = Crypt::PK::ECC->new( \$key );
1651 0         0 return $key->verify_message( @params );
1652             }
1653              
1654 0         0 die( "Unknown key type $keyp->{keytype}\n" );
1655             };
1656 0 0       0 if( $@ ) {
1657             $self->{_error} =
1658 0         0 $error = $@;
1659 0 0       0 croak( $error ) if( $self->{_dieOnError} );
1660 0         0 return;
1661             }
1662 0 0       0 return 1 if( $ok );
1663              
1664             $self->{_error} =
1665 0         0 $error = "Incorrect signature\n";
1666 0 0       0 croak( $error ) if( $self->{_dieOnError} );
1667              
1668 0         0 return 0;
1669             }
1670              
1671             sub _wrap {
1672 14     14   24 my( $to, $text ) = @_;
1673              
1674 14         19 my $wid = 76 - $to;
1675              
1676 14         32 my $out = substr( $text, 0, $wid, '' );
1677              
1678 14         26 while( length $text ) {
1679 17         44 $out .= "\n" . (' ' x $to) . substr( $text, 0, $wid, '' );
1680             }
1681 14         75 return $out;
1682             }
1683              
1684             sub _encode_PEM {
1685 12     12   1934 my $text = encode_base64( $_[0] );
1686 12 50       59 return $text if( length $text <= 65 );
1687 12         43 $text =~ tr/\n//d;
1688 12         21 my $out = '';
1689 12         115 $out .= substr( $text, 0, 64, '' ) . "\n" while( length $text );
1690 12         83 return $out;
1691             }
1692              
1693             sub as_string {
1694 1     1 0 2606 my $self = shift;
1695              
1696 1         4 local $self->{_escapeStrings} = 0;
1697 1         5 local( $@, $_, $! );
1698              
1699 1         1 my $v = $apiVersion;
1700 1 50 33     7 ref( $self )->setAPIversion( 1 ) unless( defined $v && $v == 1 );
1701              
1702 1         2 my $string = eval {
1703 1         6 $self = ref( $self )->new( $self->{_der}, acceptPEM => 0, verifySignature => 0, escapeStrings => 0 );
1704 1 50       4 return $error if( !defined $self );
1705              
1706 1         5 $self->__stringify;
1707             };
1708 1         3 my $at = $@;
1709 1 50 33     6 ref( $self )->setAPIversion( $v ) unless( defined $v && $v == 1 );
1710              
1711 1 50       3 $string = '' unless( defined $string );
1712 1 50       3 $string .= $at if( $at );
1713              
1714 1         42 return $string;
1715             }
1716              
1717             sub __stringify {
1718 1     1   2 my $self = shift;
1719              
1720 1         2 my $max = 0;
1721 1         3 foreach ($self->attributes, $self->extensions,
1722             qw/Version Subject Key_algorithm Public_key Signature_algorithm Signature/) {
1723 13 100       20 $max = length if( length > $max );
1724             }
1725              
1726 1         4 my $string = sprintf( "%-*s: %s\n", $max, 'Version', $self->version ) ;
1727              
1728 1         4 $string .= sprintf( "%-*s: %s\n", $max, 'Subject', _wrap( $max+2, scalar $self->subject ) );
1729              
1730 1         2 $string .= "\n --Attributes--\n";
1731              
1732 1 50       3 $string .= " --None--" unless( $self->attributes );
1733              
1734 1         3 foreach ($self->attributes) {
1735 4         9 $string .= sprintf( "%-*s: %s\n", $max, $_, _wrap( $max+2, scalar $self->attributes($_) ) );
1736             }
1737              
1738 1         3 $string .= "\n --Extensions--\n";
1739              
1740 1 50       2 $string .= " --None--" unless( $self->extensions );
1741              
1742 1         3 foreach ($self->extensions) {
1743 3 100       7 my $critical = $self->extensionPresent($_) == 2? 'critical,': '';
1744              
1745 3 50       10 $string .= sprintf( "%-*s: %s\n", $max, $_,
1746             _wrap( $max+2, $critical . ($_ eq 'subjectAltName'?
1747             scalar $self->subjectAltName:
1748             $self->extensionValue($_, 1) ) ) );
1749             }
1750              
1751 1         4 $string .= "\n --Key and signature--\n";
1752 1         4 $string .= sprintf( "%-*s: %s\n", $max, 'Key algorithm', $self->pkAlgorithm );
1753 1         5 $string .= sprintf( "%-*s: %s\n", $max, 'Public key', _wrap( $max+2, $self->subjectPublicKey ) );
1754 1         3 $string .= $self->subjectPublicKey(1);
1755 1         15 my $kp = $self->subjectPublicKeyParams(1);
1756 1         8 foreach (sort keys %$kp) {
1757 4         7 my $v = $kp->{$_};
1758              
1759 4 50 33     13 if( !defined $v && !defined( $v = $self->error ) ) {
    50          
1760 0         0 $v = 'undef';
1761             } elsif( ref $v ) {
1762 0         0 next;
1763             }
1764 4         8 $string .= sprintf( "%-*s: %s\n", $max, $_, _wrap( $max+2, $v ) );
1765             }
1766 1 50       4 if( exists $kp->{detail} ) {
1767 0         0 $kp = $kp->{detail};
1768 0         0 $string .= "Key details\n-----------\n";
1769 0         0 foreach (sort keys %$kp) {
1770 0 0       0 next if( ref $kp->{$_} );
1771 0         0 $string .= sprintf( "%-*s: %s\n", $max, $_, _wrap( $max+2, $kp->{$_} ) );
1772             }
1773             }
1774              
1775 1         4 $string .= sprintf( "\n%-*s: %s\n", $max, 'Signature algorithm', $self->signatureAlgorithm );
1776 1         4 $string .= sprintf( "%-*s: %s\n", $max, 'Signature', _wrap( $max+2, $self->signature ) );
1777 1         7 my $sp = $self->signature(2);
1778 1 50       3 if( $sp ) {
1779 0         0 foreach (sort keys %$sp) {
1780 0         0 my $v = $sp->{$_};
1781              
1782 0 0       0 if( ref $v ) {
1783 0 0       0 if( $v->can('as_hex') ) {
1784 0         0 $v = substr( $v->as_hex, 2 );
1785             } else {
1786 0         0 next;
1787             }
1788             }
1789 0         0 $string .= sprintf( "%-*s: %s\n", $max, $_, _wrap( $max+2, $v ) );
1790             }
1791             }
1792              
1793 1         5 $string .= "\n --Request--\n" . $self->csrRequest(1);
1794              
1795 1         8 return $string;
1796             }
1797              
1798             1;
1799              
1800             __END__