File Coverage

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