File Coverage

blib/lib/Convert/PEM.pm
Criterion Covered Total %
statement 325 347 93.6
branch 87 136 63.9
condition 22 45 48.8
subroutine 69 74 93.2
pod 17 24 70.8
total 520 626 83.0


line stmt bran cond sub pod time code
1             package Convert::PEM;
2 17     17   2001086 use strict;
  16         667  
  16         693  
3 17     17   653 use 5.008_001;
  15         59  
4              
5 15     17   135 use base qw( Class::ErrorHandler );
  17         156  
  16         8809  
6              
7 16     17   14148 use MIME::Base64;
  17         15343  
  15         1508  
8 15     16   157 use Digest::MD5 qw( md5 );
  16         36  
  16         1162  
9 16     16   8573 use Convert::ASN1;
  16         828838  
  15         1134  
10 15     16   180 use Carp qw( croak );
  16         36  
  16         887  
11 16     16   12428 use Convert::PEM::CBC;
  16         170  
  15         902  
12 15     16   162 use Crypt::PRNG qw( random_bytes );
  16         61  
  16         1016  
13              
14              
15 16     16   146 use vars qw( $VERSION $DefaultCipher );
  16         114  
  15         39505  
16             our $VERSION = '0.13'; # VERSION
17              
18             our $DefaultCipher = 'DES-EDE3-CBC';
19              
20             sub new {
21 21     20 1 2991610 my $class = shift;
22 20         70 my $pem = bless { }, $class;
23 20         94 $pem->init(@_);
24             }
25              
26             sub init {
27 20     20 0 55 my $pem = shift;
28 20         121 my %param = @_;
29 20 50       91 unless (exists $param{Name}) {
30 0         0 return (ref $pem)->error("init: Name is required");
31             }
32             else {
33 20         151 $pem->{Name} = $param{Name};
34 20 100       94 $pem->{ASN} = $param{ASN} if exists $param{ASN};
35 20 50       67 $pem->{Cipher} = $param{Cipher} if exists $param{Cipher};
36             }
37              
38 20 100       66 if (exists $pem->{ASN}) {
39 15         40 $pem->{Macro} = $param{Macro};
40 15         124 my $asn = $pem->{_asn} = Convert::ASN1->new;
41 15 50       740 $asn->prepare( $pem->{ASN} ) or
42             return (ref $pem)->error("ASN prepare failed: $asn->{error}");
43             }
44              
45 20 50       86880 $pem->_getform(%param) or return;
46 20         138 $pem;
47             }
48              
49             sub _getform {
50 20     20   49 my $pem = shift;
51 20         90 my %param = @_;
52              
53 20 100       92 my $in = defined $param{InForm} ? uc($param{InForm}) : 'PEM';
54 20 50       138 $in =~ m/^(PEM|DER)$/ or return $pem->error("Invalid InForm '$in': must be PEM or DER");
55 20         55 $pem->{InForm} = $in;
56              
57 20 100       72 my $out = defined $param{OutForm} ? uc($param{OutForm}) : 'PEM';
58 20 50       94 $out =~ m/^(PEM|DER)$/ or return $pem->error("Invalid OutForm '$out': must be PEM or DER");
59 20         50 $pem->{OutForm} = $out;
60 20         82 $pem;
61             }
62              
63             sub asn {
64 153     153 1 333 my $pem = shift;
65 153   50     1502 my $asn = $pem->{_asn} || return;
66 153         412 my %prm = @_;
67 153   66     999 my $m = $prm{Macro} || $pem->{Macro};
68 153 100       1068 $m ? $asn->find($m) : $asn;
69             }
70              
71 0     0 1 0 sub ASN { $_[0]->{ASN} }
72 128     128 1 1077 sub name { $_[0]->{Name} }
73 0     0 1 0 sub cipher { $_[0]->{Cipher} }
74 8     8 1 4433 sub inform { $_[0]->{InForm} }
75 8     8 1 53 sub outform { $_[0]->{OutForm} }
76 0     0 0 0 sub macro { $_[0]->{Macro} }
77              
78             sub read {
79 56     56 1 722073 my $pem = shift;
80 56         387 my %param = @_;
81              
82 56         122 my $blob;
83 56         185 my $fname = delete $param{Filename};
84 56 50       3417 open my $FH, $fname or
85             return $pem->error("Can't open $fname: $!");
86 56         271 binmode $FH;
87 56         2706 read($FH, $blob, -s $fname);
88 56         877 close $FH;
89              
90 56 100       646 $pem->{InForm} eq 'DER'
91             ? $pem->from_der( DER => $blob )
92             : $pem->decode(%param, Content => $blob);
93             }
94              
95             sub write {
96 29     29 1 1530090 my $pem = shift;
97 29         277 my %param = @_;
98              
99             my $fname = delete $param{Filename} or
100 29 50       141 return $pem->error("write: Filename is required");
101              
102 29 100       238 my $blob = $pem->{OutForm} eq 'DER'
103             ? $pem->to_der(%param)
104             : $pem->encode(%param);
105              
106 29 50       47942 open my $FH, ">$fname" or
107             return $pem->error("Can't open $fname: $!");
108 29         174 binmode $FH;
109 29         589 print $FH $blob;
110 29         1857 close $FH;
111 29         511 $blob;
112             }
113              
114             sub from_der {
115 62     62 1 145 my $pem = shift;
116 62         265 my %param = @_;
117              
118             # should always be unencrypted at this point
119 62         105 my $obj;
120 62 100       473 if (exists $pem->{ASN}) {
121 51         202 my $asn = $pem->asn;
122 51 100 66     1295 if (my $macro = ($param{Macro} || $pem->{Macro})) {
123 43 50       167 $asn = $asn->find($macro) or
124             return $pem->error("Can't find Macro $macro");
125             }
126 51 50       894 $obj = $asn->decode( $param{DER} ) or
127             return $pem->error("ASN encode failed: $asn->{error}");
128             }
129             else {
130 11         23 $obj = $param{DER};
131             }
132 62         6568316 $obj;
133             }
134              
135             sub decode {
136 59     59 1 8370 my $pem = shift;
137 59         319 my %param = @_;
138 59 50       248 if (exists $param{DER}) { return $pem->from_der(%param) }
  0         0  
139             my $blob = $param{Content} or
140 59 50       240 return $pem->error("'Content' is required");
141 59         299 chomp $blob;
142              
143 59 50       247 my $dec = $pem->explode($blob) or return;
144 59   33     393 my $name = $param{Name} || $pem->name;
145             return $pem->error("Object $dec->{Object} does not match " . $name)
146 59 50       226 unless $dec->{Object} eq $name;
147              
148 59         193 my $head = $dec->{Headers};
149 59         178 my $buf = $dec->{Content};
150 59         176 my %headers = map { $_->[0] => $_->[1] } @$head;
  86         355  
151 59 100 66     339 if (%headers && $headers{'Proc-Type'} eq '4,ENCRYPTED') {
152             $buf = $pem->decrypt( Ciphertext => $buf,
153             Info => $headers{'DEK-Info'},
154             Password => $param{Password} )
155 43 100       228 or return;
156             }
157 55         163 $param{DER} = $buf;
158 55 50       273 my $obj = $pem->from_der( %param )
159             or return;
160              
161 55         1343 $obj;
162             }
163              
164             sub to_der {
165 105     105 1 1948 my $pem = shift;
166 105         378 my %param = @_;
167              
168 105         236 my $buf;
169 105 100       470 if (exists $pem->{ASN}) {
170 101         507 my $asn = $pem->asn;
171 101 100 66     2649 if (my $macro = ($param{Macro} || $pem->{Macro})) {
172 94 50       313 $asn = $asn->find($macro) or
173             return $pem->error("Can't find Macro $macro");
174             }
175 101 50       1859 $buf = $asn->encode( $param{Content} ) or
176             return $pem->error("ASN encode failed: $asn->{error}");
177             }
178             else {
179             $buf = $param{Content}
180 4         8 }
181 105         24416175 $buf;
182             }
183              
184             sub encode {
185 69     69 1 8930 my $pem = shift;
186 69         279 my %param = @_;
187              
188 69   66     561 my $buf = $param{DER} || $pem->to_der(%param);
189 69         203 my (@headers);
190 69 100       340 if ($param{Password}) {
191 21         45 my ($info);
192 21 50       160 ($buf, $info) = $pem->encrypt( Plaintext => $buf,
193             %param )
194             or return;
195 21         95 push @headers, [ 'Proc-Type' => '4,ENCRYPTED' ];
196 21         70 push @headers, [ 'DEK-Info' => $info ];
197             }
198              
199 69   33     678 $pem->implode( Object => $param{Name} || $pem->name,
200             Headers => \@headers,
201             Content => $buf );
202             }
203              
204             sub explode {
205 62     62 0 2311 my $pem = shift;
206 62         160 my ($message) = @_;
207              
208             # Canonicalize line endings into "\n".
209 62         1400 $message =~ s/\r\n|\n|\r/\n/g;
210              
211 62         933 my ($head, $object, $headers, $content, $tail) = $message =~
212             m:(-----BEGIN ([^\n\-]+)-----)\n(.*?\n\n)?(.+)(-----END .*?-----)$:s;
213 62         446 my $buf = decode_base64($content);
214              
215 62         177 my @headers;
216 62 100       205 if ($headers) {
217 46         238 for my $h ( split /\n/, $headers ) {
218 95         409 my ($k, $v) = split /:\s*/, $h, 2;
219 95 50       449 push @headers, [ $k => $v ] if $k;
220             }
221             }
222              
223 62         544 { Content => $buf,
224             Object => $object,
225             Headers => \@headers }
226             }
227              
228             sub implode {
229 70     70 0 816 my $pem = shift;
230 70         428 my %param = @_;
231 70         233 my $head = "-----BEGIN $param{Object}-----";
232 70         214 my $tail = "-----END $param{Object}-----";
233 70         575 my $content = encode_base64( $param{Content}, '' );
234 70         1434 $content =~ s!(.{1,64})!$1\n!g;
235             my $headers = join '',
236 45         176 map { "$_->[0]: $_->[1]\n" }
237 70         210 @{ $param{Headers} };
  70         286  
238 70 100       257 $headers .= "\n" if $headers;
239 70         972 "$head\n$headers$content$tail\n";
240             }
241              
242 15     16   186 use vars qw( %CTYPES );
  16         45  
  16         33473  
