File Coverage

blib/lib/VOMS/Lite/PEMHelper.pm
Criterion Covered Total %
statement 159 226 70.3
branch 41 90 45.5
condition 0 3 0.0
subroutine 13 15 86.6
pod 0 10 0.0
total 213 344 61.9


line stmt bran cond sub pod time code
1             package VOMS::Lite::PEMHelper;
2              
3 1     1   26 use 5.004;
  1         4  
  1         49  
4 1     1   7 use strict;
  1         2  
  1         43  
5 1     1   962 use MIME::Base64 qw(encode_base64 decode_base64);
  1         248818  
  1         148  
6 1     1   1244 use File::Copy qw(move);
  1         3150  
  1         88  
7              
8             require Exporter;
9 1     1   7 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
  1         3  
  1         3096  
10             @ISA = qw(Exporter);
11             %EXPORT_TAGS = ( );
12             @EXPORT_OK = qw( encodeCert writeAC encodeAC readAC readCert decodeCert writeKey writeCert writeCertKey readPrivateKey );
13             @EXPORT = ( );
14             $VERSION = '0.20';
15              
16             ################################################################
17              
18             sub writeAC { #writes a PEM formatted AC
19             # Two arguments (Path to store AC and AC data as a string of chars)
20 1     1 0 215 my ($file,$data)=@_;
21             # my $umasksave=umask(0022); #ACs are not private key material
22             # if ( umask() != 0022 ) { die "Can't umask 0022\n"; }
23 1 50       81 if ( -e $file ) { move($file,"$file.old"); } #move old file away
  1         12  
24 1 50       321 open(AC,">$file") || die "Can't create AC file";
25 1         6 print AC &encodeAC($data);
26 1         56 close(AC);
27             # umask($umasksave);
28 1         5 return;
29             }
30              
31             ################################################################
32              
33             sub encodeAC {
34 2     2 0 935 return encodeCert(@_,"ATTRIBUTE CERTIFICATE");
35             }
36              
37             ################################################################
38              
39             sub readAC { #Returns BER with AC in it
40 0     0 0 0 my $file=shift;
41 0         0 return readCert($file,"ATTRIBUTE CERTIFICATE");
42             }
43              
44             ################################################################
45              
46             sub readCert { #Returns BERs with CERTs in them
47             # One arguement (path to cert file);
48 2     2 0 6 my $file=shift;
49              
50 2         4 my $type=shift;
51 2 50       7 if ( ! defined($type) ) { $type="CERTIFICATE"; }
  2         6  
52 2         6 $type =~ y/a-z/A-Z/;
53 2         9 $type =~ s/[^A-Z0-9 ]//g;
54              
55             # Load and parse cert file
56 2         6 my @myCertData=();
57 2         3 my $Certnum=-1;
58 2         4 my $read=0;
59 2 50       115 open(CERT,"<$file") || die "Can't access Public Key file: '$file'";
60 2         40 while () {
61 29         45 my $line=$_;
62 29 100       113 if ( $line =~ /^-----BEGIN $type-----\r?$/ ) {$read=1; $Certnum++; next;}
  2         79  
  2         2  
  2         11  
63 27 50       105 if ( $line =~ /^-----END $type-----\r?$/ ) {$read=0; wantarray ? next : last; }
  2 100       4  
  2         8  
64 25 50       57 if ( $read==1 ) {
65 25 50       86 if ( $line =~ /^([A-Za-z0-9+\/=]+)\r?$/ ) {$myCertData[$Certnum].=$1;}
  25         105  
66             }
67             }
68 2         25 close(CERT);
69              
70 2 50       10 if ( $myCertData[0] eq "" ) { die "I didn't understand the format of your $type file:\n$file";}
  0         0  
71 2         6 my @decoded=();
72 2         5 foreach (@myCertData) { push(@decoded,decode_base64($_)); }
  2         22  
73 2 50       14 return wantarray?@decoded:$decoded[0];
74             }
75              
76             ################################################################
77              
78             sub decodeCert {
79 0     0 0 0 my $type = pop;
80 0         0 my $pems = join "\n",@_;
81 0         0 my @ders;
82 0         0 $pems =~ s|^-----BEGIN $type-----$([a-zA-Z0-9/+=\r\n]+)^-----END $type-----$|push @ders,decode_base64($1)|mge;
  0         0  
83 0         0 return @ders;
84             }
85              
86             ################################################################
87              
88             sub encodeCert {
89             #my $certstr=shift;
90 5     5 0 11 my $certstr="";
91 5         10 my $type="CERTIFICATE";
92 5 50       20 if ( $_[-1] !~ /^\x30/ ) { $type=pop; }
  5         8  
93            
94 5         11 $type =~ y/a-z/A-Z/;
95 5         15 $type =~ s/[^A-Z0-9 ]//g;
96 5         12 foreach (@_) {
97 5         32 my $OpenSSLCompat=encode_base64($_,'');
98 5         127 $OpenSSLCompat=~s/(.{1,64})/$&\n/g;
99 5         52 $certstr .= "-----BEGIN $type-----\n".$OpenSSLCompat."-----END $type-----\n";
100             }
101 5         72 return $certstr;
102             }
103              
104             ################################################################
105              
106             sub writeCertKey {
107             # At least 3 arguements (file, public key, private key, [chain of signing certificates]);
108 1     1 0 442 my $file=shift;
109 1         2 my $pub=shift;
110 1         3 my $pri=shift;
111              
112             # Place file
113 1         5 my $umasksave=umask(0077);
114            
115 1 50       9 if ( umask() != 0077 ) {
116 0 0       0 if ( $^O =~ /^MSWin/ ) { print STDERR "WARNING: Can't umask 0077 when writing $file\n"; }
  0         0  
117 0         0 else { die "Can't umask 0077 when writing $file"; }
118             }
119 1 50       44 if ( -e $file ) { move($file,"$file.old"); } #move old file away
  1         8  
120              
121 1 50       299 open(CERTKEY,">$file") || die "Can't create file to save cert and key to.";
122 1         6 print CERTKEY encodeCert($pub,"CERTIFICATE");
123 1         5 print CERTKEY encodeCert($pri,"RSA PRIVATE KEY");
124 1         3 foreach ( @_ ) { print CERTKEY encodeCert($_,"CERTIFICATE"); }
  1         4  
125 1         51 close(CERTKEY);
126 1         4 umask($umasksave);
127 1         6 return;
128             }
129              
130             ################################################################
131              
132             sub writeKey {
133             # At least 3 arguements (file, public key, private key, [chain of signing certificates]);
134 3     3 0 948 my $file=shift;
135 3         9 my $pri=shift;
136 3         6 my $passwd=shift;
137 3         9 my $ENCRYPTION="";
138              
139 3 50       11 if ( ! defined $passwd ) {
140             # Prompt for password
141 0         0 require Term::ReadKey;
142 0         0 print "I need the passphrase used to encrypt the key in \n$file\nPassphrase: ";
143 0         0 my $dummy=Term::ReadKey::ReadMode('noecho');
144 0         0 $passwd = Term::ReadKey::ReadLine(),
145             $dummy=Term::ReadKey::ReadMode('normal');
146 0         0 chomp $passwd;
147 0         0 print "\n";
148             }
149              
150             # To encrypt or not to encrypt
151 3 100       12 if ( $passwd ne "" ) {
152              
153             # Spin up the Crypto stuff
154 2         25 require Digest::MD5;
155 2         834 require Crypt::DES_EDE3;
156              
157             # Make Initialisation vector
158 2         2148 my $iv="";
159 2         10 while (length($iv)<8 ) {$iv.=chr((rand(255)+1));}
  16         84  
160              
161             # Construct DES Key from password (Munge)
162 2         4 my $keysize=24;
163 2         7 my $SALT=$iv;
164 2         16 my $key=Digest::MD5::md5($passwd,$SALT);
165 2         11 while (length($key) < $keysize) { $key .= Digest::MD5::md5($key, $passwd, $SALT);}
  2         11  
166 2         7 $key=substr($key,0,$keysize);
167              
168             # DES Padding Data as per RFC 1423 (not 1851 which adds message payload info)
169 2         7 my $pad = ( 8 - (length($pri)%8) );
170 2         8 my $padding=chr($pad) x $pad;
171 2         6 $pri.=$padding;
172              
173             # Encode Data
174 2         15 my $DES = Crypt::DES_EDE3->new($key);
175 2         189 my $cyphertextout="";
176 2         11 while ( my $len=length($pri) ) {
177 81         165 my $block=substr($pri,0,8);
178 81         109 $pri=substr($pri,8);
179 81         119 $block = $SALT ^ $block;
180 81         203 my $cyphertext=$DES->encrypt($block);
181 81         1732 $SALT=$cyphertext;
182 81         226 $cyphertextout.=$cyphertext;
183             }
184              
185             # Set PEM encryprion header
186 2         12 $iv=unpack('H*',$iv);
187 2         7 $iv =~ y/[a-f]/[A-F]/;
188 2         6 $ENCRYPTION="Proc-Type: 4,ENCRYPTED\nDEK-Info: DES-EDE3-CBC,$iv\n\n";
189 2         23 $pri=$cyphertextout;
190             }
191              
192             # Place file
193 3         21 my $umasksave=umask(0077);
194              
195 3 50       21 if ( umask() != 0077 ) {
196 0 0       0 if ( $^O =~ /^MSWin/ ) { print STDERR "WARNING: Can't umask 0077 when writing $file\n"; }
  0         0  
197 0         0 else { die "Can't umask 0077 when writing $file"; }
198             }
199 3 50       93 if ( -e $file ) { move($file,"$file.old"); } #move old file away
  3         20  
200              
201 3 50       592 open(KEY,">$file") || die "Can't create file to save cert and key to.";
202 3         20 my $OpenSSLCompat=encode_base64($pri,'');
203 3         56 $OpenSSLCompat=~s/(.{1,64})/$&\n/g;
204 3         34 print KEY "-----BEGIN RSA PRIVATE KEY-----\n$ENCRYPTION".$OpenSSLCompat."-----END RSA PRIVATE KEY-----\n";
205 3         125 close(KEY);
206 3         9 umask($umasksave);
207 3         15 return;
208             }
209              
210             ################################################################
211              
212             sub writeCert {
213             # At least 3 arguements (file, public key, private key, [chain of signing certificates]);
214 3     3 0 1896 my $file=shift;
215 3         9 my $pub=shift;
216 3         7 my $type=shift;
217 3 50       16 if ( ! defined($type) ) { $type="CERTIFICATE"; }
  3         8  
218 3         10 $type =~ y/a-z/A-Z/;
219 3         14 $type =~ s/[^A-Z0-9 ]//g;
220              
221             # Place file
222 3 50       207 if ( -e $file ) { move($file,"$file.old"); } #move old file away
  3         22  
223 3 50       1294860 open(CERT,">$file") || die "Can't create file to save cert and key to.";
224 3         44 my $OpenSSLCompat=encode_base64($pub,'');
225 3         110 $OpenSSLCompat=~s/(.{1,64})/$&\n/g;
226 3         131 print CERT "-----BEGIN $type-----\n".$OpenSSLCompat."-----END $type-----\n";
227 3         215 close(CERT);
228 3         18 return;
229             }
230              
231              
232             ################################################################
233              
234             sub readPrivateKey { #Returns BER with Private key in it
235             # Two arguements (path to private key, and optional password);
236 1     1 0 2 my $file=shift;
237 1         1 my $passwd=shift;
238              
239             # Load and parse private key file
240 1         5 my ($myKeyData,$PEMV,$PEMType,$PEMEnc,$SALT)=("","","","","");
241 1         4 my $read=0;
242 1 50       36 open(KEY,"<$file") || die "Can't access Private Key file $file";
243 1         19 while () {
244 9         14 my $line=$_;
245 9 100       18 if ( $line =~ /^-----BEGIN RSA PRIVATE KEY-----$/ ) {$read=1; next;}
  1         2  
  1         3  
246 8 50       17 if ( $line =~ /^-----BEGIN PRIVATE KEY-----$/ ) {$read=2; next;}
  0         0  
  0         0  
247 8 100       14 if ( $line =~ /^-----END RSA PRIVATE KEY-----$/ ) {last;}
  1         4  
248 7 50       14 if ( $line =~ /^-----END PRIVATE KEY-----$/ ) {last;}
  0         0  
249 7 50       14 if ( $read==1 ) {
250 7 50       31 if ( $line =~ /^Proc-Type: ([0-9]+),(ENCRYPTED)$/ ) {$PEMV=$1; $PEMType=$2}
  0 50       0  
  0 50       0  
  0         0  
251 0         0 elsif ( $line =~ /^DEK-Info: (.*),(.*)$/ ) {$PEMEnc=$1; $SALT=$2}
  7         13  
252             elsif ( $line =~ /^([A-Za-z0-9+\/=]+)$/ ) {$myKeyData.=$1;}
253             }
254 7 50       22 if ( $read==2 ) {
255 0 0       0 if ( $line =~ /^([A-Za-z0-9+\/=]+)$/ ) {$myKeyData.=$1;}
  0         0  
256             }
257             }
258 1         8 close(KEY);
259              
260             # Return data if it's not encrypted
261 1 50       4 if ( $myKeyData eq "" ) { die "I didn't understand the format of your key file:\n$file";}
  0         0  
262              
263             # Obtain and check Encryption values
264 1         5 my $cyphertext=decode_base64($myKeyData);
265              
266             # If "PRIVATE KEY" but not "RSA PRIVATE KEY" Parse into it
267 1 50       6 if ( $read == 2 ) { # Unencrypted pkcs #8
268 0         0 die "I didn't understand the format of your key file:\n$file";
269             }
270              
271 1 50       7 return $cyphertext if ( $PEMType ne "ENCRYPTED" ); # Because actually it's not encrypted.
272 0 0         if ( $PEMEnc ne "DES-EDE3-CBC" ) { die "I don't know how to unencrypt your key\n";}
  0            
273 0 0         if ( $SALT !~ /^[a-fA-F0-9]{16}$/ ) { die "Bad Initilisation Vector (salt)'; I can't unencrypt your key!\n";}
  0            
274 0 0         if ( $PEMV ne "4" ) { print STDERR "Warning: I was expecting a version 4 PEM encrypted file you gave me a Version $PEMV\nFunny things may happen!\n"; }
  0            
275              
276              
277             # Check/get password
278 0 0 0       if ( defined $passwd && $passwd eq "" ) { return undef; } #was expecting no password so abort
  0 0          
279             elsif ( ! defined $passwd ) {
280 0           require Term::ReadKey;
281 0           require Digest::MD5;
282 0           print "I need the passphrase used to encrypt the key in \n$file\nPassphrase: ";
283 0           my $dummy=Term::ReadKey::ReadMode('noecho');
284 0           $passwd = Term::ReadKey::ReadLine(),
285             $dummy=Term::ReadKey::ReadMode('normal');
286 0           chomp $passwd;
287 0           print "\n";
288             }
289              
290             # Reconstruct DES Key from password (Munge)
291 0           my $keysize=24;
292 0           $SALT=pack('H*', $SALT);
293 0           my $key=Digest::MD5::md5($passwd,$SALT);
294 0           while (length($key) < $keysize) { $key .= Digest::MD5::md5($key, $passwd, $SALT);}
  0            
295 0           $key=substr($key,0,$keysize);
296              
297             # Decode Data
298 0           require Crypt::DES_EDE3;
299 0           my $DES = Crypt::DES_EDE3->new($key);
300 0           my $dataout="";
301 0           while ( my $len=length($cyphertext) ) {
302 0           my $block=substr($cyphertext,0,8);
303 0           $cyphertext=substr($cyphertext,8);
304 0           my $data=$SALT ^ $DES->decrypt($block);
305 0           $SALT=$block;
306 0           $dataout.=$data;
307             }
308              
309             # Remove DES Padding
310 0           my $unpad=substr ($dataout,-1);
311 0 0         if ( "$unpad" =~ /[\001-\010]/ ) { $dataout=substr($dataout,0,-ord($unpad));}
  0            
312 0           else { die "Your passphrase didn't do it for me!\n";}
313              
314 0           return $dataout;
315             }
316              
317             1;
318             __END__