| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Net::DNS::RR::TSIG; | 
| 2 |  |  |  |  |  |  |  | 
| 3 | 7 |  |  | 7 |  | 64 | use strict; | 
|  | 7 |  |  |  |  | 17 |  | 
|  | 7 |  |  |  |  | 249 |  | 
| 4 | 7 |  |  | 7 |  | 38 | use warnings; | 
|  | 7 |  |  |  |  | 14 |  | 
|  | 7 |  |  |  |  | 427 |  | 
| 5 |  |  |  |  |  |  | our $VERSION = (qw$Id: TSIG.pm 1909 2023-03-23 11:36:16Z willem $)[2]; | 
| 6 |  |  |  |  |  |  |  | 
| 7 | 7 |  |  | 7 |  | 71 | use base qw(Net::DNS::RR); | 
|  | 7 |  |  |  |  | 14 |  | 
|  | 7 |  |  |  |  | 776 |  | 
| 8 |  |  |  |  |  |  |  | 
| 9 |  |  |  |  |  |  |  | 
| 10 |  |  |  |  |  |  | =head1 NAME | 
| 11 |  |  |  |  |  |  |  | 
| 12 |  |  |  |  |  |  | Net::DNS::RR::TSIG - DNS TSIG resource record | 
| 13 |  |  |  |  |  |  |  | 
| 14 |  |  |  |  |  |  | =cut | 
| 15 |  |  |  |  |  |  |  | 
| 16 | 7 |  |  | 7 |  | 53 | use integer; | 
|  | 7 |  |  |  |  | 13 |  | 
|  | 7 |  |  |  |  | 47 |  | 
| 17 |  |  |  |  |  |  |  | 
| 18 | 7 |  |  | 7 |  | 280 | use Carp; | 
|  | 7 |  |  |  |  | 16 |  | 
|  | 7 |  |  |  |  | 544 |  | 
| 19 |  |  |  |  |  |  |  | 
| 20 | 7 |  |  | 7 |  | 47 | use Net::DNS::DomainName; | 
|  | 7 |  |  |  |  | 14 |  | 
|  | 7 |  |  |  |  | 257 |  | 
| 21 | 7 |  |  | 7 |  | 41 | use Net::DNS::Parameters qw(:class :type :rcode); | 
|  | 7 |  |  |  |  | 22 |  | 
|  | 7 |  |  |  |  | 1647 |  | 
| 22 |  |  |  |  |  |  |  | 
| 23 | 7 |  |  | 7 |  | 55 | use constant SYMLINK => defined(&CORE::readlink);		# Except Win32, VMS, RISC OS | 
|  | 7 |  |  |  |  | 23 |  | 
|  | 7 |  |  |  |  | 636 |  | 
| 24 |  |  |  |  |  |  |  | 
| 25 | 7 |  |  | 7 |  | 49 | use constant ANY  => classbyname q(ANY); | 
|  | 7 |  |  |  |  | 15 |  | 
|  | 7 |  |  |  |  | 30 |  | 
| 26 | 7 |  |  | 7 |  | 52 | use constant TSIG => typebyname q(TSIG); | 
|  | 7 |  |  |  |  | 16 |  | 
|  | 7 |  |  |  |  | 47 |  | 
| 27 |  |  |  |  |  |  |  | 
| 28 |  |  |  |  |  |  | eval { require Digest::HMAC }; | 
| 29 |  |  |  |  |  |  | eval { require Digest::MD5 }; | 
| 30 |  |  |  |  |  |  | eval { require Digest::SHA }; | 
| 31 |  |  |  |  |  |  | eval { require MIME::Base64 }; | 
| 32 |  |  |  |  |  |  |  | 
| 33 |  |  |  |  |  |  |  | 
| 34 |  |  |  |  |  |  | sub _decode_rdata {			## decode rdata from wire-format octet string | 
| 35 | 28 |  |  | 28 |  | 120 | my ( $self, $data, $offset ) = @_; | 
| 36 |  |  |  |  |  |  |  | 
| 37 | 28 |  |  |  |  | 77 | my $limit = $offset + $self->{rdlength}; | 
| 38 | 28 |  |  |  |  | 150 | ( $self->{algorithm}, $offset ) = Net::DNS::DomainName->decode( $data, $offset ); | 
| 39 |  |  |  |  |  |  |  | 
| 40 |  |  |  |  |  |  | # Design decision: Use 32 bits, which will work until the end of time()! | 
| 41 | 28 |  |  |  |  | 155 | @{$self}{qw(time_signed fudge)} = unpack "\@$offset xxN n", $$data; | 
|  | 28 |  |  |  |  | 165 |  | 
| 42 | 28 |  |  |  |  | 70 | $offset += 8; | 
| 43 |  |  |  |  |  |  |  | 
| 44 | 28 |  |  |  |  | 98 | my $mac_size = unpack "\@$offset n", $$data; | 
| 45 | 28 |  |  |  |  | 152 | $self->{macbin} = unpack "\@$offset xx a$mac_size", $$data; | 
| 46 | 28 |  |  |  |  | 67 | $offset += $mac_size + 2; | 
| 47 |  |  |  |  |  |  |  | 
| 48 | 28 |  |  |  |  | 103 | @{$self}{qw(original_id error)} = unpack "\@$offset nn", $$data; | 
|  | 28 |  |  |  |  | 94 |  | 
| 49 | 28 |  |  |  |  | 78 | $offset += 4; | 
| 50 |  |  |  |  |  |  |  | 
| 51 | 28 |  |  |  |  | 95 | my $other_size = unpack "\@$offset n", $$data; | 
| 52 | 28 |  |  |  |  | 162 | $self->{other} = unpack "\@$offset xx a$other_size", $$data; | 
| 53 | 28 |  |  |  |  | 55 | $offset += $other_size + 2; | 
| 54 |  |  |  |  |  |  |  | 
| 55 | 28 | 100 |  |  |  | 445 | croak('misplaced or corrupt TSIG') unless $limit == length $$data; | 
| 56 | 27 |  |  |  |  | 189 | my $raw = substr $$data, 0, $self->{offset}++; | 
| 57 | 27 |  |  |  |  | 119 | $self->{rawref} = \$raw; | 
| 58 | 27 |  |  |  |  | 101 | return; | 
| 59 |  |  |  |  |  |  | } | 
| 60 |  |  |  |  |  |  |  | 
| 61 |  |  |  |  |  |  |  | 
| 62 |  |  |  |  |  |  | sub _encode_rdata {			## encode rdata as wire-format octet string | 
| 63 | 36 |  |  | 36 |  | 65 | my $self = shift; | 
| 64 |  |  |  |  |  |  |  | 
| 65 | 36 |  |  |  |  | 51 | my $offset = shift; | 
| 66 | 36 |  |  |  |  | 51 | my $undef  = shift; | 
| 67 | 36 |  |  |  |  | 55 | my $packet = shift; | 
| 68 | 36 |  |  |  |  | 87 | my $macbin = $self->macbin; | 
| 69 | 36 | 100 |  |  |  | 90 | unless ($macbin) { | 
| 70 | 31 |  |  |  |  | 100 | $self->original_id( $packet->header->id ); | 
| 71 | 30 |  |  |  |  | 195 | my $sigdata = $self->sig_data($packet);		# form data to be signed | 
| 72 | 30 |  |  |  |  | 90 | $macbin = $self->macbin( $self->_mac_function($sigdata) ); | 
| 73 |  |  |  |  |  |  | } | 
| 74 |  |  |  |  |  |  |  | 
| 75 | 35 |  |  |  |  | 127 | my $rdata = $self->{algorithm}->canonical; | 
| 76 |  |  |  |  |  |  |  | 
| 77 |  |  |  |  |  |  | # Design decision: Use 32 bits, which will work until the end of time()! | 
| 78 | 35 |  |  |  |  | 133 | $rdata .= pack 'xxN n', $self->time_signed, $self->fudge; | 
| 79 |  |  |  |  |  |  |  | 
| 80 | 35 |  |  |  |  | 121 | $rdata .= pack 'na*', length($macbin), $macbin; | 
| 81 |  |  |  |  |  |  |  | 
| 82 | 35 |  |  |  |  | 81 | $rdata .= pack 'nn', $self->original_id, $self->{error}; | 
| 83 |  |  |  |  |  |  |  | 
| 84 | 35 |  |  |  |  | 86 | my $other = $self->other; | 
| 85 | 35 |  |  |  |  | 120 | $rdata .= pack 'na*', length($other), $other; | 
| 86 |  |  |  |  |  |  |  | 
| 87 | 35 |  |  |  |  | 133 | return $rdata; | 
| 88 |  |  |  |  |  |  | } | 
| 89 |  |  |  |  |  |  |  | 
| 90 |  |  |  |  |  |  |  | 
| 91 |  |  |  |  |  |  | sub _defaults {				## specify RR attribute default values | 
| 92 | 6 |  |  | 6 |  | 13 | my $self = shift; | 
| 93 |  |  |  |  |  |  |  | 
| 94 | 6 |  |  |  |  | 20 | $self->algorithm(157); | 
| 95 | 6 |  |  |  |  | 52 | $self->class('ANY'); | 
| 96 | 6 |  |  |  |  | 23 | $self->error(0); | 
| 97 | 6 |  |  |  |  | 22 | $self->fudge(300); | 
| 98 | 6 |  |  |  |  | 19 | $self->other(''); | 
| 99 | 6 |  |  |  |  | 15 | return; | 
| 100 |  |  |  |  |  |  | } | 
| 101 |  |  |  |  |  |  |  | 
| 102 |  |  |  |  |  |  |  | 
| 103 |  |  |  |  |  |  | sub _size {				## estimate encoded size | 
| 104 | 2 |  |  | 2 |  | 399 | my $self  = shift; | 
| 105 | 2 |  |  |  |  | 17 | my $clone = bless {%$self}, ref($self);			# shallow clone | 
| 106 | 2 |  |  |  |  | 38 | return length $clone->encode( 0, undef, Net::DNS::Packet->new() ); | 
| 107 |  |  |  |  |  |  | } | 
| 108 |  |  |  |  |  |  |  | 
| 109 |  |  |  |  |  |  |  | 
| 110 |  |  |  |  |  |  | sub encode {				## override RR method | 
| 111 | 35 |  |  | 35 | 1 | 91 | my ( $self, @argument ) = @_; | 
| 112 | 35 |  |  |  |  | 117 | my $kname = $self->{owner}->encode();			# uncompressed key name | 
| 113 | 35 |  | 100 |  |  | 81 | my $rdata = eval { $self->_encode_rdata(@argument) } || ''; | 
| 114 | 35 |  |  |  |  | 222 | return pack 'a* n2 N n a*', $kname, TSIG, ANY, 0, length $rdata, $rdata; | 
| 115 |  |  |  |  |  |  | } | 
| 116 |  |  |  |  |  |  |  | 
| 117 |  |  |  |  |  |  |  | 
| 118 |  |  |  |  |  |  | sub string {				## override RR method | 
| 119 | 2 |  |  | 2 | 1 | 7 | my $self	= shift; | 
| 120 | 2 |  |  |  |  | 11 | my $owner	= $self->{owner}->string; | 
| 121 | 2 |  |  |  |  | 10 | my $type	= $self->type; | 
| 122 | 2 |  |  |  |  | 4 | my $algorithm	= $self->algorithm; | 
| 123 | 2 |  |  |  |  | 3 | my $time_signed = $self->time_signed; | 
| 124 | 2 |  |  |  |  | 4 | my $fudge	= $self->fudge; | 
| 125 | 2 |  |  |  |  | 14 | my $signature	= $self->mac; | 
| 126 | 2 |  |  |  |  | 5 | my $original_id = $self->original_id; | 
| 127 | 2 |  |  |  |  | 5 | my $error	= $self->error; | 
| 128 | 2 |  |  |  |  | 5 | my $other	= $self->other; | 
| 129 |  |  |  |  |  |  |  | 
| 130 | 2 |  |  |  |  | 19 | return <<"QQ"; | 
| 131 |  |  |  |  |  |  | ; $owner	$type | 
| 132 |  |  |  |  |  |  | ;	algorithm:	$algorithm | 
| 133 |  |  |  |  |  |  | ;	time signed:	$time_signed	fudge:	$fudge | 
| 134 |  |  |  |  |  |  | ;	signature:	$signature | 
| 135 |  |  |  |  |  |  | ;	original id:	$original_id | 
| 136 |  |  |  |  |  |  | ;			$error	$other | 
| 137 |  |  |  |  |  |  | QQ | 
| 138 |  |  |  |  |  |  | } | 
| 139 |  |  |  |  |  |  |  | 
| 140 |  |  |  |  |  |  |  | 
| 141 | 122 |  |  | 122 | 1 | 291 | sub algorithm { return &_algorithm; } | 
| 142 |  |  |  |  |  |  |  | 
| 143 |  |  |  |  |  |  |  | 
| 144 |  |  |  |  |  |  | sub key { | 
| 145 | 26 |  |  | 26 | 1 | 89 | my ( $self, @argument ) = @_; | 
| 146 | 26 | 100 |  |  |  | 92 | return MIME::Base64::encode( $self->keybin(), "" ) unless scalar @argument; | 
| 147 | 25 |  |  |  |  | 158 | return $self->keybin( MIME::Base64::decode( join "", @argument ) ); | 
| 148 |  |  |  |  |  |  | } | 
| 149 |  |  |  |  |  |  |  | 
| 150 |  |  |  |  |  |  |  | 
| 151 | 27 |  |  | 27 | 1 | 91 | sub keybin { return &_keybin; } | 
| 152 |  |  |  |  |  |  |  | 
| 153 |  |  |  |  |  |  |  | 
| 154 |  |  |  |  |  |  | sub time_signed { | 
| 155 | 130 |  |  | 130 | 1 | 893 | my ( $self, @value ) = @_; | 
| 156 | 130 |  |  |  |  | 235 | for (@value) { $self->{time_signed} = 0 + $_ } | 
|  | 1 |  |  |  |  | 4 |  | 
| 157 | 130 | 100 |  |  |  | 505 | return $self->{time_signed} ? $self->{time_signed} : ( $self->{time_signed} = time() ); | 
| 158 |  |  |  |  |  |  | } | 
| 159 |  |  |  |  |  |  |  | 
| 160 |  |  |  |  |  |  |  | 
| 161 |  |  |  |  |  |  | sub fudge { | 
| 162 | 138 |  |  | 138 | 1 | 892 | my ( $self, @value ) = @_; | 
| 163 | 138 |  |  |  |  | 236 | for (@value) { $self->{fudge} = 0 + $_ } | 
|  | 10 |  |  |  |  | 27 |  | 
| 164 | 138 |  | 100 |  |  | 575 | return $self->{fudge} || 0; | 
| 165 |  |  |  |  |  |  | } | 
| 166 |  |  |  |  |  |  |  | 
| 167 |  |  |  |  |  |  |  | 
| 168 |  |  |  |  |  |  | sub mac { | 
| 169 | 5 |  |  | 5 | 1 | 722 | my ( $self, @value ) = @_; | 
| 170 | 5 | 100 |  |  |  | 18 | return MIME::Base64::encode( $self->macbin(), "" ) unless scalar @value; | 
| 171 | 1 |  |  |  |  | 16 | return $self->macbin( MIME::Base64::decode( join "", @value ) ); | 
| 172 |  |  |  |  |  |  | } | 
| 173 |  |  |  |  |  |  |  | 
| 174 |  |  |  |  |  |  |  | 
| 175 |  |  |  |  |  |  | sub macbin { | 
| 176 | 154 |  |  | 154 | 1 | 1090 | my ( $self, @value ) = @_; | 
| 177 | 154 |  |  |  |  | 305 | for (@value) { $self->{macbin} = $_ } | 
|  | 33 |  |  |  |  | 80 |  | 
| 178 | 154 |  | 100 |  |  | 605 | return $self->{macbin} || ""; | 
| 179 |  |  |  |  |  |  | } | 
| 180 |  |  |  |  |  |  |  | 
| 181 |  |  |  |  |  |  |  | 
| 182 |  |  |  |  |  |  | sub prior_mac { | 
| 183 | 3 |  |  | 3 | 1 | 412 | my ( $self, @value ) = @_; | 
| 184 | 3 | 100 |  |  |  | 14 | return MIME::Base64::encode( $self->prior_macbin(), "" ) unless scalar @value; | 
| 185 | 1 |  |  |  |  | 10 | return $self->prior_macbin( MIME::Base64::decode( join "", @value ) ); | 
| 186 |  |  |  |  |  |  | } | 
| 187 |  |  |  |  |  |  |  | 
| 188 |  |  |  |  |  |  |  | 
| 189 |  |  |  |  |  |  | sub prior_macbin { | 
| 190 | 95 |  |  | 95 | 1 | 200 | my ( $self, @value ) = @_; | 
| 191 | 95 |  |  |  |  | 182 | for (@value) { $self->{prior_macbin} = $_ } | 
|  | 33 |  |  |  |  | 76 |  | 
| 192 | 95 |  | 100 |  |  | 328 | return $self->{prior_macbin} || ""; | 
| 193 |  |  |  |  |  |  | } | 
| 194 |  |  |  |  |  |  |  | 
| 195 |  |  |  |  |  |  |  | 
| 196 |  |  |  |  |  |  | sub request_mac { | 
| 197 | 3 |  |  | 3 | 1 | 407 | my ( $self, @value ) = @_; | 
| 198 | 3 | 100 |  |  |  | 11 | return MIME::Base64::encode( $self->request_macbin(), "" ) unless scalar @value; | 
| 199 | 1 |  |  |  |  | 7 | return $self->request_macbin( MIME::Base64::decode( join "", @value ) ); | 
| 200 |  |  |  |  |  |  | } | 
| 201 |  |  |  |  |  |  |  | 
| 202 |  |  |  |  |  |  |  | 
| 203 |  |  |  |  |  |  | sub request_macbin { | 
| 204 | 60 |  |  | 60 | 1 | 141 | my ( $self, @value ) = @_; | 
| 205 | 60 |  |  |  |  | 123 | for (@value) { $self->{request_macbin} = $_ } | 
|  | 17 |  |  |  |  | 41 |  | 
| 206 | 60 |  | 100 |  |  | 237 | return $self->{request_macbin} || ""; | 
| 207 |  |  |  |  |  |  | } | 
| 208 |  |  |  |  |  |  |  | 
| 209 |  |  |  |  |  |  |  | 
| 210 |  |  |  |  |  |  | sub original_id { | 
| 211 | 84 |  |  | 84 | 1 | 161 | my ( $self, @value ) = @_; | 
| 212 | 84 |  |  |  |  | 166 | for (@value) { $self->{original_id} = 0 + $_ } | 
|  | 30 |  |  |  |  | 75 |  | 
| 213 | 84 |  | 100 |  |  | 320 | return $self->{original_id} || 0; | 
| 214 |  |  |  |  |  |  | } | 
| 215 |  |  |  |  |  |  |  | 
| 216 |  |  |  |  |  |  |  | 
| 217 |  |  |  |  |  |  | sub error { | 
| 218 | 48 |  |  | 48 | 1 | 566 | my ( $self, @value ) = @_; | 
| 219 | 48 |  |  |  |  | 93 | for (@value) { | 
| 220 | 20 |  |  |  |  | 62 | my $error = $self->{error} = rcodebyname($_); | 
| 221 | 20 | 100 |  |  |  | 84 | $self->other( time() ) if $error == 18; | 
| 222 |  |  |  |  |  |  | } | 
| 223 | 48 |  | 100 |  |  | 205 | return rcodebyval( $self->{error} || '' ); | 
| 224 |  |  |  |  |  |  | } | 
| 225 |  |  |  |  |  |  |  | 
| 226 |  |  |  |  |  |  |  | 
| 227 |  |  |  |  |  |  | sub other { | 
| 228 | 90 |  |  | 90 | 1 | 172 | my ( $self, @value ) = @_; | 
| 229 | 90 | 100 |  |  |  | 175 | for (@value) { $self->{other} = $_ ? pack( 'xxN', $_ ) : '' } | 
|  | 9 |  |  |  |  | 52 |  | 
| 230 | 90 | 100 |  |  |  | 278 | return $self->{other} ? unpack( 'N', $self->{other} ) : ''; | 
| 231 |  |  |  |  |  |  | } | 
| 232 |  |  |  |  |  |  |  | 
| 233 |  |  |  |  |  |  |  | 
| 234 | 1 |  |  | 1 | 0 | 504 | sub other_data { return &other; }				# uncoverable pod | 
| 235 |  |  |  |  |  |  |  | 
| 236 |  |  |  |  |  |  |  | 
| 237 |  |  |  |  |  |  | sub sig_function { | 
| 238 | 51 |  |  | 51 | 1 | 757 | my ( $self, @value ) = @_; | 
| 239 | 51 |  |  |  |  | 98 | for (@value) { $self->{sig_function} = $_ } | 
|  | 42 |  |  |  |  | 87 |  | 
| 240 | 51 |  |  |  |  | 115 | return $self->{sig_function}; | 
| 241 |  |  |  |  |  |  | } | 
| 242 |  |  |  |  |  |  |  | 
| 243 | 1 |  |  | 1 | 0 | 400 | sub sign_func { return &sig_function; }				# uncoverable pod | 
| 244 |  |  |  |  |  |  |  | 
| 245 |  |  |  |  |  |  |  | 
| 246 |  |  |  |  |  |  | sub sig_data { | 
| 247 | 61 |  |  | 61 | 1 | 210 | my ( $self, $message ) = @_; | 
| 248 |  |  |  |  |  |  |  | 
| 249 | 61 | 100 |  |  |  | 173 | if ( ref($message) ) { | 
| 250 | 60 | 100 |  |  |  | 293 | die 'missing packet reference' unless $message->isa('Net::DNS::Packet'); | 
| 251 | 59 |  |  |  |  | 109 | my @unsigned = grep { ref($_) ne ref($self) } @{$message->{additional}}; | 
|  | 81 |  |  |  |  | 260 |  | 
|  | 59 |  |  |  |  | 178 |  | 
| 252 | 59 |  |  |  |  | 181 | local $message->{additional} = \@unsigned;	# remake header image | 
| 253 | 59 |  |  |  |  | 153 | my @part = qw(question answer authority additional); | 
| 254 | 59 |  |  |  |  | 111 | my @size = map { scalar @{$message->{$_}} } @part; | 
|  | 236 |  |  |  |  | 322 |  | 
|  | 236 |  |  |  |  | 472 |  | 
| 255 | 59 | 100 |  |  |  | 206 | if ( my $rawref = $self->{rawref} ) { | 
| 256 | 17 |  |  |  |  | 58 | delete $self->{rawref}; | 
| 257 | 17 |  |  |  |  | 87 | my $hbin = pack 'n6', $self->original_id, $message->{status}, @size; | 
| 258 | 17 |  |  |  |  | 518 | $message = join '', $hbin, substr $$rawref, length $hbin; | 
| 259 |  |  |  |  |  |  | } else { | 
| 260 | 42 |  |  |  |  | 158 | my $data = $message->data; | 
| 261 | 42 |  |  |  |  | 133 | my $hbin = pack 'n6', $message->{id}, $message->{status}, @size; | 
| 262 | 42 |  |  |  |  | 194 | $message = join '', $hbin, substr $data, length $hbin; | 
| 263 |  |  |  |  |  |  | } | 
| 264 |  |  |  |  |  |  | } | 
| 265 |  |  |  |  |  |  |  | 
| 266 |  |  |  |  |  |  | # Design decision: Use 32 bits, which will work until the end of time()! | 
| 267 | 60 |  |  |  |  | 227 | my $time = pack 'xxN n', $self->time_signed, $self->fudge; | 
| 268 |  |  |  |  |  |  |  | 
| 269 |  |  |  |  |  |  | # Insert the prior MAC if present (multi-packet message). | 
| 270 | 60 | 100 |  |  |  | 197 | $self->prior_macbin( $self->{link}->macbin ) if $self->{link}; | 
| 271 | 60 |  |  |  |  | 170 | my $prior_macbin = $self->prior_macbin; | 
| 272 | 60 | 100 |  |  |  | 256 | return pack 'na* a* a*', length($prior_macbin), $prior_macbin, $message, $time if $prior_macbin; | 
| 273 |  |  |  |  |  |  |  | 
| 274 |  |  |  |  |  |  | # Insert the request MAC if present (used to validate responses). | 
| 275 | 41 |  |  |  |  | 89 | my $req_mac = $self->request_macbin; | 
| 276 | 41 | 100 |  |  |  | 131 | my $sigdata = $req_mac ? pack( 'na*', length($req_mac), $req_mac ) : ''; | 
| 277 |  |  |  |  |  |  |  | 
| 278 | 41 |  | 100 |  |  | 164 | $sigdata .= $message || ''; | 
| 279 |  |  |  |  |  |  |  | 
| 280 | 41 |  |  |  |  | 160 | my $kname = $self->{owner}->canonical;			# canonical key name | 
| 281 | 41 |  |  |  |  | 159 | $sigdata .= pack 'a* n N', $kname, ANY, 0; | 
| 282 |  |  |  |  |  |  |  | 
| 283 | 41 |  |  |  |  | 142 | $sigdata .= $self->{algorithm}->canonical;		# canonical algorithm name | 
| 284 |  |  |  |  |  |  |  | 
| 285 | 41 |  |  |  |  | 94 | $sigdata .= $time; | 
| 286 |  |  |  |  |  |  |  | 
| 287 | 41 |  |  |  |  | 101 | $sigdata .= pack 'n', $self->{error}; | 
| 288 |  |  |  |  |  |  |  | 
| 289 | 41 |  |  |  |  | 138 | my $other = $self->other; | 
| 290 | 41 |  |  |  |  | 104 | $sigdata .= pack 'na*', length($other), $other; | 
| 291 |  |  |  |  |  |  |  | 
| 292 | 41 |  |  |  |  | 185 | return $sigdata; | 
| 293 |  |  |  |  |  |  | } | 
| 294 |  |  |  |  |  |  |  | 
| 295 |  |  |  |  |  |  |  | 
| 296 |  |  |  |  |  |  | sub create { | 
| 297 | 54 |  |  | 54 | 1 | 3772 | my ( $class, $karg, @argument ) = @_; | 
| 298 | 54 | 100 |  |  |  | 499 | croak 'argument undefined' unless defined $karg; | 
| 299 |  |  |  |  |  |  |  | 
| 300 | 52 | 100 |  |  |  | 180 | if ( ref($karg) ) { | 
|  |  | 100 |  |  |  |  |  | 
| 301 | 32 | 100 |  |  |  | 310 | if ( $karg->isa('Net::DNS::Packet') ) { | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
| 302 | 5 |  |  |  |  | 16 | my $sigrr = $karg->sigrr; | 
| 303 | 5 | 100 |  |  |  | 251 | croak 'no TSIG in request packet' unless defined $sigrr; | 
| 304 | 3 |  |  |  |  | 17 | return Net::DNS::RR->new(		# ( request, options ) | 
| 305 |  |  |  |  |  |  | name	       => $sigrr->name, | 
| 306 |  |  |  |  |  |  | type	       => 'TSIG', | 
| 307 |  |  |  |  |  |  | algorithm      => $sigrr->algorithm, | 
| 308 |  |  |  |  |  |  | request_macbin => $sigrr->macbin, | 
| 309 |  |  |  |  |  |  | @argument | 
| 310 |  |  |  |  |  |  | ); | 
| 311 |  |  |  |  |  |  |  | 
| 312 |  |  |  |  |  |  | } elsif ( ref($karg) eq __PACKAGE__ ) { | 
| 313 | 16 |  |  |  |  | 53 | my $tsig = $karg->_chain; | 
| 314 | 16 |  |  |  |  | 45 | $tsig->{macbin} = undef; | 
| 315 | 16 |  |  |  |  | 57 | return $tsig; | 
| 316 |  |  |  |  |  |  |  | 
| 317 |  |  |  |  |  |  | } elsif ( ref($karg) eq 'Net::DNS::RR::KEY' ) { | 
| 318 | 10 |  |  |  |  | 60 | return Net::DNS::RR->new( | 
| 319 |  |  |  |  |  |  | name	  => $karg->name, | 
| 320 |  |  |  |  |  |  | type	  => 'TSIG', | 
| 321 |  |  |  |  |  |  | algorithm => $karg->algorithm, | 
| 322 |  |  |  |  |  |  | key	  => $karg->key, | 
| 323 |  |  |  |  |  |  | @argument | 
| 324 |  |  |  |  |  |  | ); | 
| 325 |  |  |  |  |  |  | } | 
| 326 |  |  |  |  |  |  |  | 
| 327 |  |  |  |  |  |  | } elsif ( ( scalar(@argument) % 2 ) == 0 ) { | 
| 328 | 19 |  |  |  |  | 91 | require File::Spec;				# ( keyfile, options ) | 
| 329 | 19 |  |  |  |  | 1649 | require Net::DNS::ZoneFile; | 
| 330 | 19 |  |  |  |  | 288 | my ($keypath) = SYMLINK ? grep( {$_} readlink($karg), $karg ) : $karg; | 
|  | 38 |  |  |  |  | 120 |  | 
| 331 | 19 |  |  |  |  | 397 | my ( $vol, $dir, $name ) = File::Spec->splitpath($keypath); | 
| 332 | 19 |  |  |  |  | 70 | $name =~ m/^K([^+]+)\+\d+\+(\d+)\./;		# BIND dnssec-keygen | 
| 333 | 19 |  |  |  |  | 53 | my ( $keyname, $keytag ) = ( $1, $2 ); | 
| 334 |  |  |  |  |  |  |  | 
| 335 | 19 |  |  |  |  | 119 | my $keyfile = Net::DNS::ZoneFile->new($karg); | 
| 336 | 18 |  |  |  |  | 40 | my ( $algorithm, $secret ); | 
| 337 | 18 |  |  |  |  | 67 | while ( $keyfile->_getline ) { | 
| 338 | 65 | 100 |  |  |  | 203 | /^key "([^"]+)"/     and $keyname   = $1;    # BIND tsig key | 
| 339 | 65 | 100 |  |  |  | 211 | /algorithm ([^;]+);/ and $algorithm = $1; | 
| 340 | 65 | 100 |  |  |  | 194 | /secret "([^"]+)";/  and $secret    = $1; | 
| 341 |  |  |  |  |  |  |  | 
| 342 | 65 | 100 |  |  |  | 147 | /^Algorithm:/ and ( undef, $algorithm ) = split;    # BIND dnssec private key | 
| 343 | 65 | 100 |  |  |  | 142 | /^Key:/	      and ( undef, $secret )	= split; | 
| 344 |  |  |  |  |  |  |  | 
| 345 | 65 | 100 |  |  |  | 253 | next unless /\bIN\s+KEY\b/;		# BIND dnssec public key | 
| 346 | 3 |  |  |  |  | 19 | my $keyrr = Net::DNS::RR->new($_); | 
| 347 | 3 | 100 | 100 |  |  | 19 | carp "$karg  does not appear to be a BIND dnssec public key" | 
| 348 |  |  |  |  |  |  | unless $keyrr->keytag == ( $keytag || 0 ); | 
| 349 | 3 |  |  |  |  | 41 | return $class->create( $keyrr, @argument ); | 
| 350 |  |  |  |  |  |  | } | 
| 351 |  |  |  |  |  |  |  | 
| 352 | 15 |  |  |  |  | 39 | foreach ( $keyname, $algorithm, $secret ) { | 
| 353 | 44 | 100 |  |  |  | 206 | croak 'key file incompatible with TSIG' unless $_; | 
| 354 |  |  |  |  |  |  | } | 
| 355 |  |  |  |  |  |  |  | 
| 356 | 14 |  |  |  |  | 126 | return Net::DNS::RR->new( | 
| 357 |  |  |  |  |  |  | name	  => $keyname, | 
| 358 |  |  |  |  |  |  | type	  => 'TSIG', | 
| 359 |  |  |  |  |  |  | algorithm => $algorithm, | 
| 360 |  |  |  |  |  |  | key	  => $secret, | 
| 361 |  |  |  |  |  |  | @argument | 
| 362 |  |  |  |  |  |  | ); | 
| 363 |  |  |  |  |  |  | } | 
| 364 |  |  |  |  |  |  |  | 
| 365 | 2 |  |  |  |  | 216 | croak "Usage:	$class->create( \$keyfile, \@options )"; | 
| 366 |  |  |  |  |  |  | } | 
| 367 |  |  |  |  |  |  |  | 
| 368 |  |  |  |  |  |  |  | 
| 369 |  |  |  |  |  |  | sub verify { | 
| 370 | 41 |  |  | 41 | 1 | 122 | my ( $self, $data, @link ) = @_; | 
| 371 | 41 |  |  |  |  | 75 | my $fail = undef; | 
| 372 |  |  |  |  |  |  |  | 
| 373 | 41 | 100 |  |  |  | 110 | if ( scalar @link ) { | 
| 374 |  |  |  |  |  |  |  | 
| 375 | 32 |  |  |  |  | 59 | my $link = shift @link; | 
| 376 | 32 | 100 |  |  |  | 103 | unless ( ref($link) ) { | 
| 377 | 1 |  |  |  |  | 4 | $self->error('BADSIG');			# (multi-packet) | 
| 378 | 1 |  |  |  |  | 3 | return $fail; | 
| 379 |  |  |  |  |  |  | } | 
| 380 |  |  |  |  |  |  |  | 
| 381 | 31 |  |  |  |  | 111 | my $signerkey = lc( join '+', $self->name, $self->algorithm ); | 
| 382 | 31 | 100 |  |  |  | 276 | if ( $link->isa('Net::DNS::Packet') ) { | 
|  |  | 100 |  |  |  |  |  | 
| 383 | 13 |  |  |  |  | 47 | my $request = $link->sigrr;		# request TSIG | 
| 384 | 13 |  |  |  |  | 54 | my $rqstkey = lc( join '+', $request->name, $request->algorithm ); | 
| 385 | 13 | 100 |  |  |  | 61 | $self->error('BADKEY') unless $signerkey eq $rqstkey; | 
| 386 | 13 |  |  |  |  | 60 | $self->request_macbin( $request->macbin ); | 
| 387 |  |  |  |  |  |  |  | 
| 388 |  |  |  |  |  |  | } elsif ( $link->isa(__PACKAGE__) ) { | 
| 389 | 17 |  |  |  |  | 63 | my $priorkey = lc( join '+', $link->name, $link->algorithm ); | 
| 390 | 17 | 100 |  |  |  | 72 | $self->error('BADKEY') unless $signerkey eq $priorkey; | 
| 391 | 17 |  |  |  |  | 65 | $self->prior_macbin( $link->macbin ); | 
| 392 |  |  |  |  |  |  |  | 
| 393 |  |  |  |  |  |  | } else { | 
| 394 | 1 |  |  |  |  | 219 | croak 'Usage: $tsig->verify( $reply, $query )'; | 
| 395 |  |  |  |  |  |  | } | 
| 396 |  |  |  |  |  |  | } | 
| 397 |  |  |  |  |  |  |  | 
| 398 | 39 | 100 |  |  |  | 169 | return $fail if $self->{error}; | 
| 399 |  |  |  |  |  |  |  | 
| 400 | 31 |  |  |  |  | 111 | my $sigdata = $self->sig_data($data);			# form data to be verified | 
| 401 | 30 |  |  |  |  | 127 | my $tsigmac = $self->_mac_function($sigdata); | 
| 402 | 30 |  |  |  |  | 698 | my $tsig    = $self->_chain; | 
| 403 |  |  |  |  |  |  |  | 
| 404 | 30 |  |  |  |  | 104 | my $macbin = $self->macbin; | 
| 405 | 30 |  |  |  |  | 66 | my $maclen = length $macbin; | 
| 406 | 30 | 100 |  |  |  | 152 | $self->error('BADSIG') if $macbin ne substr $tsigmac, 0, $maclen; | 
| 407 |  |  |  |  |  |  |  | 
| 408 | 30 |  |  |  |  | 91 | my $minlen = length($tsigmac) >> 1;			# per RFC4635, 3.1 | 
| 409 | 30 | 100 | 100 |  |  | 164 | $self->error('BADTRUNC') if $maclen < $minlen or $maclen > length $tsigmac; | 
| 410 | 30 | 100 |  |  |  | 75 | $self->error('BADTRUNC') if $maclen < 10; | 
| 411 |  |  |  |  |  |  |  | 
| 412 | 30 |  |  |  |  | 108 | my $time_signed = $self->time_signed; | 
| 413 | 30 | 100 |  |  |  | 115 | if ( abs( time() - $time_signed ) > $self->fudge ) { | 
| 414 | 1 |  |  |  |  | 8 | $self->error('BADTIME'); | 
| 415 | 1 |  |  |  |  | 5 | $self->other($time_signed); | 
| 416 |  |  |  |  |  |  | } | 
| 417 |  |  |  |  |  |  |  | 
| 418 | 30 | 100 |  |  |  | 216 | return $self->{error} ? $fail : $tsig; | 
| 419 |  |  |  |  |  |  | } | 
| 420 |  |  |  |  |  |  |  | 
| 421 | 25 |  |  | 25 | 1 | 75 | sub vrfyerrstr { return shift->error; } | 
| 422 |  |  |  |  |  |  |  | 
| 423 |  |  |  |  |  |  |  | 
| 424 |  |  |  |  |  |  | ######################################## | 
| 425 |  |  |  |  |  |  |  | 
| 426 |  |  |  |  |  |  | { | 
| 427 |  |  |  |  |  |  | # source: http://www.iana.org/assignments/tsig-algorithm-names | 
| 428 |  |  |  |  |  |  | my @algbyname = ( | 
| 429 |  |  |  |  |  |  | 'HMAC-MD5.SIG-ALG.REG.INT' => 157,		# numbers are from ISC BIND keygen | 
| 430 |  |  |  |  |  |  | 'HMAC-SHA1'		   => 161,		# and not blessed by IANA | 
| 431 |  |  |  |  |  |  | 'HMAC-SHA224'		   => 162, | 
| 432 |  |  |  |  |  |  | 'HMAC-SHA256'		   => 163, | 
| 433 |  |  |  |  |  |  | 'HMAC-SHA384'		   => 164, | 
| 434 |  |  |  |  |  |  | 'HMAC-SHA512'		   => 165, | 
| 435 |  |  |  |  |  |  | ); | 
| 436 |  |  |  |  |  |  |  | 
| 437 |  |  |  |  |  |  | my @algalias = ( | 
| 438 |  |  |  |  |  |  | 'HMAC-MD5' => 157, | 
| 439 |  |  |  |  |  |  | 'HMAC-SHA' => 161, | 
| 440 |  |  |  |  |  |  | ); | 
| 441 |  |  |  |  |  |  |  | 
| 442 |  |  |  |  |  |  | my %algbyval = reverse @algbyname; | 
| 443 |  |  |  |  |  |  |  | 
| 444 |  |  |  |  |  |  | my @algrehash = map { /^\d/ ? ($_) x 3 : uc($_) } @algbyname, @algalias; | 
| 445 |  |  |  |  |  |  | foreach (@algrehash) { s/[\W_]//g; }			# strip non-alphanumerics | 
| 446 |  |  |  |  |  |  | my %algbyname = @algrehash;				# work around broken cperl | 
| 447 |  |  |  |  |  |  |  | 
| 448 |  |  |  |  |  |  | sub _algbyname { | 
| 449 | 43 |  |  | 43 |  | 103 | my $key = uc shift;				# synthetic key | 
| 450 | 43 |  |  |  |  | 226 | $key =~ s/[\W_]//g;				# strip non-alphanumerics | 
| 451 | 43 |  |  |  |  | 182 | return $algbyname{$key}; | 
| 452 |  |  |  |  |  |  | } | 
| 453 |  |  |  |  |  |  |  | 
| 454 |  |  |  |  |  |  | sub _algbyval { | 
| 455 | 41 |  |  | 41 |  | 73 | my $value = shift; | 
| 456 | 41 |  |  |  |  | 103 | return $algbyval{$value}; | 
| 457 |  |  |  |  |  |  | } | 
| 458 |  |  |  |  |  |  | } | 
| 459 |  |  |  |  |  |  |  | 
| 460 |  |  |  |  |  |  |  | 
| 461 |  |  |  |  |  |  | { | 
| 462 |  |  |  |  |  |  | my %digest = ( | 
| 463 |  |  |  |  |  |  | '157' => ['Digest::MD5'], | 
| 464 |  |  |  |  |  |  | '161' => ['Digest::SHA'], | 
| 465 |  |  |  |  |  |  | '162' => ['Digest::SHA', 224, 64], | 
| 466 |  |  |  |  |  |  | '163' => ['Digest::SHA', 256, 64], | 
| 467 |  |  |  |  |  |  | '164' => ['Digest::SHA', 384, 128], | 
| 468 |  |  |  |  |  |  | '165' => ['Digest::SHA', 512, 128], | 
| 469 |  |  |  |  |  |  | ); | 
| 470 |  |  |  |  |  |  |  | 
| 471 |  |  |  |  |  |  |  | 
| 472 |  |  |  |  |  |  | my %keytable; | 
| 473 |  |  |  |  |  |  |  | 
| 474 |  |  |  |  |  |  | sub _algorithm {		## install sig function in key table | 
| 475 | 122 |  |  | 122 |  | 234 | my $self = shift; | 
| 476 |  |  |  |  |  |  |  | 
| 477 | 122 | 100 |  |  |  | 282 | if ( my $algname = shift ) { | 
| 478 |  |  |  |  |  |  |  | 
| 479 | 43 | 100 |  |  |  | 101 | unless ( my $digtype = _algbyname($algname) ) { | 
| 480 | 2 |  |  |  |  | 10 | $self->{algorithm} = Net::DNS::DomainName->new($algname); | 
| 481 |  |  |  |  |  |  |  | 
| 482 |  |  |  |  |  |  | } else { | 
| 483 | 41 |  |  |  |  | 93 | $algname = _algbyval($digtype); | 
| 484 | 41 |  |  |  |  | 225 | $self->{algorithm} = Net::DNS::DomainName->new($algname); | 
| 485 |  |  |  |  |  |  |  | 
| 486 | 41 |  |  |  |  | 75 | my ( $hash, @param ) = @{$digest{$digtype}}; | 
|  | 41 |  |  |  |  | 127 |  | 
| 487 | 41 |  |  |  |  | 91 | my ( undef, @block ) = @param; | 
| 488 | 41 |  |  |  |  | 258 | my $digest   = $hash->new(@param); | 
| 489 |  |  |  |  |  |  | my $function = sub { | 
| 490 | 94 |  |  | 94 |  | 23025 | my $hmac = Digest::HMAC->new( shift, $digest, @block ); | 
| 491 | 94 |  |  |  |  | 3210 | $hmac->add(shift); | 
| 492 | 94 |  |  |  |  | 1196 | return $hmac->digest; | 
| 493 | 41 |  |  |  |  | 569 | }; | 
| 494 |  |  |  |  |  |  |  | 
| 495 | 41 |  |  |  |  | 184 | $self->sig_function($function); | 
| 496 |  |  |  |  |  |  |  | 
| 497 | 41 |  | 100 |  |  | 184 | my $keyname = ( $self->{owner} || return )->canonical; | 
| 498 | 35 |  |  |  |  | 310 | $keytable{$keyname}{digest} = $function; | 
| 499 |  |  |  |  |  |  | } | 
| 500 |  |  |  |  |  |  | } | 
| 501 |  |  |  |  |  |  |  | 
| 502 | 116 | 100 |  |  |  | 518 | return defined wantarray ? $self->{algorithm}->name : undef; | 
| 503 |  |  |  |  |  |  | } | 
| 504 |  |  |  |  |  |  |  | 
| 505 |  |  |  |  |  |  |  | 
| 506 |  |  |  |  |  |  | sub _keybin {			## install key in key table | 
| 507 | 27 |  |  | 27 |  | 66 | my ( $self, @argument ) = @_; | 
| 508 | 27 | 100 |  |  |  | 166 | croak 'access to TSIG key material denied' unless scalar @argument; | 
| 509 | 26 |  | 100 |  |  | 102 | my $keyref  = $keytable{$self->{owner}->canonical} ||= {}; | 
| 510 | 26 |  |  |  |  | 67 | my $private = shift @argument;			# closure keeps private key private | 
| 511 |  |  |  |  |  |  | $keyref->{key} = sub { | 
| 512 | 60 |  |  | 60 |  | 113 | my $function = $keyref->{digest}; | 
| 513 | 60 |  |  |  |  | 176 | return &$function( $private, @_ ); | 
| 514 | 26 |  |  |  |  | 224 | }; | 
| 515 | 26 |  |  |  |  | 151 | return; | 
| 516 |  |  |  |  |  |  | } | 
| 517 |  |  |  |  |  |  |  | 
| 518 |  |  |  |  |  |  |  | 
| 519 |  |  |  |  |  |  | sub _mac_function {		## apply keyed hash function to argument | 
| 520 | 60 |  |  | 60 |  | 135 | my ( $self, @argument ) = @_; | 
| 521 | 60 |  |  |  |  | 171 | my $owner = $self->{owner}->canonical; | 
| 522 | 60 | 100 |  |  |  | 235 | $self->algorithm( $self->algorithm ) unless $keytable{$owner}{digest}; | 
| 523 | 60 |  |  |  |  | 119 | my $keyref = $keytable{$owner}; | 
| 524 | 60 | 100 |  |  |  | 148 | $keyref->{digest} = $self->sig_function unless $keyref->{digest}; | 
| 525 | 60 |  |  |  |  | 123 | my $function = $keyref->{key}; | 
| 526 | 60 |  |  |  |  | 157 | return &$function(@argument); | 
| 527 |  |  |  |  |  |  | } | 
| 528 |  |  |  |  |  |  | } | 
| 529 |  |  |  |  |  |  |  | 
| 530 |  |  |  |  |  |  |  | 
| 531 |  |  |  |  |  |  | # _chain() creates a new TSIG object linked to the original | 
| 532 |  |  |  |  |  |  | # RR, for the purpose of signing multi-message transfers. | 
| 533 |  |  |  |  |  |  |  | 
| 534 |  |  |  |  |  |  | sub _chain { | 
| 535 | 46 |  |  | 46 |  | 84 | my $self = shift; | 
| 536 | 46 |  |  |  |  | 302 | $self->{link} = undef; | 
| 537 | 46 |  |  |  |  | 540 | return bless {%$self, link => $self}, ref($self); | 
| 538 |  |  |  |  |  |  | } | 
| 539 |  |  |  |  |  |  |  | 
| 540 |  |  |  |  |  |  | ######################################## | 
| 541 |  |  |  |  |  |  |  | 
| 542 |  |  |  |  |  |  |  | 
| 543 |  |  |  |  |  |  | 1; | 
| 544 |  |  |  |  |  |  | __END__ |