File Coverage

blib/lib/Mail/DKIM/Signature.pm
Criterion Covered Total %
statement 220 249 88.3
branch 105 134 78.3
condition 15 20 75.0
subroutine 44 49 89.8
pod 25 38 65.7
total 409 490 83.4


line stmt bran cond sub pod time code
1             package Mail::DKIM::Signature;
2 14     14   64535 use strict;
  14         35  
  14         401  
3 14     14   82 use warnings;
  14         39  
  14         648  
4             our $VERSION = '1.20230212'; # VERSION
5             # ABSTRACT: represents a DKIM-Signature header
6              
7             # Copyright 2005-2007 Messiah College. All rights reserved.
8             # Jason Long
9              
10             # Copyright (c) 2004 Anthony D. Urso. All rights reserved.
11             # This program is free software; you can redistribute it and/or
12             # modify it under the same terms as Perl itself.
13              
14 14     14   6362 use Mail::DKIM::PublicKey;
  14         70  
  14         1071  
15 14     14   7492 use Mail::DKIM::Algorithm::rsa_sha1;
  14         39  
  14         463  
16 14     14   6127 use Mail::DKIM::Algorithm::rsa_sha256;
  14         37  
  14         384  
17              
18 14     14   88 use base 'Mail::DKIM::KeyValueList';
  14         32  
  14         1328  
19 14     14   94 use Carp;
  14         32  
  14         12848  
