File Coverage

blib/lib/Convert/PEM.pm
Criterion Covered Total %
statement 134 138 97.1
branch 30 52 57.6
condition 10 22 45.4
subroutine 22 23 95.6
pod 7 13 53.8
total 203 248 81.8


line stmt bran cond sub pod time code
1             package Convert::PEM;
2 4     4   2159 use strict;
  4         9  
  4         685  
3 4     4   218 use 5.008_001;
  4         15  
  4         191  
4              
5 4     4   36 use base qw( Class::ErrorHandler );
  4         6  
  4         6926  
6              
7 4     4   7118 use MIME::Base64;
  4         4816  
  4         1192  
8 4     4   29 use Digest::MD5 qw( md5 );
  4         9  
  4         241  
9 4     4   4593 use Convert::ASN1;
  4         206479  
  4         304  
10 4     4   56 use Carp qw( croak );
  4         9  
  4         202  
11 4     4   2945 use Convert::PEM::CBC;
  4         11  
  4         115  
12              
13 4     4   29 use vars qw( $VERSION );
  4         9  
  4         5507  
14             $VERSION = '0.08';
15              
16             sub new {
17 3     3 1 41 my $class = shift;
18 3         11 my $pem = bless { }, $class;
19 3         17 $pem->init(@_);
20             }
21              
22             sub init {
23 3     3 0 10 my $pem = shift;
24 3         19 my %param = @_;
25 3 50 33     41 unless (exists $param{ASN} && exists $param{Name}) {
26 0         0 return (ref $pem)->error("init: Name and ASN are required");
27             }
28             else {
29 3         33 $pem->{ASN} = $param{ASN};
30 3         11 $pem->{Name} = $param{Name};
31             }
32 3         10 $pem->{Macro} = $param{Macro};
33 3         31 my $asn = $pem->{_asn} = Convert::ASN1->new;
34 3 50       143 $asn->prepare( $pem->{ASN} ) or
35             return (ref $pem)->error("ASN prepare failed: $asn->{error}");
36 3         3637 $pem;
37             }
38              
39 12     12 1 39 sub asn { $_[0]->{_asn} }
40 0     0 1 0 sub ASN { $_[0]->{ASN} }
41 14     14 0 88 sub name { $_[0]->{Name} }
42              
43             sub read {
44 4     4 1 27 my $pem = shift;
45 4         16 my %param = @_;
46              
47 4         6 my $blob;
48 4         26 local *FH;
49 4         89 my $fname = delete $param{Filename};
50 4 50       279 open FH, $fname or
51             return $pem->error("Can't open $fname: $!");
52 4         8 $blob = do { local $/; };
  4         17  
  4         128  
53 4         52 close FH;
54              
55 4         12 $param{Content} = $blob;
56 4         22 $pem->decode(%param);
57             }
58              
59             sub write {
60 3     3 1 20 my $pem = shift;
61 3         14 my %param = @_;
62              
63 3 50       13 my $fname = delete $param{Filename} or
64             return $pem->error("write: Filename is required");
65 3         15 my $blob = $pem->encode(%param);
66              
67 3         12 local *FH;
68 3 50       37908 open FH, ">$fname" or
69             return $pem->error("Can't open $fname: $!");
70 3         69 print FH $blob;
71 3         389 close FH;
72 3         161 $blob;
73             }
74              
75             sub decode {
76 8     8 1 105 my $pem = shift;
77 8         29 my %param = @_;
78 8 50       32 my $blob = $param{Content} or
79             return $pem->error("'Content' is required");
80 8         25 chomp $blob;
81              
82 8 50       31 my $dec = $pem->explode($blob) or return;
83 8   33     46 my $name = $param{Name} || $pem->name;
84 8 50       29 return $pem->error("Object $dec->{Object} does not match " . $name)
85             unless $dec->{Object} eq $name;
86              
87 8         21 my $head = $dec->{Headers};
88 8         16 my $buf = $dec->{Content};
89 8         17 my %headers = map { $_->[0] => $_->[1] } @$head;
  8         30  
90 8 100 66     46 if (%headers && $headers{'Proc-Type'} eq '4,ENCRYPTED') {
91 4 100       23 $buf = $pem->decrypt( Ciphertext => $buf,
92             Info => $headers{'DEK-Info'},
93             Password => $param{Password} )
94             or return;
95             }
96              
97 6         23 my $asn = $pem->asn;
98 6 50 33     252 if (my $macro = ($param{Macro} || $pem->{Macro})) {
99 0 0       0 $asn = $asn->find($macro) or
100             return $pem->error("Can't find Macro $macro");
101             }
102 6 50       33 my $obj = $asn->decode($buf) or
103             return $pem->error("ASN decode failed: $asn->{error}");
104              
105 6         5906 $obj;
106             }
107              
108             sub encode {
109 6     6 1 150 my $pem = shift;
110 6         16 my %param = @_;
111              
112 6         20 my $asn = $pem->asn;
113 6 50 33     44 if (my $macro = ($param{Macro} || $pem->{Macro})) {
114 0 0       0 $asn = $asn->find($macro) or
115             return $pem->error("Can't find Macro $macro");
116             }
117 6 50       36 my $buf = $asn->encode( $param{Content} ) or
118             return $pem->error("ASN encode failed: $asn->{error}");
119              
120 6         11185 my(@headers);
121 6 100       20 if ($param{Password}) {
122 2         4 my($info);
123 2 50       14 ($buf, $info) = $pem->encrypt( Plaintext => $buf,
124             Password => $param{Password} )
125             or return;
126 2         10 push @headers, [ 'Proc-Type' => '4,ENCRYPTED' ];
127 2         8 push @headers, [ 'DEK-Info' => $info ];
128             }
129              
130 6   33     41 $pem->implode( Object => $param{Name} || $pem->name,
131             Headers => \@headers,
132             Content => $buf );
133             }
134              
135             sub explode {
136 11     11 0 81 my $pem = shift;
137 11         22 my($message) = @_;
138              
139             # Canonicalize line endings into "\n".
140 11         302 $message =~ s/\r\n|\n|\r/\n/g;
141              
142 11         199 my($head, $object, $headers, $content, $tail) = $message =~
143             m:(-----BEGIN ([^\n\-]+)-----)\n(.*?\n\n)?(.+)(-----END .*?-----)$:s;
144 11         62 my $buf = decode_base64($content);
145              
146 11         16 my @headers;
147 11 100       41 if ($headers) {
148 7         32 for my $h ( split /\n/, $headers ) {
149 17         75 my($k, $v) = split /:\s*/, $h, 2;
150 17 50       79 push @headers, [ $k => $v ] if $k;
151             }
152             }
153              
154 11         410 { Content => $buf,
155             Object => $object,
156             Headers => \@headers }
157             }
158              
159             sub implode {
160 7     7 0 41 my $pem = shift;
161 7         31 my %param = @_;
162 7         31 my $head = "-----BEGIN $param{Object}-----";
163 7         21 my $tail = "-----END $param{Object}-----";
164 7         63 my $content = encode_base64( $param{Content}, '' );
165 7         64 $content =~ s!(.{1,64})!$1\n!g;
166 7         29 my $headers = join '',
167 7         23 map { "$_->[0]: $_->[1]\n" }
168 7         16 @{ $param{Headers} };
169 7 100       24 $headers .= "\n" if $headers;
170 7         55 "$head\n$headers$content$tail\n";
171             }
172              
173 4     4   30 use vars qw( %CTYPES );
  4         10  
  4         1571  
