File Coverage

blib/lib/VOMS/Lite/X509.pm
Criterion Covered Total %
statement 355 494 71.8
branch 161 326 49.3
condition 83 138 60.1
subroutine 12 12 100.0
pod 2 2 100.0
total 613 972 63.0


line stmt bran cond sub pod time code
1             package VOMS::Lite::X509;
2              
3 1     1   1241 use 5.004;
  1         5  
  1         51  
4 1     1   6 use strict;
  1         3  
  1         63  
5 1     1   974 use VOMS::Lite::ASN1Helper qw(ASN1OIDtoOID ASN1Index ASN1Wrap ASN1Unwrap ASN1UnwrapHex DecToHex Hex ASN1BitStr OIDtoASN1OID);
  1         3  
  1         125  
6 1     1   958 use VOMS::Lite::KEY;
  1         3  
  1         47  
7 1     1   844 use Digest::SHA1 qw(sha1_hex);
  1         979  
  1         74  
8 1     1   8 use Digest::MD5 qw(md5);
  1         2  
  1         58  
9 1     1   1077 use Time::Local qw(timegm);
  1         2505  
  1         73  
10 1     1   1190 use VOMS::Lite::RSAKey;
  1         3  
  1         100  
11 1     1   1093 use VOMS::Lite::CertKeyHelper;
  1         4  
  1         69  
12              
13             require Exporter;
14 1     1   8 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
  1         2  
  1         8959  
