File Coverage

blib/lib/Net/DNS/RR/DS.pm
Criterion Covered Total %
statement 105 105 100.0
branch 38 38 100.0
path n/a
condition 12 12 100.0
subroutine 22 22 100.0
pod 8 8 100.0
total 185 185 100.0


line stmt bran path cond sub pod time code
1               package Net::DNS::RR::DS;
2                
3 5       5   1351 use strict;
  5           9  
  5           147  
4 5       5   16 use warnings;
  5           15  
  5           296  
5               our $VERSION = (qw$Id: DS.pm 2042 2025-12-24 10:23:11Z willem $)[2];
6                
7 5       5   21 use base qw(Net::DNS::RR);
  5           5  
  5           422  
8                
9                
10               =head1 NAME
11                
12               Net::DNS::RR::DS - DNS DS resource record
13                
14               =cut
15                
16 5       5   24 use integer;
  5           7  
  5           23  
17                
18 5       5   135 use Carp;
  5           8  
  5           328  
19                
20 5       5   20 use constant BABBLE => defined eval { require Digest::BubbleBabble };
  5           6  
  5           8  
  5           1814  
21                
22               eval { require Digest::SHA }; ## optional for simple Net::DNS RR
23                
24               my %digest = (
25               '1' => ['Digest::SHA', 1],
26               '2' => ['Digest::SHA', 256],
27               '4' => ['Digest::SHA', 384],
28               '6' => ['Net::DNS::SEC::Digest::SM3'],
29               );
30                
31                
32               sub _decode_rdata { ## decode rdata from wire-format octet string
33 2       2   5 my ( $self, $data, $offset ) = @_;
34                
35 2           4 my $rdata = substr $$data, $offset, $self->{rdlength};
36 2           21 @{$self}{qw(keytag algorithm digtype digestbin)} = unpack 'n C2 a*', $rdata;
  2           24  
37 2           6 return;
38               }
39                
40                
41               sub _encode_rdata { ## encode rdata as wire-format octet string
42 11       11   11 my $self = shift;
43                
44 11           13 return pack 'n C2 a*', @{$self}{qw(keytag algorithm digtype digestbin)};
  11           61  
45               }
46                
47                
48               sub _format_rdata { ## format rdata portion of RR string.
49 18       18   27 my $self = shift;
50                
51 18           23 my @rdata = @{$self}{qw(keytag algorithm digtype)};
  18           50  
52 18 100         39 if ( my $digest = $self->digest ) {
53 17           37 $self->_annotation( $self->babble ) if BABBLE;
54 17           73 push @rdata, split /(\S{64})/, $digest;
55               } else {
56 1           2 push @rdata, '""';
57               }
58 18           67 return @rdata;
59               }
60                
61                
62               sub _parse_rdata { ## populate RR from rdata in argument list
63 9       9   22 my ( $self, @argument ) = @_;
64                
65 9           27 $self->keytag( shift @argument );
66 9           14 my $algorithm = shift @argument;
67 9           33 $self->digtype( shift @argument );
68 9           25 $self->digest(@argument);
69 9           29 $self->algorithm($algorithm);
70 9           18 return;
71               }
72                
73                
74               sub keytag {
75 25       25 1 331 my ( $self, @value ) = @_;
76 25           67 for (@value) { $self->{keytag} = 0 + $_ }
  20           70  
77 25     100     84 return $self->{keytag} || 0;
78               }
79                
80                
81               sub algorithm {
82 41       41 1 1314 my ( $self, $arg ) = @_;
83                
84 41 100         91 unless ( ref($self) ) { ## class method or simple function
85 3           4 my $argn = pop;
86 3 100         15 return $argn =~ /[^0-9]/ ? _algbyname($argn) : _algbyval($argn);
87               }
88                
89 38 100         95 return $self->{algorithm} unless defined $arg;
90 27 100         60 return _algbyval( $self->{algorithm} ) if uc($arg) eq 'MNEMONIC';
91 25     100     41 return $self->{algorithm} = _algbyname($arg) || die _algbyname('') # disallow algorithm(0)
92               }
93                
94                
95               sub digtype {
96 68       68 1 1050 my ( $self, $arg ) = @_;
97                
98 68 100         125 unless ( ref($self) ) { ## class method or simple function
99 3           5 my $argn = pop;
100 3 100         16 return $argn =~ /[^0-9]/ ? _digestbyname($argn) : _digestbyval($argn);
101               }
102                
103 65 100         175 return $self->{digtype} unless defined $arg;
104 35 100         71 return _digestbyval( $self->{digtype} ) if uc($arg) eq 'MNEMONIC';
105 32     100     53 return $self->{digtype} = _digestbyname($arg) || die _digestbyname('') # disallow digtype(0)
106               }
107                
108                
109               sub digest {
110 35       35 1 2935 my ( $self, @value ) = @_;
111 35 100         73 return unpack "H*", $self->digestbin() unless scalar @value;
112 13 100         44 my @hex = map { /^"*([\dA-Fa-f]*)"*$/ || croak("corrupt hex"); $1 } @value;
  14           169  
  13           49  
113 12           77 return $self->digestbin( pack "H*", join "", @hex );
114               }
115                
116                
117               sub digestbin {
118 80       80 1 1313 my ( $self, @value ) = @_;
119 80           121 for (@value) { $self->{digestbin} = $_ }
  21           34  
120 80     100     343 return $self->{digestbin} || "";
121               }
122                
123                
124               sub babble {
125 21       21 1 1454 return BABBLE ? Digest::BubbleBabble::bubblebabble( Digest => shift->digestbin ) : '';
126               }
127                
128                
129               sub create {
130 13       13 1 74 my ( $class, $keyrr, %args ) = @_;
131 13           59 my ($type) = reverse split '::', $class;
132                
133 13 100         42 croak "Unable to create $type record for invalid key" unless $keyrr->protocol == 3;
134 12 100         26 croak "Unable to create $type record for revoked key" if $keyrr->revoke;
135 11 100         52 croak "Unable to create $type record for non-zone key" unless $keyrr->zone;
136                
137               my $self = Net::DNS::RR->new(
138               owner => $keyrr->owner, # per definition, same as keyrr
139               type => $type,
140               class => $keyrr->class,
141               ttl => $keyrr->{ttl},
142 10           31 digtype => 1, # SHA1 by default
143               %args,
144               algorithm => $keyrr->algorithm,
145               keytag => $keyrr->keytag
146               );
147                
148 10           26 my $spec = $digest{$self->digtype};
149 10           15 my $hash = eval {
150 10           26 my ( $object, @param ) = @$spec;
151 9           35 $object->new(@param);
152               };
153 10 100         183 croak join ' ', 'digtype', $self->digtype('MNEMONIC'), 'not supported' unless $hash;
154 9           41 $hash->add( $keyrr->{owner}->canonical );
155 9           24 $hash->add( $keyrr->_encode_rdata );
156 9           73 $self->digestbin( $hash->digest );
157                
158 9           49 return $self;
159               }
160                
161                
162               sub verify {
163 6       6 1 15 my ( $self, $key ) = @_;
164 6           17 my $verify = Net::DNS::RR::DS->create( $key, ( digtype => $self->digtype ) );
165 6           15 return $verify->digestbin eq $self->digestbin;
166               }
167                
168                
169               ########################################
170                
171               {
172               my @digestbyname = (
173               'SHA-1' => 1, # [RFC3658]
174               'SHA-256' => 2, # [RFC4509]
175               'GOST-R-34.11-94' => 3, # [RFC5933]
176               'SHA-384' => 4, # [RFC6605]
177               'GOST-R-34.11-2012' => 5, # [RFC9558]
178               'SM3' => 6, # [RFC9563]
179               );
180                
181               my @digestalias = ( 'SHA' => 1 );
182                
183               my %digestbyval = reverse @digestbyname;
184                
185               foreach (@digestbyname) { s/[\W_]//g; } # strip non-alphanumerics
186               my @digestrehash = map { /^\d/ ? ($_) x 3 : uc($_) } @digestbyname;
187               my %digestbyname = ( @digestalias, @digestrehash ); # work around broken cperl
188                
189               sub _digestbyname {
190 34       34   44 my $arg = shift;
191 34           44 my $key = uc $arg; # synthetic key
192 34           103 $key =~ s/[\W_]//g; # strip non-alphanumerics
193 34           52 my $val = $digestbyname{$key};
194 34 100         113 return $val if defined $val;
195 4 100         127 return $key =~ /^\d/ ? $arg : croak qq[unknown algorithm $arg];
196               }
197                
198               sub _digestbyval {
199 5       5   7 my $value = shift;
200 5     100     175 return $digestbyval{$value} || return $value;
201               }
202               }
203                
204                
205               {
206               my @algbyname = (
207               'DELETE' => 0, # [RFC4034][RFC4398][RFC8078]
208               'RSAMD5' => 1, # [RFC3110][RFC4034]
209               'DH' => 2, # [RFC2539]
210               'DSA' => 3, # [RFC3755][RFC2536]
211               ## Reserved => 4, # [RFC6725]
212               'RSASHA1' => 5, # [RFC3110][RFC4034]
213               'DSA-NSEC3-SHA1' => 6, # [RFC5155]
214               'RSASHA1-NSEC3-SHA1' => 7, # [RFC5155]
215               'RSASHA256' => 8, # [RFC5702]
216               ## Reserved => 9, # [RFC6725]
217               'RSASHA512' => 10, # [RFC5702]
218               ## Reserved => 11, # [RFC6725]
219               'ECC-GOST' => 12, # [RFC5933]
220               'ECDSAP256SHA256' => 13, # [RFC6605]
221               'ECDSAP384SHA384' => 14, # [RFC6605]
222               'ED25519' => 15, # [RFC8080]
223               'ED448' => 16, # [RFC8080]
224               'SM2SM3' => 17, # [RFC9563]
225               'ECC-GOST12' => 23, # [RFC9558]
226                
227               'INDIRECT' => 252, # [RFC4034]
228               'PRIVATEDNS' => 253, # [RFC4034]
229               'PRIVATEOID' => 254, # [RFC4034]
230               ## Reserved => 255, # [RFC4034]
231               );
232                
233               my %algbyval = reverse @algbyname;
234                
235               foreach (@algbyname) { s/[\W_]//g; } # strip non-alphanumerics
236               my @algrehash = map { /^\d/ ? ($_) x 3 : uc($_) } @algbyname;
237               my %algbyname = @algrehash; # work around broken cperl
238                
239               sub _algbyname {
240 27       27   143 my $arg = shift;
241 27           119 my $key = uc $arg; # synthetic key
242 27           57 $key =~ s/[\W_]//g; # strip non-alphanumerics
243 27           44 my $val = $algbyname{$key};
244 27 100         95 return $val if defined $val;
245 4 100         330 return $key =~ /^\d/ ? $arg : croak qq[unknown algorithm $arg];
246               }
247                
248               sub _algbyval {
249 4       4   28 my $value = shift;
250 4     100     23 return $algbyval{$value} || return $value;
251               }
252               }
253                
254               ########################################
255                
256                
257               1;
258               __END__