174             %CTYPES = ('DES-EDE3-CBC' => 'Crypt::DES_EDE3');
175              
176             sub decrypt {
177 4     4 0 8 my $pem = shift;
178 4         18 my %param = @_;
179 4   100     22 my $passphrase = $param{Password} || "";
180 4         15 my($ctype, $iv) = split /,/, $param{Info};
181 4 50       30 my $cmod = $CTYPES{$ctype} or
182             return $pem->error("Unrecognized cipher: '$ctype'");
183 4         26 $iv = pack "H*", $iv;
184 4         34 my $cbc = Convert::PEM::CBC->new(
185             Passphrase => $passphrase,
186             Cipher => $cmod,
187             IV => $iv );
188 4 100       21 my $buf = $cbc->decrypt($param{Ciphertext}) or
189             return $pem->error("Decryption failed: " . $cbc->errstr);
190 2         29 $buf;
191             }
192              
193             sub encrypt {
194 2     2 0 4 my $pem = shift;
195 2         9 my %param = @_;
196 2 50       11 $param{Password} or return $param{Plaintext};
197 2   50     24 my $ctype = $param{Cipher} || 'DES-EDE3-CBC';
198 2 50       16 my $cmod = $CTYPES{$ctype} or
199             return $pem->error("Unrecognized cipher: '$ctype'");
200 2         25 my $cbc = Convert::PEM::CBC->new(
201             Passphrase => $param{Password},
202             Cipher => $cmod );
203 2         14 my $iv = uc join '', unpack "H*", $cbc->iv;
204 2 50       14 my $buf = $cbc->encrypt($param{Plaintext}) or
205             return $pem->error("Encryption failed: " . $cbc->errstr);
206 2         71 ($buf, "$ctype,$iv");
207             }
208              
209             1;
210             __END__