15             @ISA = qw(Exporter);
16              
17             $VERSION = '0.20';
18              
19             sub Examine {
20 10     10 1 44 my ($decoded,$dataref)=@_;
21 10         72 my %Values=%$dataref;
22 10         59 my @ASN1Index=ASN1Index($decoded);
23              
24 10 50       54 return ( {Errors=>["Unable to parse certificate"]} ) if (@ASN1Index==0);
25              
26 10         169 my ($index,$ignoreuntil)=(0,0);
27 10         32 my ($X509TBSCert,$X509version,$X509serial,$X509signature,$X509issuer,$X509validity,$X509subject,
28             $X509subjectPublicKeyInfo,$X509issuerUniqueID,$X509subjectUniqueID,$X509extensions,
29             $X509SignatureAlgorithm,$X509SignatureValue);
30              
31             # Drill down into the certificate
32 10         16 shift @ASN1Index; # skip the wrapping of the certificate sequence
33 10         31 my $TBSCertRef=shift @ASN1Index; #CertificateTBS Sequence
34 10 100       67 if (defined $Values{X509TBSCert}) {
35 2         11 my ($CLASS,$CONSTRUCTED,$TAG,$HEADSTART,$HEADLEN,$CHUNKLEN) = @$TBSCertRef;
36 2         22 $Values{X509TBSCert}=substr($decoded,$HEADSTART,($HEADLEN+$CHUNKLEN));
37             }
38              
39             # Extract the main components of the certificate
40 10         29 foreach (@ASN1Index) {
41 615         927 my ($CLASS,$CONSTRUCTED,$TAG,$HEADSTART,$HEADLEN,$CHUNKLEN) = @$_;
42 615 100       780 if ( $HEADSTART < $ignoreuntil ) { next; }
  515         597  
43             else {
44 100 100 66     2861 if ($index==0 && $CLASS==2 && $TAG==0) {$X509version = substr($decoded,$HEADSTART,($HEADLEN+$CHUNKLEN));$index++;}
  10 100 66     27  
  10 100 66     13  
  10 100 66     18  
    100 66        
    100 66        
    100 66        
    50 66        
    50 100        
    100 66        
    100 100        
    50 66        
      100        
      66        
      66        
      33        
45 10         16 elsif ($index==1 && $TAG==2) {$X509serial = substr($decoded,$HEADSTART,($HEADLEN+$CHUNKLEN));$index++;}
  10         23  
46 10         15 elsif ($index==2 && $TAG==16) {$X509signature = substr($decoded,$HEADSTART,($HEADLEN+$CHUNKLEN));$index++;}
  10         25  
47 10         14 elsif ($index==3 && $TAG==16) {$X509issuer = substr($decoded,$HEADSTART,($HEADLEN+$CHUNKLEN));$index++;}
  10         23  
48 10         15 elsif ($index==4 && $TAG==16) {$X509validity = substr($decoded,$HEADSTART,($HEADLEN+$CHUNKLEN));$index++;}
  10         44  
49 10         11 elsif ($index==5 && $TAG==16) {$X509subject = substr($decoded,$HEADSTART,($HEADLEN+$CHUNKLEN));$index++;}
  10         25  
50 10         13 elsif ($index==6 && $TAG==16) {$X509subjectPublicKeyInfo = substr($decoded,$HEADSTART,($HEADLEN+$CHUNKLEN));$index++;}
  0         0  
51 0         0 elsif ($index==7 && $CLASS==2 && $TAG==1) {$X509issuerUniqueID = substr($decoded,$HEADSTART,($HEADLEN+$CHUNKLEN));}
52 10         31 elsif ($index==7 && $CLASS==2 && $TAG==2) {$X509subjectUniqueID = substr($decoded,$HEADSTART,($HEADLEN+$CHUNKLEN));}
53 10         24 elsif ($index==7 && $CLASS==2 && $TAG==3) {$X509extensions = substr($decoded,$HEADSTART,($HEADLEN+$CHUNKLEN));}
54 10         13 elsif ($index==7 && $TAG==16) {$X509SignatureAlgorithm = substr($decoded,$HEADSTART,($HEADLEN+$CHUNKLEN));$index++;}
  10         25  
55             elsif ($index==8 && $TAG==3) {$X509SignatureValue = substr($decoded,$HEADSTART,($HEADLEN+$CHUNKLEN));}
56 100         163 $ignoreuntil=$HEADSTART+$HEADLEN+$CHUNKLEN;
57             }
58             }
59 10 50       30 if ($index != 8) {return undef;} #Failed to read certificate
  0         0  
60              
61             #Standard
62 10 50       44 if (defined $Values{X509version}) {$Values{X509version}=$X509version;}
  0         0  
63 10 100       26 if (defined $Values{X509serial}) {$Values{X509serial}=$X509serial;}
  4         9  
64 10 50       35 if (defined $Values{X509signature}) {$Values{X509signature}=$X509signature;}
  0         0  
65 10 100       37 if (defined $Values{X509issuer}) {$Values{X509issuer}=$X509issuer;}
  4         7  
66 10 50       27 if (defined $Values{X509validity}) {$Values{X509validity}=$X509validity;}
  0         0  
67 10 100       28 if (defined $Values{X509subject}) {$Values{X509subject}=$X509subject;}
  5         13  
68 10 50       33 if (defined $Values{X509subjectPublicKeyInfo}) {$Values{X509subjectPublicKeyInfo}=$X509subjectPublicKeyInfo;}
  0         0  
69 10 50       22 if (defined $Values{X509issuerUniqueID}) {$Values{X509issuerUniqueID}=$X509issuerUniqueID;}
  0         0  
70 10 50       22 if (defined $Values{X509subjectUniqueID}) {$Values{X509subjectUniqueID}=$X509subjectUniqueID;}
  0         0  
71 10 50       22 if (defined $Values{X509extensions}) {$Values{X509extensions}=$X509extensions;}
  0         0  
72 10 50       31 if (defined $Values{X509SignatureValue}) {$Values{X509SignatureValue}=$X509SignatureValue;}
  0         0  
73              
74             ##################
75             # Helpers -- Deeper parsing of certificate
76              
77             # Values of Start and End Time Seconds since Epoch
78 10 100 100     64 if (defined $Values{Start} || defined $Values{End}) {
79 5         20 my @validity=ASN1Unwrap($X509validity);
80 5         20 my @st=ASN1Unwrap($validity[5]);
81 5         24 my @et=ASN1Unwrap(substr($validity[5],$st[0]+$st[1]));
82 5 50 33     68 if ( $st[4] eq "23" && $st[5]=~ /^(..)(..)(..)(..)(..)(..)Z$/ ) { $Values{Start} = timegm($6,$5,$4,$3,($2-1),$1); }
  5 0 0     50  
83 0         0 elsif ( $st[4] eq "24" && $st[5]=~ /^(....)(..)(..)(..)(..)(..)Z$/ ) { $Values{Start} = timegm($6,$5,$4,$3,($2-1),$1); }
84 5 50 33     263 if ( $et[4] eq "23" && $et[5]=~ /^(..)(..)(..)(..)(..)(..)Z$/ ) { $Values{End} = timegm($6,$5,$4,$3,($2-1),$1); }
  5 0 0     29  
85 0         0 elsif ( $et[4] eq "24" && $et[5]=~ /^(....)(..)(..)(..)(..)(..)Z$/ ) { $Values{End} = timegm($6,$5,$4,$3,($2-1),$1); }
86             }
87              
88             # The String Repersentation of the subject and issuer DNs
89 10 100 66     203 if (defined $Values{SubjectDN} || defined $Values{Proxy}) {
90 5         29 my @ASN1SubjectDNIndex=ASN1Index($X509subject);
91 5         13 shift @ASN1SubjectDNIndex;
92 5         16 $Values{SubjectDN}="";
93 5         18 while (@ASN1SubjectDNIndex) {
94 15         35 my ($CLASS,$CONSTRUCTED,$TAG,$HEADSTART,$HEADLEN,$CHUNKLEN)=(0,0,0,0,0);
95 15         38 until ($TAG == 6 ) { ($CLASS,$CONSTRUCTED,$TAG,$HEADSTART,$HEADLEN,$CHUNKLEN) = @{shift @ASN1SubjectDNIndex}; }
  45         45  
  45         248  
96 15         34 my $OID=substr($X509subject,($HEADSTART+$HEADLEN),$CHUNKLEN);
97 15         16 ($CLASS,$CONSTRUCTED,$TAG,$HEADSTART,$HEADLEN,$CHUNKLEN) = @{shift @ASN1SubjectDNIndex};
  15         35  
98 15         38 my $Value=substr($X509subject,($HEADSTART+$HEADLEN),$CHUNKLEN);
99 15         58 $Values{SubjectDN}.="/".VOMS::Lite::CertKeyHelper::OIDtoDNattrib(ASN1OIDtoOID($OID))."=$Value";
100             }
101             }
102 10 100 66     48 if (defined $Values{IssuerDN} || defined $Values{Proxy}) {
103 5         23 my @ASN1IssuerDNIndex=ASN1Index($X509issuer);
104 5         13 shift @ASN1IssuerDNIndex;
105 5         16 $Values{IssuerDN}="";
106 5         19 while (@ASN1IssuerDNIndex) {
107 15         30 my ($CLASS,$CONSTRUCTED,$TAG,$HEADSTART,$HEADLEN,$CHUNKLEN)=(0,0,0,0,0);
108 15         34 until ($TAG == 6 ) { ($CLASS,$CONSTRUCTED,$TAG,$HEADSTART,$HEADLEN,$CHUNKLEN) = @{shift @ASN1IssuerDNIndex}; }
  45         51  
  45         181  
109 15         28 my $OID=substr($X509issuer,($HEADSTART+$HEADLEN),$CHUNKLEN);
110 15         19 ($CLASS,$CONSTRUCTED,$TAG,$HEADSTART,$HEADLEN,$CHUNKLEN) = @{shift @ASN1IssuerDNIndex};
  15         40  
111 15         36 my $Value=substr($X509issuer,($HEADSTART+$HEADLEN),$CHUNKLEN);
112 15         48 $Values{IssuerDN}.="/".VOMS::Lite::CertKeyHelper::OIDtoDNattrib(ASN1OIDtoOID($OID))."=$Value";
113             }
114             }
115              
116             # Public key and Modulus
117 10 100 66     68 if ( defined $Values{KeypublicExponent} || defined $Values{Keymodulus} ) {
118 2         4 my ($OID,$modexpbitstr);
119 2         10 my @KeyIndex=ASN1Index($X509subjectPublicKeyInfo);
120 2         6 foreach (@KeyIndex) {
121 10         45 my ($CLASS,$CONSTRUCTED,$TAG,$HEADSTART,$HEADLEN,$CHUNKLEN) = @$_;
122 10 100       26 if ( ! $CONSTRUCTED ) {
123 6 100       25 $OID=Hex(substr($X509subjectPublicKeyInfo,($HEADSTART+$HEADLEN),$CHUNKLEN)) if ( $TAG == 6 );
124 6 100       22 $modexpbitstr=substr($X509subjectPublicKeyInfo,($HEADSTART+$HEADLEN),$CHUNKLEN) if ( $TAG == 3 );
125             }
126             }
127 2 50       9 if ( $OID eq "2a864886f70d010101" ) {
128 2         17 $modexpbitstr=~ s/.//; # BS always encoding 8 bit bytes
129 2         7 my @KeyIndex2=ASN1Index($modexpbitstr);
130 2         6 shift @KeyIndex2;
131 2         4 my ($CLASS,$CONSTRUCTED,$TAG,$HEADSTART,$HEADLEN,$CHUNKLEN) = @{shift @KeyIndex2};
  2         6  
132 2         7 $Values{Keymodulus}=substr($modexpbitstr,($HEADSTART+$HEADLEN),$CHUNKLEN);
133 2         4 ($CLASS,$CONSTRUCTED,$TAG,$HEADSTART,$HEADLEN,$CHUNKLEN) = @{shift @KeyIndex2};
  2         6  
134 2         12 $Values{KeypublicExponent}=substr($modexpbitstr,($HEADSTART+$HEADLEN),$CHUNKLEN);
135             }
136             }
137              
138             # Issuer and Subject Hashes
139 10 100       35 if (defined $Values{Hash}) {
140 3         27 my $data=md5($X509subject);
141 3         35 $Values{Hash}=Hex( substr($data,3,1).substr($data,2,1).substr($data,1,1).substr($data,0,1) );
142             }
143 10 100       30 if (defined $Values{IHash}) {
144 3         12 my $data=md5($X509issuer);
145 3         17 $Values{IHash}=Hex( substr($data,3,1).substr($data,2,1).substr($data,1,1).substr($data,0,1) );
146             }
147              
148             # SSLv3 Certificate extensions
149 10         37 my @Exts=(1,1,1,1,1,$X509extensions,1);
150 10         33 my @ExtIndex=ASN1Index($X509extensions);
151 10         25 shift (@ExtIndex) ; shift @ExtIndex; #unwrap twice from tag 3
  10         25  
152 10         40 while (@ExtIndex) {
153              
154             # Get OID
155 45         85 my ($CLASS,$CONSTRUCTED,$TAG,$HEADSTART,$HEADLEN,$CHUNKLEN)=(0,0,0,0,0);
156 45         95 until ($TAG == 6 ) { ($CLASS,$CONSTRUCTED,$TAG,$HEADSTART,$HEADLEN,$CHUNKLEN) = @{shift @ExtIndex}; }
  90         89  
  90         376  
157 45         83 my $OID=substr($X509extensions,($HEADSTART+$HEADLEN),$CHUNKLEN);
158             # Calculate OID string value
159 45         120 my $OIDstr=ASN1OIDtoOID($OID);
160              
161             # Check for Criticality and get data
162 45         55 ($CLASS,$CONSTRUCTED,$TAG,$HEADSTART,$HEADLEN,$CHUNKLEN) = @{shift @ExtIndex};
  45         112  
163 45         83 my $CRITICAL=0;
164 45 100       146 if ($TAG == 1) { # Check Criticality
165 20         43 $CRITICAL=ord(substr($X509extensions,($HEADSTART+$HEADLEN+2),1));
166 20         28 ($CLASS,$CONSTRUCTED,$TAG,$HEADSTART,$HEADLEN,$CHUNKLEN) = @{shift @ExtIndex};
  20         55  
167             }
168 45         97 my $Value=substr($X509extensions,($HEADSTART+$HEADLEN),$CHUNKLEN);
169              
170             # Return Extension if requested
171 45 50       133 if (defined $Values{"Extension:$OIDstr"} ) {
172 0         0 $Values{"Extension:$OIDstr"} = $Value;
173             }
174              
175             # GSI Proxy (check for Pre-RFC and RFC)
176 45 50 33     157 if (defined $Values{ProxyInfo} && # Has Proxyinfo been requested
      66        
177             ( $OIDstr eq "1.3.6.1.5.5.7.1.14" || $OIDstr eq "1.3.6.1.4.1.3536.1.222" ) ) {
178 0 0       0 next if ( ! $CRITICAL ); # MUST be critical (if it's not we can ignore it anyhow)
179 0         0 my $ProxyPolicy;
180 0         0 my $PCI = ASN1Unwrap($Value);
181 0 0       0 my $PType = ( $OIDstr eq "1.3.6.1.5.5.7.1.14" ) ? "RFC" : "Pre-RFC";
182 0 0       0 $Values{"ProxyInfo"} .= ( $Values{"ProxyInfo"} eq "" ) ? "$PType" : ":$PType"; #Could have both ProxyInfo
183 0         0 $Values{"ProxyInfo$PType"} = $PCI;
184 0         0 $Values{"ProxyPolicyOID$PType"} = undef;
185 0         0 $Values{"ProxyPolicy$PType"} = undef;
186 0         0 $Values{"ProxyPathlen$PType"} = undef;
187 0         0 until (length($PCI) == 0) { # Get the first level
188 0         0 my ($headlen,$reallen,$Class,$Constructed,$Tag,$str)=ASN1Unwrap($PCI);
189 0         0 $PCI=substr($PCI,($headlen+$reallen));
190 0 0 0     0 if ($Tag==16) { $ProxyPolicy=$str; }
  0 0       0  
    0          
191 0         0 elsif ( $Tag==2 && $PType eq "RFC" ) { $Values{"ProxyPathlen$PType"}=ord($str); }
192 0         0 elsif ( $Tag==1 ) { $Values{"ProxyPathlen$PType"}=ord(ASN1Unwrap($str)); }
193             }
194 0         0 until (length($ProxyPolicy) == 0) {
195 0         0 my ($headlen,$reallen,$Class,$Constructed,$Tag,$str)=ASN1Unwrap($ProxyPolicy);
196 0         0 $ProxyPolicy=substr($ProxyPolicy,($headlen+$reallen));
197 0 0       0 if ($Tag==6) { $Values{"ProxyPolicyOID$PType"}=$str; }
  0 0       0  
198 0         0 elsif ($Tag==4) { $Values{"ProxyPolicy$PType"}=$str; }
199             }
200 0         0 next;
201             }
202              
203             # Subject Alternative Name
204 45 50 66     143 if (defined $Values{subjectAltName} && $OIDstr eq "2.5.29.17" ) {
205 0         0 my $SAN=ASN1Unwrap($Value);
206 0         0 $Values{subjectAltName}=scalar ASN1Unwrap($Value);
207 0         0 my @SAN;
208 0         0 until (length($SAN) == 0) {
209 0         0 my ($headlen,$reallen,$Class,$Constructed,$Tag,$str)=ASN1Unwrap($SAN);
210 0         0 $SAN=substr($SAN,($headlen+$reallen));
211 0 0       0 if ($Tag==0) {push @SAN,"otherName=$str"}
  0 0       0  
  0 0       0  
    0          
    0          
    0          
    0          
    0          
    0          
212 0         0 elsif ($Tag==1) {push @SAN,"rfc822Name=$str";}
213 0         0 elsif ($Tag==2) {push @SAN,"dNSName=$str";}
214 0         0 elsif ($Tag==3) {push @SAN,"x400Address=$str";}
215 0         0 elsif ($Tag==4) {push @SAN,"directoryName=$str";}
216 0         0 elsif ($Tag==5) {push @SAN,"ediPartyName=$str";}
217 0         0 elsif ($Tag==6) {push @SAN,"uniformResourceIdentifier=$str";}
218 0         0 elsif ($Tag==7) {push @SAN,"IPAddress=$str";}
219             elsif ($Tag==8) {push @SAN,"registeredID=$str";}
220             }
221 0         0 $Values{subjectAltNameArray}=\@SAN;
222 0         0 next;
223             }
224              
225             # Issuer Alternative Name
226 45 50 33     125 if (defined $Values{issuerAltName} && $OIDstr eq "2.5.29.18" ) {
227 0         0 my $IAN=ASN1Unwrap($Value);
228 0         0 $Values{issuerAltName}=scalar ASN1Unwrap($Value);
229 0         0 my @IAN;
230 0         0 until (length($IAN) == 0) {
231 0         0 my ($headlen,$reallen,$Class,$Constructed,$Tag,$str)=ASN1Unwrap($IAN);
232 0         0 $IAN=substr($IAN,($headlen+$reallen));
233 0 0       0 if ($Tag==0) {push @IAN,"otherName=$str"}
  0 0       0  
  0 0       0  
    0          
    0          
    0          
    0          
    0          
    0          
234 0         0 elsif ($Tag==1) {push @IAN,"rfc822Name=$str";}
235 0         0 elsif ($Tag==2) {push @IAN,"dNSName=$str";}
236 0         0 elsif ($Tag==3) {push @IAN,"x400Address=$str";}
237 0         0 elsif ($Tag==4) {push @IAN,"directoryName=$str";}
238 0         0 elsif ($Tag==5) {push @IAN,"ediPartyName=$str";}
239 0         0 elsif ($Tag==6) {push @IAN,"uniformResourceIdentifier=$str";}
240 0         0 elsif ($Tag==7) {push @IAN,"IPAddress=$str";}
241             elsif ($Tag==8) {push @IAN,"registeredID=$str";}
242             }
243 0         0 $Values{issuerAltNameArray}=\@IAN;
244 0         0 next;
245             }
246              
247             # Subject Key Identifier
248 45 100 100     519 if (defined $Values{subjectKeyIdentifier} && $OIDstr eq "2.5.29.14" ) {
    100 100        
    100 100        
    100 100        
249 8         29 my $SKI=ASN1Unwrap($Value);
250 8         33 $Values{subjectKeyIdentifier}=scalar ASN1Unwrap($Value);
251 8         24 next;
252             }
253             # Authority key identifier
254             elsif (defined $Values{authorityKeyIdentifier} && $OIDstr eq "2.5.29.35" ) {
255 5         25 my $AKI=ASN1Unwrap($Value);
256 5         11 $Values{authorityKeyIdentifier}=$AKI;
257 5         16 $Values{authorityKeyIdentifierSkid} = undef; #explicitly undefine these incase they were set in the call!
258 5         19 $Values{authorityKeyIdentifierIssuer} = undef; #
259 5         11 $Values{authorityKeyIdentifierSerial} = undef; #
260 5         20 until (length($AKI) == 0) {
261 15         42 my ($headlen,$reallen,$Class,$Constructed,$Tag,$str)=ASN1Unwrap($AKI);
262 15         38 $AKI=substr($AKI,($headlen+$reallen));
263 15 100       165 if ($Tag==0) {$Values{authorityKeyIdentifierSkid}=$str;}
  5 100       20  
  5 50       20  
264 5         22 elsif ($Tag==1) {$Values{authorityKeyIdentifierIssuer}=$str;}
265             elsif ($Tag==2) {$Values{authorityKeyIdentifierSerial}=Hex($str);}
266             }
267 5         16 next;
268             }
269             # Key Usage
270             elsif (defined $Values{keyUsage} && $OIDstr eq "2.5.29.15" ) {
271 5         14 my $KU=ASN1Unwrap($Value);
272 5         9 my $ignore;
273 5 50       45 if ($KU =~ s/^(.)//) { $ignore = ord($1); }
  5         16  
274 5         9 my @B;
275 5         32 $KU =~ s|(.)|push @B,split(//, unpack("B*", $&))|ge;
  5         58  
276 5         23 splice @B,-$ignore;
277 5         31 $Values{keyUsage}=unpack("N", pack("B32",substr("0" x 32 . join("",@B), -32 )));
278 5 50       30 $Values{keyUsageDigitalSignature} = (defined $B[0])?$B[0]:0;
279 5 50       24 $Values{keyUsageNonRepudiation} = (defined $B[1])?$B[1]:0;
280 5 50       19 $Values{keyUsageKeyEncipherment} = (defined $B[2])?$B[2]:0;
281 5 50       23 $Values{keyUsageDataEncipherment} = (defined $B[3])?$B[3]:0;
282 5 50       21 $Values{keyUsageKeyAgreement} = (defined $B[4])?$B[4]:0;
283 5 100       19 $Values{keyUsageKeyCertSign} = (defined $B[5])?$B[5]:0;
284 5 100       16 $Values{keyUsageCRLSign} = (defined $B[6])?$B[6]:0;
285 5 50       16 $Values{keyUsageEncipherOnly} = (defined $B[7])?$B[7]:0;
286 5 50       15 $Values{keyUsageDecipherOnly} = (defined $B[8])?$B[8]:0;
287 5         21 next;
288             }
289             # Basic Constraints
290             elsif (defined $Values{basicConstraints} && $OIDstr eq "2.5.29.19" ) {
291 5         16 my $BC=ASN1Unwrap($Value);
292 5         13 $Values{basicConstraints} = $BC;
293 5         15 $Values{basicConstraintsCA} = 0; #explicitly undefine these incase they were set in the call!
294 5         11 $Values{basicConstraintsPathLen} = undef;
295 5 100       33 if ($BC =~ /\x01\x01(.)(.*)/) {
296 3         9 $Values{basicConstraintsCA} = ord($1);
297 3 50       15 if ($2 =~ /(.+)/) { $Values{basicConstraintsPathLen} = unpack("N",ASN1Unwrap($1));}
  0         0  
298             }
299 5         22 next;
300             }
301             }
302              
303             # Signature Value
304 10 100 66     59 if (defined $Values{SignatureValue} || defined $Values{SignatureType}) {
305 2         8 $Values{SignatureValue}=substr(ASN1Unwrap($X509SignatureValue),1);
306 2         9 my $HexX509signature=Hex($X509signature);
307 2 50       14 if ( $HexX509signature eq "300d06092a864886f70d0101040500" ) { $Values{SignatureType}="md5WithRSA"; }
  0 50       0  
    0          
    0          
308 2         5 elsif ( $HexX509signature eq "300d06092a864886f70d0101050500" ) { $Values{SignatureType}="sha1WithRSA"; }
309 0         0 elsif ( $HexX509signature eq "300d06092a864886f70d0101030500" ) { $Values{SignatureType}="md4WithRSA"; }
310 0         0 elsif ( $HexX509signature eq "300d06092a864886f70d0101020500" ) { $Values{SignatureType}="md2WithRSA"; }
311 0         0 else { $Values{SignatureType}="unrecognised"; }
312             }
313 10         428 return (\%Values);
314             }
315              
316             #########################################################
317              
318             sub Create {
319              
320             # Load in Context
321 3     3 1 613 my %context = %{ shift() };
  3         23  
322              
323             # Create error and warning arrays
324 3         8 my @Errors; my @Warnings;
325              
326             # Get request time;
327 3         15 my $now=time();
328              
329             # Check for required input values
330 3 50       14 if ( ! defined $context{'DN'} ) { push @Errors, "X509: Distinguished Name not supplied"; }
  0         0  
331 3 50       11 if ( ! defined $context{'Serial'} ) { push @Errors, "X509: Serial number not supplied"; }
  0         0  
332 3 50 66     21 if ( ! defined $context{'CACert'} && defined $context{'CAKey'} ) { push @Errors, "X509: Issuer key supplied but certificate not supplied"; }
  0         0  
333 3 50 66     15 if ( ! defined $context{'CAKey'} && defined $context{'CACert'} ) { push @Errors, "X509: Issuer certificate supplied but key not supplied"; }
  0         0  
334 3 50 66     15 if ( ! defined $context{'CACert'} && ! defined $context{'CAKey'} ) { push @Warnings, "X509: Issuer not supplied creating Self signed certificate"; }
  1         3  
335 3 50 66     14 if ( ! defined $context{'CACert'} && ! defined $context{'CA'} ) { push @Warnings, "X509: CA Assuming this is a CA"; $context{'CA'}="True"; }
  0         0  
  0         0  
336 3 50 33     13 if ( ! defined $context{'CA'} && defined $context{'CACert'} ) { push @Warnings, "X509: CA Assuming this is not a CA"; $context{'CA'}="False"; }
  0         0  
  0         0  
337              
338             # Bail if there isn't enough information
339 3 50       11 if ( @Errors > 0 ) { return { Errors => \@Errors} ; }
  0         0  
340              
341             # Check input values
342 3 50       14 if (ref($context{'DN'}) ne "ARRAY" ) { push @Errors, "X509: DN Must be a reference to an array of distinguished name component strings."; }
  0         0  
343 3 50 33     16 if ( defined ($context{'Extensions'}) &&
344 0         0 ref($context{'Extensions'}) ne "ARRAY" ) { push @Errors, "X509: Extensions must be passed by reference to an array of DER encoded extensions."; }
345 3 50       24 if ( $context{'Serial'} !~ /^([0-9]+)$/ ) { push @Errors, "X509: Serial number was not a positive integer"; }
  0         0  
346 3 50       20 if ( $context{'CA'} !~ /^(False|True)$/ ) { push @Errors, "X509: CA valus can be only \"True\" or \"False\""; }
  0         0  
347 3 50 33     28 if ( defined $context{'Lifetime'} &&
348 0         0 $context{'Lifetime'} !~ /^[0-9]+$/) { push @Errors, "X509: Invalid Lifetime $context{'Lifetime'}. Must be a +ve int."; }
349 3 50 33     27 if ( defined $context{'Bits'} &&
350 0         0 $context{'Bits'} !~ /^(512|1024|2048|4096)$/ ) { push @Errors, "X509: Key size can only be 512, 1024, 2048 or 4096."; }
351 3 50 33     14 if ( defined $context{'SubjectAltName'} &&
352 0         0 ref($context{'SubjectAltName'}) ne "ARRAY" ) { push @Errors, "X509: Extensions must be passed by reference to an array of generalnames."; }
353              
354             # Bail if inputs are not the right format
355 3 50       10 if ( @Errors > 0 ) { return { Errors => \@Errors} ; }
  0         0  
356              
357             # Load input data into local variables
358 3         8 my ($CertInfoRef,$KeyInfoRef)=(undef,undef);
359 3         4 my %CI;
360             my %KI;
361 3 100       12 if ( defined $context{'CACert'} ) {
362 2 50       31 $CertInfoRef = (($context{'CACert'} =~ /^(\060.+)$/s) ? Examine($&, {X509issuer=>"", X509subject=>"", End=>"", subjectKeyIdentifier=>"", X509serial=>"", subjectAltName=>""}) : undef);
363 2 50       29 $KeyInfoRef = (($context{'CAKey'} =~ /^(\060.+)$/s) ? VOMS::Lite::KEY::Examine($&, {Keymodulus=>"", KeyprivateExponent=>""}) : undef);
364 2 50       7 if ( defined $CertInfoRef ) { %CI=%$CertInfoRef; } else { push @Errors, "X509: Unable to parse CA certificate."; }
  2         13  
  0         0  
365 2 50 33     19 if ( %CI && defined $CI{'Errors'} ) { push @Errors, "X509: Unable to parse CA certificate errors: ".join ('; ',@{ $CI{'Errors'}}); }
  0         0  
  0         0  
366 2 50       5 if ( defined $KeyInfoRef ) { %KI=%$KeyInfoRef; } else { push @Errors, "X509: Unable to parse CA key."; }
  2         6  
  0         0  
367             }
368              
369             # Bail if there is a certificate Parse error
370 3 50       9 if ( @Errors > 0 ) { return { Errors => \@Errors} ; }
  0         0  
371              
372             # Check for unknown options
373 3 50       11 foreach (keys %context) { if ( ! /^(DN|subjectAltName|Quiet|Serial|CACert|CAKey|CA|Bits|Lifetime)$/ ) {push @Errors, "X509: $_ is an invalid option.";}}
  21         70  
  0         0  
374              
375             # Bail if any recognised options are invalid
376 3 50       11 if ( @Errors > 0 ) { return { Errors => \@Errors} ; }
  0         0  
377              
378             # Warn if there is something queer
379 3 50       9 if ( ! defined $context{'Lifetime'} ) { $context{'Lifetime'} = 43200; push @Warnings, "X509: Undefined lifetime. Defaulting to $context{'Lifetime'} seconds."; }
  0         0  
  0         0  
380 3 50       42 if ( ! defined $context{'Bits'} ) { $context{'Bits'} = 1024; push @Warnings, "X509: Undefined key size. Defaulting to $context{'Bits'} b."; }
  0         0  
  0         0  
381 3 100       9 if ( defined $context{'CACert'} ) {
382 2 50       10 if ( ( $context{'Lifetime'} ) > ( $CI{'End'} - $now ) ) { push @Warnings, "X509: Requested lifetime exceeds lifetime of issuer."; }
  0         0  
383 2 50       5 if ( ( $CI{'End'} - $now ) < 604800 ) { push @Warnings, "X509: Issuer certificate will expire in less than 1 week."; }
  2         4  
384             }
385              
386             #Get times. Now and Now + $lifetime
387 3         20 my @NOW=gmtime($now);
388 3         13 my @FUT=gmtime($now+$context{'Lifetime'});
389              
390             # UTCTIME (so two digit years, OK for the next 40 or so years!)
391 3         26 my $beforeDate=sprintf("%02i%02i%02i%02i%02i%02iZ",($NOW[5] % 100),($NOW[4]+1),$NOW[3],$NOW[2],$NOW[1],$NOW[0]);
392 3         12 my $afterDate=sprintf("%02i%02i%02i%02i%02i%02iZ",($FUT[5] % 100),($FUT[4]+1),$FUT[3],$FUT[2],$FUT[1],$FUT[0]);
393              
394             # Check and parse the DN array referenced
395 3         7 my $ASN1DN="";
396 3         5 foreach (@{ $context{'DN'} }) {
  3         9  
397 9         25 my ($attrib,$value)=split(/=/,$_,2); # Splits attribute and value
398 9         31 my $OID = VOMS::Lite::CertKeyHelper::DNattribToOID($attrib); # Convert Attribute to dot representation e.g. CN -> 2.5.4.3
399 9 50       19 if ( defined $OID ) {
400 9         7 my $STRtype;
401 9 50       30 if ( $value =~ /^[a-zA-Z0-9 \x22()+,.\/:?-]*$/ ) { $STRtype="13"; } # Printable String
  9 0       13  
402 0         0 elsif ( $value =~ /^[\x00\x07-\x0f\x11-\x14\x18-\x1b\x20-\x23\x25-\x7d\x7f]*$/ ) { $STRtype="16"; } #IA5 String
403 0         0 else { push @Errors, "X509: Can't find an apropriate encoding for $attrib+$value."; }
404 9 50       18 if ( defined $STRtype ) { $ASN1DN .= ASN1Wrap("31",ASN1Wrap("30",ASN1Wrap("06",Hex(OIDtoASN1OID($OID))).ASN1Wrap($STRtype,Hex($value)))) };
  9         25  
405             }
406 0         0 else { push @Errors, "X509: unknown Attribute: $attrib"; }
407             }
408 3 50       10 if ( $ASN1DN eq "" ) { push @Errors, "X509: No Attributes in Distunguished Name"; } ;
  0         0  
409 3         8 $ASN1DN=ASN1Wrap("30",$ASN1DN); # The DN in an apropriate X.509 ASN1 structure.
410              
411             # Make hash name
412 3         7 my $Hash=$ASN1DN;
413 3         17 $Hash =~ s/(..)/pack("C",hex($&))/ge;
  200         477  
414 3         26 $Hash = md5($Hash);
415 3         16 $Hash = Hex( substr($Hash,3,1).substr($Hash,2,1).substr($Hash,1,1).substr($Hash,0,1) );
416              
417             # Check and parse the SubjectAltName array referenced
418 3         7 my $SubjectAltName="";
419 3 100       8 if ( defined $context{'subjectAltName'} ) {
420 2         11 foreach (@{ $context{'subjectAltName'} }) {
  2         5  
421 2 50       16 if ( /^otherName=/ ) { push @Errors, "X509: otherName not supported"; }
  0 100       0  
    50          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
422             # elsif ( /^rfc822Name=([\x00\x07-\x0f\x11-\x14\x18-\x1b\x20-\x23\x25-\x7d\x7f]*)$/ )
423             elsif ( /^rfc822Name=([\x00-\x7f]*)$/ ) #IA5String -- Misconception that DNS is only [a-zA-Z0-9.-]*
424 1         4 { $SubjectAltName.=ASN1Wrap("81",Hex($1)); }
425             # elsif ( /^dNSName=([\x00\x07-\x0f\x11-\x14\x18-\x1b\x20-\x23\x25-\x7d\x7f]*)$/ )
426             elsif ( /^dNSName=([\x00-\x7f]*)$/ ) #IA5String -- Misconception that DNS is only [a-zA-Z0-9.-]*
427 1         3 { $SubjectAltName.=ASN1Wrap("82",Hex($1)); }
428 0         0 elsif ( /^x400Address=/ ) { push @Errors, "X509: x400Address not supported"; }
429 0         0 elsif ( /^directoryName=(30[0-9a-f]*)$/ ) { $SubjectAltName.=ASN1Wrap("84",$1); }
430 0         0 elsif ( /^directoryName=(\060.*)$/ ) { $SubjectAltName.=ASN1Wrap("84",Hex($1)); }
431 0         0 elsif ( /^ediPartyName=/ ) { push @Errors, "X509: ediPartyName not supported"; }
432             elsif ( /^uniformResourceIdentifier=([\x00\x07-\x0f\x11-\x14\x18-\x1b\x20-\x23\x25-\x7d\x7f]*)$/ )
433 0         0 { $SubjectAltName.=ASN1Wrap("86",Hex($1)); }
434 0         0 elsif ( /^IPAddress=(.{4})$/ ) { $SubjectAltName.=ASN1Wrap("87",$1."ffffffff");
435 0         0 push @Warnings, "X509: Assuming IPv4Address has /32 Mask"; }
436             elsif ( /^IPAddress=([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$/ )
437 0         0 { $SubjectAltName.=ASN1Wrap("87",Hex(chr($1).chr($2).chr($3).chr($4))."ffffffff");
438 0         0 push @Warnings, "X509: Assuming IPv4Address has /32 Mask";}
439             elsif ( /^IPAddress=([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)\/([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$/ )
440 0         0 { $SubjectAltName.=ASN1Wrap("87",Hex(chr($1).chr($2).chr($3).chr($4).chr($5).chr($6).chr($7).chr($8)));}
441 0         0 elsif ( /^IPAddress=(.{8})$/ ) { $SubjectAltName.=ASN1Wrap("87",Hex($1)); }
442 0         0 elsif ( /^IPAddress=(.{16})$/ ) { $SubjectAltName.=ASN1Wrap("87",Hex($1).("\xff" x 16));
443 0         0 push @Warnings, "X509: Assuming IPv6Address has /128 Mask"; }
444 0         0 elsif ( /^registeredID=/ ) { push @Errors, "X509: registeredID not supported"; }
445             elsif ( /^(rfc822Name|dNSName|directoryName|uniformResourceIdentifier|IPAddress)=/ )
446 0         0 { push @Errors, "X509: Bad data for $1 subjectAlternitiveName"; }
447 0         0 elsif ( /^([^=]+)=/ ) { push @Errors, "X509: unknown generalName $1 for subjectAlternitiveName"; }
448 0         0 else { push @Errors, "X509: malformed SubjectAltName entry"; }
449             }
450 2         7 $SubjectAltName=ASN1Wrap("04",ASN1Wrap("30",$SubjectAltName));
451             }
452              
453             # Get Set issuerAltName
454 3         5 my $IssuerAltName="";
455 3 50 66     29 if ( defined $context{'CACert'} && $CI{subjectAltName} ne "" ) { $IssuerAltName=ASN1Wrap("04",ASN1Wrap("30",Hex($CI{subjectAltName}))); }
  0 50 66     0  
456 0         0 elsif ( ! defined $context{'CACert'} && $SubjectAltName ne "" ) { $IssuerAltName=$SubjectAltName; }
457              
458             # Get Extensions
459 3         5 my $ExtraExts="";
460 3         6 foreach ( @{ $context{'Extensions'} } ) {
  3         9  
461 0 0       0 if ( /^\060/ ) { $ExtraExts.=Hex($_); }
  0 0       0  
462 0         0 elsif ( /^30[0-9a-f]*$/ ) { $ExtraExts.=$_; }
463 0         0 else { push @Errors,"X509: The format of an extension was not understood."; }
464             }
465              
466             # Bail if DN is bad or extension was not DER or hex DER
467 3 50       8 if ( @Errors > 0 ) { return { Errors => \@Errors} ; }
  0         0  
468              
469             # Generate Key Pair
470 3 50       26 my $keyref = VOMS::Lite::RSAKey::Create( { Bits => $context{'Bits'}, Verbose => (defined $context{'Quiet'})?undef:"y" } );
471 3 50       19 if ( ! defined $keyref ) { return { Errors => [ "X509: Key Generation Failure" ] } ; }
  0         0  
472 3         6 my %key = %{ $keyref };
  3         29  
473 3 50       17 if ( defined $key{'Error'} ) { return { Errors => [ "X509: Error in Key Generation ".$key{'Error'} ] } ; }
  0         0  
474              
475              
476             ###############################
477             #OK Let's create an X509 Cred!#
478             ###############################
479              
480             ### Create Key Pair ######################################################
481              
482             # Keyversion Keymodulus KeypublicExponent KeyprivateExponent
483             # Keyprime1 Keyprime2 Keyexponent1 Keyexponent2 Keycoefficient
484 3         8 my $Keyversion = "020100";
485 3         21 my $Keymodulus = ASN1Wrap("02",DecToHex($key{Modulus}));
486 3         16 my $KeypublicExponent = ASN1Wrap("02",DecToHex($key{PublicExponent}));
487 3         15 my $KeyprivateExponent = ASN1Wrap("02",DecToHex($key{PrivateExponent}));
488 3         16 my $Keyprime1 = ASN1Wrap("02",DecToHex($key{Prime1}));
489 3         13 my $Keyprime2 = ASN1Wrap("02",DecToHex($key{Prime2}));
490 3         12 my $Keyexponent1 = ASN1Wrap("02",DecToHex($key{Exponent1}));
491 3         16 my $Keyexponent2 = ASN1Wrap("02",DecToHex($key{Exponent2}));
492 3         17 my $Keycoefficient = ASN1Wrap("02",DecToHex($key{Iqmp}));
493              
494 3         24 my $Privatekey=ASN1Wrap("30",$Keyversion.$Keymodulus.$KeypublicExponent.$KeyprivateExponent.
495             $Keyprime1.$Keyprime2.$Keyexponent1.$Keyexponent2.$Keycoefficient);
496              
497             # If this is to be selfsigned, set the CA's private key and modulus
498 3 100       17 if ( ! defined $context{'CACert'} ) {
499 1         4 $KI{Keymodulus}=DecToHex($key{Modulus});
500 1         15 $KI{KeyprivateExponent}=DecToHex($key{PrivateExponent});
501 1         7 $KI{Keymodulus} =~ s/(..)/pack("C",hex($&))/ge;
  65         115  
502 1         4 $KI{KeyprivateExponent} =~ s/(..)/pack("C",hex($&))/ge;
  65         137  
503             }
504              
505             ### Create Certificate Data ##############################################
506             # TBSCertificate: X509version X509serial X509signature X509issuer X509validity X509subject X509subjectPublicKeyInfo (X509issuerUniqueID) (X509subjectUniqueID) X509extensions
507              
508             #### Certificate Version #### (x509 v3)
509 3         6 my $X509version = "a003020102";
510              
511             #### Serial Number ####
512 3         14 my $X509serial=ASN1Wrap("02",DecToHex($context{'Serial'}));
513              
514             #### Type of Signature #### Use SHA1 and RSA
515 3         7 my $X509signature="300d06092a864886f70d0101050500"; #SEQ(OID:SHA1WithSHA1Encryption NULL)
516              
517             #### Issuer (straight from certificate)
518 3 100       23 my $X509issuer=( defined $CI{X509subject} ) ? Hex($CI{X509subject}) : $ASN1DN;
519              
520             #### Validity ####
521 3         43 my $X509Validity=ASN1Wrap("30",ASN1Wrap("17",Hex($beforeDate)).ASN1Wrap("17",Hex($afterDate)));
522              
523             #### Subject ####
524 3         9 my $X509subject=$ASN1DN;
525              
526             #### Public Key (RSA) ####
527 3         14 my $PubKeyChunk=ASN1Wrap("30",$Keymodulus.$KeypublicExponent);
528 3         13 my $X509subjectPublicKeyInfo=ASN1Wrap("30",ASN1Wrap("30","06092a864886f70d0101010500").ASN1Wrap("03",ASN1BitStr($PubKeyChunk)));
529              
530             #### Extensions ####
531              
532             #KeyUsage; Critical:Certificate Sign, CRL Sign -OR- Critical:Dig sign & Key encypher & Key Agree
533 3 100       24 my $keyusage=ASN1Wrap("30","0603551d0f"."0101ff".(($context{'CA'} eq "True")?"040403020106":"0404030203a8"));
534              
535             #BasicConstraints; Critical:CA=True & Pathlen undefiend -OR- Critical:CA=False & Pathlen undefiend
536 3 100       18 my $basicconstraints=ASN1Wrap("30","0603551d13"."0101ff".(($context{'CA'} eq "True")?"040530030101ff":"04023000")); # why 04023000 not 04053003010100 (DER).
537              
538             #SKID
539             # my $PubKeyDigest=sha1_hex($PubKeyChunk); oops
540 3         7 my $digestable=$PubKeyChunk;
541 3         17 $digestable=~s/(..)/pack('C',hex($&))/ge;
  222         497  
542 3         42 my $PubKeyDigest=sha1_hex($digestable);
543 3         11 my $SKID=ASN1Wrap("30","0603551d0e".ASN1Wrap("04",ASN1Wrap("04",$PubKeyDigest)));
544              
545             #AKID
546 3         8 my $AKID;
547 3 100       10 if ( ! defined $context{'CACert'} ) {
548 1         3 $CI{subjectKeyIdentifier} = $PubKeyDigest;
549 1         2 $CI{X509issuer} = $X509issuer;
550 1         4 $CI{X509serial} = $X509serial;
551 1         90 $CI{subjectKeyIdentifier} =~ s/(..)/pack("C",hex($&))/ge;
  20         38  
552 1         3 $CI{X509issuer} =~ s/(..)/pack("C",hex($&))/ge;
  67         139  
553 1         4 $CI{X509serial} =~ s/(..)/pack("C",hex($&))/ge;
  3         6  
554             }
555              
556 3         16 $AKID=ASN1Wrap("30", "0603551d23".ASN1Wrap("04",ASN1Wrap("30",ASN1Wrap("80",Hex($CI{subjectKeyIdentifier}))
557             .ASN1Wrap("a1",ASN1Wrap("a4",Hex($CI{X509issuer})))
558             .ASN1Wrap("82",Hex(scalar ASN1Unwrap($CI{X509serial}))) )));
559              
560             #Alternative names
561 3 100       26 if ( $SubjectAltName ne "" ) { $SubjectAltName = ASN1Wrap("30","0603551d11".$SubjectAltName) }
  2         12  
562 3 50       9 if ( $IssuerAltName ne "" ) { $IssuerAltName = ASN1Wrap("30","0603551d12".$IssuerAltName) }
  0         0  
563              
564             #Concat and wrap the Extensions
565 3         24 my $X509extensions=ASN1Wrap("a3",ASN1Wrap("30",$SKID.$AKID.$keyusage.$basicconstraints.$SubjectAltName.$IssuerAltName.$ExtraExts));
566              
567             #### The whole chunck of certificate to be signed ####
568 3         21 my $TBSCertificate=ASN1Wrap("30",$X509version.$X509serial.$X509signature.$X509issuer.$X509Validity.
569             $X509subject.$X509subjectPublicKeyInfo.$X509extensions);
570              
571             ### Create Signature #################################################
572             # X509signatureAlgorithm X509signature
573              
574             # Make MD5 Checksum and RSA sign it
575 3         6 my $BinaryTBSCertificate = $TBSCertificate;
576 3         13 $BinaryTBSCertificate =~ s/(..)/pack('C',hex($&))/ge;
  1464         2925  
577 3         16 my $RSAsignedDigest = VOMS::Lite::CertKeyHelper::digestSign("sha1WithRSA",$BinaryTBSCertificate,Hex($KI{KeyprivateExponent}),Hex($KI{Keymodulus}));
578 3         21 my $Signature = ASN1Wrap("03",ASN1BitStr($RSAsignedDigest)); #(Always n*8 bits for MDnRSA and SHA1RSA)
579              
580             ### Wrap Certificate up with Signature ################################
581             # TBSCertificate X509signatureAlgorithm X509signature
582              
583 3         40 my $Certificate = ASN1Wrap("30",$TBSCertificate.$X509signature.$Signature);
584              
585             ### Pack and return Certificate and Key in DER format #################
586              
587 3         27 $Certificate=~s/(..)/pack('C',hex($&))/ge;
  1722         3216  
588 3         16 $Privatekey=~s/(..)/pack('C',hex($&))/ge;
  959         10737  
589              
590 3         255 return { Cert=>$Certificate, Key=>$Privatekey, Warnings=>\@Warnings, Hash=>$Hash };
591             }
592              
593             1;
594              
595             __END__