File Coverage

blib/lib/Net/DNS/RR/SIG.pm
Criterion Covered Total %
statement 167 167 100.0
branch 48 48 100.0
path n/a
condition 14 14 100.0
subroutine 43 43 100.0
pod 14 17 100.0
total 286 289 100.0


line stmt bran path cond sub pod time code
1               package Net::DNS::RR::SIG;
2                
3 2       2   11 use strict;
  2           2  
  2           63  
4 2       2   8 use warnings;
  2           3  
  2           144  
5               our $VERSION = (qw$Id: SIG.pm 2042 2025-12-24 10:23:11Z willem $)[2];
6                
7 2       2   9 use base qw(Net::DNS::RR);
  2           4  
  2           156  
8                
9                
10               =head1 NAME
11                
12               Net::DNS::RR::SIG - DNS SIG resource record
13                
14               =cut
15                
16 2       2   8 use integer;
  2           2  
  2           12  
17                
18 2       2   44 use Carp;
  2           4  
  2           111  
19 2       2   450 use Time::Local;
  2           1405  
  2           93  
20                
21 2       2   8 use Net::DNS::Parameters qw(:type);
  2           3  
  2           220  
22                
23 2       2   30 use constant DEBUG => 0;
  2           3  
  2           119  
24                
25 2       2   7 use constant UTIL => defined eval { require Scalar::Util; };
  2           3  
  2           3  
  2           104  
26                
27               eval { require MIME::Base64 };
28                
29               ## IMPORTANT: MUST NOT include crypto packages in metadata (strong crypto prohibited in many territories)
30 2       2   6 use constant DNSSEC => defined $INC{'Net/DNS/SEC.pm'}; ## Discover how we got here, without exposing any crypto
  2           3  
  2           2006  
