File Coverage

blib/lib/Crypt/X509/CRL.pm
Criterion Covered Total %
statement 71 282 25.1
branch 13 100 13.0
condition 2 3 66.6
subroutine 15 39 38.4
pod 30 30 100.0
total 131 454 28.8


line stmt bran cond sub pod time code
1             package Crypt::X509::CRL;
2            
3 1     1   47783 use Carp;
  1         3  
  1         108  
4 1     1   6 use strict;
  1         2  
  1         38  
5 1     1   5 use warnings;
  1         6  
  1         36  
6 1     1   1532 use Convert::ASN1 qw(:io :debug);
  1         95302  
  1         13924  
7            
8             require Exporter;
9            
10             our @ISA = qw(Exporter);
11             our %EXPORT_TAGS = ( 'all' => [qw()] );
12             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
13             our @EXPORT = qw( error new this_update next_update );
14             our $VERSION = '0.1';
15            
16             my $parser = undef;
17             my $asn = undef;
18             my $error = undef;
19            
20             my %oid2enchash= (
21             '1.2.840.113549.1.1.1' => {'enc' => 'RSA'},
22             '1.2.840.113549.1.1.2' => {'enc' => 'RSA', 'hash' => 'MD2'},
23             '1.2.840.113549.1.1.3' => {'enc' => 'RSA', 'hash' => 'MD4'},
24             '1.2.840.113549.1.1.4' => {'enc' => 'RSA', 'hash' => 'MD5'},
25             '1.2.840.113549.1.1.5' => {'enc' => 'RSA', 'hash' => 'SHA1'},
26             '1.2.840.113549.1.1.6' => {'enc' => 'OAEP'}
27             );
28            
29             my %oid2attr = (
30             "2.5.4.3" => "CN",
31             "2.5.4.6" => "C",
32             "2.5.4.7" => "l",
33             "2.5.4.8" => "S",
34             "2.5.4.10" => "O",
35             "2.5.4.11" => "OU",
36             "1.2.840.113549.1.9.1" => "E",
37             "0.9.2342.19200300.100.1.1" => "UID",
38             "0.9.2342.19200300.100.1.25" => "DC"
39             );
40            
41            
42             =head1 Crypt-X509::CRL version 0.1
43             F<===========================>
44            
45             Crypt::X509::CRL is an object oriented X.509 certificate revocation list
46             parser with numerous methods for directly extracting information from
47             certificate revocation lists.
48            
49             =head1 INSTALLATION
50            
51             To install this module type the following:
52            
53             perl Makefile.PL
54             make
55             make test
56             make install
57            
58             =head1 DEPENDENCIES
59            
60             This module requires:
61            
62             Convert::ASN1
63            
64             =head1 NAME
65            
66             Crypt::X509::CRL - Parses an X.509 certificate revocation list
67            
68             =head1 SYNOPSIS
69            
70             use Crypt::X509::CRL;
71            
72             $decoded = Crypt::X509::CRL->new( crl => $crl );
73            
74             $subject_email = $decoded->subject_email;
75             print "do not use after: ".gmtime($decoded->not_after)." GMT\n";
76            
77             =head1 REQUIRES
78            
79             Convert::ASN1
80            
81             =head1 DESCRIPTION
82            
83             B parses X.509 certificate revocation lists. Methods are
84             provided for accessing most CRL elements.
85            
86             It is based on the generic ASN.1 module by Graham Barr, on the
87             x509decode example by Norbert Klasen and contributions on the
88             perl-ldap-dev-Mailinglist by Chriss Ridd. It is also based upon the
89             works of Mike Jackson and Alexander Jung perl module Crypt::X509.
90            
91             The following RFC 3280 Extensions are available (noted are the ones I
92             have implemented).
93            
94             Authority Key Identifier (implemented)
95             CRL Number (implemented)
96             Issuing Distribution Point (implemented)
97             Issuer Alternative Name
98             Delta CRL Indicator
99             Freshest CRL (a.k.a. Delta CRL Distribution Point)
100            
101             The following RFC 3280 CRL Entry Extensions are available (noted are the
102             ones I have implemented).
103            
104             Reason Code (implemented)
105             Hold Instruction Code (implemented)
106             Invalidity Date (implemented)
107             Certificate Issuer
108            
109             NOTE: The use of 'utcTime' in determining the revocation date of a given
110             certificate is based on RFC 3280 for dates through the year 2049. Starting
111             with dates in 2050 and beyond the RFC calls for revocation dates to be
112             listed as 'generalTime'.
113            
114             =head1 CONSTRUCTOR
115            
116             =head2 new ( OPTIONS )
117            
118             Creates and returns a parsed X.509 CRL hash, containing the parsed
119             contents. The data is organised as specified in RFC 2459.
120             By default only the first ASN.1 Layer is decoded. Nested decoding
121             is done automagically through the data access methods.
122            
123             =over 4
124            
125             =item crl =E $crl
126            
127             A variable containing the DER formatted crl to be parsed
128             (eg. as stored in C attribute in an
129             LDAP-directory).
130            
131             =back
132            
133             =head3 Example:
134            
135             use Crypt::X509::CRL;
136             use Data::Dumper;
137            
138             $decoded = Crypt::X509::CRL->new( crl => $crl );
139            
140             print Dumper $decoded;
141            
142             =cut back
143            
144             sub new {
145 1     1 1 634 my ( $class , %args ) = @_;
146            
147 1 50       6 if ( not defined ( $parser ) ) {
148 1         7 $parser = _init();
149             }
150            
151 1         9 my $self = $parser->decode( $args{'crl'} );
152            
153 1         82584 $self->{'_error'} = $parser->error;
154 1         11 bless ( $self , $class );
155            
156 1         7 return $self;
157             }
158            
159             =head1 METHODS
160            
161             =head2 error
162            
163             Returns the last error from parsing, C when no error occured.
164             This error is updated on deeper parsing with the data access methods.
165            
166             =head3 Example:
167            
168             $decoded= Crypt::X509::CRL->new(crl => $crl);
169             if ( $decoded->error ) {
170             warn "Error on parsing Certificate Revocation List: ", $decoded->error;
171             }
172            
173             =cut back
174            
175             sub error {
176 1     1 1 1441 my $self = shift;
177 1         13 return $self->{'_error'};
178             }
179            
180             =head1 DATA ACCESS METHODS
181            
182             You can access all parsed data directly from the returned hash. For convenience
183             the following data access methods have been implemented to give quick access to
184             the most-used crl attributes.
185            
186             =head2 version
187            
188             Returns the certificate revocation list's version as an integer. Returns undef
189             if the version is not specified, since it is an optional field in some cases.
190            
191             =head3 NOTE that version is defined as an Integer where:
192            
193             0 = v1
194             1 = v2
195             2 = v3
196            
197             =cut back
198            
199             sub version {
200 0     0 1 0 my $self = shift;
201            
202 0 0       0 return undef if not exists $self->{'tbsCertList'}{'version'};
203            
204 0         0 return $self->{'tbsCertList'}{'version'};
205             }
206            
207             =head2 version_string
208            
209             Returns the certificate revocation list's version as a string value.
210            
211             =head3 NOTE that version is defined as an Integer where:
212            
213             0 = v1
214             1 = v2
215             2 = v3
216            
217             =cut back
218            
219             sub version_string {
220 0     0 1 0 my $self = shift;
221            
222 0 0       0 return undef if not exists $self->{'tbsCertList'}{'version'};
223            
224 0         0 my $v = $self->{'tbsCertList'}{'version'};
225 0 0       0 return "v1" if $v == 0;
226 0 0       0 return "v2" if $v == 1;
227 0 0       0 return "v3" if $v == 2;
228             }
229            
230             =head2 this_update
231            
232             Returns either the utcTime or generalTime of the certificate revocation list's date
233             of publication. Returns undef if not defined.
234            
235             =head3 Example:
236            
237             $decoded = Crypt::X509::CRL->new(crl => $crl);
238             print "CRL was published at ", gmtime( $decoded->this_update ), " GMT\n";
239            
240             =cut back
241            
242             sub this_update {
243 1     1 1 3 my $self = shift;
244 1 50       9 if ( exists $self->{'tbsCertList'}{'thisUpdate'}{'utcTime'} ) {
    0          
245 1         20 return $self->{'tbsCertList'}{'thisUpdate'}{'utcTime'};
246             } elsif ( exists $self->{'tbsCertList'}{'thisUpdate'}{'generalTime'} ) {
247 0         0 return $self->{'tbsCertList'}{'thisUpdate'}{'generalTime'};
248             } else {
249 0         0 return undef;
250             }
251             }
252            
253             =head2 next_update
254            
255             Returns either the utcTime or generalTime of the certificate revocation list's
256             date of expiration. Returns undef if not defined.
257            
258             =head3 Example:
259            
260             $decoded = Crypt::X509::CRL->new(crl => $crl);
261             if ( $decoded->next_update > time() ) {
262             warn "CRL has expired!";
263             }
264            
265             =cut back
266            
267             sub next_update {
268 1     1 1 4 my $self = shift;
269 1 50       8 if ( exists $self->{'tbsCertList'}{'nextUpdate'}{'utcTime'} ) {
    0          
270 1         7 return $self->{'tbsCertList'}{'nextUpdate'}{'utcTime'};
271             } elsif ( $self->{'tbsCertList'}{'nextUpdate'}{'generalTime'} ) {
272 0         0 return $self->{'tbsCertList'}{'nextUpdate'}{'generalTime'};
273             } else {
274 0         0 return undef;
275             }
276             }
277            
278             =head2 signature
279            
280             Return's the certificate's signature in binary DER format.
281            
282             =cut back
283            
284             sub signature {
285 0     0 1 0 my $self = shift;
286 0         0 return $self->{'signatureValue'}[0];
287             }
288            
289             =head2 signature_length
290            
291             Return's the length of the certificate's signature.
292            
293             =cut back
294            
295             sub signature_length {
296 1     1 1 6 my $self = shift;
297 1         8 return $self->{'signatureValue'}[1];
298             }
299            
300             =head2 signature_algorithm
301            
302             Returns the certificate's signature algorithm as an OID string.
303            
304             =head3 Example:
305            
306             $decoded = Crypt::X509::CRL->new(crl => $crl);
307             print "CRL signature is encrypted with:", $decoded->signature_algorithm, "\n";
308            
309             Example Output: CRL signature is encrypted with: 1.2.840.113549.1.1.5
310            
311             =cut back
312            
313             sub signature_algorithm {
314 1     1 1 3 my $self = shift;
315 1         8 return $self->{'tbsCertList'}{'signature'}{'algorithm'};
316             }
317            
318             =head2 SigEncAlg
319            
320             Returns the signature encryption algorithm (e.g. 'RSA') as a string.
321            
322             =head3 Example:
323            
324             $decoded = Crypt::X509::CRL->new(crl => $crl);
325             print "CRL signature is encrypted with:", $decoded->SigEncAlg, "\n";
326            
327             Example Output: CRL signature is encrypted with: RSA
328            
329            
330             =cut back
331            
332             sub SigEncAlg {
333 1     1 1 3 my $self = shift;
334 1         9 return $oid2enchash{ $self->{'tbsCertList'}{'signature'}->{'algorithm'} }->{'enc'};
335             }
336            
337             =head2 SigHashAlg
338            
339             Returns the signature hashing algorithm (e.g. 'SHA1') as a string.
340            
341             =head3 Example:
342            
343             $decoded = Crypt::X509::CRL->new(crl => $crl);
344             print "CRL signature is hashed with:", $decoded->SigHashAlg, "\n";
345            
346             Example Output: CRL signature is encrypted with: SHA1
347            
348             =cut back
349            
350             sub SigHashAlg {
351 1     1 1 2 my $self = shift;
352 1         7 return $oid2enchash{ $self->{'tbsCertList'}{'signature'}->{'algorithm'} }->{'hash'};
353             }
354            
355            
356             #########################################################################
357             # accessors - issuer
358             #########################################################################
359            
360             =head2 Issuer
361            
362             Returns a pointer to an array of strings building the DN of the certificate
363             issuer (= the DN of the CA). Attribute names for the most common Attributes
364             are translated from the OID-Numbers, unknown numbers are output verbatim.
365            
366             =head3 Example:
367            
368             $decoded = Crypt::X509::CRL->new( $crl );
369             print "CRL was issued by: ", join( ', ' , @{ $decoded->Issuer } ), "\n";
370            
371             =cut back
372            
373             sub Issuer {
374 1     1 1 2 my $self = shift;
375 1         2 my ( $i , $type );
376 1         4 my $issuerdn = $self->{'tbsCertList'}->{'issuer'}->{'rdnSequence'};
377            
378 1         5 $self->{'tbsCertList'}->{'issuer'}->{'dn'} = [];
379            
380 1         3 my $issuedn = $self->{'tbsCertList'}->{'issuer'}->{'dn'};
381            
382 1         23 for my $issue ( @{ $issuerdn } ) {
  1         4  
383 5         7 $i = @{ $issue }[0];
  5         18  
384 5 50       16 if ( $oid2attr{ $i->{'type'} } ) {
385 5         10 $type = $oid2attr{ $i->{'type'} };
386             } else {
387 0         0 $type = $i->{'type'};
388             }
389 5         5 my @key = keys ( %{ $i->{'value'} } );
  5         16  
390 5         5 push @{ $issuedn } , $type . "=" . $i->{'value'}->{ $key[0] };
  5         20  
391             }
392 1         16 return $issuedn;
393             }
394            
395             sub _issuer_part {
396 0     0   0 my $self = shift;
397 0         0 my $oid = shift;
398 0         0 my $issuerrdn = $self->{'tbsCertList'}->{'issuer'}->{'rdnSequence'};
399 0         0 for my $issue ( @{ $issuerrdn } ) {
  0         0  
400 0         0 my $i = @{ $issue }[0];
  0         0  
401 0 0       0 if ( $i->{'type'} eq $oid ) {
402 0         0 my @key = keys ( %{ $i->{'value'} } );
  0         0  
403 0         0 return $i->{'value'}->{ $key[0] };
404             }
405             }
406 0         0 return undef;
407             }
408            
409             =head2 issuer_cn
410            
411             Returns the string value for issuer's common name (= the value with the
412             OID 2.5.4.3 or in DN Syntax everything after C).
413             Only the first entry is returned. C if issuer contains no common name attribute.
414            
415             =cut back
416            
417             sub issuer_cn {
418 0     0 1 0 my $self = shift;
419 0         0 return _issuer_part( $self , '2.5.4.3' );
420             }
421            
422            
423             =head2 issuer_country
424            
425             Returns the string value for issuer's country (= the value with the
426             OID 2.5.4.6 or in DN Syntax everything after C).
427             Only the first entry is returned. C if issuer contains no country attribute.
428            
429             =cut back
430            
431             sub issuer_country {
432 0     0 1 0 my $self = shift;
433 0         0 return _issuer_part( $self , '2.5.4.6' );
434             }
435            
436             =head2 issuer_state
437            
438             Returns the string value for issuer's state or province (= the value with the
439             OID 2.5.4.8 or in DN Syntax everything after C).
440             Only the first entry is returned. C if issuer contains no state attribute.
441            
442             =cut back
443            
444             sub issuer_state {
445 0     0 1 0 my $self = shift;
446 0         0 return _issuer_part( $self , '2.5.4.8' );
447             }
448            
449             =head2 issuer_locality
450            
451             Returns the string value for issuer's locality (= the value with the
452             OID 2.5.4.7 or in DN Syntax everything after C).
453             Only the first entry is returned. C if issuer contains no locality attribute.
454            
455             =cut back
456            
457             sub issuer_locality {
458 0     0 1 0 my $self = shift;
459 0         0 return _issuer_part( $self , '2.5.4.7' );
460             }
461            
462             =head2 issuer_org
463            
464             Returns the string value for issuer's organization (= the value with the
465             OID 2.5.4.10 or in DN Syntax everything after C).
466             Only the first entry is returned. C if issuer contains no organization attribute.
467            
468             =cut back
469            
470             sub issuer_org {
471 0     0 1 0 my $self = shift;
472 0         0 return _issuer_part( $self , '2.5.4.10' );
473             }
474            
475             =head2 issuer_email
476            
477             Returns the string value for issuer's email address (= the value with the
478             OID 1.2.840.113549.1.9.1 or in DN Syntax everything after C).
479             Only the first entry is returned. C if issuer contains no email attribute.
480            
481             =cut back
482            
483             sub issuer_email {
484 0     0 1 0 my $self = shift;
485 0         0 return _issuer_part( $self , '1.2.840.113549.1.9.1' );
486             }
487            
488            
489             #########################################################################
490             #
491             # ------- EXTENSIONS -------
492             #
493             # valid RFC 3280 extensions:
494             # Authority Key Identifier (implemented)
495             # CRL Number (implemented)
496             # Issuing Distribution Point (implemented)
497             # Issuer Alternative Name
498             # Delta CRL Indicator
499             # Freshest CRL (a.k.a. Delta CRL Distribution Point)
500             #
501             #########################################################################
502            
503             =head2 key_identifier
504            
505             Returns the authority key identifier as a bit string.
506            
507             =head3 Example:
508            
509             $decoded = Crypt::X509::CRL->new( $crl );
510             my $s = unpack("H*" , $decoded->key_identifier);
511             print "The Authority Key Identifier in HEX is: $s\n";
512            
513             Example output:
514             The Authority Key Identifier in HEX is: 86595f93caf32da620a4f9595a4a935370e792c9
515            
516            
517             =cut back
518            
519             sub key_identifier {
520 0     0 1 0 my $self = shift;
521 0 0       0 if ( defined $self->_AuthorityKeyIdentifier ) { return ( $self->_AuthorityKeyIdentifier )->{keyIdentifier}; }
  0         0  
522 0         0 return undef;
523             }
524            
525             # _AuthorityKeyIdentifier
526             sub _AuthorityKeyIdentifier {
527 0     0   0 my $self = shift;
528 0         0 my $extensions = $self->{'tbsCertList'}->{'crlExtensions'};
529            
530 0 0       0 if ( not defined $extensions ) { return undef; } # no extensions in certificate
  0         0  
531            
532 0 0       0 if ( defined $self->{'tbsCertList'}{'AuthorityKeyIdentifier'} ) {
533 0         0 return ( $self->{'tbsCertList'}{'AuthorityKeyIdentifier'} );
534             }
535            
536 0         0 for my $extension ( @{ $extensions } ) {
  0         0  
537 0 0       0 if ( $extension->{'extnID'} eq '2.5.29.35' ) { # OID for AuthorityKeyIdentifier
538 0         0 my $parser = _init('AuthorityKeyIdentifier');
539 0         0 $self->{'tbsCertList'}{'AuthorityKeyIdentifier'} = $parser->decode( $extension->{'extnValue'} );
540 0 0       0 if ( $parser->error ) {
541 0         0 $self->{"_error"} = $parser->error;
542 0         0 return undef;
543             }
544 0         0 return $self->{'tbsCertList'}{'AuthorityKeyIdentifier'};
545             }
546             }
547 0         0 return undef;
548             }
549            
550             =head2 authorityCertIssuer
551            
552             Returns a pointer to an array of strings building the DN of the Authority Cert
553             Issuer. Attribute names for the most common Attributes are translated from the
554             OID-Numbers, unknown numbers are output verbatim. Returns undef if the
555             extension is not set in the certificate.
556            
557             =head3 Example:
558            
559             $decoded = Crypt::X509::CRL->new($cert);
560             print "Certificate was authorised by:", join( ', ', @{ $decoded->authorityCertIssuer } ), "\n";
561            
562             =cut back
563            
564             sub authorityCertIssuer {
565 0     0 1 0 my $self = shift;
566 0         0 my ( $i , $type );
567 0         0 my $rdn = _AuthorityKeyIdentifier( $self );
568 0 0       0 if ( not defined ( $rdn ) ) {
569 0         0 return (undef); # we do not have that extension
570             } else {
571 0         0 $rdn = $rdn->{'authorityCertIssuer'}[0]->{'directoryName'};
572             }
573 0         0 $rdn->{'dn'} = [];
574 0         0 my $dn = $rdn->{'dn'};
575 0         0 $rdn = $rdn->{'rdnSequence'};
576 0         0 for my $r ( @{ $rdn } ) {
  0         0  
577 0         0 $i = @{ $r }[0];
  0         0  
578 0 0       0 if ( $oid2attr{ $i->{'type'} } ) {
579 0         0 $type = $oid2attr{ $i->{'type'} };
580             } else {
581 0         0 $type = $i->{'type'};
582             }
583 0         0 my @key = keys ( %{ $i->{'value'} } );
  0         0  
584 0         0 push @{ $dn } , $type . "=" . $i->{'value'}->{ $key[0] };
  0         0  
585             }
586 0         0 return $dn;
587             }
588            
589             sub _authcert_part {
590 0     0   0 my $self = shift;
591 0         0 my $oid = shift;
592 0         0 my $rdn = _AuthorityKeyIdentifier( $self );
593 0 0       0 if ( not defined ( $rdn ) ) {
594 0         0 return (undef); # we do not have that extension
595             } else {
596 0         0 $rdn = $rdn->{'authorityCertIssuer'}[0]->{'directoryName'}->{'rdnSequence'};
597             }
598 0         0 for my $r ( @{ $rdn } ) {
  0         0  
599 0         0 my $i = @{ $r }[0];
  0         0  
600 0 0       0 if ( $i->{'type'} eq $oid ) {
601 0         0 my @key = keys ( %{ $i->{'value'} } );
  0         0  
602 0         0 return $i->{'value'}->{ $key[0] };
603             }
604             }
605 0         0 return undef;
606             }
607            
608             =head2 authority_serial
609            
610             Returns the authority's certificate serial number.
611            
612             =cut back
613            
614             sub authority_serial {
615 0     0 1 0 my $self = shift;
616 0         0 return ( $self->_AuthorityKeyIdentifier )->{authorityCertSerialNumber};
617             }
618            
619            
620             =head2 authority_cn
621            
622             Returns the authority's ca.
623            
624             =cut back
625            
626             sub authority_cn {
627 0     0 1 0 my $self = shift;
628 0         0 return _authcert_part( $self , '2.5.4.3' );
629             }
630            
631            
632             =head2 authority_country
633            
634             Returns the authority's country.
635            
636             =cut back
637            
638             sub authority_country {
639 0     0 1 0 my $self = shift;
640 0         0 return _authcert_part( $self , '2.5.4.6' );
641             }
642            
643             =head2 authority_state
644            
645             Returns the authority's state.
646            
647             =cut back
648            
649             sub authority_state {
650 0     0 1 0 my $self = shift;
651 0         0 return _authcert_part( $self , '2.5.4.8' );
652            
653             }
654            
655             =head2 authority_locality
656            
657             Returns the authority's locality.
658            
659             =cut back
660            
661             sub authority_locality {
662 0     0 1 0 my $self = shift;
663 0         0 return _authcert_part( $self , '2.5.4.7' );
664             }
665            
666             =head2 authority_org
667            
668             Returns the authority's organization.
669            
670             =cut back
671            
672             sub authority_org {
673 0     0 1 0 my $self = shift;
674 0         0 return _authcert_part( $self , '2.5.4.10' );
675             }
676            
677             =head2 authority_email
678            
679             Returns the authority's email.
680            
681             =cut back
682            
683             sub authority_email {
684 0     0 1 0 my $self = shift;
685 0         0 return _authcert_part( $self , '1.2.840.113549.1.9.1' );
686             }
687            
688             =head2 crl_number
689            
690             Returns the CRL Number as an integer.
691            
692             =cut back
693            
694             # crl_number (another extension)
695             sub crl_number {
696 1     1 1 2 my $self = shift;
697 1         2 my $extension;
698 1         4 my $extensions = $self->{'tbsCertList'}->{'crlExtensions'};
699            
700 1 50       8 if ( defined $self->{'tbsCertList'}{'cRLNumber'} ) {
701 0         0 return ( $self->{'tbsCertList'}{'cRLNumber'} );
702             }
703            
704 1 50       6 if ( not defined $extensions ) { return undef; } # no extensions in certificate
  0         0  
705            
706 1         3 for $extension ( @{ $extensions } ) {
  1         4  
707 2 100       10 if ( $extension->{'extnID'} eq '2.5.29.20' ) { # OID for CRLNumber
708 1         6 my $parser = _init('cRLNumber'); # get a parser for this
709 1         7 $self->{'tbsCertList'}{'cRLNumber'} = $parser->decode( $extension->{'extnValue'} ); # decode the value
710 1 50       106 if ( $parser->error ) {
711 0         0 $self->{"_error"} = $parser->error;
712 0         0 return undef;
713             }
714 1         16 return $self->{'tbsCertList'}{'cRLNumber'};
715             }
716             }
717 0         0 return undef;
718             }
719            
720             =head2 IDPs
721            
722             Returns the Issuing Distribution Points as a hash providing for the default values.
723            
724             =head3 Example:
725            
726             print "Issuing Distribution Points:\n";
727             my $IDPs = $decoded->IDPs;
728             for my $key ( sort keys %{ $IDPs } ) {
729             print "$key = ";
730             if ( defined $IDPs->{ $key } ) {
731             print $IDPs->{ $key }, "\n";
732             } else {
733             print "undef\n";
734             }
735             }
736            
737             =head3 Example Output:
738            
739             Issuing Distribution Points:
740             critical = 1
741             directory_addr = CN=CRL2, O=U.S. Government, C=US
742             indirectCRL = 0
743             onlyAttribCerts = 0
744             onlyCaCerts = 0
745             onlyUserCerts = 1
746             reasonFlags = undef
747             url = undef
748            
749             =head3 Example of returned data structure:
750            
751             critical = 0 or 1 # default is FALSE
752             directory_addr = CN=CR1,c=US # default is undef
753             url = ldap://ldap.gov/cn=CRL1,c=US # default is undef
754             onlyUserCerts = 0 or 1 # default is FALSE
755             onlyCaCerts = 0 or 1 # default is FALSE
756             onlyAttribCerts = 0 or 1 # default is FALSE
757             indirectCRL = 0 or 1 # default is FALSE
758             reasonFlags = BIT STRING # default is undef
759            
760             =cut back
761            
762             # IDPs
763             sub IDPs {
764 0     0 1 0 my $self = shift;
765 0         0 my $extension;
766 0         0 my $extensions = $self->{'tbsCertList'}->{'crlExtensions'};
767            
768 0 0       0 if ( defined $self->{'tbsCertList'}{'idp'} ) {
769 0         0 return ( $self->{'tbsCertList'}{'idp'} );
770             }
771            
772 0 0       0 if ( not defined $extensions ) { return undef; } # no extensions in certificate
  0         0  
773            
774 0         0 for $extension ( @{ $extensions } ) {
  0         0  
775 0 0       0 if ( $extension->{'extnID'} eq '2.5.29.28' ) { # OID for issuingDistributionPoint
776 0         0 my $parser = _init('issuingDistributionPoint'); # get a parser for this
777 0         0 my $idps = $parser->decode( $extension->{'extnValue'} ); # decode the value
778 0 0       0 if ( $parser->error ) {
779 0         0 $self->{"_error"} = $parser->error;
780 0         0 return undef;
781             }
782            
783             # set the critical flag
784 0 0       0 if ( exists $extension->{'critical'} ) {
785 0         0 $self->{'tbsCertList'}{'idp'}{'critical'} = $extension->{'critical'};
786             } else {
787 0         0 $self->{'tbsCertList'}{'idp'}{'critical'} = 0;
788             }
789            
790             # set the onlyContainsUserCerts flag
791 0 0       0 if ( exists $idps->{'onlyContainsUserCerts'} ) {
792 0         0 $self->{'tbsCertList'}{'idp'}{'onlyUserCerts'} = $idps->{'onlyContainsUserCerts'};
793             } else {
794 0         0 $self->{'tbsCertList'}{'idp'}{'onlyUserCerts'} = 0;
795             }
796            
797             # set the onlyContainsCACerts flag
798 0 0       0 if ( exists $idps->{'onlyContainsCACerts'} ) {
799 0         0 $self->{'tbsCertList'}{'idp'}{'onlyCaCerts'} = $idps->{'onlyContainsCACerts'};
800             } else {
801 0         0 $self->{'tbsCertList'}{'idp'}{'onlyCaCerts'} = 0;
802             }
803            
804             # set the onlyContainsAttributeCerts flag
805 0 0       0 if ( exists $idps->{'onlyContainsAttributeCerts'} ) {
806 0         0 $self->{'tbsCertList'}{'idp'}{'onlyAttribCerts'} = $idps->{'onlyContainsAttributeCerts'}
807             } else {
808 0         0 $self->{'tbsCertList'}{'idp'}{'onlyAttribCerts'} = 0;
809             }
810            
811             # set the indirectCRL flag
812 0 0       0 if ( exists $idps->{'indirectCRL'} ) {
813 0         0 $self->{'tbsCertList'}{'idp'}{'indirectCRL'} = $idps->{'indirectCRL'}
814             } else {
815 0         0 $self->{'tbsCertList'}{'idp'}{'indirectCRL'} = 0
816             }
817            
818             # set the defaults for directory_addr and url
819 0         0 $self->{'tbsCertList'}{'idp'}{'directory_addr'} = undef;
820 0         0 $self->{'tbsCertList'}{'idp'}{'url'} = undef;
821            
822             # set the directory_addr and/or URL values
823 0         0 for my $each_fullName ( @{ $idps->{'distributionPoint'}->{'fullName'} } ) { # this loops through multiple "fullName" values
  0         0  
824 0 0       0 if ( exists $each_fullName->{directoryName} ) {
    0          
825             # found a rdnSequence
826 0         0 $self->{'tbsCertList'}{'idp'}{'directory_addr'} =
827 0         0 join( ', ' , reverse @{ _IDP_rdn( $each_fullName->{directoryName}->{rdnSequence} ) } );
828             } elsif ( exists $each_fullName->{uniformResourceIdentifier} ) {
829             # found a URI
830 0         0 $self->{'tbsCertList'}{'idp'}{'url'} = $each_fullName->{uniformResourceIdentifier};
831             } else {
832             # found some other type of IDP value
833             # return undef;
834             }
835             }
836            
837             # set the reason flags BIT STRING
838 0 0       0 if ( exists $idps->{'onlySomeReasons'} ) {
839 0         0 $self->{'tbsCertList'}{'idp'}{'reasonFlags'} = $idps->{'onlySomeReasons'};
840             } else {
841 0         0 $self->{'tbsCertList'}{'idp'}{'reasonFlags'} = undef;
842             }
843            
844 0         0 return $self->{'tbsCertList'}{'idp'};
845             }
846             }
847 0         0 return undef;
848             }
849            
850             # internal function for parsing the rdn sequence parts
851             sub _IDP_rdn {
852 0     0   0 my $crl_rdn = shift; # this should be the passed in 'rdnSequence' array
853 0         0 my ( $i ,$type );
854 0         0 my $crl_dn = [];
855 0         0 for my $part ( @{$crl_rdn} ) {
  0         0  
856 0         0 $i = @{$part}[0];
  0         0  
857 0 0       0 if ( $oid2attr{ $i->{'type'} } ) {
858 0         0 $type = $oid2attr{ $i->{'type'} };
859             } else {
860 0         0 $type = $i->{'type'};
861             }
862 0         0 my @key = keys ( %{ $i->{'value'} } );
  0         0  
863 0         0 push @{ $crl_dn } , $type . "=" . $i->{'value'}->{ $key[0] };
  0         0  
864             }
865 0         0 return $crl_dn;
866             }
867            
868             #########################################################################
869             #
870             # ------- CRL ENTRY EXTENSIONS -------
871             #
872             # valid RFC 3280 CRL Entry Extensions:
873             # Reason Code
874             # Hold Instruction Code
875             # Invalidity Date
876             # Certificate Issuer
877             #
878             #########################################################################
879            
880             =head2 revocation_list
881            
882             Returns an array of hashes for the revoked certificates listed on the given CRL. The
883             keys to the hash are the certificate serial numbers in decimal format.
884            
885             =head3 Example:
886            
887             print "Revocation List:\n";
888             my $rls = $decoded->revocation_list;
889             my $count_of_rls = keys %{ $rls };
890             print "Found $count_of_rls revoked certificate(s) on this CRL.\n";
891             for my $key ( sort keys %{ $rls } ) {
892             print "Certificate: ", DecimalToHex( $key ), "\n";
893             for my $extn ( sort keys %{ $rls->{ $key } } ) {
894             if ( $extn =~ /date/i ) {
895             print "\t$extn: ", ConvertTime( $rls->{ $key }{ $extn } ), "\n";
896             } else {
897             print "\t$extn: ", $rls->{ $key }{ $extn }, "\n";
898             }
899             }
900             }
901            
902             =head3 Example Output:
903            
904             Revocation List:
905             Found 1 revoked certificate(s) on this CRL.
906             Certificate: 44 53 a0 f3
907             crlReason: keyCompromise
908             invalidityDate: Wednesday, September 27, 2006 12:54:51 PM
909             revocationDate: Wednesday, September 27, 2006 1:29:36 PM
910            
911             =cut back
912            
913             # revocation_list
914             sub revocation_list {
915 0     0 1 0 my $self = shift;
916 0         0 my @crl_reason = qw(unspecified keyCompromise cACompromise affiliationChanged superseded
917             cessationOfOperation certificateHold removeFromCRL privilegeWithdrawn
918             aACompromise);
919 0         0 my %hold_codes = (
920             '1.2.840.10040.2.1' => 'holdinstruction-none',
921             '1.2.840.10040.2.2' => 'holdinstruction-callissuer',
922             '1.2.840.10040.2.3' => 'holdinstruction-reject',
923             );
924            
925 0 0       0 if ( defined $self->{'tbsCertList'}{'rl'} ) {
926 0         0 return ( $self->{'tbsCertList'}{'rl'} );
927             }
928            
929 0         0 my $rls = $self->{'tbsCertList'}->{'revokedCertificates'};
930 0 0       0 if ( not defined $rls ) { # no revoked certs in this CRL
931 0         0 $self->{'tbsCertList'}{'rl'} = undef;
932 0         0 return $self->{'tbsCertList'}{'rl'};
933             }
934            
935 0         0 for my $rl ( @{ $rls } ) {
  0         0  
936             # the below assignment of 'utcTime' is based on the RFC of dates through the
937             # year 2049, after which the RFC calls for dates to be listed as
938             # 'GeneralizedTime' or in the ASN1 below for Time as 'generalTime'.
939 0 0       0 if ( exists $rl->{'revocationDate'}{'utcTime'} ) {
    0          
940 0         0 $self->{'tbsCertList'}{'rl'}{ $rl->{'userCertificate'} }{'revocationDate'} =
941             $rl->{'revocationDate'}{'utcTime'};
942             } elsif ( exists $rl->{'revocationDate'}{'generalTime'} ) {
943 0         0 $self->{'tbsCertList'}{'rl'}{ $rl->{'userCertificate'} }{'revocationDate'} =
944             $rl->{'revocationDate'}{'generalTime'};
945             } else {
946 0         0 $self->{'tbsCertList'}{'rl'}{ $rl->{'userCertificate'} }{'revocationDate'} = undef;
947             }
948            
949 0         0 for my $extension ( @{ $rl->{'crlEntryExtensions'} } ) {
  0         0  
950 0 0       0 if ( $extension->{'extnID'} eq '2.5.29.21' ) { # OID for crlReason
    0          
    0          
951 0         0 my $parser = _init('CRLReason'); # get a parser for this
952 0         0 my $reason = $parser->decode( $extension->{'extnValue'} ); # decode the value
953 0 0       0 if ( $parser->error ) {
954 0         0 $self->{"_error"} = $parser->error;
955 0         0 return undef;
956             }
957 0         0 $self->{'tbsCertList'}{'rl'}{ $rl->{'userCertificate'} }{'crlReason'} =
958             $crl_reason[ $reason ];
959            
960             } elsif ( $extension->{'extnID'} eq '2.5.29.24' ) { # OID for invalidityDate
961 0         0 my $parser = _init('invalidityDate');
962 0         0 my $invalid_date = $parser->decode( $extension->{'extnValue'} );
963 0 0       0 if ( $parser->error ) {
964 0         0 $self->{"_error"} = $parser->error;
965 0         0 return undef;
966             }
967 0         0 $self->{'tbsCertList'}{'rl'}{ $rl->{'userCertificate'} }{'invalidityDate'} =
968             $invalid_date;
969            
970             } elsif ( $extension->{'extnID'} eq '2.5.29.23' ) { # OID for holdInstructionCode
971 0         0 my $parser = _init('holdInstructionCode');
972 0         0 my $hold_code = $parser->decode( $extension->{'extnValue'} );
973 0 0       0 if ( $parser->error ) {
974 0         0 $self->{"_error"} = $parser->error;
975 0         0 return undef;
976             }
977 0         0 $self->{'tbsCertList'}{'rl'}{ $rl->{'userCertificate'} }{'holdInstructionCode'} =
978             $hold_codes{ $hold_code };
979            
980             } else {
981             # unimplemented OID(s) found
982 0         0 $self->{'tbsCertList'}{'rl'}{ $rl->{'userCertificate'} }{ $extension->{'extnID'} } =
983             $extension->{'extnValue'};
984             }
985             }
986             }
987 0         0 return $self->{'tbsCertList'}{'rl'};
988             }
989            
990            
991             #######################################################################
992             # internal function
993             #######################################################################
994            
995             # _init is the initialzation function and is also used for subsequent
996             # decoding of the inner parts of the object.
997             sub _init {
998 2     2   5 my $what = shift;
999 2 100 66     16 if ( ( not defined $what ) or ( '' eq $what ) ) { $what = 'CertificateList' }
  1         4  
1000 2 100       8 if ( not defined $asn) {
1001 1         9 $asn = Convert::ASN1->new;
1002 1         46 $asn->prepare(<
1003             -- ASN.1 from RFC 3280 and X509 (April 2002)
1004             -- Adapted for use with Convert::ASN1
1005            
1006            
1007             -- attribute data types --
1008            
1009             Attribute ::= SEQUENCE {
1010             type AttributeType,
1011             values SET OF AttributeValue
1012             -- at least one value is required --
1013             }
1014            
1015             AttributeType ::= OBJECT IDENTIFIER
1016            
1017             AttributeValue ::= DirectoryString --ANY
1018            
1019             AttributeTypeAndValue ::= SEQUENCE {
1020             type AttributeType,
1021             value AttributeValue
1022             }
1023            
1024            
1025             -- naming data types --
1026            
1027             Name ::= CHOICE { -- only one possibility for now
1028             rdnSequence RDNSequence
1029             }
1030            
1031             RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
1032            
1033             RelativeDistinguishedName ::=
1034             SET OF AttributeTypeAndValue --SET SIZE (1 .. MAX) OF
1035            
1036            
1037             -- Directory string type --
1038            
1039             DirectoryString ::= CHOICE {
1040             teletexString TeletexString, --(SIZE (1..MAX)),
1041             printableString PrintableString, --(SIZE (1..MAX)),
1042             bmpString BMPString, --(SIZE (1..MAX)),
1043             universalString UniversalString, --(SIZE (1..MAX)),
1044             utf8String UTF8String, --(SIZE (1..MAX)),
1045             ia5String IA5String, --added for EmailAddress,
1046             integer INTEGER
1047             }
1048            
1049            
1050             -- CRL specific structures begin here
1051            
1052             CertificateList ::= SEQUENCE {
1053             tbsCertList TBSCertList,
1054             signatureAlgorithm AlgorithmIdentifier,
1055             signatureValue BIT STRING
1056             }
1057            
1058            
1059             TBSCertList ::= SEQUENCE {
1060             version Version OPTIONAL, -- if present, MUST be v2
1061             signature AlgorithmIdentifier,
1062             issuer Name,
1063             thisUpdate Time,
1064             nextUpdate Time OPTIONAL,
1065            
1066             revokedCertificates RevokedCertificates OPTIONAL,
1067             crlExtensions [0] EXPLICIT Extensions OPTIONAL
1068             }
1069            
1070             RevokedCertificates ::= SEQUENCE OF RevokedCerts
1071            
1072             RevokedCerts ::= SEQUENCE {
1073             userCertificate CertificateSerialNumber,
1074             revocationDate Time,
1075             crlEntryExtensions Extensions OPTIONAL
1076             }
1077            
1078             -- Version, Time, CertificateSerialNumber, and Extensions
1079             -- are all defined in the ASN.1 in section 4.1
1080            
1081             -- AlgorithmIdentifier is defined in section 4.1.1.2
1082            
1083             Version ::= INTEGER --{ v1(0), v2(1), v3(2) }
1084            
1085             CertificateSerialNumber ::= INTEGER
1086            
1087             AlgorithmIdentifier ::= SEQUENCE {
1088             algorithm OBJECT IDENTIFIER,
1089             parameters ANY
1090             }
1091            
1092            
1093             Name ::= CHOICE { -- only one possibility for now
1094             rdnSequence RDNSequence
1095             }
1096            
1097            
1098             Time ::= CHOICE {
1099             utcTime UTCTime,
1100             generalTime GeneralizedTime
1101             }
1102            
1103             --extensions
1104            
1105             Extensions ::= SEQUENCE OF Extension --SIZE (1..MAX) OF Extension
1106            
1107             Extension ::= SEQUENCE {
1108             extnID OBJECT IDENTIFIER,
1109             critical BOOLEAN OPTIONAL, --DEFAULT FALSE,
1110             extnValue OCTET STRING
1111             }
1112            
1113             AuthorityKeyIdentifier ::= SEQUENCE {
1114             keyIdentifier [0] KeyIdentifier OPTIONAL,
1115             authorityCertIssuer [1] GeneralNames OPTIONAL,
1116             authorityCertSerialNumber [2] CertificateSerialNumber OPTIONAL }
1117             -- authorityCertIssuer and authorityCertSerialNumber shall both
1118             -- be present or both be absent
1119            
1120             KeyIdentifier ::= OCTET STRING
1121            
1122             GeneralNames ::= SEQUENCE OF GeneralName
1123            
1124             GeneralName ::= CHOICE {
1125             otherName [0] AnotherName,
1126             rfc822Name [1] IA5String,
1127             dNSName [2] IA5String,
1128             x400Address [3] ANY, --ORAddress,
1129             directoryName [4] Name,
1130             ediPartyName [5] EDIPartyName,
1131             uniformResourceIdentifier [6] IA5String,
1132             iPAddress [7] OCTET STRING,
1133             registeredID [8] OBJECT IDENTIFIER }
1134            
1135             -- AnotherName replaces OTHER-NAME ::= TYPE-IDENTIFIER, as
1136             -- TYPE-IDENTIFIER is not supported in the 88 ASN.1 syntax
1137            
1138             AnotherName ::= SEQUENCE {
1139             type OBJECT IDENTIFIER,
1140             value [0] EXPLICIT ANY } --DEFINED BY type-id }
1141            
1142             EDIPartyName ::= SEQUENCE {
1143             nameAssigner [0] DirectoryString OPTIONAL,
1144             partyName [1] DirectoryString }
1145            
1146             -- id-ce-issuingDistributionPoint OBJECT IDENTIFIER ::= { id-ce 28 }
1147            
1148             issuingDistributionPoint ::= SEQUENCE {
1149             distributionPoint [0] DistributionPointName OPTIONAL,
1150             onlyContainsUserCerts [1] BOOLEAN OPTIONAL, --DEFAULT FALSE,
1151             onlyContainsCACerts [2] BOOLEAN OPTIONAL, --DEFAULT FALSE,
1152             onlySomeReasons [3] ReasonFlags OPTIONAL,
1153             indirectCRL [4] BOOLEAN OPTIONAL, --DEFAULT FALSE,
1154             onlyContainsAttributeCerts [5] BOOLEAN OPTIONAL --DEFAULT FALSE
1155             }
1156            
1157             DistributionPointName ::= CHOICE {
1158             fullName [0] GeneralNames,
1159             nameRelativeToCRLIssuer [1] RelativeDistinguishedName }
1160            
1161             ReasonFlags ::= BIT STRING --{
1162             -- unused (0),
1163             -- keyCompromise (1),
1164             -- cACompromise (2),
1165             -- affiliationChanged (3),
1166             -- superseded (4),
1167             -- cessationOfOperation (5),
1168             -- certificateHold (6),
1169             -- privilegeWithdrawn (7),
1170             -- aACompromise (8) }
1171            
1172             -- id-ce-cRLNumber OBJECT IDENTIFIER ::= { id-ce 20 }
1173            
1174             cRLNumber ::= INTEGER --(0..MAX)
1175            
1176             -- id-ce-cRLReason OBJECT IDENTIFIER ::= { id-ce 21 }
1177            
1178             -- reasonCode ::= { CRLReason }
1179            
1180             CRLReason ::= ENUMERATED {
1181             unspecified (0),
1182             keyCompromise (1),
1183             cACompromise (2),
1184             affiliationChanged (3),
1185             superseded (4),
1186             cessationOfOperation (5),
1187             certificateHold (6),
1188             removeFromCRL (8),
1189             privilegeWithdrawn (9),
1190             aACompromise (10) }
1191            
1192             -- id-ce-holdInstructionCode OBJECT IDENTIFIER ::= { id-ce 23 }
1193            
1194             holdInstructionCode ::= OBJECT IDENTIFIER
1195            
1196             -- holdInstruction OBJECT IDENTIFIER ::=
1197             -- { iso(1) member-body(2) us(840) x9-57(10040) 2 }
1198             --
1199             -- id-holdinstruction-none OBJECT IDENTIFIER ::= {holdInstruction 1}
1200             -- id-holdinstruction-callissuer
1201             -- OBJECT IDENTIFIER ::= {holdInstruction 2}
1202             -- id-holdinstruction-reject OBJECT IDENTIFIER ::= {holdInstruction 3}
1203            
1204             -- id-ce-invalidityDate OBJECT IDENTIFIER ::= { id-ce 24 }
1205            
1206             invalidityDate ::= GeneralizedTime
1207            
1208             -- id-ce-certificateIssuer OBJECT IDENTIFIER ::= { id-ce 29 }
1209            
1210             certificateIssuer ::= GeneralNames
1211            
1212             ASN1
1213             }
1214 2         40419 my $self = $asn->find( $what );
1215 2         41 return $self;
1216             }
1217            
1218            
1219             =head1 SEE ALSO
1220            
1221             See the examples of C and the Mailing List.
1222             An example on how to load certificates can be found in F.
1223            
1224             =head1 ACKNOWLEDGEMENTS
1225            
1226             This module is based on the x509decode script, which was contributed to
1227             Convert::ASN1 in 2002 by Norbert Klasen.
1228            
1229             It is also based on the Crypt::X509 perl module, which was contributed
1230             by Mike Jackson and Alexander Jung.
1231            
1232             =head1 AUTHOR
1233            
1234             Duncan Segrest ,
1235            
1236             =head1 COPYRIGHT AND LICENSE
1237            
1238             Copyright (c) 2007 by Duncan Segrest .
1239            
1240             This library is free software; you can redistribute it and/or modify
1241             it under the same terms as Perl itself, either Perl version 5.8.8 or,
1242             at your option, any later version of Perl 5 you may have available.
1243            
1244             =cut
1245            
1246             1;
1247             __END__