File Coverage

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


line stmt bran cond sub pod time code
1             package Net::DNS::RR::DS;
2              
3 5     5   1898 use strict;
  5         13  
  5         206  
4 5     5   29 use warnings;
  5         9  
  5         447  
5             our $VERSION = (qw$Id: DS.pm 2042 2025-12-24 10:23:11Z willem $)[2];
6              
7 5     5   53 use base qw(Net::DNS::RR);
  5         9  
  5         592  
8              
9              
10             =head1 NAME
11              
12             Net::DNS::RR::DS - DNS DS resource record
13              
14             =cut
15              
16 5     5   55 use integer;
  5         10  
  5         38  
17              
18 5     5   143 use Carp;
  5         16  
  5         578  
19              
20 5     5   32 use constant BABBLE => defined eval { require Digest::BubbleBabble };
  5         11  
  5         9  
  5         2471  
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         35 my $rdata = substr $$data, $offset, $self->{rdlength};
36 2         7 @{$self}{qw(keytag algorithm digtype digestbin)} = unpack 'n C2 a*', $rdata;
  2         8  
37 2         30 return;
38             }
39              
40              
41             sub _encode_rdata { ## encode rdata as wire-format octet string
42 11     11   20 my $self = shift;
43              
44 11         19 return pack 'n C2 a*', @{$self}{qw(keytag algorithm digtype digestbin)};
  11         58  
45             }
46              
47              
48             sub _format_rdata { ## format rdata portion of RR string.
49 18     18   59 my $self = shift;
50              
51 18         31 my @rdata = @{$self}{qw(keytag algorithm digtype)};
  18         58  
52 18 100       43 if ( my $digest = $self->digest ) {
53 17         44 $self->_annotation( $self->babble ) if BABBLE;
54 17         82 push @rdata, split /(\S{64})/, $digest;
55             } else {
56 1         2 push @rdata, '""';
57             }
58 18         83 return @rdata;
59             }
60              
61              
62             sub _parse_rdata { ## populate RR from rdata in argument list
63 9     9   34 my ( $self, @argument ) = @_;
64              
65 9         216 $self->keytag( shift @argument );
66 9         17 my $algorithm = shift @argument;
67 9         34 $self->digtype( shift @argument );
68 9         30 $self->digest(@argument);
69 9         29 $self->algorithm($algorithm);
70 9         25 return;
71             }
72              
73              
74             sub keytag {
75 25     25 1 456 my ( $self, @value ) = @_;
76 25         49 for (@value) { $self->{keytag} = 0 + $_ }
  20         60  
77 25   100     116 return $self->{keytag} || 0;
78             }
79              
80              
81             sub algorithm {
82 41     41 1 1131 my ( $self, $arg ) = @_;
83              
84 41 100       95 unless ( ref($self) ) { ## class method or simple function
85 3         5 my $argn = pop;
86 3 100       16 return $argn =~ /[^0-9]/ ? _algbyname($argn) : _algbyval($argn);
87             }
88              
89 38 100       113 return $self->{algorithm} unless defined $arg;
90 27 100       70 return _algbyval( $self->{algorithm} ) if uc($arg) eq 'MNEMONIC';
91 25   100     71 return $self->{algorithm} = _algbyname($arg) || die _algbyname('') # disallow algorithm(0)
92             }
93              
94              
95             sub digtype {
96 68     68 1 996 my ( $self, $arg ) = @_;
97              
98 68 100       146 unless ( ref($self) ) { ## class method or simple function
99 3         6 my $argn = pop;
100 3 100       17 return $argn =~ /[^0-9]/ ? _digestbyname($argn) : _digestbyval($argn);
101             }
102              
103 65 100       192 return $self->{digtype} unless defined $arg;
104 35 100       88 return _digestbyval( $self->{digtype} ) if uc($arg) eq 'MNEMONIC';
105 32   100     65 return $self->{digtype} = _digestbyname($arg) || die _digestbyname('') # disallow digtype(0)
106             }
107              
108              
109             sub digest {
110 35     35 1 3308 my ( $self, @value ) = @_;
111 35 100       99 return unpack "H*", $self->digestbin() unless scalar @value;
112 13 100       27 my @hex = map { /^"*([\dA-Fa-f]*)"*$/ || croak("corrupt hex"); $1 } @value;
  14         689  
  13         58  
113 12         86 return $self->digestbin( pack "H*", join "", @hex );
114             }
115              
116              
117             sub digestbin {
118 80     80 1 1605 my ( $self, @value ) = @_;
119 80         134 for (@value) { $self->{digestbin} = $_ }
  21         86  
120 80   100     436 return $self->{digestbin} || "";
121             }
122              
123              
124             sub babble {
125 21     21 1 1986 return BABBLE ? Digest::BubbleBabble::bubblebabble( Digest => shift->digestbin ) : '';
126             }
127              
128              
129             sub create {
130 13     13 1 144 my ( $class, $keyrr, %args ) = @_;
131 13         57 my ($type) = reverse split '::', $class;
132              
133 13 100       63 croak "Unable to create $type record for invalid key" unless $keyrr->protocol == 3;
134 12 100       35 croak "Unable to create $type record for revoked key" if $keyrr->revoke;
135 11 100       29 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         35 digtype => 1, # SHA1 by default
143             %args,
144             algorithm => $keyrr->algorithm,
145             keytag => $keyrr->keytag
146             );
147              
148 10         33 my $spec = $digest{$self->digtype};
149 10         17 my $hash = eval {
150 10         30 my ( $object, @param ) = @$spec;
151 9         72 $object->new(@param);
152             };
153 10 100       187 croak join ' ', 'digtype', $self->digtype('MNEMONIC'), 'not supported' unless $hash;
154 9         58 $hash->add( $keyrr->{owner}->canonical );
155 9         28 $hash->add( $keyrr->_encode_rdata );
156 9         137 $self->digestbin( $hash->digest );
157              
158 9         72 return $self;
159             }
160              
161              
162             sub verify {
163 6     6 1 18 my ( $self, $key ) = @_;
164 6         16 my $verify = Net::DNS::RR::DS->create( $key, ( digtype => $self->digtype ) );
165 6         21 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   58 my $arg = shift;
191 34         68 my $key = uc $arg; # synthetic key
192 34         78 $key =~ s/[\W_]//g; # strip non-alphanumerics
193 34         66 my $val = $digestbyname{$key};
194 34 100       143 return $val if defined $val;
195 4 100       121 return $key =~ /^\d/ ? $arg : croak qq[unknown algorithm $arg];
196             }
197              
198             sub _digestbyval {
199 5     5   11 my $value = shift;
200 5   100     200 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   48 my $arg = shift;
241 27         87 my $key = uc $arg; # synthetic key
242 27         118 $key =~ s/[\W_]//g; # strip non-alphanumerics
243 27         83 my $val = $algbyname{$key};
244 27 100       110 return $val if defined $val;
245 4 100       324 return $key =~ /^\d/ ? $arg : croak qq[unknown algorithm $arg];
246             }
247              
248             sub _algbyval {
249 4     4   7 my $value = shift;
250 4   100     45 return $algbyval{$value} || return $value;
251             }
252             }
253              
254             ########################################
255              
256              
257             1;
258             __END__