31                
32               my @algorithms;
33               my @deprecated;
34               if (DNSSEC) {
35               foreach my $class ( map {"Net::DNS::SEC::$_"} qw(Private DSA RSA ECDSA EdDSA Digest SM2) ) {
36               my @index = eval join '', qw(r e q u i r e), " $class; ${class}::_index()"; ## no critic
37               push @algorithms, map { ( $_ => $class ) } @index;
38               push @deprecated, eval "${class}::_deprecate()"; ## no critic
39               }
40               croak 'Net::DNS::SEC version not supported' unless scalar(@algorithms);
41               }
42                
43               my %DNSSEC_verify = @algorithms;
44               my %DNSSEC_siggen = @algorithms;
45                
46               my @field = qw(typecovered algorithm labels orgttl sigexpiration siginception keytag);
47                
48                
49               sub _decode_rdata { ## decode rdata from wire-format octet string
50 2       2   4 my ( $self, $data, $offset, @opaque ) = @_;
51                
52 2           3 my $limit = $offset + $self->{rdlength};
53 2           4 @{$self}{@field} = unpack "\@$offset n C2 N3 n", $$data;
  2           8  
54 2           5 ( $self->{signame}, $offset ) = Net::DNS::DomainName->decode( $data, $offset + 18, @opaque );
55 2           5 $self->{sigbin} = substr $$data, $offset, $limit - $offset;
56                
57 2 100         176 croak('misplaced or corrupt SIG') unless $limit == length $$data;
58 1           2 my $raw = substr $$data, 0, $self->{offset}++;
59 1           2 $self->{rawref} = \$raw;
60 1           2 return;
61               }
62                
63                
64               sub _encode_rdata { ## encode rdata as wire-format octet string
65 7       7   10 my ( $self, $offset, @opaque ) = @_;
66                
67 7           8 my $signame = $self->{signame};
68                
69 7           7 if ( DNSSEC && !$self->{sigbin} ) {
70               my ( undef, $packet ) = @opaque;
71               my $private = delete $self->{private}; # one shot is all you get
72               my $sigdata = $self->_CreateSigData($packet);
73               $self->_CreateSig( $sigdata, $private || die 'missing key reference' );
74               }
75                
76 7           9 return pack 'n C2 N3 n a* a*', @{$self}{@field}, $signame->canonical, $self->sigbin;
  7           23  
77               }
78                
79                
80               sub _format_rdata { ## format rdata portion of RR string.
81 4       4   5 my $self = shift;
82                
83 4     100     24 my $sname = $self->{signame} || return '';
84 3           6 my @sig64 = split /\s+/, MIME::Base64::encode( $self->sigbin );
85 3           7 my @rdata = ( map( { $self->$_ } @field ), $sname->string, @sig64 );
  21           41  
86 3           14 return @rdata;
87               }
88                
89                
90               sub _parse_rdata { ## populate RR from rdata in argument list
91 2       2   8 my ( $self, @argument ) = @_;
92                
93 2           5 foreach ( @field, qw(signame) ) { $self->$_( shift @argument ) }
  16           60  
94 2           7 $self->signature(@argument);
95 2           5 return;
96               }
97                
98                
99               sub _defaults { ## specify RR attribute default values
100 2       2   3 my $self = shift;
101                
102 2           16 $self->class('ANY');
103 2           6 $self->typecovered('TYPE0');
104 2           5 $self->algorithm(1);
105 2           4 $self->labels(0);
106 2           5 $self->orgttl(0);
107 2           4 $self->sigval(10);
108 2           3 return;
109               }
110                
111                
112               sub typecovered {
113 10       10 0 53 my ( $self, @value ) = @_; # uncoverable pod
114 10           17 for (@value) { $self->{typecovered} = typebyname($_) }
  5           10  
115 10           14 my $typecode = $self->{typecovered};
116 10 100         33 return defined $typecode ? typebyval($typecode) : undef;
117               }
118                
119                
120               sub algorithm {
121 22       22 1 1200 my ( $self, $arg ) = @_;
122                
123 22 100         39 unless ( ref($self) ) { ## class method or simple function
124 3           5 my $argn = pop;
125 3 100         14 return $argn =~ /[^0-9]/ ? _algbyname($argn) : _algbyval($argn);
126               }
127                
128 19 100         46 return $self->{algorithm} unless defined $arg;
129 9 100         24 return _algbyval( $self->{algorithm} ) if $arg =~ /MNEMONIC/i;
130 8           35 return $self->{algorithm} = _algbyname($arg);
131               }
132                
133                
134               sub labels {
135 10       10 0 801 return shift->{labels} = 0; # uncoverable pod
136               }
137                
138                
139               sub orgttl {
140 10       10 0 751 return shift->{orgttl} = 0; # uncoverable pod
141               }
142                
143                
144               sub sigexpiration {
145 10       10 1 707 my ( $self, @value ) = @_;
146 10           12 for (@value) { $self->{sigexpiration} = _string2time($_) }
  3           6  
147 10           119 my $time = $self->{sigexpiration};
148 10 100   100     35 return unless defined wantarray && defined $time;
149 6           12 return UTIL ? Scalar::Util::dualvar( $time, _time2string($time) ) : _time2string($time);
150               }
151                
152               sub siginception {
153 10       10 1 736 my ( $self, @value ) = @_;
154 10           15 for (@value) { $self->{siginception} = _string2time($_) }
  3           6  
155 10           133 my $time = $self->{siginception};
156 10 100   100     40 return unless defined wantarray && defined $time;
157 6           11 return UTIL ? Scalar::Util::dualvar( $time, _time2string($time) ) : _time2string($time);
158               }
159                
160 2       2 1 534 sub sigex { return &sigexpiration; } ## historical
161                
162 2       2 1 502 sub sigin { return &siginception; } ## historical
163                
164               sub sigval {
165 2       2 1 4 my ( $self, @value ) = @_;
166 2       2   12 no integer;
  2           2  
  2           10  
167 2           2 ( $self->{sigval} ) = map { int( 60.0 * $_ ) } @value;
  2           6  
168 2           4 return;
169               }
170                
171                
172               sub keytag {
173 8       8 1 727 my ( $self, @value ) = @_;
174 8           11 for (@value) { $self->{keytag} = 0 + $_ }
  3           7  
175 8     100     30 return $self->{keytag} || 0;
176               }
177                
178                
179               sub signame {
180 5       5 1 736 my ( $self, @value ) = @_;
181 5           8 for (@value) { $self->{signame} = Net::DNS::DomainName2535->new($_) }
  3           17  
182 5 100         22 return $self->{signame} ? $self->{signame}->name : undef;
183               }
184                
185                
186               sub sig {
187 7       7 1 522 my ( $self, @value ) = @_;
188 7 100         17 return MIME::Base64::encode( $self->sigbin(), "" ) unless scalar @value;
189 3           19 return $self->sigbin( MIME::Base64::decode( join "", @value ) );
190               }
191                
192                
193               sub sigbin {
194 19       19 1 25 my ( $self, @value ) = @_;
195 19           23 for (@value) { $self->{sigbin} = $_ }
  5           8  
196 19     100     85 return $self->{sigbin} || "";
197               }
198                
199                
200 5       5 1 745 sub signature { return &sig; }
201                
202                
203               sub create {
204 2       2 1 269 unless (DNSSEC) {
205 2           288 croak qq[No "use Net::DNS::SEC" declaration in application code];
206               } else {
207               my ( $class, $data, $priv_key, %etc ) = @_;
208                
209               my $private = ref($priv_key) ? $priv_key : ( Net::DNS::SEC::Private->new($priv_key) );
210               croak 'Unable to parse private key' unless ref($private) eq 'Net::DNS::SEC::Private';
211                
212               my $self = Net::DNS::RR->new(
213               type => 'SIG',
214               typecovered => 'TYPE0',
215               siginception => time(),
216               algorithm => $private->algorithm,
217               keytag => $private->keytag,
218               signame => $private->signame,
219               );
220                
221               while ( my ( $attribute, $value ) = each %etc ) {
222               $self->$attribute($value);
223               }
224                
225               $self->{sigexpiration} = $self->{siginception} + $self->{sigval}
226               unless $self->{sigexpiration};
227                
228               $self->_CreateSig( $self->_CreateSigData($data), $private ) if $data;
229                
230               $self->{private} = $private unless $data; # mark packet for SIG0 generation
231               return $self;
232               }
233               }
234                
235                
236               sub verify {
237                
238               # Reminder...
239                
240               # $dataref may be either a data string or a reference to a
241               # Net::DNS::Packet object.
242               #
243               # $keyref is either a key object or a reference to an array
244               # of keys.
245                
246 2       2 1 270 unless (DNSSEC) {
247 2           185 croak qq[No "use Net::DNS::SEC" declaration in application code];
248               } else {
249               my ( $self, $dataref, $keyref ) = @_;
250                
251               if ( my $isa = ref($dataref) ) {
252               print '$dataref argument is ', $isa, "\n" if DEBUG;
253               croak '$dataref must be scalar or a Net::DNS::Packet'
254               unless $isa =~ /Net::DNS/ && $dataref->isa('Net::DNS::Packet');
255               }
256                
257               print '$keyref argument is of class ', ref($keyref), "\n" if DEBUG;
258               if ( ref($keyref) eq "ARRAY" ) {
259                
260               # We will iterate over the supplied key list and
261               # return when there is a successful verification.
262               # If not, continue so that we survive key-id collision.
263                
264               print "Iterating over ", scalar(@$keyref), " keys\n" if DEBUG;
265               my @error;
266               foreach my $keyrr (@$keyref) {
267               my $result = $self->verify( $dataref, $keyrr );
268               return $result if $result;
269               my $error = $self->{vrfyerrstr};
270               my $keyid = $keyrr->keytag;
271               push @error, "key $keyid: $error";
272               print "key $keyid: $error\n" if DEBUG;
273               next;
274               }
275                
276               $self->{vrfyerrstr} = join "\n", @error;
277               return 0;
278                
279               } elsif ( $keyref->isa('Net::DNS::RR::DNSKEY') ) {
280                
281               print "Validating using key with keytag: ", $keyref->keytag, "\n" if DEBUG;
282                
283               } else {
284               croak join ' ', ref($keyref), 'can not be used as SIG0 key';
285               }
286                
287               croak "SIG typecovered is TYPE$self->{typecovered}" if $self->{typecovered};
288                
289               if (DEBUG) {
290               print "\n ---------------------- SIG DEBUG ----------------------";
291               print "\n SIG:\t", $self->string;
292               print "\n KEY:\t", $keyref->string;
293               print "\n -------------------------------------------------------\n";
294               }
295                
296               $self->{vrfyerrstr} = '';
297               unless ( $self->algorithm == $keyref->algorithm ) {
298               $self->{vrfyerrstr} = 'algorithm does not match';
299               return 0;
300               }
301                
302               unless ( $self->keytag == $keyref->keytag ) {
303               $self->{vrfyerrstr} = 'keytag does not match';
304               return 0;
305               }
306                
307               # The data that is to be verified
308               my $sigdata = $self->_CreateSigData($dataref);
309                
310               my $verified = $self->_VerifySig( $sigdata, $keyref ) || return 0;
311                
312               # time to do some time checking.
313               my $t = time;
314                
315               if ( _ordered( $self->{sigexpiration}, $t ) ) {
316               $self->{vrfyerrstr} = join ' ', 'Signature expired at', $self->sigexpiration;
317               return 0;
318               } elsif ( _ordered( $t, $self->{siginception} ) ) {
319               $self->{vrfyerrstr} = join ' ', 'Signature valid from', $self->siginception;
320               return 0;
321               }
322                
323               return 1;
324               }
325               } #END verify
326                
327                
328               sub vrfyerrstr {
329 2       2 1 546 return shift->{vrfyerrstr};
330               }
331                
332                
333               ########################################
334                
335               {
336               my @algbyname = (
337               'DELETE' => 0, # [RFC4034][RFC4398][RFC8078]
338               'RSAMD5' => 1, # [RFC3110][RFC4034]
339               'DH' => 2, # [RFC2539]
340               'DSA' => 3, # [RFC3755][RFC2536]
341               ## Reserved => 4, # [RFC6725]
342               'RSASHA1' => 5, # [RFC3110][RFC4034]
343               'DSA-NSEC3-SHA1' => 6, # [RFC5155]
344               'RSASHA1-NSEC3-SHA1' => 7, # [RFC5155]
345               'RSASHA256' => 8, # [RFC5702]
346               ## Reserved => 9, # [RFC6725]
347               'RSASHA512' => 10, # [RFC5702]
348               ## Reserved => 11, # [RFC6725]
349               'ECC-GOST' => 12, # [RFC5933]
350               'ECDSAP256SHA256' => 13, # [RFC6605]
351               'ECDSAP384SHA384' => 14, # [RFC6605]
352               'ED25519' => 15, # [RFC8080]
353               'ED448' => 16, # [RFC8080]
354               'SM2SM3' => 17, # [RFC9563]
355               'ECC-GOST12' => 23, # [RFC9558]
356                
357               'INDIRECT' => 252, # [RFC4034]
358               'PRIVATEDNS' => 253, # [RFC4034]
359               'PRIVATEOID' => 254, # [RFC4034]
360               ## Reserved => 255, # [RFC4034]
361               );
362                
363               my %algbyval = reverse @algbyname;
364                
365               foreach (@algbyname) { s/[\W_]//g; } # strip non-alphanumerics
366               my @algrehash = map { /^\d/ ? ($_) x 3 : uc($_) } @algbyname;
367               my %algbyname = @algrehash; # work around broken cperl
368                
369               sub _algbyname {
370 9       9   10 my $arg = shift;
371 9           14 my $key = uc $arg; # synthetic key
372 9           26 $key =~ s/[\W_]//g; # strip non-alphanumerics
373 9           15 my $val = $algbyname{$key};
374 9 100         28 return $val if defined $val;
375 2 100         146 return $key =~ /^\d/ ? $arg : croak qq[unknown algorithm $arg];
376               }
377                
378               sub _algbyval {
379 3       3   4 my $value = shift;
380 3     100     21 return $algbyval{$value} || return $value;
381               }
382               }
383                
384                
385               {
386               my %siglen = (
387               1 => 128,
388               3 => 41,
389               5 => 256,
390               6 => 41,
391               7 => 256,
392               8 => 256,
393               10 => 256,
394               12 => 64,
395               13 => 64,
396               14 => 96,
397               15 => 64,
398               16 => 114,
399               );
400                
401               sub _size { ## estimate encoded size
402 2       2   253 my $self = shift;
403 2           11 my $clone = bless {%$self}, ref($self); # shallow clone
404 2           6 $clone->sigbin( 'x' x $siglen{$self->algorithm} );
405 2           5 return length $clone->encode();
406               }
407               }
408                
409                
410               sub _CreateSigData {
411 1       1   254 if (DNSSEC) {
412               my ( $self, $message ) = @_;
413                
414               if ( ref($message) ) {
415               die 'missing packet reference' unless $message->isa('Net::DNS::Packet');
416               my @unsigned = grep { ref($_) ne ref($self) } @{$message->{additional}};
417               local $message->{additional} = \@unsigned; # remake header image
418               my @part = qw(question answer authority additional);
419               my @size = map { scalar @{$message->{$_}} } @part;
420               my $rref = delete $self->{rawref};
421               my $data = $rref ? $$rref : $message->encode;
422               my ( $id, $status ) = unpack 'n2', $data;
423               my $hbin = pack 'n6 a*', $id, $status, @size;
424               $message = $hbin . substr $data, length $hbin;
425               }
426                
427               my $sigdata = pack 'n C2 N3 n a*', @{$self}{@field}, $self->{signame}->encode;
428               print "\npreamble\t", unpack( 'H*', $sigdata ), "\nrawdata\t", unpack( 'H100', $message ), " ...\n"
429               if DEBUG;
430               return join '', $sigdata, $message;
431               }
432               }
433                
434                
435               sub _CreateSig {
436 1       1   24 if (DNSSEC) {
437               my ( $self, @argument ) = @_;
438                
439               my $algorithm = $self->algorithm;
440               return eval {
441               my $class = $DNSSEC_siggen{$algorithm};
442               die "algorithm $algorithm not supported\n" unless $class;
443               $self->sigbin( $class->sign(@argument) );
444               } || return croak "${@}signature generation failed";
445               }
446               }
447                
448                
449               sub _VerifySig {
450 1       1   295 if (DNSSEC) {
451               my ( $self, @argument ) = @_;
452                
453               my $algorithm = $self->algorithm;
454               my $returnval = eval {
455               my $class = $DNSSEC_verify{$algorithm};
456               die "algorithm $algorithm not supported\n" unless $class;
457               $class->verify( @argument, $self->sigbin );
458               };
459                
460               unless ($returnval) {
461               $self->{vrfyerrstr} = "${@}signature verification failed";
462               print "\n", $self->{vrfyerrstr}, "\n" if DEBUG;
463               return 0;
464               }
465                
466               # uncoverable branch true # unexpected return value from EVP_DigestVerify
467               croak "internal error in algorithm $algorithm verification" unless $returnval == 1;
468               print "\nalgorithm $algorithm verification successful\n" if DEBUG;
469               return $returnval;
470               }
471               }
472                
473                
474               sub _ordered() { ## irreflexive 32-bit partial ordering
475 12       12   3089 my ( $n1, $n2 ) = @_;
476                
477 12 100         44 return 0 unless defined $n2; # ( any, undef )
478 11 100         27 return 1 unless defined $n1; # ( undef, any )
479                
480               # unwise to assume 64-bit arithmetic, or that 32-bit integer overflow goes unpunished
481 2       2   3222 use integer; # fold, leaving $n2 non-negative
  2           4  
  2           8  
482 10           26 $n1 = ( $n1 & 0xFFFFFFFF ) ^ ( $n2 & 0x80000000 ); # -2**31 <= $n1 < 2**32
483 10           17 $n2 = ( $n2 & 0x7FFFFFFF ); # 0 <= $n2 < 2**31
484                
485 10 100         65 return $n1 < $n2 ? ( $n1 > ( $n2 - 0x80000000 ) ) : ( $n2 < ( $n1 - 0x80000000 ) );
486               }
487                
488                
489               my $y1998 = timegm( 0, 0, 0, 1, 0, 1998 );
490               my $y2026 = timegm( 0, 0, 0, 1, 0, 2026 );
491               my $y2082 = $y2026 << 1;
492               my $y2054 = $y2082 - $y1998;
493               my $m2026 = int( 0x80000000 - $y2026 );
494               my $m2054 = int( 0x80000000 - $y2054 );
495               my $t2082 = int( $y2082 & 0x7FFFFFFF );
496               my $t2100 = 1960058752;
497                
498               sub _string2time { ## parse time specification string
499 14       14   4724 my $arg = shift;
500 14 100         31 return int($arg) if length($arg) < 12;
501 13           49 my ( $y, $m, @dhms ) = unpack 'a4 a2 a2 a2 a2 a2', $arg . '00';
502 13 100         36 if ( $arg lt '20380119031408' ) { # calendar folding
    100            
503 8 100         33 return timegm( reverse(@dhms), $m - 1, $y ) if $y < 2026;
504 1           5 return timegm( reverse(@dhms), $m - 1, $y - 56 ) + $y2026;
505               } elsif ( $y > 2082 ) {
506 4           17 my $z = timegm( reverse(@dhms), $m - 1, $y - 84 ); # expunge 29 Feb 2100
507 4 100         171 return $z < 1456790400 ? $z + $y2054 : $z + $y2054 - 86400;
508               }
509 1           5 return ( timegm( reverse(@dhms), $m - 1, $y - 56 ) + $y2054 ) - $y1998;
510               }
511                
512                
513               sub _time2string { ## format time specification string
514 26       26   3575 my $arg = shift;
515 26           37 my $ls31 = int( $arg & 0x7FFFFFFF );
516 26 100         80 if ( $arg & 0x80000000 ) {
    100            
517                
518 10 100         20 if ( $ls31 > $t2082 ) {
519 8 100         18 $ls31 += 86400 unless $ls31 < $t2100; # expunge 29 Feb 2100
520 8           29 my ( $yy, $mm, @dhms ) = reverse( ( gmtime( $ls31 + $m2054 ) )[0 .. 5] );
521 8           41 return sprintf '%d%02d%02d%02d%02d%02d', $yy + 1984, $mm + 1, @dhms;
522               }
523                
524 2           9 my ( $yy, $mm, @dhms ) = reverse( ( gmtime( $ls31 + $m2026 ) )[0 .. 5] );
525 2           10 return sprintf '%d%02d%02d%02d%02d%02d', $yy + 1956, $mm + 1, @dhms;
526                
527                
528               } elsif ( $ls31 > $y2026 ) {
529 2           8 my ( $yy, $mm, @dhms ) = reverse( ( gmtime( $ls31 - $y2026 ) )[0 .. 5] );
530 2           10 return sprintf '%d%02d%02d%02d%02d%02d', $yy + 1956, $mm + 1, @dhms;
531               }
532                
533 14           44 my ( $yy, $mm, @dhms ) = reverse( ( gmtime $ls31 )[0 .. 5] );
534 14           89 return sprintf '%d%02d%02d%02d%02d%02d', $yy + 1900, $mm + 1, @dhms;
535               }
536                
537               ########################################
538                
539                
540               1;
541               __END__