File Coverage

blib/lib/Crypt/OpenPGP/Certificate.pm
Criterion Covered Total %
statement 202 253 79.8
branch 52 76 68.4
condition 10 21 47.6
subroutine 31 38 81.5
pod 20 26 76.9
total 315 414 76.0


line stmt bran cond sub pod time code
1             package Crypt::OpenPGP::Certificate;
2 9     9   90 use strict;
  9         24  
  9         462  
3 9     9   83 use warnings;
  9         18  
  9         959  
4              
5             our $VERSION = '1.19'; # VERSION
6              
7 9     9   5767 use Crypt::OpenPGP::S2k;
  9         34  
  9         392  
8 9     9   1722 use Crypt::OpenPGP::Key::Public;
  9         23  
  9         321  
9 9     9   15681 use Crypt::OpenPGP::Key::Secret;
  9         37  
  9         372  
10 9     9   82 use Crypt::OpenPGP::Buffer;
  9         20  
  9         287  
11 9     9   49 use Crypt::OpenPGP::Util qw( mp2bin bin2mp bitsize );
  9         29  
  9         878  
12 9         94 use Crypt::OpenPGP::Constants qw( DEFAULT_CIPHER
13             PGP_PKT_PUBLIC_KEY
14             PGP_PKT_PUBLIC_SUBKEY
15             PGP_PKT_SECRET_KEY
16 9     9   73 PGP_PKT_SECRET_SUBKEY );
  9         22  
17 9     9   4921 use Crypt::OpenPGP::Cipher;
  9         50  
  9         315  
18 9     9   55 use Crypt::OpenPGP::ErrorHandler;
  9         19  
  9         273  
19 9     9   53 use base qw( Crypt::OpenPGP::ErrorHandler );
  9         16  
  9         35233  