20              
21              
22             sub new {
23 150     150 1 351 my $class = shift;
24 150         351 my %prms = @_;
25 150         251 my $self = {};
26 150         295 bless $self, $class;
27              
28 150         429 $self->version('1');
29 150   100     692 $self->algorithm( $prms{'Algorithm'} || 'rsa-sha1' );
30 150         575 $self->signature( $prms{'Signature'} );
31 150 100       457 $self->canonicalization( $prms{'Method'} ) if exists $prms{'Method'};
32 150         485 $self->domain( $prms{'Domain'} );
33 150         586 $self->headerlist( $prms{'Headers'} );
34 150 50       365 $self->protocol( $prms{'Query'} ) if exists $prms{'Query'};
35 150         483 $self->selector( $prms{'Selector'} );
36 150 100       399 $self->identity( $prms{'Identity'} ) if exists $prms{'Identity'};
37 150 100       338 $self->timestamp( $prms{'Timestamp'} ) if defined $prms{'Timestamp'};
38 150 100       309 $self->expiration( $prms{'Expiration'} ) if defined $prms{'Expiration'};
39 150 100       319 $self->key( $prms{'Key'} ) if defined $prms{'Key'};
40              
41 150         401 return $self;
42             }
43              
44              
45             sub parse {
46 868     868 1 1839 my $class = shift;
47 868 50       1890 croak 'wrong number of arguments' unless ( @_ == 1 );
48 868         1749 my ($string) = @_;
49              
50             # remove line terminator, if present
51 868         3004 $string =~ s/\015\012\z//;
52              
53             # remove field name, if present
54 868         1990 my $prefix = $class->prefix();
55 868 50       13799 if ( $string =~ s/^($prefix)//i ) {
56              
57             # save the field name (capitalization), so that it can be
58             # restored later
59 868         2584 $prefix = $1;
60             }
61              
62 868         3282 my $self = $class->SUPER::parse($string);
63 866         1740 $self->{prefix} = $prefix;
64              
65 866         2229 return $self;
66             }
67              
68              
69             # deprecated
70             sub wantheader {
71 0     0 0 0 my $self = shift;
72 0         0 my $attr = shift;
73              
74 0 0       0 $self->headerlist
75             or return 1;
76              
77 0         0 foreach my $key ( $self->headerlist ) {
78 0 0       0 lc $attr eq lc $key
79             and return 1;
80             }
81              
82 0         0 return;
83             }
84              
85              
86             sub algorithm {
87 2941     2941 1 5354 my $self = shift;
88              
89 2941 100       5601 if (@_) {
90 922         2016 $self->set_tag( 'a', shift );
91             }
92              
93 2941         6486 my $a = $self->get_tag('a');
94 2941 100       9847 return defined $a ? lc $a : undef;
95             }
96              
97              
98             sub as_string {
99 911     911 1 6231 my $self = shift;
100              
101 911         1849 return $self->prefix() . $self->SUPER::as_string;
102             }
103              
104             # undocumented method
105             sub as_string_debug {
106 0     0 0 0 my $self = shift;
107              
108             return $self->prefix()
109 0         0 . join( ';', map { '>' . $_->{raw} . '<' } @{ $self->{tags} } );
  0         0  
  0         0  
110             }
111              
112              
113             sub as_string_without_data {
114 411     411 1 619 my $self = shift;
115 411 50       881 croak 'wrong number of arguments' unless ( @_ == 0 );
116              
117 411         1098 my $alt = $self->clone;
118 411         1368 $alt->signature('');
119              
120 411         868 return $alt->as_string;
121             }
122              
123              
124             sub body_count {
125 252     252 1 403 my $self = shift;
126              
127             # set new body count if provided
128 252 50       920 (@_)
129             and $self->set_tag( 'l', shift );
130              
131 252         653 return $self->get_tag('l');
132             }
133              
134              
135             sub body_hash {
136 182     182 1 295 my $self = shift;
137              
138             # set new body hash if provided
139 182 100       478 (@_)
140             and $self->set_tag( 'bh', shift );
141              
142 182         502 my $result = $self->get_tag('bh');
143 182 50       425 if ( defined $result ) {
144 182         605 $result =~ s/\s+//gs;
145             }
146 182         403 return $result;
147             }
148              
149              
150             sub canonicalization {
151 530     530 1 1685 my $self = shift;
152              
153 530 100       1116 if (@_) {
154 35         139 $self->set_tag( 'c', join( '/', @_ ) );
155             }
156              
157 530         1131 my $c = $self->get_tag('c');
158 530 100       1333 $c = lc $c if defined $c;
159 530 100       1029 if ( not $c ) {
160 4         8 $c = 'simple/simple';
161             }
162 530         1382 my ( $c1, $c2 ) = split( /\//, $c, 2 );
163 530 100       1269 if ( not defined $c2 ) {
164              
165             # default body canonicalization is "simple"
166 140         203 $c2 = 'simple';
167             }
168              
169 530 100       1007 if (wantarray) {
170 487         1394 return ( $c1, $c2 );
171             }
172             else {
173 43         141 return "$c1/$c2";
174             }
175             }
176              
177 14     14   115 use MIME::Base64;
  14         35  
  14         30728  
178              
179             # checks whether this signature specifies a legal canonicalization method
180             # returns true if the canonicalization is acceptable, false otherwise
181             #
182             sub check_canonicalization {
183 416     416 0 710 my $self = shift;
184              
185 416         927 my ( $c1, $c2 ) = $self->canonicalization;
186              
187 416         1049 my @known = ( 'nowsp', 'simple', 'relaxed', 'seal' );
188 416 100       816 return undef unless ( grep { $_ eq $c1 } @known );
  1664         3294  
189 413 50       658 return undef unless ( grep { $_ eq $c2 } @known );
  1652         2933  
190 413         1188 return 1;
191             }
192              
193             # checks whether the expiration time on this signature is acceptable
194             # returns a true value if acceptable, false otherwise
195             #
196             sub check_expiration {
197 426     426 0 624 my $self = shift;
198 426         817 my $x = $self->expiration;
199 426 100       1328 return 1 if not defined $x;
200              
201 3   33     18 $self->{_verify_time} ||= time();
202 3         15 return ( $self->{_verify_time} <= $x );
203             }
204              
205             # Returns a filtered list of protocols that can be used to fetch the
206             # public key corresponding to this signature. An empty list means that
207             # all designated protocols are unrecognized.
208             # Note: at this time, the only recognized protocol is "dns/txt".
209             #
210             sub check_protocol {
211 815     815 0 1124 my $self = shift;
212              
213 815         1387 my $v = $self->version;
214              
215 815         1730 foreach my $prot ( split /:/, $self->protocol ) {
216 817         1913 my ( $type, $options ) = split( /\//, $prot, 2 );
217 817 100       1840 if ( $type eq 'dns' ) {
218 814 100 100     3723 return ('dns/txt') if $options && $options eq 'txt';
219              
220             # prior to DKIM version 1, the '/txt' part was optional
221 13 100       28 if ( !$v ) {
222 12 50       49 return ('dns/txt') if !defined($options);
223             }
224             }
225             }
226              
227             # unrecognized
228 2         7 return;
229             }
230              
231             # checks whether the version tag has an acceptable value
232             # returns true if so, otherwise false
233             #
234             sub check_version {
235 427     427 0 671 my $self = shift;
236              
237             # check version
238 427 100       893 if ( my $version = $self->version ) {
239 46         115 my @ALLOWED_VERSIONS = ( '0.5', '1' );
240 46         87 return ( grep { $_ eq $version } @ALLOWED_VERSIONS );
  92         293  
241             }
242              
243             # we still consider a missing v= tag acceptable,
244             # for backwards-compatibility
245 381         1029 return 1;
246             }
247              
248              
249             sub data {
250 2348     2348 1 3518 my $self = shift;
251              
252 2348 100       5181 if (@_) {
253 1414         3047 $self->set_tag( 'b', shift );
254             }
255              
256 2348         5255 my $b = $self->get_tag('b');
257 2348 100       6870 $b =~ tr/\015\012 \t//d if defined $b;
258 2348         5221 return $b;
259             }
260              
261             *signature = \*data;
262              
263             #undocumented, private function
264             #derived from MIME::Base64::Perl (allowed, thanks to the Perl license)
265             #
266             sub decode_qp {
267 217     217 0 379 my $res = shift;
268              
269             #TODO- should I worry about non-ASCII systems here?
270 217 50       538 $res =~ s/=([\da-fA-F]{2})/pack('C', hex($1))/ge
  6         40  
271             if defined $res;
272 217         578 return $res;
273             }
274              
275             #undocumented, private function
276             #derived from MIME::Base64::Perl (allowed, thanks to the Perl license)
277             #
278             sub encode_qp {
279 2     2 0 5 my $res = shift;
280              
281             # note- unlike MIME quoted-printable, we don't allow whitespace chars
282 2         9 my $DISALLOWED = qr/[^!"#\$%&'()*+,\-.\/0-9:;<>?\@A-Z[\\\]^_`a-z{|}~]/;
283              
284             #TODO- should I worry about non-ASCII systems here?
285 2 50       80 $res =~ s/($DISALLOWED)/sprintf('=%02X', ord($1))/eg
  0         0  
286             if defined $res;
287 2         13 return $res;
288             }
289              
290             sub DEFAULT_PREFIX {
291 203     203 0 611 return 'DKIM-Signature:';
292             }
293              
294             sub prefix {
295 1902     1902 0 2891 my $class = shift;
296 1902 100       4326 if ( ref($class) ) {
297 1034 50       2056 $class->{prefix} = shift if @_;
298 1034 100       3551 return $class->{prefix} if $class->{prefix};
299             }
300 1110         2762 return $class->DEFAULT_PREFIX();
301             }
302              
303              
304             sub domain {
305 2925     2925 1 4355 my $self = shift;
306              
307 2925 100       5603 if (@_) {
308 921         1889 $self->set_tag( 'd', shift );
309             }
310              
311 2925         6152 my $d = $self->get_tag('d');
312 2925 100       11274 return defined $d ? lc $d : undef;
313             }
314              
315              
316             sub expiration {
317 413     413 1 560 my $self = shift;
318              
319 413 100       863 (@_)
320             and $self->set_tag( 'x', shift );
321              
322 413         915 return $self->get_tag('x');
323             }
324              
325             # allows the type of signature to determine what "algorithm" gets used
326             sub get_algorithm_class {
327 874     874 0 1337 my $self = shift;
328 874 50       1846 croak 'wrong number of arguments' unless ( @_ == 1 );
329 874         1574 my ($algorithm) = @_;
330              
331 874 100       2209 my $class =
    100          
332             $algorithm eq 'rsa-sha1' ? 'Mail::DKIM::Algorithm::rsa_sha1'
333             : $algorithm eq 'rsa-sha256' ? 'Mail::DKIM::Algorithm::rsa_sha256'
334             : undef;
335 874         3132 return $class;
336             }
337              
338             # [private method]
339             # fetch_public_key() - initiate a DNS query for fetching the key
340             #
341             # This method does NOT return the public key.
342             # Use get_public_key() for that.
343             #
344             sub fetch_public_key {
345 417     417 0 645 my $self = shift;
346 417 50       879 return if exists $self->{public_key_query};
347              
348             my $on_success = sub {
349 355 100   355   762 if ( $_[0] ) {
350 351         1007 $self->{public} = $_[0];
351             }
352             else {
353 4         17 $self->{public_error} = "not available\n";
354             }
355 417         1622 };
356              
357 417         884 my @methods = $self->check_protocol;
358             $self->{public_key_query} = Mail::DKIM::PublicKey->fetch_async(
359             Protocol => $methods[0],
360             Selector => $self->selector,
361             Domain => $self->domain,
362             Callbacks => {
363             Success => $on_success,
364 2     2   9 Error => sub { $self->{public_error} = shift },
365             },
366 417         1000 );
367 415         1149 return;
368             }
369              
370             #EXPERIMENTAL
371             sub _refetch_public_key {
372 0     0   0 my $self = shift;
373 0 0       0 if ( $self->{public_key_query} ) {
374              
375             # clear the existing query by waiting for it to complete
376 0         0 $self->{public_key_query}->();
377             }
378 0         0 delete $self->{public_key_query};
379 0         0 delete $self->{public};
380 0         0 delete $self->{public_error};
381 0         0 $self->fetch_public_key;
382             }
383              
384              
385             sub get_public_key {
386 704     704 1 1009 my $self = shift;
387              
388             # this ensures we only try fetching once, even if an error occurs
389 704 50       1651 if ( not exists $self->{public_key_query} ) {
390 0         0 $self->fetch_public_key;
391             }
392              
393 704 100       1374 if ( $self->{public_key_query} ) {
394              
395             # wait for public key query to finish
396 363         1142 $self->{public_key_query}->();
397 357         5152 $self->{public_key_query} = 0;
398             }
399              
400 698 100       1507 if ( exists $self->{public} ) {
401 692         1687 return $self->{public};
402             }
403             else {
404 6         63 die $self->{public_error};
405             }
406             }
407              
408              
409             sub hash_algorithm {
410 338     338 1 525 my $self = shift;
411 338         722 my $algorithm = $self->algorithm;
412              
413             return
414 338 50       1428 $algorithm eq 'rsa-sha1' ? 'sha1'
    100          
415             : $algorithm eq 'rsa-sha256' ? 'sha256'
416             : undef;
417             }
418              
419              
420             sub headerlist {
421 1086     1086 1 1636 my $self = shift;
422              
423 1086 100       2785 (@_)
424             and $self->set_tag( 'h', shift );
425              
426 1086   100     2484 my $h = $self->get_tag('h') || '';
427              
428             # remove whitespace next to colons
429 1086         2751 $h =~ s/\s+:/:/g;
430 1086         2501 $h =~ s/:\s+/:/g;
431 1086         1943 $h = lc $h;
432              
433 1086 100 100     3445 if ( wantarray and $h ) {
    100          
434 266         1035 my @list = split /:/, $h;
435 266         607 @list = map { s/^\s+|\s+$//g; $_ } @list;
  1508         4485  
  1508         3049  
436 266         1121 return @list;
437             }
438             elsif (wantarray) {
439 8         39 return ();
440             }
441              
442 812         1806 return $h;
443             }
444              
445              
446             sub identity {
447 291     291 1 464 my $self = shift;
448              
449             # set new identity if provided
450 291 100       665 (@_)
451             and $self->set_tag( 'i', encode_qp(shift) );
452              
453 291         762 my $i = $self->get_tag('i');
454 291 100       688 if ( defined $i ) {
455 217         563 return decode_qp($i);
456             }
457             else {
458 74   50     158 return '@' . ( $self->domain || '' );
459             }
460             }
461              
462             sub identity_matches {
463 0     0 0 0 my $self = shift;
464 0         0 my ($addr) = @_;
465              
466 0         0 my $id = $self->identity;
467 0 0       0 if ( $id =~ /^\@/ ) {
468              
469             # the identity is a domain-name only, so it only needs to match
470             # the domain part of the sender address
471 0         0 return ( lc( substr( $addr, -length($id) ) ) eq lc($id) );
472              
473             # TODO - compare the parent domains?
474             }
475 0         0 return lc($addr) eq lc($id);
476             }
477              
478              
479             sub key {
480 50     50 1 87 my $self = shift;
481 50 50       143 if (@_) {
482 50         93 $self->{Key} = shift;
483 50         95 $self->{KeyFile} = undef;
484             }
485 50         92 return $self->{Key};
486             }
487              
488              
489             sub method {
490 0     0 1 0 my $self = shift;
491              
492 0 0       0 if (@_) {
493 0         0 $self->set_tag( 'c', shift );
494             }
495              
496 0   0     0 return ( lc $self->get_tag('c') ) || 'simple';
497             }
498              
499              
500             sub protocol {
501 819     819 1 1230 my $self = shift;
502              
503 819 50       1523 (@_)
504             and $self->set_tag( 'q', shift );
505              
506 819         1564 my $q = $self->get_tag('q');
507 819 100       1449 if ( defined $q ) {
508 73         216 return $q;
509             }
510             else {
511 746         2048 return 'dns/txt';
512             }
513             }
514              
515              
516             sub result {
517 1034     1034 1 1916 my $self = shift;
518 1034 100       2411 @_ and $self->{verify_result} = shift;
519 1034 100       1979 @_ and $self->{verify_details} = shift;
520 1034         3007 return $self->{verify_result};
521             }
522              
523              
524             sub result_detail {
525 471     471 1 712 my $self = shift;
526 471 50       975 croak 'wrong number of arguments' unless ( @_ == 0 );
527              
528 471 100 100     1512 if ( $self->{verify_result} && $self->{verify_details} ) {
529 147         694 return $self->{verify_result} . ' (' . $self->{verify_details} . ')';
530             }
531 324         1240 return $self->{verify_result};
532             }
533              
534              
535             sub selector {
536 1781     1781 1 2541 my $self = shift;
537              
538 1781 100       4585 (@_)
539             and $self->set_tag( 's', shift );
540              
541 1781         3700 return $self->get_tag('s');
542             }
543              
544              
545             sub prettify {
546 63     63 1 117 my $self = shift;
547 63         176 $self->wrap(
548             Start => length( $self->prefix() ),
549             Tags => {
550             b => 'b64',
551             bh => 'b64',
552             h => 'list',
553             },
554             );
555             }
556              
557              
558             sub prettify_safe {
559 60     60 1 112 my $self = shift;
560 60         145 $self->wrap(
561             Start => length( $self->prefix() ),
562             Tags => {
563             b => 'b64',
564             },
565             PreserveNames => 1,
566             Default => 'preserve', #preserves unknown tags
567             );
568             }
569              
570              
571             sub timestamp {
572 405     405 1 568 my $self = shift;
573              
574 405 50       1303 (@_)
575             and $self->set_tag( 't', shift );
576              
577 405         884 return $self->get_tag('t');
578             }
579              
580              
581             sub version {
582 1394     1394 1 1881 my $self = shift;
583              
584 1394 100       3025 (@_)
585             and $self->set_tag( 'v', shift );
586              
587 1394         2979 return $self->get_tag('v');
588             }
589              
590              
591             1;
592              
593             __END__