dnsruby 1.74.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +16 -4
- data/DNSSEC +3 -2
- data/EXAMPLES +1 -1
- data/README.md +370 -64
- data/RELEASE_NOTES.md +302 -1
- data/Rakefile +0 -3
- data/demo/axfr.rb +24 -6
- data/demo/rubydig.rb +0 -2
- data/dnsruby.gemspec +5 -4
- data/lib/dnsruby/cache.rb +279 -47
- data/lib/dnsruby/code_mapper.rb +35 -8
- data/lib/dnsruby/code_mappers.rb +63 -49
- data/lib/dnsruby/config.rb +75 -4
- data/lib/dnsruby/deadline_queue.rb +75 -0
- data/lib/dnsruby/dnssec.rb +148 -45
- data/lib/dnsruby/ipv6.rb +45 -5
- data/lib/dnsruby/key_cache.rb +202 -35
- data/lib/dnsruby/message/decoder.rb +85 -19
- data/lib/dnsruby/message/encoder.rb +27 -16
- data/lib/dnsruby/message/header.rb +8 -12
- data/lib/dnsruby/message/message.rb +53 -22
- data/lib/dnsruby/message/section.rb +12 -34
- data/lib/dnsruby/name.rb +5 -2
- data/lib/dnsruby/packet_sender.rb +645 -203
- data/lib/dnsruby/presentation.rb +147 -0
- data/lib/dnsruby/query_control.rb +132 -0
- data/lib/dnsruby/recursor.rb +795 -339
- data/lib/dnsruby/resolver.rb +468 -186
- data/lib/dnsruby/resource/CAA.rb +67 -16
- data/lib/dnsruby/resource/DLV.rb +6 -0
- data/lib/dnsruby/resource/DNSKEY.rb +108 -16
- data/lib/dnsruby/resource/DS.rb +27 -28
- data/lib/dnsruby/resource/HTTPS.rb +16 -0
- data/lib/dnsruby/resource/IN.rb +19 -0
- data/lib/dnsruby/resource/MX.rb +2 -2
- data/lib/dnsruby/resource/NSEC.rb +6 -10
- data/lib/dnsruby/resource/NSEC3.rb +55 -19
- data/lib/dnsruby/resource/NSEC3PARAM.rb +4 -6
- data/lib/dnsruby/resource/RR.rb +108 -65
- data/lib/dnsruby/resource/RRSIG.rb +5 -5
- data/lib/dnsruby/resource/RRSet.rb +38 -22
- data/lib/dnsruby/resource/SRV.rb +2 -2
- data/lib/dnsruby/resource/SVCB.rb +775 -0
- data/lib/dnsruby/resource/TSIG.rb +266 -231
- data/lib/dnsruby/resource/TXT.rb +34 -42
- data/lib/dnsruby/resource/domain_name.rb +1 -1
- data/lib/dnsruby/resource/generic.rb +31 -0
- data/lib/dnsruby/resource/generic_cache.rb +384 -0
- data/lib/dnsruby/resource/generic_constants.rb +79 -0
- data/lib/dnsruby/select_thread.rb +979 -291
- data/lib/dnsruby/serial_time.rb +25 -0
- data/lib/dnsruby/single_resolver.rb +25 -6
- data/lib/dnsruby/single_verifier.rb +1620 -530
- data/lib/dnsruby/transport_clock.rb +9 -0
- data/lib/dnsruby/update.rb +8 -6
- data/lib/dnsruby/validation_budget.rb +143 -0
- data/lib/dnsruby/validator_thread.rb +139 -58
- data/lib/dnsruby/version.rb +1 -1
- data/lib/dnsruby/zone_reader.rb +196 -129
- data/lib/dnsruby/zone_transfer.rb +157 -25
- data/lib/dnsruby.rb +7 -2
- data/test/ci_watchdog.rb +33 -0
- data/test/fixtures/org-ds-2026-11-05.zone +5 -0
- data/test/fixtures/root-dnskey-2026-11-05.zone +7 -0
- data/test/fork_test_helper.rb +37 -0
- data/test/local_dns_server.rb +57 -0
- data/test/soak_server.rb +152 -0
- data/test/socket_helpers.rb +26 -0
- data/test/spec_helper.rb +8 -13
- data/test/tc_attempt_configuration.rb +137 -0
- data/test/tc_caa.rb +2 -2
- data/test/tc_cache_keys.rb +36 -0
- data/test/tc_cancelled_send_capacity.rb +158 -0
- data/test/tc_dnssec_alias_validation.rb +289 -0
- data/test/tc_dnssec_budget.rb +489 -0
- data/test/tc_dnssec_cache_bounds.rb +197 -0
- data/test/tc_dnssec_cache_lifetime.rb +615 -0
- data/test/tc_dnssec_denial_proofs.rb +285 -0
- data/test/tc_dnssec_recursion_limits.rb +186 -0
- data/test/tc_dnssec_recursor_review_fixes.rb +351 -0
- data/test/tc_dnssec_trust_anchors.rb +286 -0
- data/test/tc_dnssec_trust_races.rb +147 -0
- data/test/tc_dnssec_wildcard_proofs.rb +249 -0
- data/test/tc_ds.rb +5 -5
- data/test/tc_edns_retry_regressions.rb +255 -0
- data/test/tc_escapedchars.rb +8 -0
- data/test/tc_final_review_fixes.rb +167 -0
- data/test/tc_final_transport_contracts.rb +104 -0
- data/test/tc_generic_cache.rb +852 -0
- data/test/tc_header.rb +26 -0
- data/test/tc_ipv6.rb +120 -0
- data/test/tc_key_cache_identity.rb +382 -0
- data/test/tc_malformed_tcp.rb +712 -0
- data/test/tc_name.rb +99 -5
- data/test/tc_nameserver_bootstrap.rb +322 -0
- data/test/tc_nsec.rb +4 -45
- data/test/tc_nsec3.rb +17 -1
- data/test/tc_pipeline_retirement.rb +358 -0
- data/test/tc_pipeline_retry.rb +462 -0
- data/test/tc_pipeline_send.rb +663 -0
- data/test/tc_protocol_review_fixes.rb +95 -0
- data/test/tc_query_control.rb +90 -0
- data/test/tc_record_codec_contracts.rb +107 -0
- data/test/tc_record_regressions.rb +225 -0
- data/test/tc_recur.rb +1 -0
- data/test/tc_recursor_address_fallback.rb +169 -0
- data/test/tc_recursor_limits.rb +139 -0
- data/test/tc_release_auxiliary_fixes.rb +288 -0
- data/test/tc_release_configuration_fixes.rb +612 -0
- data/test/tc_release_dnssec_completion.rb +405 -0
- data/test/tc_release_dnssec_fixes.rb +305 -0
- data/test/tc_release_negative_cache.rb +327 -0
- data/test/tc_release_recursor_cache.rb +212 -0
- data/test/tc_release_review_transport.rb +372 -0
- data/test/tc_release_review_transport_native.rb +56 -0
- data/test/tc_release_transport.rb +906 -0
- data/test/tc_release_transport_fixes.rb +196 -0
- data/test/tc_release_zone_defaults.rb +157 -0
- data/test/tc_release_zone_ttls.rb +39 -0
- data/test/tc_res_config.rb +42 -20
- data/test/tc_res_opt.rb +21 -1
- data/test/tc_resolver.rb +21 -19
- data/test/tc_resolver_concurrency.rb +383 -0
- data/test/tc_resolver_configuration_contracts.rb +31 -0
- data/test/tc_resolver_validation.rb +211 -0
- data/test/tc_review_cache.rb +163 -0
- data/test/tc_review_code_mapper.rb +279 -0
- data/test/tc_review_control_deadlines.rb +169 -0
- data/test/tc_review_followup_transport.rb +583 -0
- data/test/tc_review_packet_contracts.rb +369 -0
- data/test/tc_review_presentation.rb +213 -0
- data/test/tc_review_record_compatibility.rb +101 -0
- data/test/tc_review_transport_fixes.rb +195 -0
- data/test/tc_review_transport_native.rb +415 -0
- data/test/tc_review_validation_priming.rb +178 -0
- data/test/tc_rr-opt.rb +5 -2
- data/test/tc_rr-unknown.rb +19 -0
- data/test/tc_rr.rb +14 -0
- data/test/tc_rrset_grouping.rb +50 -0
- data/test/tc_rrset_review_fixes.rb +58 -0
- data/test/tc_rrsig.rb +21 -1
- data/test/tc_scoped_ipv6.rb +153 -0
- data/test/tc_scoped_ipv6_select.rb +157 -0
- data/test/tc_scoped_ipv6_transport.rb +329 -0
- data/test/tc_selector_wakeup.rb +42 -0
- data/test/tc_soak.rb +118 -196
- data/test/tc_socket_helpers.rb +90 -0
- data/test/tc_supporting_lookup_limits.rb +252 -0
- data/test/tc_svcb.rb +1043 -0
- data/test/tc_svcb_signed_target.rb +145 -0
- data/test/tc_svcb_transport.rb +120 -0
- data/test/tc_tcp.rb +69 -43
- data/test/tc_tcp_pipelining.rb +95 -204
- data/test/tc_transport_fork.rb +179 -0
- data/test/tc_tsig.rb +2 -2
- data/test/tc_tsig_api.rb +251 -0
- data/test/tc_tsig_rfc8945.rb +357 -0
- data/test/tc_v2_release_dnssec.rb +442 -0
- data/test/tc_v2_release_transport.rb +304 -0
- data/test/tc_v2_zone_transfer_native.rb +58 -0
- data/test/tc_zone_reader.rb +217 -1
- data/test/tcp_pipelining_server.rb +150 -0
- data/test/ts_offline.rb +77 -6
- data/test/ts_online.rb +2 -1
- metadata +105 -10
- data/.coveralls.yml +0 -2
data/test/tc_tsig_api.rb
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
require_relative 'spec_helper'
|
|
2
|
+
require 'stringio'
|
|
3
|
+
require 'minitest/mock'
|
|
4
|
+
|
|
5
|
+
class TestTsigApi < Minitest::Test
|
|
6
|
+
KEY_NAME = 'api-key.example.'
|
|
7
|
+
SECRET = ['a test-only shared secret'].pack('m0')
|
|
8
|
+
|
|
9
|
+
def test_default_sha256_and_explicit_algorithms_across_public_apis
|
|
10
|
+
configurations = [Dnsruby::Resolver.new(nameserver: '127.0.0.1'),
|
|
11
|
+
Dnsruby::SingleResolver.new(server: '127.0.0.1'),
|
|
12
|
+
Dnsruby::PacketSender.new(server: '127.0.0.1'), Dnsruby::ZoneTransfer.new]
|
|
13
|
+
configurations.each do |configuration|
|
|
14
|
+
[nil, 'hmac-sha256', 'hmac-md5'].each do |algorithm|
|
|
15
|
+
configuration.tsig = algorithm ? [KEY_NAME, SECRET, algorithm] : [KEY_NAME, SECRET]
|
|
16
|
+
key = Dnsruby::Resolver.get_tsig([configuration.tsig])
|
|
17
|
+
if configuration.is_a?(Dnsruby::ZoneTransfer)
|
|
18
|
+
wire = transfer_query(configuration, Time.now.to_i).encode
|
|
19
|
+
else
|
|
20
|
+
sender = configuration.is_a?(Dnsruby::Resolver) ? configuration.single_resolvers.first : configuration
|
|
21
|
+
wire = sender.make_query_packet(Dnsruby::Message.new('example.'))
|
|
22
|
+
end
|
|
23
|
+
assert_signed_exchange(wire, key, algorithm)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
[nil, 'hmac-sha256', 'hmac-md5'].each do |algorithm|
|
|
27
|
+
message = Dnsruby::Message.new('example.')
|
|
28
|
+
if algorithm
|
|
29
|
+
message.set_tsig(name: KEY_NAME, key: SECRET, algorithm: algorithm)
|
|
30
|
+
else
|
|
31
|
+
message.set_tsig(KEY_NAME, SECRET)
|
|
32
|
+
end
|
|
33
|
+
wire = message.encode
|
|
34
|
+
assert_signed_exchange(wire, message.tsig, algorithm)
|
|
35
|
+
end
|
|
36
|
+
ensure
|
|
37
|
+
configurations&.each { |configuration| configuration.close if configuration.respond_to?(:close) }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def test_explicit_algorithms_keep_the_existing_configuration_forms
|
|
41
|
+
%w[hmac-md5 hmac-sha1 hmac-sha256 hmac-sha512].each do |algorithm|
|
|
42
|
+
args = [KEY_NAME, SECRET, algorithm]
|
|
43
|
+
direct = Dnsruby::Resolver.get_tsig(args)
|
|
44
|
+
nested = Dnsruby::Resolver.get_tsig([args])
|
|
45
|
+
assert_equal(direct.algorithm, nested.algorithm)
|
|
46
|
+
assert_same(direct, Dnsruby::Resolver.get_tsig([direct]))
|
|
47
|
+
message = Dnsruby::Message.new('example.')
|
|
48
|
+
message.set_tsig(name: KEY_NAME, key: SECRET, algorithm: algorithm)
|
|
49
|
+
assert_equal(direct.algorithm, Dnsruby::Message.decode(message.encode).tsig.algorithm)
|
|
50
|
+
end
|
|
51
|
+
assert_nil(Dnsruby::Resolver.get_tsig([nil]))
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def test_explicit_md5_constructor_options_sign_and_verify
|
|
55
|
+
configurations = []
|
|
56
|
+
['hmac-md5', 'HMAC-MD5.SIG-ALG.REG.INT.', Dnsruby::RR::TSIG::HMAC_MD5].each do |algorithm|
|
|
57
|
+
args = [KEY_NAME, SECRET, algorithm]
|
|
58
|
+
configurations = [Dnsruby::Resolver.new(nameserver: '127.0.0.1', tsig: args),
|
|
59
|
+
Dnsruby::SingleResolver.new(server: '127.0.0.1', tsig: args),
|
|
60
|
+
Dnsruby::PacketSender.new(server: '127.0.0.1', tsig: args)]
|
|
61
|
+
configurations.each do |configuration|
|
|
62
|
+
key = Dnsruby::Resolver.get_tsig([configuration.tsig])
|
|
63
|
+
sender = configuration.is_a?(Dnsruby::Resolver) ? configuration.single_resolvers.first : configuration
|
|
64
|
+
wire = sender.make_query_packet(Dnsruby::Message.new('example.'))
|
|
65
|
+
assert_signed_exchange(wire, key, 'hmac-md5')
|
|
66
|
+
end
|
|
67
|
+
configurations.each(&:close)
|
|
68
|
+
end
|
|
69
|
+
ensure
|
|
70
|
+
configurations&.each(&:close)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def test_invalid_tsig_configuration_fails_before_sending
|
|
74
|
+
algorithms = ['unsupported-tsig-algorithm', Dnsruby::Name.create('unsupported-tsig-algorithm.')]
|
|
75
|
+
configurations = [Dnsruby::Resolver.new(nameserver: '127.0.0.1'),
|
|
76
|
+
Dnsruby::SingleResolver.new(server: '127.0.0.1'),
|
|
77
|
+
Dnsruby::PacketSender.new(server: '127.0.0.1'), Dnsruby::ZoneTransfer.new]
|
|
78
|
+
algorithms.each do |algorithm|
|
|
79
|
+
args = [KEY_NAME, SECRET, algorithm]
|
|
80
|
+
[args, [args]].each do |form|
|
|
81
|
+
assert_configuration_error { Dnsruby::Resolver.get_tsig(form) }
|
|
82
|
+
end
|
|
83
|
+
configurations.each do |configuration|
|
|
84
|
+
assert_configuration_error { configuration.tsig = args }
|
|
85
|
+
assert_nil(configuration.tsig)
|
|
86
|
+
end
|
|
87
|
+
assert_configuration_error do
|
|
88
|
+
Dnsruby::Message.new('example.').set_tsig(name: KEY_NAME, key: SECRET, algorithm: algorithm)
|
|
89
|
+
end
|
|
90
|
+
assert_configuration_error do
|
|
91
|
+
Dnsruby::Resolver.new(nameserver: '127.0.0.1', tsig: args)
|
|
92
|
+
end
|
|
93
|
+
[Dnsruby::SingleResolver, Dnsruby::PacketSender].each do |klass|
|
|
94
|
+
assert_configuration_error { klass.new(server: '127.0.0.1', tsig: args) }
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
ensure
|
|
98
|
+
configurations&.each { |configuration| configuration.close if configuration.respond_to?(:close) }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def test_key_configuration_does_not_log_the_secret
|
|
102
|
+
output = StringIO.new
|
|
103
|
+
Dnsruby.stub(:log, Logger.new(output)) do
|
|
104
|
+
Dnsruby::Resolver.get_tsig([[KEY_NAME, SECRET]])
|
|
105
|
+
end
|
|
106
|
+
assert_includes(output.string, KEY_NAME.delete_suffix('.'))
|
|
107
|
+
refute_includes(output.string, SECRET)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def test_md5_warning_is_once_per_process_and_never_logs_the_key
|
|
111
|
+
klass = Dnsruby::RR::TSIG
|
|
112
|
+
mutex = klass.const_get(:LEGACY_WARNING_MUTEX)
|
|
113
|
+
previous = mutex.synchronize do
|
|
114
|
+
old = klass.instance_variable_get(:@legacy_warning_issued)
|
|
115
|
+
klass.instance_variable_set(:@legacy_warning_issued, false)
|
|
116
|
+
old
|
|
117
|
+
end
|
|
118
|
+
output = StringIO.new
|
|
119
|
+
logger = Logger.new(output)
|
|
120
|
+
logger.level = Logger::WARN
|
|
121
|
+
Dnsruby.stub(:log, logger) do
|
|
122
|
+
Dnsruby::Resolver.get_tsig([[KEY_NAME, SECRET]])
|
|
123
|
+
assert_empty(output.string)
|
|
124
|
+
threads = Array.new(8) do
|
|
125
|
+
Thread.new { Dnsruby::Resolver.get_tsig([[KEY_NAME, SECRET, 'hmac-md5']]) }
|
|
126
|
+
end
|
|
127
|
+
keys = threads.map(&:value)
|
|
128
|
+
3.times do
|
|
129
|
+
query = Dnsruby::Message.new('example.')
|
|
130
|
+
keys.first.apply(query)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
assert_equal(1, output.string.lines.size)
|
|
134
|
+
assert_match(/WARN.*HMAC-MD5/, output.string)
|
|
135
|
+
assert_match(/RFC 8945.*MUST NOT/, output.string)
|
|
136
|
+
assert_match(/hmac-sha256.*DNS server and client/, output.string)
|
|
137
|
+
refute_includes(output.string, SECRET)
|
|
138
|
+
refute_includes(output.string, KEY_NAME)
|
|
139
|
+
ensure
|
|
140
|
+
mutex&.synchronize { klass.instance_variable_set(:@legacy_warning_issued, previous) }
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def test_tsig_is_rejected_outside_the_final_additional_position
|
|
144
|
+
[:answer, :authority, :additional].each do |section|
|
|
145
|
+
message = Dnsruby::Message.new('example.')
|
|
146
|
+
signature = unsigned_tsig
|
|
147
|
+
message.public_send("add_#{section}", signature)
|
|
148
|
+
message.add_additional(Dnsruby::RR.create('example. 0 IN A 192.0.2.1'))
|
|
149
|
+
error = assert_raises(Dnsruby::DecodeError) { Dnsruby::Message.decode(message.encode) }
|
|
150
|
+
assert_instance_of(Dnsruby::Message, error.partial_message)
|
|
151
|
+
end
|
|
152
|
+
message = Dnsruby::Message.new('example.')
|
|
153
|
+
message.add_additional(unsigned_tsig)
|
|
154
|
+
other = unsigned_tsig
|
|
155
|
+
other.name = 'another-key.example.'
|
|
156
|
+
message.add_additional(other)
|
|
157
|
+
assert_raises(Dnsruby::DecodeError) { Dnsruby::Message.decode(message.encode) }
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def test_tsig_class_and_ttl_are_checked_before_record_dispatch
|
|
161
|
+
[[Dnsruby::Classes::IN, 0], [Dnsruby::Classes::ANY, 60]].each do |klass, ttl|
|
|
162
|
+
signature = unsigned_tsig
|
|
163
|
+
message = Dnsruby::Message.new('example.')
|
|
164
|
+
message.add_additional(signature)
|
|
165
|
+
wire = message.encode
|
|
166
|
+
decoded = Dnsruby::Message.decode(wire)
|
|
167
|
+
fields = wire.index([Dnsruby::Types::TSIG, Dnsruby::Classes::ANY, 0].pack('nnN'), decoded.tsigstart)
|
|
168
|
+
refute_nil(fields)
|
|
169
|
+
wire[fields + 2, 6] = [klass, ttl].pack('nN')
|
|
170
|
+
assert_raises(Dnsruby::DecodeError) { Dnsruby::Message.decode(wire) }
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def test_reused_zone_transfer_gets_a_fresh_timestamp_and_keeps_key_changes
|
|
175
|
+
transfer = Dnsruby::ZoneTransfer.new
|
|
176
|
+
transfer.tsig = KEY_NAME, SECRET
|
|
177
|
+
first = transfer_query(transfer, 10_000)
|
|
178
|
+
transfer.tsig.fudge = 600
|
|
179
|
+
transfer.tsig.mac_length = 16
|
|
180
|
+
second = transfer_query(transfer, 20_000)
|
|
181
|
+
assert_equal(10_000, first.tsig.time_signed)
|
|
182
|
+
assert_equal(20_000, second.tsig.time_signed)
|
|
183
|
+
assert_equal(600, second.tsig.fudge)
|
|
184
|
+
assert_equal(16, second.tsig.mac_size)
|
|
185
|
+
assert_equal(second.tsig.mac, transfer.tsig.mac)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def test_zone_transfer_respects_explicit_times_and_can_return_to_current_time
|
|
189
|
+
transfer = Dnsruby::ZoneTransfer.new
|
|
190
|
+
transfer.tsig = KEY_NAME, SECRET
|
|
191
|
+
transfer.tsig.time_signed = 1234
|
|
192
|
+
assert_equal(1234, transfer_query(transfer, 10_000).tsig.time_signed)
|
|
193
|
+
assert_equal(1234, transfer_query(transfer, 20_000).tsig.time_signed)
|
|
194
|
+
transfer.tsig.time_signed = 2345
|
|
195
|
+
assert_equal(2345, transfer_query(transfer, 30_000).tsig.time_signed)
|
|
196
|
+
transfer.tsig.time_signed = nil
|
|
197
|
+
assert_equal(40_000, transfer_query(transfer, 40_000).tsig.time_signed)
|
|
198
|
+
assert_equal(50_000, transfer_query(transfer, 50_000).tsig.time_signed)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
private
|
|
202
|
+
|
|
203
|
+
def assert_configuration_error
|
|
204
|
+
error = assert_raises(ArgumentError) { yield }
|
|
205
|
+
assert_match(/Invalid TSIG algorithm/, error.message)
|
|
206
|
+
refute_includes(error.message, SECRET)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def assert_signed_exchange(wire, client_key, algorithm)
|
|
210
|
+
md5 = algorithm == 'hmac-md5'
|
|
211
|
+
expected = md5 ? Dnsruby::RR::TSIG::HMAC_MD5 : Dnsruby::RR::TSIG::HMAC_SHA256
|
|
212
|
+
query = Dnsruby::Message.decode(wire)
|
|
213
|
+
signature = query.tsig
|
|
214
|
+
assert_equal(expected, client_key.algorithm)
|
|
215
|
+
assert_equal(expected, signature.algorithm)
|
|
216
|
+
assert_equal(md5 ? 16 : 32, signature.mac_size)
|
|
217
|
+
unsigned = wire.byteslice(0, query.tsigstart).dup
|
|
218
|
+
unsigned[10, 2] = [query.header.arcount - 1].pack('n')
|
|
219
|
+
name_wire = ->(name) { name.to_s.downcase.sub(/\.$/, '').split('.').map { |label| [label.bytesize].pack('C') + label }.join.b + "\0".b }
|
|
220
|
+
variables = name_wire.call(signature.name) + [255, 0].pack('nN') + name_wire.call(signature.algorithm) +
|
|
221
|
+
[signature.time_signed >> 32, signature.time_signed & 0xffff_ffff, signature.fudge,
|
|
222
|
+
signature.error, signature.other_size].pack('nNn3') + signature.other_data
|
|
223
|
+
assert_equal(OpenSSL::HMAC.digest(md5 ? 'MD5' : 'SHA256', SECRET.unpack1('m0'), unsigned + variables), signature.mac)
|
|
224
|
+
server_key = Dnsruby::RR.create(name: KEY_NAME, type: 'TSIG', klass: 'ANY', key: SECRET,
|
|
225
|
+
algorithm: algorithm || 'hmac-sha256')
|
|
226
|
+
reply = Dnsruby::Message.new('example.')
|
|
227
|
+
reply.header.qr = true
|
|
228
|
+
server_key.apply(reply, query)
|
|
229
|
+
response_wire = reply.encode
|
|
230
|
+
decoded = Dnsruby::Message.decode(response_wire)
|
|
231
|
+
assert_equal(true, client_key.verify(query, decoded, response_wire))
|
|
232
|
+
assert(decoded.verified?)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def transfer_query(transfer, clock)
|
|
236
|
+
writes = []
|
|
237
|
+
socket = Object.new
|
|
238
|
+
socket.define_singleton_method(:write_nonblock) { |bytes| writes << bytes; bytes.bytesize }
|
|
239
|
+
Time.stub(:now, Time.at(clock)) do
|
|
240
|
+
transfer.send_message(socket, Dnsruby::Message.new('example.', Dnsruby::Types::AXFR))
|
|
241
|
+
end
|
|
242
|
+
frame = writes.join
|
|
243
|
+
assert_equal(frame.bytesize - 2, frame.unpack1('n'))
|
|
244
|
+
Dnsruby::Message.decode(frame.byteslice(2..-1))
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def unsigned_tsig
|
|
248
|
+
Dnsruby::RR.create(name: KEY_NAME, type: 'TSIG', klass: 'ANY',
|
|
249
|
+
time_signed: Time.now.to_i, error: Dnsruby::RCode::BADKEY)
|
|
250
|
+
end
|
|
251
|
+
end
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
require_relative 'spec_helper'
|
|
2
|
+
require 'minitest/mock'
|
|
3
|
+
require 'stringio'
|
|
4
|
+
|
|
5
|
+
class TsigRfc8945Test < Minitest::Test
|
|
6
|
+
SECRET = 'secret'.b
|
|
7
|
+
|
|
8
|
+
def key(algorithm = 'hmac-sha256', **options)
|
|
9
|
+
Dnsruby::RR.create({name: 'key.example.', type: 'TSIG', klass: 'ANY',
|
|
10
|
+
key: [SECRET].pack('m0'), algorithm: algorithm}.merge(options))
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def request(signer)
|
|
14
|
+
Dnsruby::Message.new('example.', 'A').tap do |message|
|
|
15
|
+
message.header.id = 12_345
|
|
16
|
+
signer.apply(message)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def dns_name(text)
|
|
21
|
+
text.sub(/\.$/, '').split('.').map { |label| [label.bytesize].pack('C') + label }.join.b + "\0".b
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Build expected signatures with OpenSSL and explicit wire fields, independently
|
|
25
|
+
# of TSIG#generate, #calculate_mac and #sig_data.
|
|
26
|
+
def signed_wire(query, algorithm: 'hmac-sha256', length: nil, time: Time.now.to_i,
|
|
27
|
+
fudge: 300, error: 0, other: ''.b, original_id: query.header.id,
|
|
28
|
+
header_id: query.header.id, prior: nil, klass: 255, ttl: 0)
|
|
29
|
+
message = Dnsruby::Message.new('example.', 'A')
|
|
30
|
+
message.header.id, message.header.qr = original_id, true
|
|
31
|
+
message.header.rcode = 9 unless error.zero?
|
|
32
|
+
unsigned = message.encode
|
|
33
|
+
timers = [time >> 32, time & 0xffff_ffff, fudge].pack('nNn')
|
|
34
|
+
variables = dns_name('key.example.') + [klass, ttl].pack('nN') +
|
|
35
|
+
dns_name(algorithm.downcase) + timers + [error, other.bytesize].pack('n2') + other
|
|
36
|
+
prefix = prior || [query.tsig.mac_size].pack('n') + query.tsig.mac
|
|
37
|
+
digest = {'hmac-md5.sig-alg.reg.int' => 'MD5', 'hmac-sha1' => 'SHA1',
|
|
38
|
+
'hmac-sha256' => 'SHA256', 'hmac-sha512' => 'SHA512'}.fetch(algorithm.downcase.sub(/\.$/, ''))
|
|
39
|
+
mac = OpenSSL::HMAC.digest(digest, SECRET, prefix + unsigned + (prior ? timers : variables))
|
|
40
|
+
mac = mac.byteslice(0, length) if length && length <= mac.bytesize
|
|
41
|
+
mac += 'x' * (length - mac.bytesize) if length && length > mac.bytesize
|
|
42
|
+
rdata = dns_name(algorithm) + timers + [mac.bytesize].pack('n') + mac +
|
|
43
|
+
[original_id, error, other.bytesize].pack('n3') + other
|
|
44
|
+
wire = unsigned.dup
|
|
45
|
+
wire[0, 2], wire[10, 2] = [header_id].pack('n'), [1].pack('n')
|
|
46
|
+
wire << dns_name('key.example.') << [250, klass, ttl, rdata.bytesize].pack('n2Nn') << rdata
|
|
47
|
+
[wire, mac]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def verify(signer, query, wire)
|
|
51
|
+
decoded = Dnsruby::Message.decode(wire)
|
|
52
|
+
[signer.verify(query, decoded, wire), decoded]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def test_omitted_algorithm_uses_sha256
|
|
56
|
+
default = Dnsruby::RR.create(name: 'key.example.', type: 'TSIG', klass: 'ANY', key: [SECRET].pack('m0'))
|
|
57
|
+
assert_equal(Dnsruby::RR::TSIG::HMAC_SHA256, default.algorithm)
|
|
58
|
+
query = request(default)
|
|
59
|
+
assert_equal(32, query.tsig.mac_size)
|
|
60
|
+
wire, = signed_wire(query)
|
|
61
|
+
assert_equal(true, verify(default, query, wire).first)
|
|
62
|
+
assert_equal(32, request(key('hmac-sha256')).tsig.mac_size)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def test_explicit_md5_aliases_sign_and_verify_independent_responses
|
|
66
|
+
['hmac-md5', 'HMAC-MD5.SIG-ALG.REG.INT.', Dnsruby::RR::TSIG::HMAC_MD5].each do |algorithm|
|
|
67
|
+
signer = key(algorithm)
|
|
68
|
+
query = request(signer)
|
|
69
|
+
assert_equal(Dnsruby::RR::TSIG::HMAC_MD5, query.tsig.algorithm)
|
|
70
|
+
assert_equal(16, query.tsig.mac_size)
|
|
71
|
+
wire, = signed_wire(query, algorithm: 'hmac-md5.sig-alg.reg.int')
|
|
72
|
+
result, reply = verify(signer, query, wire)
|
|
73
|
+
assert_equal(true, result)
|
|
74
|
+
assert(reply.verified?)
|
|
75
|
+
restored_key = Dnsruby::Message.decode(wire).tsig
|
|
76
|
+
restored_key.key = [SECRET].pack('m0')
|
|
77
|
+
assert_equal(16, request(restored_key).tsig.mac_size)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def test_md5_requires_explicit_selection_and_authenticates_errors
|
|
82
|
+
signer = key('hmac-md5')
|
|
83
|
+
query = request(signer)
|
|
84
|
+
[0, 18, 22].each do |error|
|
|
85
|
+
wire, = signed_wire(query, algorithm: 'hmac-md5.sig-alg.reg.int', error: error,
|
|
86
|
+
time: query.tsig.time_signed, other: error == 18 ? [0, Time.now.to_i].pack('nN') : ''.b)
|
|
87
|
+
result, response = verify(signer, query, wire)
|
|
88
|
+
assert_equal(error.zero?, result)
|
|
89
|
+
assert(response.verified?)
|
|
90
|
+
assert_equal(error, response.tsigerror)
|
|
91
|
+
decoded = Dnsruby::Message.decode(wire)
|
|
92
|
+
forged = wire.dup
|
|
93
|
+
mac_offset = decoded.tsigstart + dns_name('key.example.').bytesize + 10 + dns_name('hmac-md5.sig-alg.reg.int').bytesize + 10
|
|
94
|
+
forged.setbyte(mac_offset, forged.getbyte(mac_offset) ^ 1)
|
|
95
|
+
result, response = verify(signer, query, forged)
|
|
96
|
+
assert_equal(false, result)
|
|
97
|
+
assert_equal(Dnsruby::RCode::BADSIG, response.tsigerror)
|
|
98
|
+
refute(response.verified?)
|
|
99
|
+
end
|
|
100
|
+
[['hmac-sha256', 'hmac-md5.sig-alg.reg.int'], ['hmac-md5', 'hmac-sha256']].each do |configured, received|
|
|
101
|
+
signer = key(configured)
|
|
102
|
+
query = request(signer)
|
|
103
|
+
wire, = signed_wire(query, algorithm: received)
|
|
104
|
+
OpenSSL::HMAC.stub(:digest, ->(*) { flunk('Wrong algorithm reached HMAC verification') }) do
|
|
105
|
+
result, response = verify(signer, query, wire)
|
|
106
|
+
assert_equal(false, result)
|
|
107
|
+
assert_equal(Dnsruby::RCode::BADKEY, response.tsigerror)
|
|
108
|
+
refute(response.verified?)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def test_inbound_md5_does_not_warn_but_presigned_outbound_md5_does
|
|
114
|
+
sender = nil
|
|
115
|
+
signer = key('hmac-md5')
|
|
116
|
+
query = request(signer)
|
|
117
|
+
wire, = signed_wire(query, algorithm: 'hmac-md5.sig-alg.reg.int')
|
|
118
|
+
klass = Dnsruby::RR::TSIG
|
|
119
|
+
mutex = klass.const_get(:LEGACY_WARNING_MUTEX)
|
|
120
|
+
previous = mutex.synchronize do
|
|
121
|
+
old = klass.instance_variable_get(:@legacy_warning_issued)
|
|
122
|
+
klass.instance_variable_set(:@legacy_warning_issued, false)
|
|
123
|
+
old
|
|
124
|
+
end
|
|
125
|
+
output = StringIO.new
|
|
126
|
+
logger = Logger.new(output)
|
|
127
|
+
logger.level = Logger::WARN
|
|
128
|
+
Dnsruby.stub(:log, logger) do
|
|
129
|
+
assert_equal(Dnsruby::RR::TSIG::HMAC_MD5, Dnsruby::Message.decode(wire).tsig.algorithm)
|
|
130
|
+
assert_equal(true, verify(signer, query, wire).first)
|
|
131
|
+
assert_equal(false, verify(key, query, wire).first)
|
|
132
|
+
assert_empty(output.string)
|
|
133
|
+
sender = Dnsruby::PacketSender.new(server: '127.0.0.1')
|
|
134
|
+
message = Dnsruby::Message.decode(wire)
|
|
135
|
+
message.send_raw = true
|
|
136
|
+
sender.make_query_packet(message)
|
|
137
|
+
assert_match(/WARN.*HMAC-MD5/, output.string)
|
|
138
|
+
sender.make_query_packet(message)
|
|
139
|
+
assert_equal(1, output.string.lines.size)
|
|
140
|
+
end
|
|
141
|
+
ensure
|
|
142
|
+
sender&.close
|
|
143
|
+
mutex&.synchronize { klass.instance_variable_set(:@legacy_warning_issued, previous) }
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def test_algorithm_names_are_case_insensitive_strings_and_names
|
|
147
|
+
['HMAC-SHA256.', Dnsruby::Name.new([Dnsruby::Name::Label.new('HmAc-ShA256')])].each do |algorithm|
|
|
148
|
+
signer = key(algorithm)
|
|
149
|
+
query = request(signer)
|
|
150
|
+
wire, = signed_wire(query, algorithm: 'HmAc-ShA256')
|
|
151
|
+
assert_equal(true, verify(signer, query, wire).first)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def test_allowed_mac_prefixes_and_protocol_bounds
|
|
156
|
+
{'hmac-sha1' => [10, 12, 20], 'hmac-sha256' => [16, 24, 32], 'hmac-sha512' => [32, 48, 64]}.each do |algorithm, lengths|
|
|
157
|
+
signer = key(algorithm)
|
|
158
|
+
query = request(signer)
|
|
159
|
+
lengths.each do |length|
|
|
160
|
+
wire, = signed_wire(query, algorithm: algorithm, length: length)
|
|
161
|
+
result, reply = verify(signer, query, wire)
|
|
162
|
+
assert_equal(true, result, "#{algorithm}/#{length}")
|
|
163
|
+
assert(reply.verified?)
|
|
164
|
+
end
|
|
165
|
+
[0, lengths.first - 1, lengths.last + 1].each do |length|
|
|
166
|
+
wire, = signed_wire(query, algorithm: algorithm, length: length)
|
|
167
|
+
result, reply = verify(signer, query, wire)
|
|
168
|
+
assert_equal(false, result)
|
|
169
|
+
assert_equal(Dnsruby::RCode::FORMERR, reply.tsigerror)
|
|
170
|
+
refute(reply.verified?)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def test_configured_mac_length_is_separate_from_wire_size_and_sets_local_minimum
|
|
176
|
+
signer = key(mac_length: 16)
|
|
177
|
+
query = request(signer)
|
|
178
|
+
assert_equal(16, query.tsig.mac_size)
|
|
179
|
+
assert_equal(16, query.tsig.mac_length)
|
|
180
|
+
[16, 24, 32].each do |length|
|
|
181
|
+
wire, = signed_wire(query, length: length)
|
|
182
|
+
assert_equal(true, verify(query.tsig, query, wire).first)
|
|
183
|
+
end
|
|
184
|
+
strict = key(mac_length: 32)
|
|
185
|
+
query = request(strict)
|
|
186
|
+
wire, = signed_wire(query, length: 16)
|
|
187
|
+
result, reply = verify(strict, query, wire)
|
|
188
|
+
assert_equal(false, result)
|
|
189
|
+
assert_equal(22, reply.tsigerror)
|
|
190
|
+
[1, 15, 33].each do |length|
|
|
191
|
+
assert_raises(Dnsruby::EncodeError) { request(key(mac_length: length)) }
|
|
192
|
+
end
|
|
193
|
+
[0, -1, 16.5, '16'].each do |length|
|
|
194
|
+
assert_raises(ArgumentError) { key(mac_length: length) }
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def test_mac_is_checked_before_time_and_badtime_remains_authenticatable
|
|
199
|
+
signer = key
|
|
200
|
+
query = request(signer)
|
|
201
|
+
wire, = signed_wire(query, time: Time.now.to_i - 10_000)
|
|
202
|
+
valid, reply = verify(signer, query, wire)
|
|
203
|
+
assert_equal(false, valid)
|
|
204
|
+
assert_equal(Dnsruby::RCode::BADTIME, reply.tsigerror)
|
|
205
|
+
forged = wire.dup
|
|
206
|
+
decoded = Dnsruby::Message.decode(wire)
|
|
207
|
+
mac_offset = decoded.tsigstart + dns_name('key.example.').bytesize + 10 + dns_name('hmac-sha256').bytesize + 10
|
|
208
|
+
forged.setbyte(mac_offset, forged.getbyte(mac_offset) ^ 1)
|
|
209
|
+
valid, reply = verify(signer, query, forged)
|
|
210
|
+
assert_equal(false, valid)
|
|
211
|
+
assert_equal(Dnsruby::RCode::BADSIG, reply.tsigerror)
|
|
212
|
+
wire, = signed_wire(query, error: 18, time: Time.now.to_i - 10_000, other: [0, Time.now.to_i].pack('nN'))
|
|
213
|
+
result, reply = verify(signer, query, wire)
|
|
214
|
+
assert_equal(false, result)
|
|
215
|
+
refute(reply.verified?)
|
|
216
|
+
wire, = signed_wire(query, error: 18, time: query.tsig.time_signed, other: [0, Time.now.to_i + 10_000].pack('nN'))
|
|
217
|
+
result, reply = verify(signer, query, wire)
|
|
218
|
+
assert_equal(false, result)
|
|
219
|
+
assert(reply.verified?)
|
|
220
|
+
assert_equal(18, reply.tsigerror)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def test_original_id_is_substituted_in_the_mac_input_without_mutating_the_message
|
|
224
|
+
signer = key
|
|
225
|
+
query = request(signer)
|
|
226
|
+
wire, = signed_wire(query, original_id: query.header.id, header_id: 54321)
|
|
227
|
+
frozen_wire = wire.freeze
|
|
228
|
+
result, reply = verify(signer, query, frozen_wire)
|
|
229
|
+
assert_equal(true, result)
|
|
230
|
+
assert_equal(54321, reply.header.id)
|
|
231
|
+
assert_equal(frozen_wire, wire)
|
|
232
|
+
tampered = wire.dup
|
|
233
|
+
tampered[-6, 2] = [query.header.id + 1].pack('n')
|
|
234
|
+
result, reply = verify(signer, query, tampered)
|
|
235
|
+
assert_equal(false, result)
|
|
236
|
+
assert_equal(Dnsruby::RCode::BADSIG, reply.tsigerror)
|
|
237
|
+
assert_equal(54321, reply.header.id)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def test_other_data_is_checked_even_with_a_valid_mac
|
|
241
|
+
signer = key
|
|
242
|
+
query = request(signer)
|
|
243
|
+
[[0, 'unexpected'], [18, 'short'], [22, 'unexpected']].each do |error, other|
|
|
244
|
+
wire, = signed_wire(query, error: error, other: other)
|
|
245
|
+
result, reply = verify(signer, query, wire)
|
|
246
|
+
assert_equal(false, result)
|
|
247
|
+
assert_equal(Dnsruby::RCode::FORMERR, reply.tsigerror)
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def test_badtime_generation_echoes_request_timers_and_preserves_server_time
|
|
252
|
+
signer = key(fudge: 123)
|
|
253
|
+
query = request(signer)
|
|
254
|
+
server_time = Time.now.to_i + 10_000
|
|
255
|
+
error_key = key(error: 18, time_signed: server_time, fudge: 30,
|
|
256
|
+
other_size: 6, other_data: [0, server_time].pack('nN'))
|
|
257
|
+
reply = Dnsruby::Message.new('example.', 'A')
|
|
258
|
+
reply.header.qr, reply.header.rcode = true, 9
|
|
259
|
+
error_key.apply(reply, query)
|
|
260
|
+
assert_equal(query.tsig.time_signed, reply.tsig.time_signed)
|
|
261
|
+
assert_equal(query.tsig.fudge, reply.tsig.fudge)
|
|
262
|
+
assert_equal([0, server_time].pack('nN'), reply.tsig.other_data)
|
|
263
|
+
_, expected_mac = signed_wire(query, time: query.tsig.time_signed, fudge: 123,
|
|
264
|
+
error: 18, other: [0, server_time].pack('nN'))
|
|
265
|
+
assert_equal(expected_mac, reply.tsig.mac)
|
|
266
|
+
result, decoded = verify(signer, query, reply.encode)
|
|
267
|
+
assert_equal(false, result)
|
|
268
|
+
assert(decoded.verified?)
|
|
269
|
+
[16, 17].each do |error|
|
|
270
|
+
reply = Dnsruby::Message.new('example.')
|
|
271
|
+
reply.header.qr, reply.header.rcode = true, 9
|
|
272
|
+
assert_raises(Dnsruby::EncodeError) { key(error: error).apply(reply, query) }
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def test_response_to_a_forwarded_request_retains_transport_and_original_ids
|
|
277
|
+
signer = key
|
|
278
|
+
query = request(signer)
|
|
279
|
+
forwarded = query.encode
|
|
280
|
+
forwarded[0, 2] = [54321].pack('n')
|
|
281
|
+
forwarded = Dnsruby::Message.decode(forwarded)
|
|
282
|
+
reply = Dnsruby::Message.new('example.', 'A')
|
|
283
|
+
reply.header.qr = true
|
|
284
|
+
signer.apply(reply, forwarded)
|
|
285
|
+
assert_equal(54321, reply.header.id)
|
|
286
|
+
assert_equal(query.header.id, reply.tsig.original_id)
|
|
287
|
+
_, expected_mac = signed_wire(forwarded, time: reply.tsig.time_signed,
|
|
288
|
+
original_id: query.header.id, header_id: 54321)
|
|
289
|
+
assert_equal(expected_mac, reply.tsig.mac)
|
|
290
|
+
assert_equal(true, verify(signer, query, reply.encode).first)
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def test_stream_chains_received_truncated_macs_and_accepts_99_unsigned_messages
|
|
294
|
+
signer = key(mac_length: 16)
|
|
295
|
+
query = request(signer)
|
|
296
|
+
now = Time.now.to_i
|
|
297
|
+
wire, mac = signed_wire(query, length: 16, time: now)
|
|
298
|
+
assert_equal(true, signer.verify_envelope(Dnsruby::Message.decode(wire), wire))
|
|
299
|
+
prior = [mac.bytesize].pack('n') + mac
|
|
300
|
+
99.times do
|
|
301
|
+
unsigned = Dnsruby::Message.new('example.').encode
|
|
302
|
+
reply = Dnsruby::Message.decode(unsigned)
|
|
303
|
+
assert_equal(true, signer.verify_envelope(reply, unsigned))
|
|
304
|
+
assert_equal(:Intermediate, reply.tsigstate)
|
|
305
|
+
prior += unsigned
|
|
306
|
+
end
|
|
307
|
+
wire, mac = signed_wire(query, length: 32, prior: prior, time: now)
|
|
308
|
+
assert_equal(true, signer.verify_envelope(Dnsruby::Message.decode(wire), wire))
|
|
309
|
+
wire, = signed_wire(query, length: 16, prior: [mac.bytesize].pack('n') + mac, time: now)
|
|
310
|
+
assert_equal(true, signer.verify_envelope(Dnsruby::Message.decode(wire), wire))
|
|
311
|
+
99.times { signer.verify_envelope(Dnsruby::Message.new, Dnsruby::Message.new.encode) }
|
|
312
|
+
assert_equal(false, signer.verify_envelope(Dnsruby::Message.new, Dnsruby::Message.new.encode))
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def test_signed_stream_times_cannot_decrease
|
|
316
|
+
signer = key
|
|
317
|
+
query = request(signer)
|
|
318
|
+
now = Time.now.to_i
|
|
319
|
+
wire, mac = signed_wire(query, time: now)
|
|
320
|
+
assert_equal(true, signer.verify_envelope(Dnsruby::Message.decode(wire), wire))
|
|
321
|
+
wire, = signed_wire(query, prior: [mac.bytesize].pack('n') + mac, time: now - 1)
|
|
322
|
+
reply = Dnsruby::Message.decode(wire)
|
|
323
|
+
assert_equal(false, signer.verify_envelope(reply, wire))
|
|
324
|
+
assert_equal(Dnsruby::RCode::BADTIME, reply.tsigerror)
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def test_signing_rejects_invalid_request_fields_and_supports_full_fudge_range
|
|
328
|
+
assert_equal(65_535, key(fudge: 65_535).fudge)
|
|
329
|
+
[{time_signed: 1 << 48}, {time_signed: -1}, {ttl: 60}, {error: 18}, {other_size: 1}].each do |fields|
|
|
330
|
+
assert_raises(Dnsruby::EncodeError) { request(key(**fields)) }
|
|
331
|
+
end
|
|
332
|
+
query = request(key)
|
|
333
|
+
decoded = Dnsruby::Message.decode(query.encode)
|
|
334
|
+
assert_raises(Dnsruby::EncodeError) { key.apply(decoded) }
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
def test_algorithm_name_compression_is_rejected
|
|
338
|
+
signer = key
|
|
339
|
+
query = request(signer)
|
|
340
|
+
wire, = signed_wire(query)
|
|
341
|
+
decoded = Dnsruby::Message.decode(wire)
|
|
342
|
+
rdata_at = decoded.tsigstart + dns_name('key.example.').bytesize + 10
|
|
343
|
+
wire[rdata_at, dns_name('hmac-sha256').bytesize] = [0xc00c].pack('n')
|
|
344
|
+
length_at = rdata_at - 2
|
|
345
|
+
wire[length_at, 2] = [wire.bytesize - rdata_at].pack('n')
|
|
346
|
+
assert_raises(Dnsruby::DecodeError) { Dnsruby::Message.decode(wire) }
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def test_signing_rejects_existing_signatures_in_any_section
|
|
350
|
+
[:answer, :authority, :additional].each do |section|
|
|
351
|
+
message = Dnsruby::Message.new('example.')
|
|
352
|
+
message.public_send(section) << request(key).tsig
|
|
353
|
+
message.add_additional(Dnsruby::RR.create('extra.example. 0 IN TXT "data"'))
|
|
354
|
+
assert_raises(Dnsruby::EncodeError) { key.apply(message) }
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
end
|