243             %CTYPES = (
244             'DES-CBC' => {c => 'Crypt::DES', ks=>8, bs=>8, },
245             'DES-EDE3-CBC' => {c => 'Crypt::DES_EDE3', ks=>24, bs=>8, },
246             'AES-128-CBC' => {c => 'Crypt::Rijndael', ks=>16, bs=>16, },
247             'AES-192-CBC' => {c => 'Crypt::Rijndael', ks=>24, bs=>16, },
248             'AES-256-CBC' => {c => 'Crypt::Rijndael', ks=>32, bs=>16, },
249             'CAMELLIA-128-CBC' => {c => 'Crypt::Camellia', ks=>16, bs=>16, },
250             'CAMELLIA-192-CBC' => {c => 'Crypt::Camellia', ks=>24, bs=>16, },
251             'CAMELLIA-256-CBC' => {c => 'Crypt::Camellia', ks=>32, bs=>16, },
252             'IDEA-CBC' => {c => 'Crypt::IDEA', ks=>16, bs=>8, },
253             'SEED-CBC' => {c => 'Crypt::SEED', ks=>16, bs=>16, },
254             );
255              
256             #### cipher module support and configuration
257 2 100   2 0 75 sub list_ciphers { return wantarray ? sort keys %CTYPES : join(':', sort keys %CTYPES); }
258              
259             sub list_cipher_modules {
260             # expect a cipher name, if found, return the module name used for encryption/decryption
261 0 0 0 0 1 0 my $pem = ref($_[0]) || $_[0] eq __PACKAGE__ ? shift : '';
262 0 0       0 if (defined $_[0]) {
263 0   0     0 my $cn = has_cipher(shift) || return undef;
264 0         0 return $CTYPES{$cn}->{c};
265             }
266             return wantarray
267 0         0 ? map { $CTYPES{$_}->{c} } sort keys %CTYPES
268 0 0       0 : join(':', map { $CTYPES{$_}->{c} } sort keys %CTYPES);
  0         0  
269             }
270              
271             sub has_cipher {
272             # expect a cipher name, return the cipher name if found
273 35 100 100 35 1 153176 my $pem = ref($_[0]) || $_[0] eq __PACKAGE__ ? shift : '';
274 35         88 my $cn = uc(+shift);
275 35 50 66     320 return $cn if exists $CTYPES{$cn} && exists $CTYPES{$cn}->{c};
276             # try to figure out what cipher is meant in an overkill fashion
277 14         90 $cn =~ s/(DES.*3|3DES|EDE)|(DES)|([a-zA-Z]+)(?:-?(\d+)(?:-?(\w+))?)/
278 10 100       27 if ($1) {
    100          
279 2         5 'DES-EDE3-CBC'
280             } elsif ($2) {
281 2         5 'DES-CBC'
282             }
283             else {
284 6 50       40 $3.($4 ? "-".$4 : "").($5 ? "-$5" : "")
    50          
285             }
286             /e;
287 14         38 my @c = sort grep { $_ =~ m/$cn/ } keys %CTYPES;
  140         370  
288             # return undef unless @c;
289 14         59 $c[0];
290             }
291              
292             sub has_cipher_module
293             {
294 0 0 0 0 1 0 my $pem = ref($_[0]) || $_[0] eq __PACKAGE__ ? shift : '';
295 0 0       0 if (my $cn = has_cipher($_[0])) {
296 0         0 eval "use $CTYPES{$cn}->{c};";
297 0 0       0 if ($@) { undef $@; return undef; }
  0         0  
  0         0  
298 0         0 return $CTYPES{$cn}->{c};
299             }
300             }
301              
302             sub set_cipher_module
303             {
304 27 50 33 27 1 122922 my $pem = ref($_[0]) || $_[0] eq __PACKAGE__ ? shift : '';
305             # cipher name, cipher module name, replace all
306 27         88 my ($cn,$cm,$all) = @_;
307 27 50       67 $all = 1 unless defined $all;
308             # when setting ciphers, must use exact name
309 27 50       82 if (exists $CTYPES{$cn}) {
310 27     4   2701 eval "use $cm ;";
  4     4   42  
  4     1   79  
  4     1   194  
  4     1   41  
  4     1   11  
  4     1   134  
  1     1   5  
  1     1   2  
  1     1   39  
  1     1   10  
  1     1   3  
  1     1   21  
  1     1   12  
  1         3  
  1         23  
  1         10  
  1         2  
  1         24  
  1         11  
  1         2  
  1         23  
  1         13  
  1         2  
  1         23  
  1         10  
  1         3  
  1         23  
  1         11  
  1         4  
  1         25  
  1         12  
  1         4  
  1         21  
  1         10  
  1         3  
  1         24  
  1         10  
  1         3  
  1         32  
  1         11  
  1         3  
  1         26  
311 27 100       136 if ($@) { undef $@; return undef; }
  13         41  
  13         64  
312 14 50 33     122 if ($all && exists $CTYPES{$cn}->{c}) {
313 14         45 my $old_cm = $CTYPES{$cn}->{c};
314 14         56 foreach my $def (values %CTYPES) {
315 140 100       340 $def->{c} = $cm if $def->{c} eq $old_cm;
316             }
317             }
318             else {
319 0         0 $CTYPES{$cn}->{c} = $cm;
320             }
321 14         138 return $cm;
322             }
323 0         0 return undef;
324             }
325              
326             #### cipher functions
327             sub decrypt {
328 43     43 0 82 my $pem = shift;
329 43         202 my %param = @_;
330 43   100     185 my $passphrase = $param{Password} || "";
331 43         200 my ($ctype, $iv) = split /,/, $param{Info};
332 43 50       208 my $cmod = $CTYPES{$ctype} or
333             return $pem->error("Unrecognized cipher: '$ctype'");
334 43         240 $iv = pack "H*", $iv;
335 43 50   13   5736 eval "use $cmod->{c}; 1;" || croak "Failed loading cipher module '$cmod->{c}'";
  13     7   1094  
  11     4   1316  
  11     1   454  
  7     1   55  
  7     1   13  
  7     1   156  
  4     1   36  
  4     1   10  
  4     1   123  
  1     1   7  
  1     1   2  
  1     1   12  
  1     1   35  
  1     1   4  
  1         21  
  1         8  
  1         2  
  1         22  
  1         11  
  1         3  
  1         21  
  1         6  
  1         1  
  1         16  
  1         13  
  1         3  
  1         27  
  1         8  
  1         3  
  1         20  
  1         12  
  1         3  
  1         23  
  1         5  
  1         2  
  1         15  
  1         7  
  1         2  
  1         14  
  1         11  
  1         3  
  1         24  
  1         12  
  1         4  
  1         20  
336 43         395 my $key = Convert::PEM::CBC::bytes_to_key($passphrase,$iv,\&md5,$cmod->{ks});
337 43         114 my $cm = $cmod->{c}; $cm =~ s/^Crypt::(?=IDEA$)//; # fix IDEA
  43         201  
338 43         701 my $cbc = Convert::PEM::CBC->new(
339             Cipher => $cm->new($key),
340             IV => $iv );
341 43 100       182 my $buf = $cbc->decrypt($param{Ciphertext}) or
342             return $pem->error("Decryption failed: " . $cbc->errstr);
343 39         428 $buf;
344             }
345              
346             sub encrypt {
347 21     21 0 52 my $pem = shift;
348 21         165 my %param = @_;
349 21 50       97 $param{Password} or return $param{Plaintext};
350              
351 21 100       80 $param{Cipher} = $DefaultCipher if !$param{Cipher};
352 21         126 my $ctype = $pem->has_cipher( $param{Cipher} );
353 21 50       110 my $cmod = $CTYPES{$ctype} or
354             return $pem->error("Unrecognized cipher: '$ctype'");
355 21 50   13   2712 eval "use $cmod->{c}; 1;" || croak "Error loading cypher module '$cmod->{c}'";
  13     1   6408  
  11     1   12644  
  11     1   364  
  1     1   10  
  1     1   3  
  1     1   23  
  1     1   12  
  1         3  
  1         20  
  1         8  
  1         2  
  1         15  
  1         10  
  1         3  
  1         17  
  1         11  
  1         2  
  1         17  
  1         5  
  1         2  
  1         16  
  1         10  
  1         2  
  1         21  
356              
357             ## allow custom IV for encryption
358 21 50       194 my $iv = $pem->_getiv(%param, bs => $cmod->{bs}) or return;
359 21         207 my $key = Convert::PEM::CBC::bytes_to_key( $param{Password}, $iv, \&md5, $cmod->{ks} );
360 21         57 my $cm = $cmod->{c}; $cm =~ s/^Crypt::(?=IDEA$)//; # fix IDEA
  21         122  
361 21         274 my $cbc = Convert::PEM::CBC->new(
362             IV => $iv,
363             Cipher => $cm->new($key) );
364 21         93 $iv = uc join '', unpack "H*", $cbc->iv;
365 21 50       108 my $buf = $cbc->encrypt($param{Plaintext}) or
366             return $pem->error("Encryption failed: " . $cbc->errstr);
367 21         266 ($buf, "$ctype,$iv");
368             }
369              
370             sub _getiv {
371 21     21   51 my $pem = shift;
372 21         138 my %p = @_;
373              
374 21         42 my $iv;
375 21 100       93 if (exists $p{IV}) {
376 1 50       10 if ($p{IV} =~ m/^[a-fA-F\d]+$/) {
377 1         7 $iv = pack("H*",$p{IV});
378             return length($iv) == $p{bs}
379 1 50       10 ? $iv
380             : $pem->error("Provided IV length is invalid");
381             }
382             else {
383 0         0 return $pem->error("Provided IV must be in hex format");
384             }
385             }
386 20         159 $iv = random_bytes($p{bs});
387 20 50       1424 croak "Internal error: unexpected IV length" if length($iv) != $p{bs};
388 20         124 $iv;
389             }
390              
391             1;
392             __END__