File Coverage

blib/lib/Mail/DomainKeys/Signature.pm
Criterion Covered Total %
statement 81 152 53.2
branch 39 102 38.2
condition 4 12 33.3
subroutine 18 25 72.0
pod 0 21 0.0
total 142 312 45.5


line stmt bran cond sub pod time code
1             # Copyright (c) 2004 Anthony D. Urso. All rights reserved.
2             # This program is free software; you can redistribute it and/or
3             # modify it under the same terms as Perl itself.
4              
5             package Mail::DomainKeys::Signature;
6              
7 7     7   2286 use strict;
  7         14  
  7         10105  
8              
9             our $VERSION = "0.88";
10              
11             sub new {
12 0     0 0 0 my $type = shift;
13 0         0 my %prms = @_;
14 0         0 my $self = {};
15              
16 0         0 $self->{'ALGO'} = $prms{'Algorithm'};
17 0         0 $self->{'DATA'} = $prms{'Signature'};
18 0         0 $self->{'METH'} = $prms{'Method'};
19 0         0 $self->{'DOMN'} = $prms{'Domain'};
20 0         0 $self->{'HDRS'} = $prms{'Headers'};
21 0         0 $self->{'PROT'} = $prms{'Query'};
22 0         0 $self->{'SLCT'} = $prms{'Selector'};
23 0         0 $self->{'SHDR'} = $prms{'SignHeaders'};
24 0         0 $self->{'SIGN'} = $prms{'Signing'};
25 0         0 $self->{'CFWS'} = $prms{'FWS'};
26              
27 0         0 bless $self, $type;
28             }
29              
30             sub parse {
31 7     7 0 16 my $type = shift;
32 7         26 my %prms = @_;
33 7         18 my $self = {};
34              
35              
36 7         124 foreach my $tag (split /;/, $prms{"String"}) {
37 43         738 $tag =~ s/^\s*|\s*$//g;
38              
39 43         82 foreach ($tag) {
40 43 100       483 /^a=(rsa-sha1)$/i and
41             $self->{'ALGO'} = lc $1;
42 43 100       666 /^b=([A-Za-z0-9\+\/\=\s]+)$/ and
43             $self->{'DATA'} = $1;
44 43 100       202 /^c=(nofws|simple)$/i and
45             $self->{'METH'} = lc $1;
46 43 100       137 /^d=([A-Za-z0-9\-\.]+)$/ and
47             $self->{'DOMN'} = lc $1;
48 43 100       111 /^h=(.*)$/s and
49             $self->{'HDRS'} = lc $1;
50 43 100       477 /^q=(dns)$/i and
51             $self->{'PROT'} = lc $1;
52 43 100       196 /^s=(\S+)$/ and
53             $self->{'SLCT'} = $1;
54             }
55             }
56              
57 7         50 bless $self, $type;
58             }
59              
60             sub wantheader {
61 30     30 0 41 my $self = shift;
62 30         42 my $attr = shift;
63              
64             # we are signing, and a list of headers to sign was specified
65 30 50       76 if ($self->signheaderlist) {
66 0         0 foreach my $key ($self->signheaderlist) {
67 0 0       0 lc $attr eq lc $key and
68             return 1;
69             }
70              
71 0         0 return;
72             }
73              
74             # we are verifying
75 30 50       77 if ($self->headerlist) {
76 0         0 foreach my $key ($self->headerlist) {
77 0 0       0 lc $attr eq lc $key and
78             return 1;
79             }
80              
81 0         0 return;
82             }
83              
84             # we are signing and a list of headers to sign was not specified,
85             # or we are verifying and the DomainKeys-Signature header does not
86             # have a h= term
87 30         180 return 1;
88             }
89              
90             sub as_string {
91 0     0 0 0 my $self = shift;
92              
93 0         0 my $text;
94              
95 0 0       0 $self->algorithm and
96             $text .= "a=" . $self->algorithm . "; ";
97              
98 0 0       0 $self->headerlist and
99             $text .= "h=" . $self->headerlist . "; ";
100              
101 0         0 $text .= "q=" . $self->protocol . "; ";
102 0         0 $text .= "c=" . $self->method . "; ";
103 0         0 $text .= "s=" . $self->selector . "; ";
104 0         0 $text .= "d=" . $self->domain . "; ";
105 0         0 $text .= "b=" . $self->signature;
106              
107 0 0       0 if (defined (my $cfws = $self->fws)) {
108 0         0 require Text::Wrap;
109              
110 0         0 local $Text::Wrap::columns = 78;
111              
112 0         0 $text = Text::Wrap::wrap("", $cfws, $text);
113 0         0 $text .= "\n";
114             }
115            
116 0         0 return $text;
117             }
118              
119             sub sign {
120 7     7   12866 use MIME::Base64;
  7         12728  
  7         4459  
121              
122 0     0 0 0 my $self = shift;
123 0         0 my %prms = @_;
124              
125 0 0       0 $self->method($prms{'Method'}) if $prms{'Method'};
126 0 0       0 $self->selector($prms{'Selector'}) if $prms{'Selector'};
127 0 0       0 $self->private($prms{'Private'}) if $prms{'Private'};
128              
129 0 0       0 my $text = $prms{'Text'} or
130             $self->errorstr("no text given"),
131             return;
132              
133 0 0       0 $self->method or
134             $self->errorstr("no method specified"),
135             return;
136              
137 0 0       0 $self->private or
138             $self->errorstr("no private key specified"),
139             return;
140              
141 0 0       0 $self->selector or
142             $self->errorstr("no selector specified"),
143             return;
144              
145 0 0       0 $self->domain or
146             $self->errorstr("no domain specified"),
147             return;
148              
149 0 0       0 $self->protocol or $self->protocol("dns");
150 0 0       0 $self->algorithm or $self->algorithm("rsa-sha1");
151              
152             # d=... The value in this tag MUST match the domain of the sending
153             # email address or MUST be one of the parent domains of the sending
154             # email address. Domain name comparison is case insensitive.
155 0         0 my $signing_domain = $self->domain;
156 0 0       0 $prms{'Sender'}->host =~ /(^|\.)\Q$signing_domain\E\z/i or
157             $self->errorstr("domain does not match address"),
158             return;
159              
160 0         0 my $sign = $self->private->sign($text);
161 0         0 my $signb64 = encode_base64($sign, "");
162              
163 0         0 $self->signature($signb64);
164              
165 0         0 $self->status("good");
166              
167 0         0 return 1;
168             }
169              
170              
171             sub verify {
172 7     7   8473 use Mail::DomainKeys::Key::Public;
  7         29  
  7         1015  
173 7     7   89 use MIME::Base64;
  7         14  
  7         15981  
174              
175 6     6 0 27 my $self = shift;
176 6         310 my %prms = @_;
177              
178              
179 6 50       32 $self->status("bad format"),
180              
181             $self->selector or
182             $self->errorstr("no selector specified"),
183             return;
184              
185 6 50       56 $self->domain or
186             $self->errorstr("no domain specified"),
187             return;
188            
189 6 50       25 unless ($self->public) {
190 0 0       0 my $pubk = fetch Mail::DomainKeys::Key::Public(
191             Protocol => $self->protocol,
192             Selector => $self->selector,
193             Domain => $self->domain) or
194             $self->status("no key"),
195             $self->errorstr("no public key available"),
196             return;
197              
198 0 0       0 $pubk->revoked and
199             $self->status("revoked"),
200             $self->errorstr("public key has been revoked"),
201             return;
202              
203 0         0 $self->public($pubk);
204             }
205              
206 6         34 $self->status("bad");
207              
208             # d=... The value in this tag MUST match the domain of the sending
209             # email address or MUST be one of the parent domains of the sending
210             # email address. Domain name comparison is case insensitive.
211 6         19 my $signing_domain = $self->domain;
212 6 50       40 $prms{'Sender'}->host =~ /(^|\.)\Q$signing_domain\E\z/i or
213             $self->errorstr("domain does not match address"),
214             return;
215              
216 6 50       491 $prms{'Sender'}->host eq $self->domain or
217             $self->errorstr("domain does not match address"),
218             return;
219              
220 6 50 33     40 $self->public->granularity and
221             $prms{'Sender'}->user ne $self->public->granularity and
222             $self->errorstr("granularity does not match address"),
223             return;
224              
225 6 100       21 $self->public->verify(Text => $prms{'Text'},
226             Signature => decode_base64($self->signature)) and
227             $self->errorstr(undef),
228             $self->status("good"),
229             return 1;
230              
231 2         12 $self->errorstr("signature invalid");
232              
233 2         14 return;
234             }
235              
236             sub algorithm {
237 0     0 0 0 my $self = shift;
238              
239 0 0       0 @_ and
240             $self->{'ALGO'} = shift;
241              
242 0         0 $self->{'ALGO'};
243             }
244              
245             sub domain {
246 19     19 0 179 my $self = shift;
247              
248 19 50       54 @_ and
249             $self->{'DOMN'} = shift;
250              
251 19         79 $self->{'DOMN'};
252             }
253              
254             sub errorstr {
255 6     6 0 13 my $self = shift;
256              
257 6 50       35 @_ and
258             $self->{'ESTR'} = shift;
259              
260 6         23 $self->{'ESTR'};
261             }
262              
263             sub fws {
264 0     0 0 0 my $self = shift;
265              
266 0 0       0 @_ and
267             $self->{'CFWS'} = shift;
268              
269 0         0 return $self->{'CFWS'};
270             }
271              
272             sub headerlist {
273 31     31 0 35 my $self = shift;
274              
275 31 50       90 @_ and
276             $self->{'HDRS'} = shift;
277              
278 31 100 66     396 if (wantarray and $self->{'HDRS'}) {
279 1         9 my @list = split /[ \t]*:[ \t]*/, $self->{'HDRS'};
280 1         5 return @list;
281             }
282              
283 30         2168 $self->{'HDRS'};
284             }
285              
286             sub method {
287 15     15 0 24 my $self = shift;
288              
289 15 50       48 @_ and
290             $self->{'METH'} = shift;
291              
292 15         214 $self->{'METH'};
293             }
294              
295             sub public {
296 24     24 0 46 my $self = shift;
297              
298 24 100       199 @_ and
299             $self->{'PBLC'} = shift;
300              
301 24         208 $self->{'PBLC'};
302             }
303            
304             sub private {
305 0     0 0 0 my $self = shift;
306              
307 0 0       0 @_ and
308             $self->{'PRIV'} = shift;
309              
310 0         0 $self->{'PRIV'};
311             }
312            
313             sub protocol {
314 1     1 0 1013 my $self = shift;
315              
316 1 50       18 @_ and
317             $self->{'PROT'} = shift;
318              
319 1         11 $self->{'PROT'};
320             }
321              
322             sub selector {
323 7     7 0 16 my $self = shift;
324              
325 7 50       30 @_ and
326             $self->{'SLCT'} = shift;
327              
328 7         113 $self->{'SLCT'};
329             }
330              
331             sub signature {
332 6     6 0 22 my $self = shift;
333              
334 6 50       23 @_ and
335             $self->{'DATA'} = shift;
336              
337 6         157 $self->{'DATA'};
338             }
339              
340             sub signheaderlist {
341 36     36 0 44 my $self = shift;
342              
343 36 50       82 @_ and
344             $self->{'SHDR'} = shift;
345              
346 36 50 33     147 if (wantarray and $self->{'SHDR'}) {
347 0         0 my @list = split /:/, $self->{'SHDR'};
348 0         0 return @list;
349             }
350              
351 36         246 $self->{'SHDR'};
352             }
353              
354             sub signing {
355 6     6 0 14 my $self = shift;
356              
357 6 50       43 @_ and
358             $self->{'SIGN'} = shift;
359              
360 6         59 $self->{'SIGN'};
361             }
362              
363             sub status {
364 16     16 0 31 my $self = shift;
365              
366 16 50       69 @_ and
367             $self->{'STAT'} = shift;
368              
369 16         73 $self->{'STAT'};
370             }
371              
372             sub testing {
373 0     0 0   my $self = shift;
374              
375 0 0 0       $self->public and $self->public->testing and
376             return 1;
377              
378 0           return;
379             }
380              
381              
382             1;