File Coverage

blib/lib/Mail/DKIM/Signature.pm
Criterion Covered Total %
statement 226 260 86.9
branch 110 140 78.5
condition 16 20 80.0
subroutine 46 52 88.4
pod 25 40 62.5
total 423 512 82.6


line stmt bran cond sub pod time code
1             package Mail::DKIM::Signature;
2 15     15   83530 use strict;
  15         37  
  15         599  
3 15     15   131 use warnings;
  15         47  
  15         1162  
4             our $VERSION = '1.20240923'; # 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 15     15   8223 use Mail::DKIM::PublicKey;
  15         116  
  15         1368  
15 15     15   10050 use Mail::DKIM::Algorithm::rsa_sha1;
  15         96  
  15         754  
16 15     15   8817 use Mail::DKIM::Algorithm::rsa_sha256;
  15         71  
  15         520  
17 15     15   8094 use Mail::DKIM::Algorithm::ed25519_sha256;
  15         59  
  15         592  
18              
19 15     15   109 use base 'Mail::DKIM::KeyValueList';
  15         34  
  15         2591  
20 15     15   158 use Carp;
  15         37  
  15         18637  
21              
22              
23             sub new {
24 171     171 1 183627 my $class = shift;
25 171         437 my %prms = @_;
26 171         372 my $self = {};
27 171         402 bless $self, $class;
28              
29 171         619 $self->version('1');
30 171   100     1046 $self->algorithm( $prms{'Algorithm'} || 'rsa-sha1' );
31 171         873 $self->signature( $prms{'Signature'} );
32 171 100       611 $self->canonicalization( $prms{'Method'} ) if exists $prms{'Method'};
33 171         775 $self->domain( $prms{'Domain'} );
34 171         1908 $self->headerlist( $prms{'Headers'} );
35 171 50       529 $self->protocol( $prms{'Query'} ) if exists $prms{'Query'};
36 171         774 $self->selector( $prms{'Selector'} );
37 171 100       543 $self->identity( $prms{'Identity'} ) if exists $prms{'Identity'};
38 171 100       536 $self->timestamp( $prms{'Timestamp'} ) if defined $prms{'Timestamp'};
39 171 100       510 $self->expiration( $prms{'Expiration'} ) if defined $prms{'Expiration'};
40 171 50       529 $self->tags( $prms{'Tags'} ) if defined $prms{'Tags'};
41 171 100       514 $self->key( $prms{'Key'} ) if defined $prms{'Key'};
42 171         558 return $self;
43             }
44              
45              
46             sub parse {
47 886     886 1 306383 my $class = shift;
48 886 50       3091 croak 'wrong number of arguments' unless ( @_ == 1 );
49 886         2391 my ($string) = @_;
50              
51             # remove line terminator, if present
52 886         4206 $string =~ s/\015\012\z//;
53              
54             # remove field name, if present
55 886         2812 my $prefix = $class->prefix();
56 886 50       31831 if ( $string =~ s/^($prefix)//i ) {
57              
58             # save the field name (capitalization), so that it can be
59             # restored later
60 886         3330 $prefix = $1;
61             }
62              
63 886         4034 my $self = $class->SUPER::parse($string);
64 884         2298 $self->{prefix} = $prefix;
65              
66 884         2811 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 2992     2992 1 5674 my $self = shift;
89              
90 2992 100       6763 if (@_) {
91 943         3175 $self->set_tag( 'a', shift );
92             }
93              
94 2992         8031 my $a = $self->get_tag('a');
95 2992 100       14088 return defined $a ? lc $a : undef;
96             }
97              
98              
99             sub as_string {
100 939     939 1 8808 my $self = shift;
101              
102 939         2701 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 421     421 1 737 my $self = shift;
116 421 50       1216 croak 'wrong number of arguments' unless ( @_ == 0 );
117              
118 421         1686 my $alt = $self->clone;
119 421         1789 $alt->signature('');
120              
121 421         3314 return $alt->as_string;
122             }
123              
124              
125             sub body_count {
126 262     262 1 524 my $self = shift;
127              
128             # set new body count if provided
129 262 50       797 (@_)
130             and $self->set_tag( 'l', shift );
131              
132 262         1048 return $self->get_tag('l');
133             }
134              
135              
136             sub body_hash {
137 188     188 1 383 my $self = shift;
138              
139             # set new body hash if provided
140 188 100       795 (@_)
141             and $self->set_tag( 'bh', shift );
142              
143 188         703 my $result = $self->get_tag('bh');
144 188 50       544 if ( defined $result ) {
145 188         843 $result =~ s/\s+//gs;
146             }
147 188         529 return $result;
148             }
149              
150              
151             sub canonicalization {
152 551     551 1 2273 my $self = shift;
153              
154 551 100       1392 if (@_) {
155 38         227 $self->set_tag( 'c', join( '/', @_ ) );
156             }
157              
158 551         1561 my $c = $self->get_tag('c');
159 551 100       2057 $c = lc $c if defined $c;
160 551 100       1293 if ( not $c ) {
161 4         10 $c = 'simple/simple';
162             }
163 551         3094 my ( $c1, $c2 ) = split( /\//, $c, 2 );
164 551 100       1634 if ( not defined $c2 ) {
165              
166             # default body canonicalization is "simple"
167 149         258 $c2 = 'simple';
168             }
169              
170 551 100       1415 if (wantarray) {
171 505         2111 return ( $c1, $c2 );
172             }
173             else {
174 46         182 return "$c1/$c2";
175             }
176             }
177              
178 15     15   784 use MIME::Base64;
  15         34  
  15         48105  
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 424     424 0 742 my $self = shift;
185              
186 424         1409 my ( $c1, $c2 ) = $self->canonicalization;
187              
188 424         1607 my @known = ( 'nowsp', 'simple', 'relaxed', 'seal' );
189 424 100       1044 return undef unless ( grep { $_ eq $c1 } @known );
  1696         3814  
190 421 50       823 return undef unless ( grep { $_ eq $c2 } @known );
  1684         3394  
191 421         1825 return 1;
192             }
193              
194             sub set_verify_time {
195 1     1 0 3 my ( $self, $verify_time ) = @_;
196 1         3 $self->{_verify_time} = $verify_time;
197             }
198              
199             # checks whether the expiration time on this signature is acceptable
200             # returns a true value if acceptable, false otherwise
201             #
202             sub check_expiration {
203 434     434 0 965 my $self = shift;
204 434         1220 my $x = $self->expiration;
205 434 100       1839 return 1 if not defined $x;
206 5   66     23 $self->{_verify_time} ||= time();
207 5         22 return ( $self->{_verify_time} <= $x );
208             }
209              
210             # Returns a filtered list of protocols that can be used to fetch the
211             # public key corresponding to this signature. An empty list means that
212             # all designated protocols are unrecognized.
213             # Note: at this time, the only recognized protocol is "dns/txt".
214             #
215             sub check_protocol {
216 830     830 0 1442 my $self = shift;
217              
218 830         1881 my $v = $self->version;
219              
220 830         2386 foreach my $prot ( split /:/, $self->protocol ) {
221 832         2492 my ( $type, $options ) = split( /\//, $prot, 2 );
222 832 100       2254 if ( $type eq 'dns' ) {
223 829 100 100     5394 return ('dns/txt') if $options && $options eq 'txt';
224              
225             # prior to DKIM version 1, the '/txt' part was optional
226 13 100       44 if ( !$v ) {
227 12 50       61 return ('dns/txt') if !defined($options);
228             }
229             }
230             }
231              
232             # unrecognized
233 2         9 return;
234             }
235              
236             # checks whether the version tag has an acceptable value
237             # returns true if so, otherwise false
238             #
239             sub check_version {
240 435     435 0 799 my $self = shift;
241              
242             # check version
243 435 100       1361 if ( my $version = $self->version ) {
244 54         167 my @ALLOWED_VERSIONS = ( '0.5', '1' );
245 54         143 return ( grep { $_ eq $version } @ALLOWED_VERSIONS );
  108         431  
246             }
247              
248             # we still consider a missing v= tag acceptable,
249             # for backwards-compatibility
250 381         1217 return 1;
251             }
252              
253              
254             sub data {
255 2396     2396 1 4174 my $self = shift;
256              
257 2396 100       9959 if (@_) {
258 1448         3896 $self->set_tag( 'b', shift );
259             }
260              
261 2396         6892 my $b = $self->get_tag('b');
262 2396 100       7818 $b =~ tr/\015\012 \t//d if defined $b;
263 2396         6483 return $b;
264             }
265              
266             *signature = \*data;
267              
268             #undocumented, private function
269             #derived from MIME::Base64::Perl (allowed, thanks to the Perl license)
270             #
271             sub decode_qp {
272 217     217 0 440 my $res = shift;
273              
274             #TODO- should I worry about non-ASCII systems here?
275 217 50       953 $res =~ s/=([\da-fA-F]{2})/pack('C', hex($1))/ge
  6         67  
276             if defined $res;
277 217         697 return $res;
278             }
279              
280             #undocumented, private function
281             #derived from MIME::Base64::Perl (allowed, thanks to the Perl license)
282             #
283             sub encode_qp {
284 2     2 0 4 my $res = shift;
285              
286             # note- unlike MIME quoted-printable, we don't allow whitespace chars
287 2         11 my $DISALLOWED = qr/[^!"#\$%&'()*+,\-.\/0-9:;<>?\@A-Z[\\\]^_`a-z{|}~]/;
288              
289             #TODO- should I worry about non-ASCII systems here?
290 2 50       66 $res =~ s/($DISALLOWED)/sprintf('=%02X', ord($1))/eg
  0         0  
291             if defined $res;
292 2         12 return $res;
293             }
294              
295             sub DEFAULT_PREFIX {
296 238     238 0 816 return 'DKIM-Signature:';
297             }
298              
299             sub prefix {
300 1954     1954 0 4296 my $class = shift;
301 1954 100       5265 if ( ref($class) ) {
302 1068 50       2585 $class->{prefix} = shift if @_;
303 1068 100       5176 return $class->{prefix} if $class->{prefix};
304             }
305 1145         4267 return $class->DEFAULT_PREFIX();
306             }
307              
308              
309             sub domain {
310 2985     2985 1 7100 my $self = shift;
311              
312 2985 100       6668 if (@_) {
313 942         2472 $self->set_tag( 'd', shift );
314             }
315              
316 2985         7273 my $d = $self->get_tag('d');
317 2985 100       15302 return defined $d ? lc $d : undef;
318             }
319              
320              
321             sub expiration {
322 422     422 1 717 my $self = shift;
323              
324 422 100       1116 (@_)
325             and $self->set_tag( 'x', shift );
326              
327 422         1149 return $self->get_tag('x');
328             }
329              
330             sub tags {
331 0     0 0 0 my $self = shift;
332 0         0 my $tags = shift;
333              
334 0         0 for my $tag (sort keys %$tags) {
335 0         0 $self->set_tag( $tag, $tags->{$tag} );
336             }
337 0         0 (@_)
338              
339             }
340              
341             # allows the type of signature to determine what "algorithm" gets used
342             sub get_algorithm_class {
343 892     892 0 1487 my $self = shift;
344 892 50       2440 croak 'wrong number of arguments' unless ( @_ == 1 );
345 892         1743 my ($algorithm) = @_;
346              
347 892 100       2675 my $class =
    100          
    100          
348             $algorithm eq 'rsa-sha1' ? 'Mail::DKIM::Algorithm::rsa_sha1'
349             : $algorithm eq 'rsa-sha256' ? 'Mail::DKIM::Algorithm::rsa_sha256'
350             : $algorithm eq 'ed25519-sha256' ? 'Mail::DKIM::Algorithm::ed25519_sha256'
351             : undef;
352 892         4136 return $class;
353             }
354              
355             # [private method]
356             # fetch_public_key() - initiate a DNS query for fetching the key
357             #
358             # This method does NOT return the public key.
359             # Use get_public_key() for that.
360             #
361             sub fetch_public_key {
362 424     424 0 813 my $self = shift;
363 424 50       1242 return if exists $self->{public_key_query};
364              
365             my $on_success = sub {
366 359 100   359   1052 if ( $_[0] ) {
367 355         1928 $self->{public} = $_[0];
368             }
369             else {
370 4         17 $self->{public_error} = "not available\n";
371             }
372 424         2517 };
373              
374 424         1281 my @methods = $self->check_protocol;
375             $self->{public_key_query} = Mail::DKIM::PublicKey->fetch_async(
376             Protocol => $methods[0],
377             Selector => $self->selector,
378             Domain => $self->domain,
379             Callbacks => {
380             Success => $on_success,
381 3     3   16 Error => sub { $self->{public_error} = shift },
382             },
383 424         1472 );
384 422         1649 return;
385             }
386              
387             #EXPERIMENTAL
388             sub _refetch_public_key {
389 0     0   0 my $self = shift;
390 0 0       0 if ( $self->{public_key_query} ) {
391              
392             # clear the existing query by waiting for it to complete
393 0         0 $self->{public_key_query}->();
394             }
395 0         0 delete $self->{public_key_query};
396 0         0 delete $self->{public};
397 0         0 delete $self->{public_error};
398 0         0 $self->fetch_public_key;
399             }
400              
401              
402             sub get_public_key {
403 715     715 1 1164 my $self = shift;
404              
405             # this ensures we only try fetching once, even if an error occurs
406 715 50       1867 if ( not exists $self->{public_key_query} ) {
407 0         0 $self->fetch_public_key;
408             }
409              
410 715 100       1862 if ( $self->{public_key_query} ) {
411              
412             # wait for public key query to finish
413 370         1765 $self->{public_key_query}->();
414 362         9374 $self->{public_key_query} = 0;
415             }
416              
417 707 100       2017 if ( exists $self->{public} ) {
418 700         2418 return $self->{public};
419             }
420             else {
421 7         78 die $self->{public_error};
422             }
423             }
424              
425              
426             sub hash_algorithm {
427 342     342 1 629 my $self = shift;
428 342         1273 my $algorithm = $self->algorithm;
429              
430             return
431 342 50       2294 $algorithm eq 'rsa-sha1' ? 'sha1'
    100          
    100          
432             : $algorithm eq 'rsa-sha256' ? 'sha256'
433             : $algorithm eq 'ed25519-sha256' ? 'sha256'
434             : undef;
435             }
436              
437              
438             sub headerlist {
439 1127     1127 1 1941 my $self = shift;
440              
441 1127 100       3782 (@_)
442             and $self->set_tag( 'h', shift );
443              
444 1127   100     3074 my $h = $self->get_tag('h') || '';
445              
446             # remove whitespace next to colons
447 1127         3817 $h =~ s/\s+:/:/g;
448 1127         3569 $h =~ s/:\s+/:/g;
449 1127         2558 $h = lc $h;
450              
451 1127 100 100     4475 if ( wantarray and $h ) {
    100          
452 276         1543 my @list = split /:/, $h;
453 276         815 @list = map { s/^\s+|\s+$//g; $_ } @list;
  1596         6109  
  1596         3719  
454 276         1477 return @list;
455             }
456             elsif (wantarray) {
457 8         33 return ();
458             }
459              
460 843         2249 return $h;
461             }
462              
463              
464             sub identity {
465 302     302 1 590 my $self = shift;
466              
467             # set new identity if provided
468 302 100       882 (@_)
469             and $self->set_tag( 'i', encode_qp(shift) );
470              
471 302         1067 my $i = $self->get_tag('i');
472 302 100       1029 if ( defined $i ) {
473 217         872 return decode_qp($i);
474             }
475             else {
476 85   50     214 return '@' . ( $self->domain || '' );
477             }
478             }
479              
480             sub identity_matches {
481 0     0 0 0 my $self = shift;
482 0         0 my ($addr) = @_;
483              
484 0         0 my $id = $self->identity;
485 0 0       0 if ( $id =~ /^\@/ ) {
486              
487             # the identity is a domain-name only, so it only needs to match
488             # the domain part of the sender address
489 0         0 return ( lc( substr( $addr, -length($id) ) ) eq lc($id) );
490              
491             # TODO - compare the parent domains?
492             }
493 0         0 return lc($addr) eq lc($id);
494             }
495              
496              
497             sub key {
498 53     53 1 149 my $self = shift;
499 53 50       136 if (@_) {
500 53         122 $self->{Key} = shift;
501 53         169 $self->{KeyFile} = undef;
502             }
503 53         142 return $self->{Key};
504             }
505              
506              
507             sub method {
508 0     0 1 0 my $self = shift;
509              
510 0 0       0 if (@_) {
511 0         0 $self->set_tag( 'c', shift );
512             }
513              
514 0   0     0 return ( lc $self->get_tag('c') ) || 'simple';
515             }
516              
517              
518             sub protocol {
519 834     834 1 1416 my $self = shift;
520              
521 834 50       1820 (@_)
522             and $self->set_tag( 'q', shift );
523              
524 834         1919 my $q = $self->get_tag('q');
525 834 100       1949 if ( defined $q ) {
526 85         305 return $q;
527             }
528             else {
529 749         2666 return 'dns/txt';
530             }
531             }
532              
533              
534             sub result {
535 1050     1050 1 4517 my $self = shift;
536 1050 100       3314 @_ and $self->{verify_result} = shift;
537 1050 100       3482 @_ and $self->{verify_details} = shift;
538 1050         4593 return $self->{verify_result};
539             }
540              
541              
542             sub result_detail {
543 471     471 1 778 my $self = shift;
544 471 50       1181 croak 'wrong number of arguments' unless ( @_ == 0 );
545              
546 471 100 100     2026 if ( $self->{verify_result} && $self->{verify_details} ) {
547 147         888 return $self->{verify_result} . ' (' . $self->{verify_details} . ')';
548             }
549 324         1643 return $self->{verify_result};
550             }
551              
552              
553             sub selector {
554 1816     1816 1 3024 my $self = shift;
555              
556 1816 100       5321 (@_)
557             and $self->set_tag( 's', shift );
558              
559 1816         4829 return $self->get_tag('s');
560             }
561              
562              
563             sub prettify {
564 66     66 1 140 my $self = shift;
565 66         267 $self->wrap(
566             Start => length( $self->prefix() ),
567             Tags => {
568             b => 'b64',
569             bh => 'b64',
570             h => 'list',
571             },
572             );
573             }
574              
575              
576             sub prettify_safe {
577 63     63 1 153 my $self = shift;
578 63         248 $self->wrap(
579             Start => length( $self->prefix() ),
580             Tags => {
581             b => 'b64',
582             },
583             PreserveNames => 1,
584             Default => 'preserve', #preserves unknown tags
585             );
586             }
587              
588              
589             sub timestamp {
590 406     406 1 779 my $self = shift;
591              
592 406 50       1839 (@_)
593             and $self->set_tag( 't', shift );
594              
595 406         1067 return $self->get_tag('t');
596             }
597              
598              
599             sub version {
600 1438     1438 1 2354 my $self = shift;
601              
602 1438 100       3729 (@_)
603             and $self->set_tag( 'v', shift );
604              
605 1438         4526 return $self->get_tag('v');
606             }
607              
608              
609             1;
610              
611             __END__