File Coverage

blib/lib/Crypt/OpenPGP/Signature.pm
Criterion Covered Total %
statement 187 202 92.5
branch 44 64 68.7
condition 9 13 69.2
subroutine 18 20 90.0
pod 7 11 63.6
total 265 310 85.4


line stmt bran cond sub pod time code
1             package Crypt::OpenPGP::Signature;
2 9     9   90 use strict;
  9         24  
  9         657  
3 9     9   54 use warnings;
  9         19  
  9         986  
4              
5             our $VERSION = '1.19'; # VERSION
6              
7 9     9   3528 use Crypt::OpenPGP::Digest;
  9         27  
  9         350  
8 9     9   5692 use Crypt::OpenPGP::Signature::SubPacket;
  9         39  
  9         479  
9 9     9   3429 use Crypt::OpenPGP::Key::Public;
  9         31  
  9         347  
10 9     9   58 use Crypt::OpenPGP::Constants qw( DEFAULT_DIGEST );
  9         21  
  9         164  
11 9     9   60 use Crypt::OpenPGP::ErrorHandler;
  9         17  
  9         275  
12 9     9   52 use base qw( Crypt::OpenPGP::ErrorHandler );
  9         19  
  9         24175  
13              
14 10     10 0 31 sub pkt_hdrlen { 2 }
15              
16             our %KEY_ALG = (
17             1 => 'RSA',
18             16 => 'ElGamal',
19             17 => 'DSA',
20             );
21              
22             sub key_id {
23 17     17 1 2908 my $sig = shift;
24 17 100       115 unless ($sig->{key_id}) {
25 7         45 my $sp = $sig->find_subpacket(16);
26 7         32 $sig->{key_id} = $sp->{data};
27             }
28 17         114 $sig->{key_id};
29             }
30              
31             sub timestamp {
32 0     0 1 0 my $sig = shift;
33             $sig->{version} < 4 ?
34             $sig->{timestamp} :
35 0 0       0 $sig->find_subpacket(2)->{data};
36             }
37              
38             sub digest {
39 0     0 1 0 my $sig = shift;
40 0         0 Crypt::OpenPGP::Digest->new($sig->{hash_alg});
41             }
42              
43             sub find_subpacket {
44 7     7 0 15 my $sig = shift;
45 7         19 my($type) = @_;
46 7         12 my @sp = (@{$sig->{subpackets_hashed}}, @{$sig->{subpackets_unhashed}});
  7         23  
  7         26  
47 7         22 for my $sp (@sp) {
48 14 100       58 return $sp if $sp->{type} == $type;
49             }
50             }
51              
52             sub new {
53 78     78 1 184 my $class = shift;
54 78         265 my $sig = bless { }, $class;
55 78         398 $sig->init(@_);
56             }
57              
58             sub init {
59 78     78 0 162 my $sig = shift;
60 78         313 my %param = @_;
61 78         373 $sig->{subpackets_hashed} = [];
62 78         250 $sig->{subpackets_unhashed} = [];
63 78 100 66     537 if ((my $obj = $param{Data}) && (my $cert = $param{Key})) {
64 8   100     46 $sig->{version} = $param{Version} || 4;
65 8   100     42 $sig->{type} = $param{Type} || 0x00;
66             $sig->{hash_alg} = $param{Digest} ? $param{Digest} :
67 8 100       83 $sig->{version} == 4 ? DEFAULT_DIGEST : 1;
    50          
68 8         75 $sig->{pk_alg} = $cert->key->alg_id;
69 8 100       42 if ($sig->{version} < 4) {
70 1         3 $sig->{timestamp} = time;
71 1         5 $sig->{key_id} = $cert->key_id;
72 1         4 $sig->{hash_len} = 5;
73             }
74             else {
75 7         108 my $sp = Crypt::OpenPGP::Signature::SubPacket->new;
76 7         28 $sp->{type} = 2;
77 7         10666 $sp->{data} = time;
78 7         18 push @{ $sig->{subpackets_hashed} }, $sp;
  7         32  
79 7         27 $sp = Crypt::OpenPGP::Signature::SubPacket->new;
80 7         21 $sp->{type} = 16;
81 7         40 $sp->{data} = $cert->key_id;
82 7         19 push @{ $sig->{subpackets_unhashed} }, $sp;
  7         21  
83             }
84 8 100       69 my $hash = $sig->hash_data(ref($obj) eq 'ARRAY' ? @$obj : $obj);
85 8         43 $sig->{chk} = substr $hash, 0, 2;
86             my $sig_data = $cert->key->sign($hash,
87 8         38 Crypt::OpenPGP::Digest->alg($sig->{hash_alg}));
88 8         7310386 my @sig = $cert->key->sig_props;
89 8         29 for my $e (@sig) {
90 15         125 $sig->{$e} = $sig_data->{$e};
91             }
92             }
93 78         343 $sig;
94             }
95              
96             sub sig_trailer {
97 19     19 0 38 my $sig = shift;
98 19         138 my $buf = Crypt::OpenPGP::Buffer->new;
99 19 100       212 if ($sig->{version} < 4) {
100 3         20 $buf->put_int8($sig->{type});
101 3         51 $buf->put_int32($sig->{timestamp});
102             }
103             else {
104 16         86 $buf->put_int8($sig->{version});
105 16         173 $buf->put_int8($sig->{type});
106 16         116 $buf->put_int8($sig->{pk_alg});
107 16         104 $buf->put_int8($sig->{hash_alg});
108 16         126 my $sp_data = $sig->_save_subpackets('hashed');
109 16 50       241 $buf->put_int16(defined $sp_data ? length($sp_data) : 0);
110 16 50       161 $buf->put_bytes($sp_data) if $sp_data;
111 16         214 my $len = $buf->length;
112 16         86 $buf->put_int8($sig->{version});
113 16         107 $buf->put_int8(0xff);
114 16         101 $buf->put_int32($len);
115             }
116 19         167 $buf->bytes;
117             }
118              
119             sub parse {
120 70     70 1 180 my $class = shift;
121 70         212 my($buf) = @_;
122 70         349 my $sig = $class->new;
123              
124 70         312 $sig->{version} = $buf->get_int8;
125 70 100       1632 if ($sig->{version} < 4) {
126 2         97 $sig->{sig_data} = $buf->bytes($buf->offset+1, 5);
127 2         29 $sig->{hash_len} = $buf->get_int8;
128             return $class->error("Hash len $sig->{hash_len} != 5")
129 2 50       30 unless $sig->{hash_len} == 5;
130 2         8 $sig->{type} = $buf->get_int8;
131 2         44 $sig->{timestamp} = $buf->get_int32;
132 2         43 $sig->{key_id} = $buf->get_bytes(8);
133 2         52 $sig->{pk_alg} = $buf->get_int8;
134 2         38 $sig->{hash_alg} = $buf->get_int8;
135             }
136             else {
137 68         257 $sig->{sig_data} = $buf->bytes($buf->offset-1, 6);
138 68         1011 $sig->{type} = $buf->get_int8;
139 68         1139 $sig->{pk_alg} = $buf->get_int8;
140 68         1342 $sig->{hash_alg} = $buf->get_int8;
141 68         1123 for my $h (qw( hashed unhashed )) {
142 136         880 my $subpack_len = $buf->get_int16;
143 136         2894 my $sp_buf = $buf->extract($subpack_len);
144 136 100       6294 $sig->{sig_data} .= $sp_buf->bytes if $h eq 'hashed';
145 136         916 while ($sp_buf->offset < $sp_buf->length) {
146 297         2109 my $len = $sp_buf->get_int8;
147 297 100 66     5610 if ($len >= 192 && $len < 255) {
    50          
148 1         10 my $len2 = $sp_buf->get_int8;
149 1         23 $len = (($len-192) << 8) + $len2 + 192;
150             } elsif ($len == 255) {
151 0         0 $len = $sp_buf->get_int32;
152             }
153 297         730 my $this_buf = $sp_buf->extract($len);
154 297         10707 my $sp = Crypt::OpenPGP::Signature::SubPacket->parse($this_buf);
155 297         1239 push @{ $sig->{"subpackets_$h"} }, $sp;
  297         1858  
156             }
157             }
158             }
159 70         600 our %KEY_ALG;
160 70 100       6943 eval require Crypt::DSA if $KEY_ALG{$sig->{pk_alg}} eq 'DSA';
161 70         838 $sig->{chk} = $buf->get_bytes(2);
162             ## XXX should be Crypt::OpenPGP::Signature->new($sig->{pk_alg})?
163             my $key = Crypt::OpenPGP::Key::Public->new($sig->{pk_alg})
164 70 50       2311 or return $class->error(Crypt::OpenPGP::Key::Public->errstr);
165 70         336 my @sig = $key->sig_props;
166 70         226 for my $e (@sig) {
167 117         3999 $sig->{$e} = $buf->get_mp_int;
168             }
169 70         10056 $sig;
170             }
171              
172             sub save {
173 10     10 1 24 my $sig = shift;
174 10         76 my $buf = Crypt::OpenPGP::Buffer->new;
175 10         154 $buf->put_int8($sig->{version});
176 10 100       146 if ($sig->{version} < 4) {
177 1         7 $buf->put_int8($sig->{hash_len});
178 1         11 $buf->put_int8($sig->{type});
179 1         11 $buf->put_int32($sig->{timestamp});
180 1         12 $buf->put_bytes($sig->{key_id}, 8);
181 1         13 $buf->put_int8($sig->{pk_alg});
182 1         10 $buf->put_int8($sig->{hash_alg});
183             }
184             else {
185 9         48 $buf->put_int8($sig->{type});
186 9         63 $buf->put_int8($sig->{pk_alg});
187 9         62 $buf->put_int8($sig->{hash_alg});
188 9         57 for my $h (qw( hashed unhashed )) {
189 18         122 my $sp_data = $sig->_save_subpackets($h);
190 18 50       239 $buf->put_int16(defined $sp_data ? length($sp_data) : 0);
191 18 50       173 $buf->put_bytes($sp_data) if $sp_data;
192             }
193             }
194 10         116 $buf->put_bytes($sig->{chk}, 2);
195             ## XXX should be Crypt::OpenPGP::Signature->new($sig->{pk_alg})?
196 10         168 my $key = Crypt::OpenPGP::Key::Public->new($sig->{pk_alg});
197 10         43 my @sig = $key->sig_props;
198 10         32 for my $e (@sig) {
199 17         258 $buf->put_mp_int($sig->{$e});
200             }
201 10         186 $buf->bytes;
202             }
203              
204             sub _save_subpackets {
205 34     34   61 my $sig = shift;
206 34         111 my($h) = @_;
207 34         114 my @sp;
208             return unless $sig->{"subpackets_$h"} &&
209 34 50 33     162 (@sp = @{ $sig->{"subpackets_$h"} });
  34         186  
210 34         105 my $sp_buf = Crypt::OpenPGP::Buffer->new;
211 34         317 for my $sp (@sp) {
212 41         289 my $data = $sp->save;
213 41         594 my $len = length $data;
214 41 50       108 if ($len < 192) {
    0          
215 41         107 $sp_buf->put_int8($len);
216             } elsif ($len < 8384) {
217 0         0 $len -= 192;
218 0         0 $sp_buf->put_int8( int($len / 256) + 192 );
219 0         0 $sp_buf->put_int8( $len % 256 );
220             } else {
221 0         0 $sp_buf->put_int8(255);
222 0         0 $sp_buf->put_int32($len);
223             }
224 41         333 $sp_buf->put_bytes($data);
225             }
226 34         369 $sp_buf->bytes;
227             }
228              
229             sub hash_data {
230 19     19 1 45 my $sig = shift;
231 19         170 my $buf = Crypt::OpenPGP::Buffer->new;
232 19         329 my $type = ref($_[0]);
233 19 100       330 if ($type eq 'Crypt::OpenPGP::Certificate') {
    50          
234 4         9 my $cert = shift;
235 4         36 $buf->put_int8(0x99);
236 4         79 my $pk = $cert->public_cert->save;
237 4         85 $buf->put_int16(length $pk);
238 4         37 $buf->put_bytes($pk);
239              
240 4 50       38 if (@_) {
241 4 50       35 if (ref($_[0]) eq 'Crypt::OpenPGP::UserID') {
    0          
242 4         18 my $uid = shift;
243 4         22 my $ud = $uid->save;
244 4 100       21 if ($sig->{version} >= 4) {
245 2         8 $buf->put_int8(0xb4);
246 2         17 $buf->put_int32(length $ud);
247             }
248 4         19 $buf->put_bytes($ud);
249             }
250             elsif (ref($_[0]) eq 'Crypt::OpenPGP::Certificate') {
251 0         0 my $subcert = shift;
252 0         0 $buf->put_int8(0x99);
253 0         0 my $k = $subcert->public_cert->save;
254 0         0 $buf->put_int16(length $k);
255 0         0 $buf->put_bytes($k);
256             }
257             }
258             }
259             elsif ($type eq 'Crypt::OpenPGP::Plaintext') {
260 15         54 my $pt = shift;
261 15         298 my $data = $pt->data;
262 15 100       58 if ($pt->mode eq 't') {
263 4         33 require Crypt::OpenPGP::Util;
264 4         35 $buf->put_bytes(Crypt::OpenPGP::Util::canonical_text($data));
265             }
266             else {
267 11         62 $buf->put_bytes($data);
268             }
269             }
270              
271 19         351 $buf->put_bytes($sig->sig_trailer);
272              
273 19 50       461 my $hash = Crypt::OpenPGP::Digest->new($sig->{hash_alg}) or
274             return $sig->error( Crypt::OpenPGP::Digest->errstr );
275 19         79 $hash->hash($buf->bytes);
276             }
277              
278             1;
279             __END__