File Coverage

blib/lib/Mail/DKIM/Signature.pm
Criterion Covered Total %
statement 224 258 86.8
branch 110 140 78.5
condition 15 20 75.0
subroutine 45 51 88.2
pod 25 39 64.1
total 419 508 82.4


line stmt bran cond sub pod time code
1             package Mail::DKIM::Signature;
2 14     14   65726 use strict;
  14         44  
  14         425  
3 14     14   91 use warnings;
  14         42  
  14         664  
4             our $VERSION = '1.20230911'; # 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   6634 use Mail::DKIM::PublicKey;
  14         85  
  14         1120  
15 14     14   7991 use Mail::DKIM::Algorithm::rsa_sha1;
  14         44  
  14         527  
16 14     14   6260 use Mail::DKIM::Algorithm::rsa_sha256;
  14         34  
  14         389  
17 14     14   6030 use Mail::DKIM::Algorithm::ed25519_sha256;
  14         38  
  14         399  
18              
19 14     14   85 use base 'Mail::DKIM::KeyValueList';
  14         27  
  14         1323  
20 14     14   94 use Carp;
  14         25  
  14         13341  
21              
22              
23             sub new {
24 166     166 1 428 my $class = shift;
25 166         344 my %prms = @_;
26 166         305 my $self = {};
27 166         333 bless $self, $class;
28              
29 166         471 $self->version('1');
30 166   100     784 $self->algorithm( $prms{'Algorithm'} || 'rsa-sha1' );
31 166         661 $self->signature( $prms{'Signature'} );
32 166 100       473 $self->canonicalization( $prms{'Method'} ) if exists $prms{'Method'};
33 166         565 $self->domain( $prms{'Domain'} );
34 166         649 $self->headerlist( $prms{'Headers'} );
35 166 50       416 $self->protocol( $prms{'Query'} ) if exists $prms{'Query'};
36 166         574 $self->selector( $prms{'Selector'} );
37 166 100       424 $self->identity( $prms{'Identity'} ) if exists $prms{'Identity'};
38 166 100       406 $self->timestamp( $prms{'Timestamp'} ) if defined $prms{'Timestamp'};
39 166 100       324 $self->expiration( $prms{'Expiration'} ) if defined $prms{'Expiration'};
40 166 50       335 $self->tags( $prms{'Tags'} ) if defined $prms{'Tags'};
41 166 100       345 $self->key( $prms{'Key'} ) if defined $prms{'Key'};
42 166         465 return $self;
43             }
44              
45              
46             sub parse {
47 882     882 1 2080 my $class = shift;
48 882 50       1936 croak 'wrong number of arguments' unless ( @_ == 1 );
49 882         1733 my ($string) = @_;
50              
51             # remove line terminator, if present
52 882         3293 $string =~ s/\015\012\z//;
53              
54             # remove field name, if present
55 882         2201 my $prefix = $class->prefix();
56 882 50       14511 if ( $string =~ s/^($prefix)//i ) {
57              
58             # save the field name (capitalization), so that it can be
59             # restored later
60 882         2733 $prefix = $1;
61             }
62              
63 882         3406 my $self = $class->SUPER::parse($string);
64 880         1925 $self->{prefix} = $prefix;
65              
66 880         2455 return $self;
67             }
68              
69              
70             # deprecated
71             sub wantheader {
72 0     0 0 0 my $self = shift;
73 0         0 my $attr = shift;
74              
75 0 0       0 $self->headerlist
76             or return 1;
77              
78 0         0 foreach my $key ( $self->headerlist ) {
79 0 0       0 lc $attr eq lc $key
80             and return 1;
81             }
82              
83 0         0 return;
84             }
85              
86              
87             sub algorithm {
88 2980     2980 1 5574 my $self = shift;
89              
90 2980 100       6134 if (@_) {
91 938         2113 $self->set_tag( 'a', shift );
92             }
93              
94 2980         6228 my $a = $self->get_tag('a');
95 2980 100       10410 return defined $a ? lc $a : undef;
96             }
97              
98              
99             sub as_string {
100 931     931 1 8826 my $self = shift;
101              
102 931         1973 return $self->prefix() . $self->SUPER::as_string;
103             }
104              
105             # undocumented method
106             sub as_string_debug {
107 0     0 0 0 my $self = shift;
108              
109             return $self->prefix()
110 0         0 . join( ';', map { '>' . $_->{raw} . '<' } @{ $self->{tags} } );
  0         0  
  0         0  
111             }
112              
113              
114             sub as_string_without_data {
115 419     419 1 643 my $self = shift;
116 419 50       977 croak 'wrong number of arguments' unless ( @_ == 0 );
117              
118 419         1047 my $alt = $self->clone;
119 419         1301 $alt->signature('');
120              
121 419         1012 return $alt->as_string;
122             }
123              
124              
125             sub body_count {
126 260     260 1 465 my $self = shift;
127              
128             # set new body count if provided
129 260 50       574 (@_)
130             and $self->set_tag( 'l', shift );
131              
132 260         704 return $self->get_tag('l');
133             }
134              
135              
136             sub body_hash {
137 186     186 1 310 my $self = shift;
138              
139             # set new body hash if provided
140 186 100       489 (@_)
141             and $self->set_tag( 'bh', shift );
142              
143 186         476 my $result = $self->get_tag('bh');
144 186 50       407 if ( defined $result ) {
145 186         604 $result =~ s/\s+//gs;
146             }
147 186         398 return $result;
148             }
149              
150              
151             sub canonicalization {
152 546     546 1 1763 my $self = shift;
153              
154 546 100       1167 if (@_) {
155 37         139 $self->set_tag( 'c', join( '/', @_ ) );
156             }
157              
158 546         1158 my $c = $self->get_tag('c');
159 546 100       1425 $c = lc $c if defined $c;
160 546 100       1128 if ( not $c ) {
161 4         8 $c = 'simple/simple';
162             }
163 546         1508 my ( $c1, $c2 ) = split( /\//, $c, 2 );
164 546 100       1200 if ( not defined $c2 ) {
165              
166             # default body canonicalization is "simple"
167 144         222 $c2 = 'simple';
168             }
169              
170 546 100       1042 if (wantarray) {
171 501         1451 return ( $c1, $c2 );
172             }
173             else {
174 45         138 return "$c1/$c2";
175             }
176             }
177              
178 14     14   197 use MIME::Base64;
  14         33  
  14         32563  
179              
180             # checks whether this signature specifies a legal canonicalization method
181             # returns true if the canonicalization is acceptable, false otherwise
182             #
183             sub check_canonicalization {
184 422     422 0 670 my $self = shift;
185              
186 422         1031 my ( $c1, $c2 ) = $self->canonicalization;
187              
188 422         1080 my @known = ( 'nowsp', 'simple', 'relaxed', 'seal' );
189 422 100       806 return undef unless ( grep { $_ eq $c1 } @known );
  1688         3285  
190 419 50       706 return undef unless ( grep { $_ eq $c2 } @known );
  1676         2926  
191 419         1231 return 1;
192             }
193              
194             # checks whether the expiration time on this signature is acceptable
195             # returns a true value if acceptable, false otherwise
196             #
197             sub check_expiration {
198 432     432 0 757 my $self = shift;
199 432         893 my $x = $self->expiration;
200 432 100       1351 return 1 if not defined $x;
201              
202 3   33     20 $self->{_verify_time} ||= time();
203 3         16 return ( $self->{_verify_time} <= $x );
204             }
205              
206             # Returns a filtered list of protocols that can be used to fetch the
207             # public key corresponding to this signature. An empty list means that
208             # all designated protocols are unrecognized.
209             # Note: at this time, the only recognized protocol is "dns/txt".
210             #
211             sub check_protocol {
212 827     827 0 1249 my $self = shift;
213              
214 827         1427 my $v = $self->version;
215              
216 827         1691 foreach my $prot ( split /:/, $self->protocol ) {
217 829         2023 my ( $type, $options ) = split( /\//, $prot, 2 );
218 829 100       1888 if ( $type eq 'dns' ) {
219 826 100 100     3800 return ('dns/txt') if $options && $options eq 'txt';
220              
221             # prior to DKIM version 1, the '/txt' part was optional
222 13 100       33 if ( !$v ) {
223 12 50       49 return ('dns/txt') if !defined($options);
224             }
225             }
226             }
227              
228             # unrecognized
229 2         8 return;
230             }
231              
232             # checks whether the version tag has an acceptable value
233             # returns true if so, otherwise false
234             #
235             sub check_version {
236 433     433 0 648 my $self = shift;
237              
238             # check version
239 433 100       870 if ( my $version = $self->version ) {
240 52         147 my @ALLOWED_VERSIONS = ( '0.5', '1' );
241 52         100 return ( grep { $_ eq $version } @ALLOWED_VERSIONS );
  104         313  
242             }
243              
244             # we still consider a missing v= tag acceptable,
245             # for backwards-compatibility
246 381         1037 return 1;
247             }
248              
249              
250             sub data {
251 2385     2385 1 3683 my $self = shift;
252              
253 2385 100       5058 if (@_) {
254 1440         3162 $self->set_tag( 'b', shift );
255             }
256              
257 2385         5409 my $b = $self->get_tag('b');
258 2385 100       6571 $b =~ tr/\015\012 \t//d if defined $b;
259 2385         5249 return $b;
260             }
261              
262             *signature = \*data;
263              
264             #undocumented, private function
265             #derived from MIME::Base64::Perl (allowed, thanks to the Perl license)
266             #
267             sub decode_qp {
268 217     217 0 362 my $res = shift;
269              
270             #TODO- should I worry about non-ASCII systems here?
271 217 50       592 $res =~ s/=([\da-fA-F]{2})/pack('C', hex($1))/ge
  6         43  
272             if defined $res;
273 217         580 return $res;
274             }
275              
276             #undocumented, private function
277             #derived from MIME::Base64::Perl (allowed, thanks to the Perl license)
278             #
279             sub encode_qp {
280 2     2 0 5 my $res = shift;
281              
282             # note- unlike MIME quoted-printable, we don't allow whitespace chars
283 2         11 my $DISALLOWED = qr/[^!"#\$%&'()*+,\-.\/0-9:;<>?\@A-Z[\\\]^_`a-z{|}~]/;
284              
285             #TODO- should I worry about non-ASCII systems here?
286 2 50       99 $res =~ s/($DISALLOWED)/sprintf('=%02X', ord($1))/eg
  0         0  
287             if defined $res;
288 2         14 return $res;
289             }
290              
291             sub DEFAULT_PREFIX {
292 227     227 0 672 return 'DKIM-Signature:';
293             }
294              
295             sub prefix {
296 1940     1940 0 3190 my $class = shift;
297 1940 100       4517 if ( ref($class) ) {
298 1058 50       2094 $class->{prefix} = shift if @_;
299 1058 100       3694 return $class->{prefix} if $class->{prefix};
300             }
301 1134         3026 return $class->DEFAULT_PREFIX();
302             }
303              
304              
305             sub domain {
306 2974     2974 1 4683 my $self = shift;
307              
308 2974 100       5903 if (@_) {
309 937         1938 $self->set_tag( 'd', shift );
310             }
311              
312 2974         5935 my $d = $self->get_tag('d');
313 2974 100       11364 return defined $d ? lc $d : undef;
314             }
315              
316              
317             sub expiration {
318 419     419 1 609 my $self = shift;
319              
320 419 100       817 (@_)
321             and $self->set_tag( 'x', shift );
322              
323 419         806 return $self->get_tag('x');
324             }
325              
326             sub tags {
327 0     0 0 0 my $self = shift;
328 0         0 my $tags = shift;
329              
330 0         0 for my $tag (sort keys %$tags) {
331 0         0 $self->set_tag( $tag, $tags->{$tag} );
332             }
333 0         0 (@_)
334              
335             }
336              
337             # allows the type of signature to determine what "algorithm" gets used
338             sub get_algorithm_class {
339 888     888 0 1399 my $self = shift;
340 888 50       1939 croak 'wrong number of arguments' unless ( @_ == 1 );
341 888         1504 my ($algorithm) = @_;
342              
343 888 100       2230 my $class =
    100          
    100          
344             $algorithm eq 'rsa-sha1' ? 'Mail::DKIM::Algorithm::rsa_sha1'
345             : $algorithm eq 'rsa-sha256' ? 'Mail::DKIM::Algorithm::rsa_sha256'
346             : $algorithm eq 'ed25519-sha256' ? 'Mail::DKIM::Algorithm::ed25519_sha256'
347             : undef;
348 888         3343 return $class;
349             }
350              
351             # [private method]
352             # fetch_public_key() - initiate a DNS query for fetching the key
353             #
354             # This method does NOT return the public key.
355             # Use get_public_key() for that.
356             #
357             sub fetch_public_key {
358 423     423 0 627 my $self = shift;
359 423 50       850 return if exists $self->{public_key_query};
360              
361             my $on_success = sub {
362 358 100   358   792 if ( $_[0] ) {
363 354         1080 $self->{public} = $_[0];
364             }
365             else {
366 4         16 $self->{public_error} = "not available\n";
367             }
368 423         1663 };
369              
370 423         857 my @methods = $self->check_protocol;
371             $self->{public_key_query} = Mail::DKIM::PublicKey->fetch_async(
372             Protocol => $methods[0],
373             Selector => $self->selector,
374             Domain => $self->domain,
375             Callbacks => {
376             Success => $on_success,
377 3     3   11 Error => sub { $self->{public_error} = shift },
378             },
379 423         1157 );
380 421         1131 return;
381             }
382              
383             #EXPERIMENTAL
384             sub _refetch_public_key {
385 0     0   0 my $self = shift;
386 0 0       0 if ( $self->{public_key_query} ) {
387              
388             # clear the existing query by waiting for it to complete
389 0         0 $self->{public_key_query}->();
390             }
391 0         0 delete $self->{public_key_query};
392 0         0 delete $self->{public};
393 0         0 delete $self->{public_error};
394 0         0 $self->fetch_public_key;
395             }
396              
397              
398             sub get_public_key {
399 713     713 1 1074 my $self = shift;
400              
401             # this ensures we only try fetching once, even if an error occurs
402 713 50       1550 if ( not exists $self->{public_key_query} ) {
403 0         0 $self->fetch_public_key;
404             }
405              
406 713 100       1382 if ( $self->{public_key_query} ) {
407              
408             # wait for public key query to finish
409 369         1184 $self->{public_key_query}->();
410 361         5303 $self->{public_key_query} = 0;
411             }
412              
413 705 100       1509 if ( exists $self->{public} ) {
414 698         1718 return $self->{public};
415             }
416             else {
417 7         51 die $self->{public_error};
418             }
419             }
420              
421              
422             sub hash_algorithm {
423 341     341 1 500 my $self = shift;
424 341         764 my $algorithm = $self->algorithm;
425              
426             return
427 341 50       1425 $algorithm eq 'rsa-sha1' ? 'sha1'
    100          
    100          
428             : $algorithm eq 'rsa-sha256' ? 'sha256'
429             : $algorithm eq 'ed25519-sha256' ? 'sha256'
430             : undef;
431             }
432              
433              
434             sub headerlist {
435 1118     1118 1 1644 my $self = shift;
436              
437 1118 100       3051 (@_)
438             and $self->set_tag( 'h', shift );
439              
440 1118   100     2499 my $h = $self->get_tag('h') || '';
441              
442             # remove whitespace next to colons
443 1118         2835 $h =~ s/\s+:/:/g;
444 1118         2630 $h =~ s/:\s+/:/g;
445 1118         2029 $h = lc $h;
446              
447 1118 100 100     3640 if ( wantarray and $h ) {
    100          
448 274         1119 my @list = split /:/, $h;
449 274         613 @list = map { s/^\s+|\s+$//g; $_ } @list;
  1592         4527  
  1592         3196  
450 274         1205 return @list;
451             }
452             elsif (wantarray) {
453 8         28 return ();
454             }
455              
456 836         1881 return $h;
457             }
458              
459              
460             sub identity {
461 300     300 1 455 my $self = shift;
462              
463             # set new identity if provided
464 300 100       629 (@_)
465             and $self->set_tag( 'i', encode_qp(shift) );
466              
467 300         804 my $i = $self->get_tag('i');
468 300 100       650 if ( defined $i ) {
469 217         533 return decode_qp($i);
470             }
471             else {
472 83   50     178 return '@' . ( $self->domain || '' );
473             }
474             }
475              
476             sub identity_matches {
477 0     0 0 0 my $self = shift;
478 0         0 my ($addr) = @_;
479              
480 0         0 my $id = $self->identity;
481 0 0       0 if ( $id =~ /^\@/ ) {
482              
483             # the identity is a domain-name only, so it only needs to match
484             # the domain part of the sender address
485 0         0 return ( lc( substr( $addr, -length($id) ) ) eq lc($id) );
486              
487             # TODO - compare the parent domains?
488             }
489 0         0 return lc($addr) eq lc($id);
490             }
491              
492              
493             sub key {
494 52     52 1 87 my $self = shift;
495 52 50       119 if (@_) {
496 52         96 $self->{Key} = shift;
497 52         89 $self->{KeyFile} = undef;
498             }
499 52         98 return $self->{Key};
500             }
501              
502              
503             sub method {
504 0     0 1 0 my $self = shift;
505              
506 0 0       0 if (@_) {
507 0         0 $self->set_tag( 'c', shift );
508             }
509              
510 0   0     0 return ( lc $self->get_tag('c') ) || 'simple';
511             }
512              
513              
514             sub protocol {
515 831     831 1 1358 my $self = shift;
516              
517 831 50       1542 (@_)
518             and $self->set_tag( 'q', shift );
519              
520 831         1881 my $q = $self->get_tag('q');
521 831 100       1957 if ( defined $q ) {
522 85         216 return $q;
523             }
524             else {
525 746         2075 return 'dns/txt';
526             }
527             }
528              
529              
530             sub result {
531 1046     1046 1 1920 my $self = shift;
532 1046 100       2192 @_ and $self->{verify_result} = shift;
533 1046 100       2361 @_ and $self->{verify_details} = shift;
534 1046         2966 return $self->{verify_result};
535             }
536              
537              
538             sub result_detail {
539 471     471 1 683 my $self = shift;
540 471 50       932 croak 'wrong number of arguments' unless ( @_ == 0 );
541              
542 471 100 100     1490 if ( $self->{verify_result} && $self->{verify_details} ) {
543 147         717 return $self->{verify_result} . ' (' . $self->{verify_details} . ')';
544             }
545 324         1233 return $self->{verify_result};
546             }
547              
548              
549             sub selector {
550 1809     1809 1 2696 my $self = shift;
551              
552 1809 100       4653 (@_)
553             and $self->set_tag( 's', shift );
554              
555 1809         3893 return $self->get_tag('s');
556             }
557              
558              
559             sub prettify {
560 65     65 1 121 my $self = shift;
561 65         201 $self->wrap(
562             Start => length( $self->prefix() ),
563             Tags => {
564             b => 'b64',
565             bh => 'b64',
566             h => 'list',
567             },
568             );
569             }
570              
571              
572             sub prettify_safe {
573 62     62 1 118 my $self = shift;
574 62         149 $self->wrap(
575             Start => length( $self->prefix() ),
576             Tags => {
577             b => 'b64',
578             },
579             PreserveNames => 1,
580             Default => 'preserve', #preserves unknown tags
581             );
582             }
583              
584              
585             sub timestamp {
586 405     405 1 583 my $self = shift;
587              
588 405 50       1345 (@_)
589             and $self->set_tag( 't', shift );
590              
591 405         900 return $self->get_tag('t');
592             }
593              
594              
595             sub version {
596 1428     1428 1 1898 my $self = shift;
597              
598 1428 100       3305 (@_)
599             and $self->set_tag( 'v', shift );
600              
601 1428         3230 return $self->get_tag('v');
602             }
603              
604              
605             1;
606              
607             __END__