20              
21             {
22             our %KEY_ALG = (
23             1 => 'RSA',
24             16 => 'ElGamal',
25             17 => 'DSA',
26             );
27             my @PKT_TYPES = (
28             PGP_PKT_PUBLIC_KEY,
29             PGP_PKT_PUBLIC_SUBKEY,
30             PGP_PKT_SECRET_KEY,
31             PGP_PKT_SECRET_SUBKEY
32             );
33             sub pkt_type {
34 3     3 0 9 my $cert = shift;
35 3         17 $PKT_TYPES[ ($cert->{is_secret} << 1) | $cert->{is_subkey} ];
36             }
37             }
38              
39             sub new {
40 108     108 1 256 my $class = shift;
41 108         347 my $cert = bless { }, $class;
42 108         556 $cert->init(@_);
43             }
44              
45             sub init {
46 108     108 0 242 my $cert = shift;
47 108         362 my %param = @_;
48 108 100       531 if (my $key = $param{Key}) {
49 4   50     20 $cert->{version} = $param{Version} || 4;
50 4         23 $cert->{key} = $key;
51 4         7 our %KEY_ALG;
52 4 100       40 eval require Crypt::DSA if $KEY_ALG{$key->alg_id} eq 'DSA';
53 4         37 $cert->{is_secret} = $key->is_secret;
54 4   50     45 $cert->{is_subkey} = $param{Subkey} || 0;
55 4         12 $cert->{timestamp} = time;
56 4         14 $cert->{pk_alg} = $key->alg_id;
57 4 100       26 if ($cert->{version} < 4) {
58 2   50     25 $cert->{validity} = $param{Validity} || 0;
59 2 50       44 $key->alg eq 'RSA' or
60             return (ref $cert)->error("Version 3 keys must be RSA");
61             }
62 4         72 $cert->{s2k} = Crypt::OpenPGP::S2k->new('Salt_Iter');
63              
64 4 100       24 if ($cert->{is_secret}) {
65             $param{Passphrase} or
66 2 50       6 return (ref $cert)->error("Need a Passphrase to lock key");
67 2   33     33 $cert->{cipher} = $param{Cipher} || DEFAULT_CIPHER;
68 2         43 $cert->lock($param{Passphrase});
69             }
70             }
71 108         410 $cert;
72             }
73              
74 0     0 0 0 sub type { $_[0]->{type} }
75 0     0 1 0 sub version { $_[0]->{version} }
76 0     0 1 0 sub timestamp { $_[0]->{timestamp} }
77 0     0 1 0 sub validity { $_[0]->{validity} }
78 9     9 0 453 sub pk_alg { $_[0]->{pk_alg} }
79 65     65 1 690 sub key { $_[0]->{key} }
80 9     9 1 95 sub is_secret { $_[0]->{key}->is_secret }
81 95     95 1 608 sub is_subkey { $_[0]->{is_subkey} }
82 20     20 1 2678 sub is_protected { $_[0]->{is_protected} }
83 30     30 1 228 sub can_encrypt { $_[0]->{key}->can_encrypt }
84 6     6 1 64 sub can_sign { $_[0]->{key}->can_sign }
85             sub uid {
86 26     26 0 59 my $cert = shift;
87 26 50       120 $cert->{_uid} = shift if @_;
88 26         76 $cert->{_uid};
89             }
90              
91             sub public_cert {
92 9     9 1 52 my $cert = shift;
93 9         16 our %KEY_ALG;
94 9 100       53 eval require Crypt::DSA if $KEY_ALG{$cert->pk_alg} eq 'DSA';
95 9 100       43 return $cert unless $cert->is_secret;
96 3         17 my $pub = (ref $cert)->new;
97 3         14 for my $f (qw( version timestamp pk_alg is_subkey )) {
98 12         56 $pub->{$f} = $cert->{$f};
99             }
100 3 50       14 $pub->{validity} = $cert->{validity} if $cert->{version} < 4;
101 3         28 $pub->{key} = $cert->{key}->public_key;
102 3         27 $pub;
103             }
104              
105             sub key_id {
106 174     174 1 2668 my $cert = shift;
107 174 100       666 unless ($cert->{key_id}) {
108 99 100       360 if ($cert->{version} < 4) {
109 3         66 $cert->{key_id} = substr(mp2bin($cert->{key}->n), -8);
110             }
111             else {
112 96         467 $cert->{key_id} = substr($cert->fingerprint, -8);
113             }
114             }
115 174         1720 $cert->{key_id};
116             }
117              
118 0     0 1 0 sub key_id_hex { uc unpack 'H*', $_[0]->key_id }
119              
120             sub fingerprint {
121 96     96 1 234 my $cert = shift;
122 96 100       370 unless ($cert->{fingerprint}) {
123 3 50       10 if ($cert->{version} < 4) {
124 0         0 my $dgst = Crypt::OpenPGP::Digest->new('MD5');
125             $cert->{fingerprint} =
126 0         0 $dgst->hash(mp2bin($cert->{key}->n) . mp2bin($cert->{key}->e));
127             }
128             else {
129 3         19 my $data = $cert->public_cert->save;
130 3         90 $cert->{fingerprint} = _gen_v4_fingerprint($data);
131             }
132             }
133 96         550 $cert->{fingerprint};
134             }
135              
136 0     0 1 0 sub fingerprint_hex { uc unpack 'H*', $_[0]->fingerprint }
137              
138             sub fingerprint_words {
139 0     0 1 0 require Crypt::OpenPGP::Words;
140 0         0 Crypt::OpenPGP::Words->encode($_[0]->fingerprint);
141             }
142              
143             sub _gen_v4_fingerprint {
144 102     102   337 my($data) = @_;
145 102         697 my $buf = Crypt::OpenPGP::Buffer->new;
146 102         1787 $buf->put_int8(0x99);
147 102         1775 $buf->put_int16(length $data);
148 102         1290 $buf->put_bytes($data);
149 102         2267 my $dgst = Crypt::OpenPGP::Digest->new('SHA1');
150 102         519 $dgst->hash($buf->bytes);
151             }
152              
153             sub parse {
154 101     101 1 265 my $class = shift;
155 101         321 my($buf, $secret, $subkey) = @_;
156 101         444 my $cert = $class->new;
157 101         426 $cert->{is_secret} = $secret;
158 101         342 $cert->{is_subkey} = $subkey;
159              
160 101         540 $cert->{version} = $buf->get_int8;
161 101         2337 $cert->{timestamp} = $buf->get_int32;
162 101 100       2599 if ($cert->{version} < 4) {
163 1         7 $cert->{validity} = $buf->get_int16;
164             }
165 101         339 $cert->{pk_alg} = $buf->get_int8;
166              
167 101 100       1768 my $key_class = 'Crypt::OpenPGP::Key::' . ($secret ? 'Secret' : 'Public');
168 101         1151 my $key = $cert->{key} = $key_class->new($cert->{pk_alg});
169 101 100       454 if( ! defined $key ) {
170 1         6 $cert->{unparsed_key} = $buf->get_bytes( $buf->length - $buf->offset );
171 1         37 return $cert;
172             }
173              
174 100         376 my @pub = $key->public_props;
175 100         305 for my $e (@pub) {
176 313         32998 $key->$e($buf->get_mp_int);
177             }
178              
179 100 100       12122 if ($cert->{version} >= 4) {
180 99         703 my $data = $buf->bytes(0, $buf->offset);
181 99         2208 $cert->{fingerprint} = _gen_v4_fingerprint($data);
182             }
183              
184 100 100       489 if ($secret) {
185 39         247 $cert->{cipher} = $buf->get_int8;
186 39 50       1011 if ($cert->{cipher}) {
187 39         200 $cert->{is_protected} = 1;
188 39 50 66     282 if ($cert->{cipher} == 255 || $cert->{cipher} == 254) {
189 39         183 $cert->{sha1check} = $cert->{cipher} == 254;
190 39         129 $cert->{cipher} = $buf->get_int8;
191 39         1014 $cert->{s2k} = Crypt::OpenPGP::S2k->parse($buf);
192             }
193             else {
194 0         0 $cert->{s2k} = Crypt::OpenPGP::S2k->new('Simple');
195 0         0 $cert->{s2k}->set_hash('MD5');
196             }
197              
198 39         131 $cert->{iv} = $buf->get_bytes(8);
199             }
200              
201 39 50       937 if ($cert->{is_protected}) {
202 39 50       163 if ($cert->{version} < 4) {
203 0         0 $cert->{encrypted} = {};
204 0         0 my @sec = $key->secret_props;
205 0         0 for my $e (@sec) {
206 0         0 my $h = $cert->{encrypted}{"${e}h"} = $buf->get_bytes(2);
207 0         0 $cert->{encrypted}{"${e}b"} =
208             $buf->get_bytes(int((unpack('n', $h)+7)/8));
209             }
210 0         0 $cert->{csum} = $buf->get_int16;
211             }
212             else {
213             $cert->{encrypted} =
214 39         172 $buf->get_bytes($buf->length - $buf->offset);
215             }
216             }
217             else {
218 0         0 my @sec = $key->secret_props;
219 0         0 for my $e (@sec) {
220 0         0 $key->$e($buf->get_mp_int);
221             }
222             }
223             }
224              
225 100         1693 $cert;
226             }
227              
228             sub save {
229 10     10 1 36 my $cert = shift;
230 10         78 my $buf = Crypt::OpenPGP::Buffer->new;
231              
232 10         182 $buf->put_int8($cert->{version});
233 10         149 $buf->put_int32($cert->{timestamp});
234 10 100       110 if ($cert->{version} < 4) {
235 3         29 $buf->put_int16($cert->{validity});
236             }
237 10         64 $buf->put_int8($cert->{pk_alg});
238              
239 10         95 my $key = $cert->{key};
240 10 100 66     62 if( ! defined $key && defined $cert->{unparsed_key} ) {
241 1         5 $buf->put_bytes($cert->{unparsed_key});
242 1         14 return $buf->bytes;
243             }
244              
245 9         74 my @pub = $key->public_props;
246 9         29 for my $e (@pub) {
247 29         624 $buf->put_mp_int($key->$e());
248             }
249              
250 9 50       222 if ($cert->{key}->is_secret) {
251 0 0       0 if ($cert->{cipher}) {
252 0         0 $buf->put_int8(255);
253 0         0 $buf->put_int8($cert->{cipher});
254 0         0 $buf->append($cert->{s2k}->save);
255 0         0 $buf->put_bytes($cert->{iv});
256              
257 0 0       0 if ($cert->{version} < 4) {
258 0         0 my @sec = $key->secret_props;
259 0         0 for my $e (@sec) {
260 0         0 $buf->put_bytes($cert->{encrypted}{"${e}h"});
261 0         0 $buf->put_bytes($cert->{encrypted}{"${e}b"});
262             }
263 0         0 $buf->put_int16($cert->{csum});
264             }
265             else {
266 0         0 $buf->put_bytes($cert->{encrypted});
267             }
268             }
269             else {
270 0         0 my @sec = $key->secret_props;
271 0         0 for my $e (@sec) {
272 0         0 $key->$e($buf->get_mp_int);
273             }
274             }
275             }
276 9         48 $buf->bytes;
277             }
278              
279             sub v3_checksum {
280 1     1 0 3 my $cert = shift;
281 1         4 my $k = $cert->{encrypted};
282 1         2 my $sum = 0;
283 1         9 my @sec = $cert->{key}->secret_props;
284 1         3 for my $e (@sec) {
285 4         14 $sum += unpack '%16C*', $k->{"${e}h"};
286 4         16 $sum += unpack '%16C*', $k->{"${e}b"};
287             }
288 1         29 $sum & 0xFFFF;
289             }
290              
291             sub unlock {
292 9     9 1 109 my $cert = shift;
293 9 50 33     74 return 1 unless $cert->{is_secret} && $cert->{is_protected};
294 9         31 my($passphrase) = @_;
295 9 50       137 my $cipher = Crypt::OpenPGP::Cipher->new($cert->{cipher}) or
296             return $cert->error( Crypt::OpenPGP::Cipher->errstr );
297 9         52 my $key = $cert->{s2k}->generate($passphrase, $cipher->keysize);
298 9         108 $cipher->init($key, $cert->{iv});
299 9         117 my @sec = $cert->{key}->secret_props;
300 9 50       51 if ($cert->{version} < 4) {
301 0         0 my $k = $cert->{encrypted};
302 0         0 my $r = {};
303 0         0 for my $e (@sec) {
304 0         0 $r->{$e} = $k->{"${e}b"};
305 0         0 $k->{"${e}b"} = $cipher->decrypt($r->{$e});
306             }
307 0 0       0 unless ($cert->{csum} == $cert->v3_checksum) {
308 0         0 $k->{"${_}b"} = $r->{$_} for @sec;
309 0         0 return $cert->error("Bad checksum");
310             }
311 0         0 for my $e (@sec) {
312 0         0 $cert->{key}->$e(bin2mp($k->{"${e}b"}));
313             }
314 0 0       0 unless ($cert->{key}->check) {
315 0         0 $k->{"${_}b"} = $r->{$_} for @sec;
316 0         0 return $cert->error("p*q != n");
317             }
318             }
319             else {
320 9         105 my $decrypted = $cipher->decrypt($cert->{encrypted});
321 9 100       67 if ($cert->{sha1check}) {
322 1         11 my $dgst = Crypt::OpenPGP::Digest->new('SHA1');
323 1         4 my $csum = substr $decrypted, -20, 20, '';
324 1 50       2 unless ($dgst->hash($decrypted) eq $csum) {
325 0         0 return $cert->error("Bad SHA-1 hash");
326             }
327             } else {
328 8         62 my $csum = unpack "n", substr $decrypted, -2, 2, '';
329 8         43 my $gen_csum = unpack '%16C*', $decrypted;
330 8 50       39 unless ($csum == $gen_csum) {
331 0         0 return $cert->error("Bad simple checksum");
332             }
333             }
334 9         89 my $buf = Crypt::OpenPGP::Buffer->new;
335 9         198 $buf->append($decrypted);
336 9         79 for my $e (@sec) {
337 9         72 $cert->{key}->$e( $buf->get_mp_int );
338             }
339             }
340              
341 9         717 $cert->{is_protected} = 0;
342              
343 9         226 1;
344             }
345              
346             sub lock {
347 2     2 1 6 my $cert = shift;
348 2 50 33     43 return if !$cert->{is_secret} || $cert->{is_protected};
349 2         6 my($passphrase) = @_;
350 2         75 my $cipher = Crypt::OpenPGP::Cipher->new($cert->{cipher});
351 2         12 my $sym_key = $cert->{s2k}->generate($passphrase, $cipher->keysize);
352 2         16 $cert->{iv} = Crypt::OpenPGP::Util::get_random_bytes(8);
353 2         201 $cipher->init($sym_key, $cert->{iv});
354 2         14 my @sec = $cert->{key}->secret_props;
355 2 100       7 if ($cert->{version} < 4) {
356 1         6 my $k = $cert->{encrypted} = {};
357 1         23 my $key = $cert->key;
358 1         3 for my $e (@sec) {
359 4         8669 $k->{"${e}b"} = mp2bin($key->$e());
360 4         52 $k->{"${e}h"} = pack 'n', bitsize($key->$e());
361             }
362 1         2647 $cert->{csum} = $cert->v3_checksum;
363 1         4 for my $e (@sec) {
364 4         30 $k->{"${e}b"} = $cipher->encrypt( $k->{"${e}b"} );
365             }
366             }
367             else {
368 1         14 my $buf = Crypt::OpenPGP::Buffer->new;
369 1         28 for my $e (@sec) {
370 1         10 $buf->put_mp_int($cert->{key}->$e());
371             }
372 1         19 my $cnt = $buf->bytes;
373 1         27 $cnt .= pack 'n', unpack '%16C*', $cnt;
374 1         24 $cert->{encrypted} = $cipher->encrypt($cnt);
375             }
376              
377 2         29 $cert->{is_protected} = 1;
378 2         47 1;
379             }
380              
381             1;
382             __END__