File Coverage

blib/lib/Crypt/PKCS10.pm
Criterion Covered Total %
statement 568 717 79.2
branch 252 410 61.4
condition 74 143 51.7
subroutine 55 59 93.2
pod 24 26 92.3
total 973 1355 71.8


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   117501 use strict;
  2         14  
  2         72  
15 2     2   10 use warnings;
  2         6  
  2         60  
16 2     2   10 use Carp;
  2         3  
  2         180  
17              
18 2     2   1024 use overload( q("") => 'as_string' );
  2         807  
  2         25  
19              
20 2     2   1017 use Convert::ASN1( qw/:tag :const/ );
  2         64686  
  2         406  
21 2     2   15 use Encode ();
  2         4  
  2         38  
22 2     2   880 use MIME::Base64;
  2         1084  
  2         95  
23 2     2   12 use Scalar::Util ();
  2         3  
  2         6242  
24              
25             our $VERSION = '2.004';
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.26' => 'sha1',
126             '1.3.14.3.2.29' => [ 'sha1WithRSAEncryption', 'SHA1 with RSA signature' ],
127             '1.3.6.1.4.1.311.13.1' => 'RENEWAL_CERTIFICATE', # Microsoft
128             '1.3.6.1.4.1.311.13.2.1' => 'ENROLLMENT_NAME_VALUE_PAIR', # Microsoft
129             '1.3.6.1.4.1.311.13.2.2' => 'ENROLLMENT_CSP_PROVIDER', # Microsoft
130             '1.3.6.1.4.1.311.2.1.14' => 'CERT_EXTENSIONS', # Microsoft
131             '1.3.6.1.5.2.3.5' => [ 'keyPurposeKdc', 'KDC Authentication' ],
132             '1.3.6.1.5.5.7.9.5' => 'countryOfResidence',
133             '2.16.840.1.101.3.4.2.1' => [ 'sha256', 'SHA-256' ],
134             '2.16.840.1.101.3.4.2.2' => [ 'sha384', 'SHA-384' ],
135             '2.16.840.1.101.3.4.2.3' => [ 'sha512', 'SHA-512' ],
136             '2.16.840.1.101.3.4.2.4' => [ 'sha224', 'SHA-224' ],
137             '2.16.840.1.101.3.4.3.1' => 'dsaWithSha224',
138             '2.16.840.1.101.3.4.3.2' => 'dsaWithSha256',
139             '2.16.840.1.101.3.4.3.3' => 'dsaWithSha384',
140             '2.16.840.1.101.3.4.3.4' => 'dsaWithSha512',
141             '2.5.4.12' => [ 'title', 'Title' ],
142             '2.5.4.13' => [ 'description', 'Description' ],
143             '2.5.4.14' => 'searchGuide',
144             '2.5.4.15' => 'businessCategory',
145             '2.5.4.16' => 'postalAddress',
146             '2.5.4.17' => 'postalCode',
147             '2.5.4.18' => 'postOfficeBox',
148             '2.5.4.19', => 'physicalDeliveryOfficeName',
149             '2.5.4.20', => 'telephoneNumber',
150             '2.5.4.23', => 'facsimileTelephoneNumber',
151             '2.5.4.4' => [ 'surname', 'Surname' ],
152             '2.5.4.41' => [ 'name', 'Name' ],
153             '2.5.4.42' => 'givenName',
154             '2.5.4.43' => 'initials',
155             '2.5.4.44' => 'generationQualifier',
156             '2.5.4.45' => 'uniqueIdentifier',
157             '2.5.4.46' => 'dnQualifier',
158             '2.5.4.51' => 'houseIdentifier',
159             '2.5.4.65' => 'pseudonym',
160             '2.5.4.5' => 'serialNumber',
161             '2.5.4.9' => 'streetAddress',
162             '2.5.29.32' => 'certificatePolicies',
163             '2.5.29.32.0' => 'anyPolicy',
164             '1.3.6.1.5.5.7.2.1' => 'CPS',
165             '1.3.6.1.5.5.7.2.2' => 'userNotice',
166             );
167              
168             my %variantNames;
169              
170             foreach (keys %oids) {
171             my $val = $oids{$_};
172             if( ref $val ) {
173             $variantNames{$_} = $val; # OID to [ new, trad ]
174             $variantNames{$val->[0]} = $val->[1]; # New name to traditional for lookups
175             $variantNames{'$' . $val->[1]} = $val->[0]; # \$Traditional to new
176             $oids{$_} = $val->[!$apiVersion || 0];
177             }
178             }
179              
180             my %oid2extkeyusage = (
181             '1.3.6.1.5.5.7.3.1' => 'serverAuth',
182             '1.3.6.1.5.5.7.3.2' => 'clientAuth',
183             '1.3.6.1.5.5.7.3.3' => 'codeSigning',
184             '1.3.6.1.5.5.7.3.4' => 'emailProtection',
185             '1.3.6.1.5.5.7.3.8' => 'timeStamping',
186             '1.3.6.1.5.5.7.3.9' => 'OCSPSigning',
187              
188             '1.3.6.1.5.5.7.3.21' => 'sshClient',
189             '1.3.6.1.5.5.7.3.22' => 'sshServer',
190              
191             # Microsoft usages
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 OPTIONAL,
418             maskGenAlgorithm [1] EXPLICIT AlgorithmIdentifier OPTIONAL,
419             saltLength [2] EXPLICIT INTEGER OPTIONAL,
420             trailerField [3] EXPLICIT INTEGER 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 2681 my( $class, $version ) = @_;
484              
485 2 50 33     28 croak( substr(($error = "Wrong number of arguments\n"), 0, -1) ) unless( @_ == 2 && defined $class && !ref $class );
      33        
486 2 50       8 $version = 0 unless( defined $version );
487 2 50 33     10 croak( substr(($error = "Unsupported API version $version\n"), 0, -1) ) unless( $version >= 0 && $version <= 1 );
488 2         4 $apiVersion = $version;
489              
490 2   100     7 $version = !$version || 0;
491              
492 2         26 foreach (keys %variantNames) {
493 136 100       257 $oids{$_} = $variantNames{$_}[$version] if( /^\d/ ); # Map OID to selected name
494             }
495 2         214 %name2oid = reverse (%oids, %oid2extkeyusage);
496              
497 2         17 return 1;
498             }
499              
500             sub getAPIversion {
501 2     2 1 1805 my( $class ) = @_;
502              
503 2 50       6 croak( "Class not specified for getAPIversion()" ) unless( defined $class );
504              
505 2 50 33     7 return $class->{_apiVersion} if( ref $class && $class->isa( __PACKAGE__ ) );
506              
507 2         9 return $apiVersion;
508             }
509              
510             sub name2oid {
511 4     4 1 2653 my $class = shift;
512 4         8 my( $oid ) = @_;
513              
514 4 50       12 croak( "Class not specifed for name2oid()" ) unless( defined $class );
515              
516 4 50 33     27 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 8 my $class = shift;
523 4         17 my( $oid ) = @_;
524              
525 4 50       13 croak( "Class not specifed for oid2name()" ) unless( defined $class );
526              
527 4 50 33     19 return $oid unless( defined $apiVersion && $apiVersion > 0 );
528              
529 4         12 return $class->_oid2name( @_ );
530             }
531              
532             # Should not be exported, as overloading may break ASN lookups
533              
534             sub _oid2name {
535 75     75   94 my $class = shift;
536 75         115 my( $oid ) = @_;
537              
538 75 100       129 return unless($oid);
539              
540 74 100       219 if( exists $oids{$oid} ) {
    100          
541 24         43 $oid = $oids{$oid};
542             }elsif( exists $oid2extkeyusage{$oid} ) {
543 1         3 $oid = $oid2extkeyusage{$oid};
544             }
545 74         131 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 5516 my( $class, $oid, $longname, $shortname ) = @_;
555              
556 11 50       27 croak( "Class not specifed for registerOID()" ) unless( defined $class );
557              
558 11 50       30 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     143 croak( "Not enough arguments" ) unless( @_ >= 3 && defined $oid && ( defined $longname || defined $shortname ) );
      66        
      66        
566 7 100 66     133 croak( "Invalid OID $oid" ) unless( defined $oid && $oid =~ /^\d+(?:\.\d+)*$/ );
567              
568 6 100       16 if( defined $longname ) {
569 3 100 66     104 croak( "$oid already registered" ) if( exists $oids{$oid} || exists $oid2extkeyusage{$oid} );
570 2 100       260 croak( "$longname already registered" ) if( grep /^$longname$/, values %oids );
571             } else {
572 3 50 66     111 croak( "$oid not registered" ) unless( exists $oids{$oid} || exists $oid2extkeyusage{$oid} );
573             }
574 3 100 100     234 croak( "$shortname already registered" ) if( defined $shortname && grep /^\U$shortname\E$/,
575             values %shortnames );
576              
577 2 100       6 if( defined $longname ) {
578 1         3 $oids{$oid} = $longname;
579 1         3 $name2oid{$longname} = $oid;
580             } else {
581 1         3 $longname = $class->_oid2name( $oid );
582             }
583 2 100       8 $shortnames{$longname} = uc $shortname if( defined $shortname );
584 2         8 return 1;
585             }
586              
587             sub new {
588 16     16 1 18996 my $class = shift;
589              
590 16         36 undef $error;
591              
592 16 50 33     96 $class = ref $class if( defined $class && ref $class && $class->isa( __PACKAGE__ ) );
      33        
593              
594 16 50       42 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 16         43 my( $void, $die ) = ( !defined wantarray, 0 );
600 16         24 my $self = eval {
601 16 50 33     56 die( "Insufficient arguments for new\n" ) unless( defined $class && @_ >= 1 );
602 16 100       48 die( "Value of Crypt::PKCS10->new ignored\n" ) if( $void );
603 15         59 return $class->_new( \$die, @_ );
604 16 100       61 }; if( $@ ) {
605 4         7 $error = $@;
606 4 100 100     60 if( !$apiVersion || $die || !defined wantarray ) {
      100        
607 3         11 1 while( chomp $@ );
608 3         475 croak( $@ );
609             }
610 1         10 return;
611             }
612              
613 12         276 return $self;
614             }
615              
616             sub error {
617 1     1 1 690 my $class = shift;
618              
619 1 50       5 croak( "Class not specifed for error()" ) unless( defined $class );
620              
621 1 50 33     5 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 15     15   46 my( $class, $die, $der ) = splice( @_, 0, 3 );
631              
632 15         128 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 15 100 66     77 %options = ( %options, %{ shift @_ } ) if( @_ >= 1 && ref( $_[0] ) eq 'HASH' );
  1         16  
643              
644 15 50       54 die( "Every option to new() must have a value\n" ) unless( @_ % 2 == 0 );
645              
646 15 50       89 %options = ( %options, @_ ) if( @_ );
647              
648 15         52 my $self = { _apiVersion => $apiVersion };
649              
650 15         25 my $keys = join( '|', qw/escapeStrings acceptPEM PEMonly binaryMode readFile verifySignature ignoreNonBase64 warnings dieOnError/ );
651              
652 15         52 $self->{"_$_"} = delete $options{$_} foreach (grep { /^(?:$keys)$/ } keys %options);
  106         617  
653              
654 15   66     66 $$die = $self->{_dieOnError} &&= $apiVersion >= 1;
655              
656 15 100       50 die( "\$csr argument to new() is not defined\n" ) unless( defined $der );
657              
658 14 50       42 if( keys %options ) {
659 0         0 die( "Invalid option(s) specified: " . join( ', ', sort keys %options ) . "\n" );
660             }
661              
662 14 100       60 $self->{_binaryMode} = !$self->{_acceptPEM} unless( exists $self->{_binaryMode} );
663              
664 14         24 my $parser;
665              
666             # malformed requests can produce various warnings; don't proceed in that case.
667              
668 14     1   115 local $SIG{__WARN__} = sub { my $msg = $_[0]; $msg =~ s/\A(.*?) at .*\Z/$1/s; 1 while( chomp $msg ); die "$msg\n" };
  1         4  
  1         2  
  1         7  
  1         11  
669              
670 14 100       47 if( $self->{_readFile} ) {
671 9 50       595 open( my $fh, '<', $der ) or die( "Failed to open $der: $!\n" );
672 9         42 $der = $fh;
673             }
674              
675 14 100       59 if( Scalar::Util::openhandle( $der ) ) {
676 11         50 local $/;
677              
678 11 100       45 binmode $der if( $self->{_binaryMode} );
679              
680 11         527 $der = <$der>; # Note: this closes files opened by readFile
681 11 50       203 die( "Failed to read request: $!\n" ) unless( defined $der );
682             }
683              
684 14         32 my $isPEM;
685              
686 14 100 66     719 if( $self->{_PEMonly} ) {
    100          
687 2 100       71 if( $der =~ $pemre ) {
688 1         4 $der = $1;
689 1         2 $isPEM = 1;
690             } else {
691 1         11 die( "No certificate request found\n" );
692             }
693             } elsif( $self->{_acceptPEM} && $der =~ $pemre ) {
694 9         42 $der = $1;
695 9         20 $isPEM = 1;
696             }
697 13 100       35 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 10         146 $der =~ s/\s+//g; # Delete whitespace, which is legal but meaningless
702 10 100       37 $der =~ tr~A-Za-z0-9+=/~~cd if( $self->{_ignoreNonBase64} );
703              
704 10 100 66     81 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 9         77 $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 12         21 $der = eval { # Catch out of range errors caused by bad DER & report as format errors.
714             # SEQUENCE -- CertificationRequest
715              
716 12         56 my( $tlen, undef, $tag ) = asn_decode_tag2( $der );
717 12 50 33     208 die( "SEQUENCE not present\n" ) unless( $tlen && $tag == ASN_SEQUENCE );
718              
719 12         67 my( $llen, $len ) = asn_decode_length( substr( $der, $tlen ) );
720 12 50 33     254 die( "Invalid SEQUENCE length\n" ) unless( $llen && $len );
721              
722 12         23 $len += $tlen + $llen;
723 12         21 $tlen = length $der;
724 12 50       23 die( "DER too short to contain request\n" ) if( $tlen < $len );
725              
726 12 50 66     39 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 12         43 return substr( $der, 0, $len );
732 12 50       23 }; if( $@ ) {
733 0         0 1 while( chomp $@ );
734 0         0 die( "Invalid format for request: $@\n" );
735             }
736              
737 12         31 $self->{_der} = $der;
738              
739 12         51 bless( $self, $class );
740              
741 12         64 $self->{_bmpenc} = Encode::find_encoding('UCS2-BE');
742              
743 12         4445 my $asn = Convert::ASN1->new;
744 12         438 $self->{_asn} = $asn;
745 12 50       48 $asn->prepare($schema) or die( "Internal error in " . __PACKAGE__ . ": " . $asn->error );
746              
747 12         555550 $asn->registertype( 'qualifier', '1.3.6.1.5.5.7.2.1', $self->_init('CPSuri') );
748 12         142 $asn->registertype( 'qualifier', '1.3.6.1.5.5.7.2.2', $self->_init('UserNotice') );
749              
750 12         96 $parser = $self->_init( 'CertificationRequest' );
751              
752 12 50       54 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 12         19468 = $top->{certificationRequestInfo}{subject};
759              
760             $self->{certificationRequestInfo}{subject}
761 12         54 = $self->_convert_rdn( $top->{certificationRequestInfo}{subject} );
762              
763             $self->{certificationRequestInfo}{version}
764 12         44 = $top->{certificationRequestInfo}{version};
765              
766             $self->{certificationRequestInfo}{attributes} = $self->_convert_attributes(
767 12         45 $top->{certificationRequestInfo}{attributes} );
768              
769             $self->{_pubkey} = "-----BEGIN PUBLIC KEY-----\n" .
770             _encode_PEM( $self->_init('SubjectPublicKeyInfo')->
771 12         31 encode( $top->{certificationRequestInfo}{subjectPKInfo} ) ) .
772             "-----END PUBLIC KEY-----\n";
773              
774             $self->{certificationRequestInfo}{subjectPKInfo} = $self->_convert_pkinfo(
775 12         75 $top->{certificationRequestInfo}{subjectPKInfo} );
776              
777 12         27 $self->{signature} = $top->{signature};
778              
779             $self->{signatureAlgorithm}
780 12         39 = $self->_convert_signatureAlgorithm( $top->{signatureAlgorithm} );
781              
782             # parse parameters for RSA PSS
783 12 100       50 if ($self->{signatureAlgorithm}{algorithm} eq 'rsassaPss') {
784             my $params = $self->_init('rsassaPssParam')
785 4         13 ->decode($self->{signatureAlgorithm}{parameters});
786              
787             my $sigInfo = {
788 4   100     1341 'saltLength' => ($params->{saltLength} || 20),
789             'digestAlgorithm' => 'sha1',
790             };
791             $sigInfo->{digestAlgorithm} = $self->_oid2name($params->{digestAlgorithm}{algorithm})
792 4 100       31 if($params->{digestAlgorithm}{algorithm});
793              
794             $sigInfo->{maskGenAlgorithm} = $self->_oid2name($params->{maskGenAlgorithm}{algorithm})
795 4 100       17 if($params->{maskGenAlgorithm}{algorithm});
796              
797 4         14 $self->{signatureAlgorithm}{parameters} = $sigInfo;
798             }
799              
800             # Extract bundle of bits that is signed
801             # The DER is SEQUENCE -- CertificationRequest
802             # SEQUENCE -- CertificationRequestInfo [SIGNED]
803              
804 12         24 my( $CRtaglen, $CRtag, $CRllen, $CRlen );
805 12         61 ($CRtaglen, undef, $CRtag) = asn_decode_tag2( $der );
806 12 50       167 die( "Invalid CSR format: missing SEQUENCE 1\n" ) unless( $CRtag == ASN_SEQUENCE );
807 12         57 ($CRllen, $CRlen) = asn_decode_length( substr( $der, $CRtaglen ) );
808              
809 12         230 my( $CItaglen, $CItag, $CIllen, $CIlen );
810 12         42 ($CItaglen, undef, $CItag) = asn_decode_tag2( substr( $der, $CRtaglen + $CRllen ) );
811 12 50       138 die( "Invalid CSR format: missing SEQUENCE 2\n" ) unless( $CItag == ASN_SEQUENCE );
812 12         46 ($CIllen, $CIlen) = asn_decode_length( substr( $der, $CRtaglen + $CRllen + $CItaglen ) );
813              
814 12         147 $self->{_signed} = substr( $der, $CRtaglen + $CRllen, $CItaglen + $CIllen + $CIlen );
815              
816 12 50 33     42 die( $error ) if( $self->{_verifySignature} && !$self->checkSignature );
817              
818 12         188 return $self;
819             }
820              
821             # Convert::ASN1 returns BMPStrings as 16-bit fixed-width characters, e.g. UCS2-BE
822              
823             sub _bmpstring {
824 9     9   11 my $self = shift;
825              
826 9         27 my $enc = $self->{_bmpenc};
827              
828 9         90 $_ = $enc->decode( $_ ) foreach (@_);
829              
830 9         17 return;
831             }
832              
833             # Find the obvious BMPStrings in a value and convert them
834             # This doesn't catch direct values, but does find them in hashes
835             # (generally as a result of a CHOICE)
836             #
837             # Convert iPAddresses as well
838              
839             sub _scanvalue {
840 213     213   225 my $self = shift;
841              
842 213         258 my( $value ) = @_;
843              
844 213 100       348 return unless( ref $value );
845 102 100       175 if( ref $value eq 'ARRAY' ) {
846 24         42 foreach (@$value) {
847 90         137 $self->_scanvalue( $_ );
848             }
849 24         31 return;
850             }
851 78 50       126 if( ref $value eq 'HASH' ) {
852 78         161 foreach my $k (keys %$value) {
853 105 100       144 if( $k eq 'bmpString' ) {
854 3         10 $self->_bmpstring( $value->{bmpString} );
855 3         5 next;
856             }
857 102 100       142 if( $k eq 'iPAddress' ) {
858 2     2   19 use bytes;
  2         3  
  2         13  
859 6         12 my $addr = $value->{iPAddress};
860 6 100       10 if( length $addr == 4 ) {
861 3         17 $value->{iPAddress} = sprintf( '%vd', $addr );
862             } else {
863 3         15 $addr = sprintf( '%*v02X', ':', $addr );
864 3         65 $addr =~ s/([[:xdigit:]]{2}):([[:xdigit:]]{2})/$1$2/g;
865 3         8 $value->{iPAddress} = $addr;
866             }
867 6         11 next;
868             }
869 96         149 $self->_scanvalue( $value->{$k} );
870             }
871 78         109 return;
872             }
873 0         0 return;
874             }
875              
876             sub _convert_signatureAlgorithm {
877 12     12   20 my $self = shift;
878              
879 12         17 my $signatureAlgorithm = shift;
880             $signatureAlgorithm->{algorithm}
881             = $oids{$signatureAlgorithm->{algorithm}}
882             if( defined $signatureAlgorithm->{algorithm}
883 12 50 33     64 && exists $oids{$signatureAlgorithm->{algorithm}} );
884              
885 12         46 return $signatureAlgorithm;
886             }
887              
888             sub _convert_pkinfo {
889 12     12   20 my $self = shift;
890              
891 12         22 my $pkinfo = shift;
892              
893             $pkinfo->{algorithm}{algorithm}
894             = $oids{$pkinfo->{algorithm}{algorithm}}
895             if( defined $pkinfo->{algorithm}{algorithm}
896 12 50 33     103 && exists $oids{$pkinfo->{algorithm}{algorithm}} );
897 12         31 return $pkinfo;
898             }
899              
900             # OIDs requiring some sort of special handling
901             #
902             # Called with decoded value, returns updated value.
903             # Key is ASN macro name
904              
905             my %special;
906             %special =
907             (
908             EnhancedKeyUsage => sub {
909             my $self = shift;
910             my( $value, $id ) = @_;
911              
912             foreach (@{$value}) {
913             $_ = $oid2extkeyusage{$_} if(defined $oid2extkeyusage{$_});
914             }
915             return $value;
916             },
917             KeyUsage => sub {
918             my $self = shift;
919             my( $value, $id ) = @_;
920              
921             my $bit = unpack('C*', @{$value}[0]); #get the decimal representation
922             my $length = int(log($bit) / log(2) + 1); #get its bit length
923             my @usages = reverse( $id eq 'KeyUsage'? # Following are in order from bit 0 upwards
924             qw(digitalSignature nonRepudiation keyEncipherment dataEncipherment
925             keyAgreement keyCertSign cRLSign encipherOnly decipherOnly) :
926             qw(client server email objsign reserved sslCA emailCA objCA) );
927             my $shift = ($#usages + 1) - $length; # computes the unused area in @usages
928              
929             @usages = @usages[ grep { $bit & (1 << $_ - $shift) } 0 .. $#usages ]; #transfer bitmap to barewords
930              
931             return [ @usages ] if( $self->{_apiVersion} >= 1 );
932              
933             return join( ', ', @usages );
934             },
935             netscapeCertType => sub {
936             goto &{$special{KeyUsage}};
937             },
938             SubjectKeyIdentifier => sub {
939             my $self = shift;
940             my( $value, $id ) = @_;
941              
942             return unpack( "H*", $value );
943             },
944             ApplicationCertPolicies => sub {
945             goto &{$special{certificatePolicies}} if( $_[0]->{_apiVersion} > 0 );
946              
947             my $self = shift;
948             my( $value, $id ) = @_;
949              
950             foreach my $entry (@{$value}) {
951             $entry->{policyIdentifier} = $self->_oid2name( $entry->{policyIdentifier} );
952             }
953              
954             return $value;
955             },
956             certificateTemplate => sub {
957             my $self = shift;
958             my( $value, $id ) = @_;
959              
960             $value->{templateID} = $self->_oid2name( $value->{templateID} ) if( $self->{_apiVersion} > 0 );
961             return $value;
962             },
963             EnrollmentCSP => sub {
964             my $self = shift;
965             my( $value, $id ) = @_;
966              
967             $self->_bmpstring( $value->{Name} );
968              
969             return $value;
970             },
971             ENROLLMENT_CSP_PROVIDER => sub {
972             my $self = shift;
973             my( $value, $id ) = @_;
974              
975             $self->_bmpstring( $value->{cspName} );
976              
977             return $value;
978             },
979             certificatePolicies => sub {
980             my $self = shift;
981             my( $value, $id ) = @_;
982              
983             foreach my $policy (@$value) {
984             $policy->{policyIdentifier} = $self->_oid2name( $policy->{policyIdentifier} );
985             if( exists $policy->{policyQualifier} ) {
986             foreach my $qualifier (@{$policy->{policyQualifier}}) {
987             $qualifier->{policyQualifierId} = $self->_oid2name( $qualifier->{policyQualifierId} );
988             my $qv = $qualifier->{qualifier};
989             if( ref $qv eq 'HASH' ) {
990             foreach my $qt (keys %$qv) {
991             if( $qt eq 'explicitText' ) {
992             $qv->{$qt} = (values %{$qv->{$qt}})[0];
993             } elsif( $qt eq 'noticeRef' ) {
994             my $userNotice = $qv->{$qt};
995             $userNotice->{organization} = (values %{$userNotice->{organization}})[0];
996             }
997             }
998             $qv->{userNotice} = delete $qv->{noticeRef}
999             if( exists $qv->{noticeRef} );
1000             }
1001             }
1002             }
1003             }
1004             return $value;
1005             },
1006             CERT_EXTENSIONS => sub {
1007             my $self = shift;
1008             my( $value, $id, $entry ) = @_;
1009              
1010             return $self->_convert_extensionRequest( [ $value ] ) if( $self->{_apiVersion} > 0 ); # Untested
1011             },
1012             BasicConstraints => sub {
1013             my $self = shift;
1014             my( $value, $id, $entry ) = @_;
1015              
1016             my $r = {
1017             CA => $value->{cA}? 'TRUE' : 'FALSE',
1018             };
1019             my $string = "CA:$r->{CA}";
1020              
1021             if( exists $value->{pathLenConstraint} ) {
1022             $r->{pathlen} = $value->{pathLenConstraint};
1023             $string .= sprintf( ',pathlen:%u', $value->{pathLenConstraint} );
1024             }
1025             $entry->{_FMT} = [ $r, $string ]; # [ Raw, formatted ]
1026             return $value;
1027             },
1028             unstructuredName => sub {
1029             my $self = shift;
1030             my( $value, $id ) = @_;
1031              
1032             return $self->_hash2string( $value );
1033             },
1034             challengePassword => sub {
1035             my $self = shift;
1036             my( $value, $id ) = @_;
1037              
1038             return $self->_hash2string( $value );
1039             },
1040             ); # %special
1041              
1042             sub _convert_attributes {
1043 12     12   40 my $self = shift;
1044 12         38 my( $typeandvalues ) = @_;
1045              
1046 12         20 foreach my $entry ( @{$typeandvalues} ) {
  12         26  
1047 24         36 my $oid = $entry->{type};
1048 24         79 my $name = $oids{$oid};
1049 24 50 33     101 $name = $variantNames{$name} if( defined $name && exists $variantNames{$name} );
1050              
1051 24 50       40 next unless( defined $name );
1052              
1053 24         30 $entry->{type} = $name;
1054              
1055 24 100       64 if ($name eq 'extensionRequest') {
    100          
1056 6         18 $entry->{values} = $self->_convert_extensionRequest($entry->{values}[0]);
1057              
1058             } elsif ($name eq 'ENROLLMENT_NAME_VALUE_PAIR') {
1059 3         9 my $parser = $self->_init( $name );
1060 3         6 my @values;
1061 3         6 foreach my $der (@{$entry->{values}}) {
  3         8  
1062 3 50       17 my $pair = $parser->decode( $der ) or
1063             confess( "Looks like damaged input parsing attribute $name" );
1064 3         387 $self->_bmpstring( $pair->{name}, $pair->{value} );
1065 3         7 push @values, $pair;
1066             };
1067 3         12 $entry->{values} = \@values;
1068              
1069             } else {
1070 15 50       36 my $parser = $self->_init( $name, 1 ) or next; # Skip unknown attributes
1071              
1072 15 50       32 if($entry->{values}[1]) {
1073 0         0 confess( "Incomplete parsing of attribute type: $name" );
1074             }
1075 15 50       44 my $value = $entry->{values} = $parser->decode( $entry->{values}[0] ) or
1076             confess( "Looks like damaged input parsing attribute $name" );
1077              
1078 15 100       2459 if( exists $special{$name} ) {
1079 9         23 my $action = $special{$name};
1080 9         35 $entry->{values} = $action->( $self, $value, $name, $entry );
1081             }
1082             }
1083             }
1084 12         40 return $typeandvalues;
1085             }
1086              
1087             sub _convert_extensionRequest {
1088 6     6   13 my $self = shift;
1089 6         11 my( $extensionRequest ) = @_;
1090              
1091 6         13 my $parser = $self->_init('extensionRequest');
1092 6 50       18 my $decoded = $parser->decode($extensionRequest) or return [];
1093              
1094 6         3581 foreach my $entry (@{$decoded}) {
  6         17  
1095 27         72 my $name = $oids{ $entry->{extnID} };
1096 27 100 66     104 $name = $variantNames{$name} if( defined $name && exists $variantNames{$name} );
1097 27 50       46 if (defined $name) {
1098 27         36 my $asnName = $name;
1099 27         52 $asnName =~ tr/ //d;
1100 27         76 my $parser = $self->_init($asnName, 1);
1101 27 50       44 if(!$parser) {
1102 0         0 $entry = undef;
1103 0         0 next;
1104             }
1105 27         41 $entry->{extnID} = $name;
1106 27 50       62 my $dec = $parser->decode($entry->{extnValue}) or
1107             confess( $parser->error . ".. looks like damaged input parsing extension $asnName" );
1108              
1109 27         7794 $self->_scanvalue( $dec );
1110              
1111 27 100       63 if( exists $special{$asnName} ) {
1112 21         30 my $action = $special{$asnName};
1113 21         48 $dec = $action->( $self, $dec, $asnName, $entry );
1114             }
1115 27         86 $entry->{extnValue} = $dec;
1116             }
1117             }
1118 6         11 @{$decoded} = grep { defined } @{$decoded};
  6         18  
  27         40  
  6         12  
1119 6         28 return $decoded;
1120             }
1121              
1122             sub _convert_rdn {
1123 12     12   27 my $self = shift;
1124 12         20 my $typeandvalue = shift;
1125 12         49 my %hash = ( _subject => [], );
1126 12         30 foreach my $entry ( @$typeandvalue ) {
1127 46         77 foreach my $item (@$entry) {
1128 47         74 my $oid = $item->{type};
1129 47 50       156 my $name = (exists $variantNames{$oid})? $variantNames{$oid}[1]: $oids{ $oid };
1130 47 50       81 if( defined $name ) {
1131 47         60 push @{$hash{$name}}, sort values %{$item->{value}};
  47         112  
  47         153  
1132 47         74 push @{$hash{_subject}}, $name, [ sort values %{$item->{value}} ];
  47         89  
  47         152  
1133 47 50       124 my @names = (exists $variantNames{$oid})? @{$variantNames{$oid}} : ( $name );
  0         0  
1134 47         85 foreach my $name ( @names ) {
1135 47 100       253 unless( $self->can( $name ) ) {
1136 2     2   3491 no strict 'refs'; ## no critic
  2         5  
  2         594  
1137             *$name = sub {
1138 2     2   4 my $self = shift;
1139 2 100       7 return @{ $self->{certificationRequestInfo}{subject}{$name} } if( wantarray );
  1         7  
1140 1   50     7 return $self->{certificationRequestInfo}{subject}{$name}[0] || '';
1141             }
1142 2         17 }
1143             }
1144             }
1145             }
1146             }
1147              
1148 12         102 return \%hash;
1149             }
1150              
1151             sub _init {
1152 107     107   160 my $self = shift;
1153 107         211 my( $node, $optional ) = @_;
1154              
1155 107         306 my $parsed = $self->{_asn}->find($node);
1156              
1157 107 50 33     1359 unless( defined $parsed || $optional ) {
1158 0         0 croak( "Missing node $node in ASN.1" );
1159             }
1160 107         272 return $parsed;
1161             }
1162              
1163             ###########################################################################
1164             # interface methods
1165              
1166             sub csrRequest {
1167 6     6 1 1871 my $self = shift;
1168 6         13 my $format = shift;
1169              
1170             return( "-----BEGIN CERTIFICATE REQUEST-----\n" .
1171 6 100       27 _encode_PEM( $self->{_der} ) .
1172             "-----END CERTIFICATE REQUEST-----\n" ) if( $format );
1173              
1174 3         36 return $self->{_der};
1175             }
1176              
1177             # Common subject components documented to be always present:
1178              
1179             foreach my $component (qw/commonName organizationalUnitName organizationName
1180             emailAddress stateOrProvinceName countryName domainComponent/ ) {
1181 2     2   13 no strict 'refs'; ## no critic
  2         3  
  2         7883  
1182              
1183             unless( defined &$component ) {
1184             *$component = sub {
1185 9     9   1400 my $self = shift;
1186 9 50       23 return @{ $self->{certificationRequestInfo}{subject}{$component} || [] } if( wantarray );
  7 100       58  
1187 2   50     11 return $self->{certificationRequestInfo}{subject}{$component}[0] || '';
1188             }
1189             }
1190             }
1191              
1192             # Complete subject
1193              
1194             sub subject {
1195 5     5 1 673 my $self = shift;
1196 5         11 my $long = shift;
1197              
1198 5 100       20 return @{ $self->{certificationRequestInfo}{subject}{_subject} } if( wantarray );
  1         15  
1199              
1200 4         7 my @subject = @{ $self->{certificationRequestInfo}{subject}{_subject} };
  4         24  
1201              
1202 4         9 my $subj = '';
1203 4         13 while( @subject ) {
1204 19         41 my( $name, $value ) = splice( @subject, 0, 2 );
1205 19 100 66     73 $name = $shortnames{$name} if( !$long && exists $shortnames{$name} );
1206 19         72 $subj .= "/$name=" . join( ',', @$value );
1207             }
1208              
1209 4         39 return $subj;
1210             }
1211              
1212              
1213             sub subjectRaw {
1214              
1215 1     1 1 3 my $self = shift;
1216 1         3 my @subject;
1217 1         3 foreach my $rdn (@{$self->{certificationRequestInfo}{subject_raw}}) {
  1         4  
1218             my @sequence = map {
1219 6         7 $_->{format} = (keys %{$_->{value}})[0];
  6         14  
1220 6         7 $_->{value} = (values %{$_->{value}})[0];
  6         12  
1221 6         14 $_;
1222 5         7 } @{$rdn};
  5         8  
1223 5 100       9 if (scalar @sequence > 1) {
1224 1         2 push @subject, \@sequence;
1225             } else {
1226 4         15 push @subject, $sequence[0];
1227             }
1228             }
1229 1         13 return \@subject;
1230              
1231             }
1232              
1233              
1234             sub subjectAltName {
1235 4     4 1 1833 my $self = shift;
1236 4         9 my( $type ) = @_;
1237              
1238 4         10 my $san = $self->extensionValue( 'subjectAltName' );
1239 4 50       9 unless( defined $san ) {
1240 0 0       0 return () if( wantarray );
1241 0         0 return undef; ## no critic
1242             }
1243              
1244 4 100       7 if( !defined $type ) {
1245 2 100       6 if( wantarray ) {
1246 1         2 my %comps;
1247 1         3 $comps{$_} = 1 foreach (map { keys %$_ } @$san);
  9         21  
1248 1         11 return sort keys %comps; ## no critic
1249             }
1250 1         3 my @string;
1251 1         3 foreach my $comp (@$san) {
1252 9         22 push @string, join( '+', map { "$_:$comp->{$_}" } sort keys %$comp );
  9         59  
1253             }
1254 1         11 return join( ',', @string );
1255             }
1256              
1257 2         5 my $result = [ map { $_->{$type} } grep { exists $_->{$type} } @$san ];
  4         8  
  18         26  
1258              
1259 2 100       10 return @$result if( wantarray );
1260 1         4 return $result->[0];
1261             }
1262              
1263             sub version {
1264 2     2 1 1105 my $self = shift;
1265 2         6 my $v = $self->{certificationRequestInfo}{version};
1266 2         36 return sprintf( "v%u", $v+1 );
1267             }
1268              
1269             sub pkAlgorithm {
1270 12     12 1 22 my $self = shift;
1271 12         57 return $self->{certificationRequestInfo}{subjectPKInfo}{algorithm}{algorithm};
1272             }
1273              
1274             sub subjectPublicKey {
1275 4     4 1 7 my $self = shift;
1276 4         8 my $format = shift;
1277              
1278 4 100       14 return $self->{_pubkey} if( $format );
1279 2         16 return unpack('H*', $self->{certificationRequestInfo}{subjectPKInfo}{subjectPublicKey}[0]);
1280             }
1281              
1282             sub subjectPublicKeyParams {
1283 4     4 1 584 my $self = shift;
1284 4         6 my $detail = shift;
1285              
1286 4 100       169 croak( "Requires API version 1" ) unless( $self->{_apiVersion} >= 1 );
1287              
1288 3         7 undef $error;
1289 3         4 delete $self->{_error};
1290              
1291 3         7 my $rv = {};
1292 3         8 my $at = $self->pkAlgorithm;
1293 3 50       10 $at = 'undef' unless( defined $at );
1294              
1295              
1296             # this is wrong but unfortunately seen in the wild
1297 3 50       10 if( $at eq 'rsassaPss') {
1298 0         0 $at = 'rsaEncryption';
1299 0         0 warn 'Got rsassaPss as pkAlgorithm - converting to RSA';
1300             }
1301              
1302 3 100       14 if( $at eq 'rsaEncryption' ) {
    50          
    50          
1303 2         6 $rv->{keytype} = 'RSA';
1304 2         8 my $par = $self->_init( 'rsaKey' );
1305 2         10 my $rsa = $par->decode( $self->{certificationRequestInfo}{subjectPKInfo}{subjectPublicKey}[0] );
1306 2         135160 $rv->{keylen} = 4 * ( length( $rsa->{modulus}->as_hex ) -2 ); # 2 == length( '0x' )
1307 2         30056 $rv->{modulus} = substr( $rsa->{modulus}->as_hex, 2 );
1308             $rv->{publicExponent} = ( ref( $rsa->{publicExponent} )?
1309             $rsa->{publicExponent}->as_hex :
1310 2 50       29449 sprintf( '%x', $rsa->{publicExponent} ) );
1311             } elsif( $at eq 'ecPublicKey' ) {
1312 0         0 $rv->{keytype} = 'ECC';
1313              
1314 0         0 eval { require Crypt::PK::ECC; };
  0         0  
1315 0 0       0 if( $@ ) {
1316 0         0 $rv->{keytype} = undef;
1317             $self->{_error} =
1318 0         0 $error = "ECC public key requires Crypt::PK::ECC\n";
1319 0 0       0 croak( $error ) if( $self->{_dieOnError} );
1320 0         0 return $rv;
1321             }
1322 0         0 my $key = $self->subjectPublicKey(1);
1323 0         0 $key = Crypt::PK::ECC->new( \$key )->key2hash;
1324 0         0 $rv->{keylen} = $key->{curve_bits};
1325 0         0 $rv->{pub_x} = $key->{pub_x};
1326 0         0 $rv->{pub_y} = $key->{pub_y};
1327 0 0       0 $rv->{detail} = { %$key } if( $detail );
1328              
1329 0         0 my $par = $self->_init( 'eccName' );
1330 0         0 $rv->{curve} = $par->decode( $self->{certificationRequestInfo}{subjectPKInfo}{algorithm}{parameters} );
1331 0 0       0 $rv->{curve} = $self->_oid2name( $rv->{curve} ) if ($rv->{curve});
1332             } elsif( $at eq 'dsa' ) {
1333 1         3 $rv->{keytype} = 'DSA';
1334 1         2 my $par = $self->_init( 'dsaKey' );
1335 1         5 my $dsa = $par->decode( $self->{certificationRequestInfo}{subjectPKInfo}{subjectPublicKey}[0] );
1336 1         27902 $rv->{keylen} = 4 * ( length( $dsa->as_hex ) -2 );
1337 1 50       6798 if( exists $self->{certificationRequestInfo}{subjectPKInfo}{algorithm}{parameters} ) {
1338 1         7 $par = $self->_init('dsaPars');
1339 1         6 $dsa = $par->decode($self->{certificationRequestInfo}{subjectPKInfo}{algorithm}{parameters});
1340 1         59855 $rv->{G} = substr( $dsa->{G}->as_hex, 2 );
1341 1         6822 $rv->{P} = substr( $dsa->{P}->as_hex, 2 );
1342 1         6771 $rv->{Q} = substr( $dsa->{Q}->as_hex, 2 );
1343             }
1344             } else {
1345 0         0 $rv->{keytype} = undef;
1346             $self->{_error} =
1347 0         0 $error = "Unrecognized public key type $at\n";
1348 0 0       0 croak( $error ) if( $self->{_dieOnError} );
1349             }
1350 3         584 return $rv;
1351             }
1352              
1353             sub signatureAlgorithm {
1354 7     7 1 3008 my $self = shift;
1355 7         47 return $self->{signatureAlgorithm}{algorithm};
1356             }
1357              
1358             sub signatureParams {
1359 1     1 1 3 my $self = shift;
1360              
1361 1 50       4 return unless ( exists $self->{signatureAlgorithm}{parameters} );
1362              
1363             # For RSA PSS the parameters have been parsed to a hash already
1364 1 50       4 if (ref $self->{signatureAlgorithm}{parameters} eq 'HASH') {
1365 0         0 return $self->{signatureAlgorithm}{parameters};
1366             }
1367              
1368 1         7 my( $tlen, undef, $tag ) = asn_decode_tag2( $self->{signatureAlgorithm}{parameters} );
1369 1 50 33     28 if( $tlen != 0 && $tag != ASN_NULL ) {
1370             return $self->{signatureAlgorithm}{parameters}
1371 0         0 }
1372             # Known algorithm's parameters MAY return a hash of decoded fields.
1373             # For now, leaving that to the caller...
1374              
1375 1         5 return;
1376             }
1377              
1378             sub signature {
1379 9     9 1 23 my $self = shift;
1380 9         16 my $format = shift;
1381              
1382 9 100 66     58 if( defined $format && $format == 2 ) { # Per keytype decoding
1383 7 50       25 if( $self->pkAlgorithm eq 'ecPublicKey' ) { # ECDSA
1384 0         0 my $par = $self->_init( 'ecdsaSigValue' );
1385 0         0 return $par->decode( $self->{signature}[0] );
1386             }
1387 7         31 return; # Unknown
1388             }
1389 2 50       7 return $self->{signature}[0] if( $format );
1390              
1391 2         15 return unpack('H*', $self->{signature}[0]);
1392             }
1393              
1394             sub certificationRequest {
1395 1     1 1 3 my $self = shift;
1396              
1397 1         9 return $self->{_signed};
1398             }
1399              
1400             sub _attributes {
1401 55     55   67 my $self = shift;
1402              
1403 55         101 my $attributes = $self->{certificationRequestInfo}{attributes};
1404 55 50       97 return unless( defined $attributes );
1405              
1406 55         93 return { map { $_->{type} => $_->{values} } @$attributes };
  213         506  
1407             }
1408              
1409             sub attributes {
1410 22     22 1 3223 my $self = shift;
1411 22         60 my( $name ) = @_;
1412              
1413 22 50       65 if( $self->{_apiVersion} < 1 ) {
1414 0         0 my $attributes = $self->{certificationRequestInfo}{attributes};
1415 0 0       0 return () unless( defined $attributes );
1416              
1417 0         0 my %hash = map { $_->{type} => $_->{values} }
1418 0         0 @{$attributes};
  0         0  
1419 0         0 return %hash;
1420             }
1421              
1422 22         43 my $attributes = $self->_attributes;
1423 22 50       45 unless( defined $attributes ) {
1424 0 0       0 return () if( wantarray );
1425 0         0 return undef; ## no critic
1426             }
1427              
1428 22 100       45 unless( defined $name ) {
1429 5         24 return grep { $_ ne 'extensionRequest' } sort keys %$attributes;
  23         67  
1430             }
1431              
1432 17         58 $name = $self->_oid2name( $name );
1433              
1434 17 50       43 if( $name eq 'extensionRequest' ) { # Meaningless, and extensions/extensionValue handle
1435 0 0       0 return () if( wantarray );
1436 0         0 return undef; ## no critic
1437             }
1438              
1439             # There can only be one matching the name.
1440             # If the match becomes wider, sort the keys.
1441              
1442              
1443 17         56 my @attrs = grep { $_ eq $name } keys %$attributes;
  73         112  
1444 17 100       74 unless( @attrs ) {
1445 1 50       3 return () if( wantarray );
1446 1         6 return undef; ## no critic
1447             }
1448              
1449 16         23 my @values;
1450 16         28 foreach my $attr (@attrs) {
1451 16         22 my $values = $attributes->{$attr};
1452 16 100       48 $values = [ $values ] unless( ref $values eq 'ARRAY' );
1453 16         26 foreach my $value (@$values) {
1454 16         33 my $value = $self->_hash2string( $value );
1455 16 100       50 push @values, (wantarray? $value : $self->_value2strings( $value ));
1456             }
1457             }
1458 16 100       62 return @values if( wantarray );
1459              
1460 11 50       23 if( @values == 1 ) {
1461 11         51 $values[0] =~ s/^\((.*)\)$/$1/;
1462 11         61 return $values[0];
1463             }
1464 0         0 return join( ',', @values );
1465             }
1466              
1467             sub certificateTemplate {
1468 1     1 1 3 my $self = shift;
1469              
1470 1         4 return $self->extensionValue( 'certificateTemplate', @_ );
1471             }
1472              
1473             # If a hash contains one string (e.g. a CHOICE containing type=>value), return the string.
1474             # If the hash is nested, try recursing.
1475             # If the string can't be identified (clutter in the hash), return the hash
1476             # Some clutter can be filtered by specifying $exclude (a regexp)
1477              
1478             sub _hash2string {
1479 43     43   64 my $self = shift;
1480 43         57 my( $hash, $exclude ) = @_;
1481              
1482 43 100       105 return $hash unless( ref $hash eq 'HASH' );
1483              
1484 20         56 my @keys = keys %$hash;
1485              
1486 20 100       40 @keys = grep { $_ !~ /$exclude/ } @keys if( defined $exclude );
  1         41  
1487              
1488 20 100       46 return $hash if( @keys != 1 );
1489              
1490 11 100       43 return $self->_hash2string( $hash->{$keys[0]} ) if( ref $hash->{$keys[0]} eq 'HASH' );
1491              
1492 7         43 return $hash->{$keys[0]};
1493             }
1494              
1495             # Convert a value to a printable string
1496              
1497             sub _value2strings {
1498 99     99   113 my $self = shift;
1499 99         133 my( $value ) = @_;
1500              
1501 99         114 my @strings;
1502 99 100       163 if( ref $value eq 'ARRAY' ) {
1503 10         23 foreach my $value (@$value) {
1504 34         76 push @strings, $self->_value2strings( $value );
1505             }
1506 10 100       64 return '(' . join( ',', @strings ) . ')' if( @strings > 1 );
1507 1         3 return join( ',', @strings );
1508             }
1509 89 100       133 if( ref $value eq 'HASH' ) {
1510 25         72 foreach my $k (sort keys %$value) {
1511 46         101 push @strings, "$k=" . $self->_value2strings( $value->{$k} );
1512             }
1513 25 100       121 return '(' . join( ',', @strings ) . ')' if( @strings > 1 );
1514 10         26 return join( ',', @strings );
1515             }
1516              
1517 64 100       184 return $value if( $value =~ /^\d+$/ );
1518              
1519             # OpenSSL and Perl-compatible string syntax
1520              
1521 54 100       101 $value =~ s/(["\\\$])/\\$1/g if( $self->{_escapeStrings} );
1522              
1523 54 100       512 return $value if( $value =~ m{\A[\w!\@$%^&*_=+\[\]\{\}:;|<>./?"'-]+\z} ); # Barewords
1524              
1525 10         36 return '"' . $value . '"'; # Must quote: whitespace, non-printable, comma, (), \, null string
1526             }
1527              
1528             sub extensions {
1529 4     4 1 2767 my $self = shift;
1530              
1531 4         9 my $attributes = $self->_attributes;
1532 4 50 33     20 return () unless( defined $attributes && exists $attributes->{extensionRequest} );
1533              
1534 4         6 my @present = map { $_->{extnID} } @{$attributes->{extensionRequest}};
  15         27  
  4         16  
1535 4 50       12 if( $self->{_apiVersion} >= 1 ) {
1536 4         7 foreach my $ext (@present) {
1537 15 100       49 $ext = $variantNames{'$' . $ext} if( exists $variantNames{'$' . $ext} );
1538             }
1539             }
1540 4         19 return @present;
1541             }
1542              
1543             sub extensionValue {
1544 23     23 1 3552 my $self = shift;
1545 23         51 my( $extensionName, $format ) = @_;
1546              
1547 23         45 my $attributes = $self->_attributes;
1548 23         31 my $value;
1549 23 50 33     89 return unless( defined $attributes && exists $attributes->{extensionRequest} );
1550              
1551 23         54 $extensionName = $self->_oid2name( $extensionName );
1552              
1553 23 100       54 $extensionName = $variantNames{$extensionName} if( exists $variantNames{$extensionName} );
1554              
1555 23         27 foreach my $entry (@{$attributes->{extensionRequest}}) {
  23         59  
1556 76 100       129 if ($entry->{extnID} eq $extensionName) {
1557 21         29 $value = $entry->{extnValue};
1558 21 100       46 if( $self->{_apiVersion} == 0 ) {
1559 1         6 while (ref $value eq 'HASH') {
1560 0         0 my @keys = sort keys %{$value};
  0         0  
1561 0         0 $value = $value->{ shift @keys } ;
1562             }
1563             } else {
1564 20 100       33 if( $entry->{_FMT} ) { # Special formatting
1565 3 100       8 $value = $entry->{_FMT}[$format? 1:0];
1566             } else {
1567 17         44 $value = $self->_hash2string( $value, '(?i:^(?:critical|.*id)$)' );
1568 17 100       50 $value = $self->_value2strings( $value ) if( $format );
1569             }
1570             }
1571 21         36 last;
1572             }
1573             }
1574 23 100       62 $value =~ s/^\((.*)\)$/$1/ if( $format );
1575              
1576 23         122 return $value;
1577             }
1578              
1579             sub extensionPresent {
1580 6     6 1 11 my $self = shift;
1581 6         11 my( $extensionName ) = @_;
1582              
1583 6         10 my $attributes = $self->_attributes;
1584 6 50 33     26 return unless( defined $attributes && exists $attributes->{extensionRequest} );
1585              
1586 6         15 $extensionName = $self->_oid2name( $extensionName );
1587              
1588 6 100       17 $extensionName = $variantNames{$extensionName} if( exists $variantNames{$extensionName} );
1589              
1590 6         11 foreach my $entry (@{$attributes->{extensionRequest}}) {
  6         11  
1591 18 100       32 if ($entry->{extnID} eq $extensionName) {
1592 5 100       20 return 2 if ($entry->{critical});
1593 3         13 return 1;
1594             }
1595             }
1596 1         5 return;
1597             }
1598              
1599             sub checkSignature {
1600 0     0 1 0 my $self = shift;
1601              
1602 0         0 undef $error;
1603 0         0 delete $self->{_error};
1604              
1605 0         0 my $ok = eval {
1606 0 0       0 die( "checkSignature requires API version 1\n" ) unless( $self->{_apiVersion} >= 1 );
1607              
1608 0         0 my $key = $self->subjectPublicKey(1); # Key as PEM
1609 0         0 my $sig = $self->signature(1); # Signature as DER
1610 0         0 my $alg = $self->signatureAlgorithm; # Algorithm name
1611              
1612             # Determine the signature hash type from the algorithm name
1613              
1614 0         0 my @params = ( $sig, $self->certificationRequest );
1615 0 0       0 if( $alg =~ /sha-?(\d+)/i ) {
    0          
    0          
1616 0         0 push @params, "SHA$1";
1617              
1618             } elsif( $alg =~ /md-?(\d)/i ) {
1619 0         0 push @params, "MD$1";
1620              
1621             } elsif ( $alg eq 'rsassaPss' ) {
1622              
1623 0         0 my $sigParam = $self->signatureParams;
1624 0         0 push @params, uc($sigParam->{digestAlgorithm});
1625 0         0 push @params, 'pss';
1626 0         0 push @params, $sigParam->{saltLength};
1627              
1628             } else {
1629              
1630 0         0 die( "Unknown hash in signature algorithm $alg\n" );
1631             }
1632              
1633 0         0 my $keyp = $self->subjectPublicKeyParams;
1634              
1635 0 0       0 die( "Unknown public key type\n" ) unless( defined $keyp->{keytype} );
1636              
1637             # Verify signature using the correct module and hash type.
1638              
1639 0 0       0 if( $keyp->{keytype} eq 'RSA' ) {
1640              
1641 0         0 eval { require Crypt::PK::RSA; };
  0         0  
1642 0 0       0 die( "Unable to load Crypt::PK::RSA\n" ) if( $@ );
1643              
1644             # We have seen requests in the wild that accidentially
1645             # have the rsaPSS OID set for the key type which is wrong
1646             # and therefore not supported by Crypt::PK::RSA. We therefore
1647             # build the key directly from the components extracted earlier
1648             $key = Crypt::PK::RSA->new( {
1649             e => $keyp->{publicExponent},
1650             N => $keyp->{modulus},
1651 0         0 } );
1652             # if we have NOT pss padding we need to add v1.5 padding
1653 0 0       0 push @params, 'v1.5' if ( $alg ne 'rsassaPss' );
1654 0         0 return $key->verify_message( @params );
1655              
1656             }
1657              
1658 0 0       0 if( $keyp->{keytype} eq 'DSA' ) {
1659              
1660 0         0 eval { require Crypt::PK::DSA; };
  0         0  
1661 0 0       0 die( "Unable to load Crypt::PK::DSA\n" ) if( $@ );
1662              
1663 0         0 $key = Crypt::PK::DSA->new( \$key );
1664 0         0 return $key->verify_message( @params );
1665             }
1666              
1667 0 0       0 if( $keyp->{keytype} eq 'ECC' ) {
1668 0         0 eval { require Crypt::PK::ECC; };
  0         0  
1669 0 0       0 die( "Unable to load Crypt::PK::ECC\n" ) if( $@ );
1670              
1671 0         0 $key = Crypt::PK::ECC->new( \$key );
1672 0         0 return $key->verify_message( @params );
1673             }
1674              
1675 0         0 die( "Unknown key type $keyp->{keytype}\n" );
1676             };
1677 0 0       0 if( $@ ) {
1678             $self->{_error} =
1679 0         0 $error = $@;
1680 0 0       0 croak( $error ) if( $self->{_dieOnError} );
1681 0         0 return;
1682             }
1683 0 0       0 return 1 if( $ok );
1684              
1685             $self->{_error} =
1686 0         0 $error = "Incorrect signature\n";
1687 0 0       0 croak( $error ) if( $self->{_dieOnError} );
1688              
1689 0         0 return 0;
1690             }
1691              
1692             sub _wrap {
1693 14     14   26 my( $to, $text ) = @_;
1694              
1695 14         17 my $wid = 76 - $to;
1696              
1697 14         34 my $out = substr( $text, 0, $wid, '' );
1698              
1699 14         38 while( length $text ) {
1700 17         59 $out .= "\n" . (' ' x $to) . substr( $text, 0, $wid, '' );
1701             }
1702 14         101 return $out;
1703             }
1704              
1705             sub _encode_PEM {
1706 15     15   2881 my $text = encode_base64( $_[0] );
1707 15 50       53 return $text if( length $text <= 65 );
1708 15         63 $text =~ tr/\n//d;
1709 15         30 my $out = '';
1710 15         189 $out .= substr( $text, 0, 64, '' ) . "\n" while( length $text );
1711 15         132 return $out;
1712             }
1713              
1714             sub as_string {
1715 1     1 0 2904 my $self = shift;
1716              
1717 1         3 local $self->{_escapeStrings} = 0;
1718 1         6 local( $@, $_, $! );
1719              
1720 1         3 my $v = $apiVersion;
1721 1 50 33     7 ref( $self )->setAPIversion( 1 ) unless( defined $v && $v == 1 );
1722              
1723 1         2 my $string = eval {
1724 1         7 $self = ref( $self )->new( $self->{_der}, acceptPEM => 0, verifySignature => 0, escapeStrings => 0 );
1725 1 50       6 return $error if( !defined $self );
1726              
1727 1         4 $self->__stringify;
1728             };
1729 1         2 my $at = $@;
1730 1 50 33     8 ref( $self )->setAPIversion( $v ) unless( defined $v && $v == 1 );
1731              
1732 1 50       3 $string = '' unless( defined $string );
1733 1 50       3 $string .= $at if( $at );
1734              
1735 1         61 return $string;
1736             }
1737              
1738             sub __stringify {
1739 1     1   3 my $self = shift;
1740              
1741 1         2 my $max = 0;
1742 1         3 foreach ($self->attributes, $self->extensions,
1743             qw/Version Subject Key_algorithm Public_key Signature_algorithm Signature/) {
1744 13 100       20 $max = length if( length > $max );
1745             }
1746              
1747 1         5 my $string = sprintf( "%-*s: %s\n", $max, 'Version', $self->version ) ;
1748              
1749 1         5 $string .= sprintf( "%-*s: %s\n", $max, 'Subject', _wrap( $max+2, scalar $self->subject ) );
1750              
1751 1         4 $string .= "\n --Attributes--\n";
1752              
1753 1 50       3 $string .= " --None--" unless( $self->attributes );
1754              
1755 1         3 foreach ($self->attributes) {
1756 4         11 $string .= sprintf( "%-*s: %s\n", $max, $_, _wrap( $max+2, scalar $self->attributes($_) ) );
1757             }
1758              
1759 1         3 $string .= "\n --Extensions--\n";
1760              
1761 1 50       3 $string .= " --None--" unless( $self->extensions );
1762              
1763 1         4 foreach ($self->extensions) {
1764 3 100       7 my $critical = $self->extensionPresent($_) == 2? 'critical,': '';
1765              
1766 3 50       10 $string .= sprintf( "%-*s: %s\n", $max, $_,
1767             _wrap( $max+2, $critical . ($_ eq 'subjectAltName'?
1768             scalar $self->subjectAltName:
1769             $self->extensionValue($_, 1) ) ) );
1770             }
1771              
1772 1         4 $string .= "\n --Key and signature--\n";
1773 1         5 $string .= sprintf( "%-*s: %s\n", $max, 'Key algorithm', $self->pkAlgorithm );
1774 1         4 $string .= sprintf( "%-*s: %s\n", $max, 'Public key', _wrap( $max+2, $self->subjectPublicKey ) );
1775 1         5 $string .= $self->subjectPublicKey(1);
1776 1         4 my $kp = $self->subjectPublicKeyParams(1);
1777 1         9 foreach (sort keys %$kp) {
1778 4         8 my $v = $kp->{$_};
1779              
1780 4 50 33     16 if( !defined $v && !defined( $v = $self->error ) ) {
    50          
1781 0         0 $v = 'undef';
1782             } elsif( ref $v ) {
1783 0         0 next;
1784             }
1785 4         9 $string .= sprintf( "%-*s: %s\n", $max, $_, _wrap( $max+2, $v ) );
1786             }
1787 1 50       5 if( exists $kp->{detail} ) {
1788 0         0 $kp = $kp->{detail};
1789 0         0 $string .= "Key details\n-----------\n";
1790 0         0 foreach (sort keys %$kp) {
1791 0 0       0 next if( ref $kp->{$_} );
1792 0         0 $string .= sprintf( "%-*s: %s\n", $max, $_, _wrap( $max+2, $kp->{$_} ) );
1793             }
1794             }
1795              
1796 1         5 $string .= sprintf( "\n%-*s: %s\n", $max, 'Signature algorithm', $self->signatureAlgorithm );
1797 1         6 $string .= sprintf( "%-*s: %s\n", $max, 'Signature', _wrap( $max+2, $self->signature ) );
1798 1         7 my $sp = $self->signature(2);
1799 1 50       5 if( $sp ) {
1800 0         0 foreach (sort keys %$sp) {
1801 0         0 my $v = $sp->{$_};
1802              
1803 0 0       0 if( ref $v ) {
1804 0 0       0 if( $v->can('as_hex') ) {
1805 0         0 $v = substr( $v->as_hex, 2 );
1806             } else {
1807 0         0 next;
1808             }
1809             }
1810 0         0 $string .= sprintf( "%-*s: %s\n", $max, $_, _wrap( $max+2, $v ) );
1811             }
1812             }
1813              
1814 1         7 $string .= "\n --Request--\n" . $self->csrRequest(1);
1815              
1816 1         8 return $string;
1817             }
1818              
1819             1;
1820              
1821             __END__