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   63501 use strict;
  14         40  
  14         417  
3 14     14   71 use warnings;
  14         29  
  14         580  
4             our $VERSION = '1.20230630'; # 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   6596 use Mail::DKIM::PublicKey;
  14         78  
  14         1131  
15 14     14   7702 use Mail::DKIM::Algorithm::rsa_sha1;
  14         51  
  14         480  
16 14     14   6178 use Mail::DKIM::Algorithm::rsa_sha256;
  14         40  
  14         377  
17 14     14   5935 use Mail::DKIM::Algorithm::ed25519_sha256;
  14         32  
  14         387  
18              
19 14     14   82 use base 'Mail::DKIM::KeyValueList';
  14         30  
  14         1283  
20 14     14   85 use Carp;
  14         28  
  14         13681  
21              
22              
23             sub new {
24 166     166 1 396 my $class = shift;
25 166         351 my %prms = @_;
26 166         319 my $self = {};
27 166         399 bless $self, $class;
28              
29 166         470 $self->version('1');
30 166   100     855 $self->algorithm( $prms{'Algorithm'} || 'rsa-sha1' );
31 166         637 $self->signature( $prms{'Signature'} );
32 166 100       473 $self->canonicalization( $prms{'Method'} ) if exists $prms{'Method'};
33 166         545 $self->domain( $prms{'Domain'} );
34 166         617 $self->headerlist( $prms{'Headers'} );
35 166 50       425 $self->protocol( $prms{'Query'} ) if exists $prms{'Query'};
36 166         565 $self->selector( $prms{'Selector'} );
37 166 100       437 $self->identity( $prms{'Identity'} ) if exists $prms{'Identity'};
38 166 100       387 $self->timestamp( $prms{'Timestamp'} ) if defined $prms{'Timestamp'};
39 166 100       335 $self->expiration( $prms{'Expiration'} ) if defined $prms{'Expiration'};
40 166 50       324 $self->tags( $prms{'Tags'} ) if defined $prms{'Tags'};
41 166 100       368 $self->key( $prms{'Key'} ) if defined $prms{'Key'};
42 166         441 return $self;
43             }
44              
45              
46             sub parse {
47 882     882 1 1974 my $class = shift;
48 882 50       1909 croak 'wrong number of arguments' unless ( @_ == 1 );
49 882         1739 my ($string) = @_;
50              
51             # remove line terminator, if present
52 882         3138 $string =~ s/\015\012\z//;
53              
54             # remove field name, if present
55 882         2067 my $prefix = $class->prefix();
56 882 50       14032 if ( $string =~ s/^($prefix)//i ) {
57              
58             # save the field name (capitalization), so that it can be
59             # restored later
60 882         3166 $prefix = $1;
61             }
62              
63 882         2849 my $self = $class->SUPER::parse($string);
64 880         1831 $self->{prefix} = $prefix;
65              
66 880         2529 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 5291 my $self = shift;
89              
90 2980 100       5675 if (@_) {
91 938         2196 $self->set_tag( 'a', shift );
92             }
93              
94 2980         6159 my $a = $self->get_tag('a');
95 2980 100       10055 return defined $a ? lc $a : undef;
96             }
97              
98              
99             sub as_string {
100 931     931 1 6864 my $self = shift;
101              
102 931         1803 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 661 my $self = shift;
116 419 50       942 croak 'wrong number of arguments' unless ( @_ == 0 );
117              
118 419         1022 my $alt = $self->clone;
119 419         1655 $alt->signature('');
120              
121 419         992 return $alt->as_string;
122             }
123              
124              
125             sub body_count {
126 260     260 1 410 my $self = shift;
127              
128             # set new body count if provided
129 260 50       581 (@_)
130             and $self->set_tag( 'l', shift );
131              
132 260         665 return $self->get_tag('l');
133             }
134              
135              
136             sub body_hash {
137 186     186 1 346 my $self = shift;
138              
139             # set new body hash if provided
140 186 100       490 (@_)
141             and $self->set_tag( 'bh', shift );
142              
143 186         463 my $result = $self->get_tag('bh');
144 186 50       439 if ( defined $result ) {
145 186         553 $result =~ s/\s+//gs;
146             }
147 186         415 return $result;
148             }
149              
150              
151             sub canonicalization {
152 546     546 1 2039 my $self = shift;
153              
154 546 100       1094 if (@_) {
155 37         127 $self->set_tag( 'c', join( '/', @_ ) );
156             }
157              
158 546         1130 my $c = $self->get_tag('c');
159 546 100       1426 $c = lc $c if defined $c;
160 546 100       1115 if ( not $c ) {
161 4         7 $c = 'simple/simple';
162             }
163 546         1461 my ( $c1, $c2 ) = split( /\//, $c, 2 );
164 546 100       1220 if ( not defined $c2 ) {
165              
166             # default body canonicalization is "simple"
167 144         243 $c2 = 'simple';
168             }
169              
170 546 100       1054 if (wantarray) {
171 501         1415 return ( $c1, $c2 );
172             }
173             else {
174 45         157 return "$c1/$c2";
175             }
176             }
177              
178 14     14   126 use MIME::Base64;
  14         30  
  14         32179  
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 746 my $self = shift;
185              
186 422         931 my ( $c1, $c2 ) = $self->canonicalization;
187              
188 422         1087 my @known = ( 'nowsp', 'simple', 'relaxed', 'seal' );
189 422 100       746 return undef unless ( grep { $_ eq $c1 } @known );
  1688         3388  
190 419 50       685 return undef unless ( grep { $_ eq $c2 } @known );
  1676         2994  
191 419         1148 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 634 my $self = shift;
199 432         875 my $x = $self->expiration;
200 432 100       1408 return 1 if not defined $x;
201              
202 3   33     18 $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 1195 my $self = shift;
213              
214 827         1677 my $v = $self->version;
215              
216 827         1648 foreach my $prot ( split /:/, $self->protocol ) {
217 829         1957 my ( $type, $options ) = split( /\//, $prot, 2 );
218 829 100       1783 if ( $type eq 'dns' ) {
219 826 100 100     3673 return ('dns/txt') if $options && $options eq 'txt';
220              
221             # prior to DKIM version 1, the '/txt' part was optional
222 13 100       43 if ( !$v ) {
223 12 50       46 return ('dns/txt') if !defined($options);
224             }
225             }
226             }
227              
228             # unrecognized
229 2         6 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 661 my $self = shift;
237              
238             # check version
239 433 100       789 if ( my $version = $self->version ) {
240 52         125 my @ALLOWED_VERSIONS = ( '0.5', '1' );
241 52         104 return ( grep { $_ eq $version } @ALLOWED_VERSIONS );
  104         336  
242             }
243              
244             # we still consider a missing v= tag acceptable,
245             # for backwards-compatibility
246 381         942 return 1;
247             }
248              
249              
250             sub data {
251 2385     2385 1 3576 my $self = shift;
252              
253 2385 100       4837 if (@_) {
254 1440         3354 $self->set_tag( 'b', shift );
255             }
256              
257 2385         5324 my $b = $self->get_tag('b');
258 2385 100       6714 $b =~ tr/\015\012 \t//d if defined $b;
259 2385         5353 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 352 my $res = shift;
269              
270             #TODO- should I worry about non-ASCII systems here?
271 217 50       562 $res =~ s/=([\da-fA-F]{2})/pack('C', hex($1))/ge
  6         44  
272             if defined $res;
273 217         560 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 4 my $res = shift;
281              
282             # note- unlike MIME quoted-printable, we don't allow whitespace chars
283 2         8 my $DISALLOWED = qr/[^!"#\$%&'()*+,\-.\/0-9:;<>?\@A-Z[\\\]^_`a-z{|}~]/;
284              
285             #TODO- should I worry about non-ASCII systems here?
286 2 50       81 $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 682 return 'DKIM-Signature:';
293             }
294              
295             sub prefix {
296 1940     1940 0 3067 my $class = shift;
297 1940 100       4193 if ( ref($class) ) {
298 1058 50       2014 $class->{prefix} = shift if @_;
299 1058 100       3671 return $class->{prefix} if $class->{prefix};
300             }
301 1134         2794 return $class->DEFAULT_PREFIX();
302             }
303              
304              
305             sub domain {
306 2974     2974 1 4653 my $self = shift;
307              
308 2974 100       5448 if (@_) {
309 937         2026 $self->set_tag( 'd', shift );
310             }
311              
312 2974         5691 my $d = $self->get_tag('d');
313 2974 100       11064 return defined $d ? lc $d : undef;
314             }
315              
316              
317             sub expiration {
318 419     419 1 574 my $self = shift;
319              
320 419 100       788 (@_)
321             and $self->set_tag( 'x', shift );
322              
323 419         911 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 1418 my $self = shift;
340 888 50       1746 croak 'wrong number of arguments' unless ( @_ == 1 );
341 888         1451 my ($algorithm) = @_;
342              
343 888 100       2069 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         3022 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 608 my $self = shift;
359 423 50       883 return if exists $self->{public_key_query};
360              
361             my $on_success = sub {
362 358 100   358   746 if ( $_[0] ) {
363 354         994 $self->{public} = $_[0];
364             }
365             else {
366 4         16 $self->{public_error} = "not available\n";
367             }
368 423         1604 };
369              
370 423         861 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   14 Error => sub { $self->{public_error} = shift },
378             },
379 423         1066 );
380 421         1083 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 1186 my $self = shift;
400              
401             # this ensures we only try fetching once, even if an error occurs
402 713 50       1572 if ( not exists $self->{public_key_query} ) {
403 0         0 $self->fetch_public_key;
404             }
405              
406 713 100       1384 if ( $self->{public_key_query} ) {
407              
408             # wait for public key query to finish
409 369         1113 $self->{public_key_query}->();
410 361         5277 $self->{public_key_query} = 0;
411             }
412              
413 705 100       1458 if ( exists $self->{public} ) {
414 698         1736 return $self->{public};
415             }
416             else {
417 7         56 die $self->{public_error};
418             }
419             }
420              
421              
422             sub hash_algorithm {
423 341     341 1 529 my $self = shift;
424 341         803 my $algorithm = $self->algorithm;
425              
426             return
427 341 50       1326 $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 1685 my $self = shift;
436              
437 1118 100       2988 (@_)
438             and $self->set_tag( 'h', shift );
439              
440 1118   100     2409 my $h = $self->get_tag('h') || '';
441              
442             # remove whitespace next to colons
443 1118         2838 $h =~ s/\s+:/:/g;
444 1118         2593 $h =~ s/:\s+/:/g;
445 1118         1981 $h = lc $h;
446              
447 1118 100 100     3457 if ( wantarray and $h ) {
    100          
448 274         1054 my @list = split /:/, $h;
449 274         681 @list = map { s/^\s+|\s+$//g; $_ } @list;
  1592         4336  
  1592         3226  
450 274         1108 return @list;
451             }
452             elsif (wantarray) {
453 8         33 return ();
454             }
455              
456 836         1713 return $h;
457             }
458              
459              
460             sub identity {
461 300     300 1 463 my $self = shift;
462              
463             # set new identity if provided
464 300 100       640 (@_)
465             and $self->set_tag( 'i', encode_qp(shift) );
466              
467 300         700 my $i = $self->get_tag('i');
468 300 100       703 if ( defined $i ) {
469 217         501 return decode_qp($i);
470             }
471             else {
472 83   50     179 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 81 my $self = shift;
495 52 50       107 if (@_) {
496 52         88 $self->{Key} = shift;
497 52         94 $self->{KeyFile} = undef;
498             }
499 52         91 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 1234 my $self = shift;
516              
517 831 50       1470 (@_)
518             and $self->set_tag( 'q', shift );
519              
520 831         1529 my $q = $self->get_tag('q');
521 831 100       1511 if ( defined $q ) {
522 85         240 return $q;
523             }
524             else {
525 746         1958 return 'dns/txt';
526             }
527             }
528              
529              
530             sub result {
531 1046     1046 1 2057 my $self = shift;
532 1046 100       2113 @_ and $self->{verify_result} = shift;
533 1046 100       1946 @_ and $self->{verify_details} = shift;
534 1046         3072 return $self->{verify_result};
535             }
536              
537              
538             sub result_detail {
539 471     471 1 697 my $self = shift;
540 471 50       898 croak 'wrong number of arguments' unless ( @_ == 0 );
541              
542 471 100 100     1423 if ( $self->{verify_result} && $self->{verify_details} ) {
543 147         622 return $self->{verify_result} . ' (' . $self->{verify_details} . ')';
544             }
545 324         1153 return $self->{verify_result};
546             }
547              
548              
549             sub selector {
550 1809     1809 1 2641 my $self = shift;
551              
552 1809 100       4643 (@_)
553             and $self->set_tag( 's', shift );
554              
555 1809         3806 return $self->get_tag('s');
556             }
557              
558              
559             sub prettify {
560 65     65 1 139 my $self = shift;
561 65         169 $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 131 my $self = shift;
574 62         145 $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 586 my $self = shift;
587              
588 405 50       1228 (@_)
589             and $self->set_tag( 't', shift );
590              
591 405         914 return $self->get_tag('t');
592             }
593              
594              
595             sub version {
596 1428     1428 1 2067 my $self = shift;
597              
598 1428 100       3015 (@_)
599             and $self->set_tag( 'v', shift );
600              
601 1428         3017 return $self->get_tag('v');
602             }
603              
604              
605             1;
606              
607             __END__