dnsruby 1.73.1 → 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.
Files changed (167) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +16 -4
  3. data/DNSSEC +3 -2
  4. data/EXAMPLES +1 -1
  5. data/README.md +370 -64
  6. data/RELEASE_NOTES.md +314 -1
  7. data/Rakefile +0 -3
  8. data/demo/axfr.rb +24 -6
  9. data/dnsruby.gemspec +6 -4
  10. data/lib/dnsruby/cache.rb +279 -49
  11. data/lib/dnsruby/code_mapper.rb +35 -8
  12. data/lib/dnsruby/code_mappers.rb +63 -49
  13. data/lib/dnsruby/config.rb +77 -4
  14. data/lib/dnsruby/deadline_queue.rb +75 -0
  15. data/lib/dnsruby/dnssec.rb +148 -45
  16. data/lib/dnsruby/ipv6.rb +45 -5
  17. data/lib/dnsruby/key_cache.rb +202 -35
  18. data/lib/dnsruby/message/decoder.rb +85 -19
  19. data/lib/dnsruby/message/encoder.rb +27 -16
  20. data/lib/dnsruby/message/header.rb +8 -12
  21. data/lib/dnsruby/message/message.rb +53 -22
  22. data/lib/dnsruby/message/section.rb +12 -34
  23. data/lib/dnsruby/name.rb +5 -2
  24. data/lib/dnsruby/packet_sender.rb +664 -207
  25. data/lib/dnsruby/presentation.rb +147 -0
  26. data/lib/dnsruby/query_control.rb +132 -0
  27. data/lib/dnsruby/recursor.rb +798 -341
  28. data/lib/dnsruby/resolver.rb +472 -176
  29. data/lib/dnsruby/resource/CAA.rb +67 -16
  30. data/lib/dnsruby/resource/DLV.rb +6 -0
  31. data/lib/dnsruby/resource/DNSKEY.rb +108 -16
  32. data/lib/dnsruby/resource/DS.rb +27 -28
  33. data/lib/dnsruby/resource/HTTPS.rb +16 -0
  34. data/lib/dnsruby/resource/IN.rb +21 -2
  35. data/lib/dnsruby/resource/MX.rb +2 -2
  36. data/lib/dnsruby/resource/NSEC.rb +6 -10
  37. data/lib/dnsruby/resource/NSEC3.rb +55 -19
  38. data/lib/dnsruby/resource/NSEC3PARAM.rb +4 -6
  39. data/lib/dnsruby/resource/OPT.rb +2 -3
  40. data/lib/dnsruby/resource/RR.rb +108 -65
  41. data/lib/dnsruby/resource/RRSIG.rb +5 -5
  42. data/lib/dnsruby/resource/RRSet.rb +38 -22
  43. data/lib/dnsruby/resource/SRV.rb +2 -2
  44. data/lib/dnsruby/resource/SVCB.rb +775 -0
  45. data/lib/dnsruby/resource/TSIG.rb +266 -231
  46. data/lib/dnsruby/resource/TXT.rb +34 -42
  47. data/lib/dnsruby/resource/domain_name.rb +1 -1
  48. data/lib/dnsruby/resource/generic.rb +31 -0
  49. data/lib/dnsruby/resource/generic_cache.rb +384 -0
  50. data/lib/dnsruby/resource/generic_constants.rb +79 -0
  51. data/lib/dnsruby/select_thread.rb +986 -291
  52. data/lib/dnsruby/serial_time.rb +25 -0
  53. data/lib/dnsruby/single_resolver.rb +25 -6
  54. data/lib/dnsruby/single_verifier.rb +1620 -530
  55. data/lib/dnsruby/transport_clock.rb +9 -0
  56. data/lib/dnsruby/update.rb +8 -6
  57. data/lib/dnsruby/validation_budget.rb +143 -0
  58. data/lib/dnsruby/validator_thread.rb +139 -58
  59. data/lib/dnsruby/version.rb +1 -1
  60. data/lib/dnsruby/zone_reader.rb +196 -129
  61. data/lib/dnsruby/zone_transfer.rb +157 -25
  62. data/lib/dnsruby.rb +7 -2
  63. data/test/ci_watchdog.rb +33 -0
  64. data/test/fixtures/org-ds-2026-11-05.zone +5 -0
  65. data/test/fixtures/root-dnskey-2026-11-05.zone +7 -0
  66. data/test/fork_test_helper.rb +37 -0
  67. data/test/local_dns_server.rb +57 -0
  68. data/test/soak_server.rb +152 -0
  69. data/test/socket_helpers.rb +26 -0
  70. data/test/spec_helper.rb +8 -13
  71. data/test/tc_attempt_configuration.rb +137 -0
  72. data/test/tc_caa.rb +2 -2
  73. data/test/tc_cache_keys.rb +36 -0
  74. data/test/tc_cancelled_send_capacity.rb +158 -0
  75. data/test/tc_dnssec_alias_validation.rb +289 -0
  76. data/test/tc_dnssec_budget.rb +489 -0
  77. data/test/tc_dnssec_cache_bounds.rb +197 -0
  78. data/test/tc_dnssec_cache_lifetime.rb +615 -0
  79. data/test/tc_dnssec_denial_proofs.rb +285 -0
  80. data/test/tc_dnssec_recursion_limits.rb +186 -0
  81. data/test/tc_dnssec_recursor_review_fixes.rb +351 -0
  82. data/test/tc_dnssec_trust_anchors.rb +286 -0
  83. data/test/tc_dnssec_trust_races.rb +147 -0
  84. data/test/tc_dnssec_wildcard_proofs.rb +249 -0
  85. data/test/tc_ds.rb +5 -5
  86. data/test/tc_edns_retry_regressions.rb +255 -0
  87. data/test/tc_escapedchars.rb +8 -0
  88. data/test/tc_final_review_fixes.rb +167 -0
  89. data/test/tc_final_transport_contracts.rb +104 -0
  90. data/test/tc_generic_cache.rb +852 -0
  91. data/test/tc_header.rb +26 -0
  92. data/test/tc_ipv6.rb +120 -0
  93. data/test/tc_key_cache_identity.rb +382 -0
  94. data/test/tc_malformed_tcp.rb +712 -0
  95. data/test/tc_name.rb +99 -5
  96. data/test/tc_nameserver_bootstrap.rb +322 -0
  97. data/test/tc_nsec.rb +4 -45
  98. data/test/tc_nsec3.rb +17 -1
  99. data/test/tc_pipeline_retirement.rb +358 -0
  100. data/test/tc_pipeline_retry.rb +462 -0
  101. data/test/tc_pipeline_send.rb +663 -0
  102. data/test/tc_protocol_review_fixes.rb +95 -0
  103. data/test/tc_query_control.rb +90 -0
  104. data/test/tc_record_codec_contracts.rb +107 -0
  105. data/test/tc_record_regressions.rb +225 -0
  106. data/test/tc_recur.rb +1 -0
  107. data/test/tc_recursor_address_fallback.rb +169 -0
  108. data/test/tc_recursor_limits.rb +139 -0
  109. data/test/tc_release_auxiliary_fixes.rb +288 -0
  110. data/test/tc_release_configuration_fixes.rb +612 -0
  111. data/test/tc_release_dnssec_completion.rb +405 -0
  112. data/test/tc_release_dnssec_fixes.rb +305 -0
  113. data/test/tc_release_negative_cache.rb +327 -0
  114. data/test/tc_release_recursor_cache.rb +212 -0
  115. data/test/tc_release_review_transport.rb +372 -0
  116. data/test/tc_release_review_transport_native.rb +56 -0
  117. data/test/tc_release_transport.rb +906 -0
  118. data/test/tc_release_transport_fixes.rb +196 -0
  119. data/test/tc_release_zone_defaults.rb +157 -0
  120. data/test/tc_release_zone_ttls.rb +39 -0
  121. data/test/tc_res_config.rb +53 -2
  122. data/test/tc_res_opt.rb +21 -1
  123. data/test/tc_resolver.rb +34 -19
  124. data/test/tc_resolver_concurrency.rb +383 -0
  125. data/test/tc_resolver_configuration_contracts.rb +31 -0
  126. data/test/tc_resolver_validation.rb +211 -0
  127. data/test/tc_review_cache.rb +163 -0
  128. data/test/tc_review_code_mapper.rb +279 -0
  129. data/test/tc_review_control_deadlines.rb +169 -0
  130. data/test/tc_review_followup_transport.rb +583 -0
  131. data/test/tc_review_packet_contracts.rb +369 -0
  132. data/test/tc_review_presentation.rb +213 -0
  133. data/test/tc_review_record_compatibility.rb +101 -0
  134. data/test/tc_review_transport_fixes.rb +195 -0
  135. data/test/tc_review_transport_native.rb +415 -0
  136. data/test/tc_review_validation_priming.rb +178 -0
  137. data/test/tc_rr-opt.rb +5 -2
  138. data/test/tc_rr-unknown.rb +19 -0
  139. data/test/tc_rr.rb +14 -0
  140. data/test/tc_rrset_grouping.rb +50 -0
  141. data/test/tc_rrset_review_fixes.rb +58 -0
  142. data/test/tc_rrsig.rb +21 -1
  143. data/test/tc_scoped_ipv6.rb +153 -0
  144. data/test/tc_scoped_ipv6_select.rb +157 -0
  145. data/test/tc_scoped_ipv6_transport.rb +329 -0
  146. data/test/tc_selector_wakeup.rb +42 -0
  147. data/test/tc_soak.rb +118 -196
  148. data/test/tc_socket_helpers.rb +90 -0
  149. data/test/tc_supporting_lookup_limits.rb +252 -0
  150. data/test/tc_svcb.rb +1043 -0
  151. data/test/tc_svcb_signed_target.rb +145 -0
  152. data/test/tc_svcb_transport.rb +120 -0
  153. data/test/tc_tcp.rb +69 -43
  154. data/test/tc_tcp_pipelining.rb +95 -204
  155. data/test/tc_transport_fork.rb +179 -0
  156. data/test/tc_tsig.rb +2 -2
  157. data/test/tc_tsig_api.rb +251 -0
  158. data/test/tc_tsig_rfc8945.rb +357 -0
  159. data/test/tc_v2_release_dnssec.rb +442 -0
  160. data/test/tc_v2_release_transport.rb +304 -0
  161. data/test/tc_v2_zone_transfer_native.rb +58 -0
  162. data/test/tc_zone_reader.rb +217 -1
  163. data/test/tcp_pipelining_server.rb +150 -0
  164. data/test/ts_offline.rb +77 -6
  165. data/test/ts_online.rb +2 -1
  166. metadata +127 -14
  167. data/.coveralls.yml +0 -2
@@ -15,6 +15,7 @@
15
15
  # ++
16
16
  require 'digest/sha2'
17
17
  require 'dnsruby/key_cache'
18
+ require 'dnsruby/validation_budget'
18
19
  require 'dnsruby/single_verifier'
19
20
  module Dnsruby
20
21
 
@@ -27,13 +28,10 @@ module Dnsruby
27
28
  # able to treat the DNSSEC signatures as trust relationships between
28
29
  # the zone administrators and the stub resolver itself. "
29
30
  #
30
- # Dnsruby is configured to validate responses by default. However, it is not
31
- # configured with any trusted keys by default. Applications may use the
32
- # verify() method to perform verification with of RRSets of Messages with
33
- # given keys. Alternatively, trusted keys may be added to this class (either
34
- # directly, or by loading the IANA TAR or the DLV ISC ZSK). Validation will then
35
- # be performed from these keys (or the DLV registry, if configured). Negative
36
- # and positive responses are validation.
31
+ # Resolver queries do not validate responses by default; set
32
+ # Resolver#do_validation to true to enable validation. Root DS trust anchors
33
+ # are configured below. Applications may also use verify() to verify RRSets
34
+ # or Messages with given keys, or add their own trust anchors to this class.
37
35
  #
38
36
  # Messages are tagged with the current security_level (Message::SecurityLevel).
39
37
  # UNCHECKED means Dnsruby has not attempted to validate the response.
@@ -44,14 +42,32 @@ module Dnsruby
44
42
  # Several validators are provided, with each maintaining its own cache of trusted keys.
45
43
  # If validators are added or removed, the caches of the other validators are not affected.
46
44
  class Dnssec
45
+ # Limits apply to one complete validation, including trust-chain discovery.
46
+ # A limit is a VerifyError; it never downgrades a signed answer to insecure.
47
+ @validation_limits = ValidationBudget::DEFAULT_LIMITS
48
+ def self.validation_limits
49
+ @validation_limits
50
+ end
51
+
52
+ def self.validation_limits=(limits)
53
+ unknown = limits.keys - ValidationBudget::DEFAULT_LIMITS.keys
54
+ raise ArgumentError, "Unknown DNSSEC validation limits: #{unknown.join(', ')}" unless unknown.empty?
55
+ merged = ValidationBudget::DEFAULT_LIMITS.merge(limits)
56
+ merged.each do |key, value|
57
+ valid = key == :timeout ? value.is_a?(Numeric) && value.finite? && value > 0 : value.is_a?(Integer) && value > 0
58
+ raise ArgumentError, "Invalid DNSSEC validation limit #{key}" unless valid
59
+ end
60
+ @validation_limits = merged.freeze
61
+ end
47
62
  # A class to cache trusted keys
48
63
 
49
64
 
50
65
  class ValidationPolicy
51
66
  # @TODO@ Could do this by getting client to add verifiers in the order they
52
67
  # want them to be used. Could then dispense with all this logic
53
- # Note that any DLV registries which have been configured will only be tried
54
- # after both the root and any local trust anchors (RFC 5074 section 5)
68
+ # Configured DLV registries are a final fallback for the two ordered
69
+ # policies, after root/local anchors, when those paths are not bogus.
70
+ # The two ONLY policies never consult another source of trust.
55
71
 
56
72
  # * Always use the root and ignore local trust anchors.
57
73
  ALWAYS_ROOT_ONLY = 1
@@ -74,15 +90,16 @@ module Dnsruby
74
90
  @@validation_policy
75
91
  end
76
92
 
77
- @@root_verifier = SingleVerifier.new(SingleVerifier::VerifierType::ROOT)
78
-
79
93
  # #NOTE# You may wish to import these via a secure channel yourself, if
80
94
  # using Dnsruby for validation.
81
- @@root_key = RR.create(". IN DS 19036 8 2 49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5")
82
- @@root_verifier.add_root_ds(@@root_key)
95
+ # KSK-2017 and KSK-2024, published at https://data.iana.org/root-anchors/root-anchors.xml
96
+ ROOT_TRUST_ANCHORS = [
97
+ ". IN DS 20326 8 2 E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D".freeze,
98
+ ". IN DS 38696 8 2 683D2D0ACB8C9B712A1948B27F741219298D0A450D612C483AF444A4C0FB2B16".freeze
99
+ ].freeze
83
100
 
84
- @@root_key_new = RR.create(". IN DS 20326 8 2 E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D")
85
- @@root_verifier.add_root_ds(@@root_key_new)
101
+ @@root_verifier = SingleVerifier.new(SingleVerifier::VerifierType::ROOT)
102
+ ROOT_TRUST_ANCHORS.each {|ds| @@root_verifier.add_root_ds(RR.create(ds))}
86
103
 
87
104
  @@dlv_verifier = SingleVerifier.new(SingleVerifier::VerifierType::DLV)
88
105
 
@@ -104,11 +121,12 @@ module Dnsruby
104
121
  # Create a new verifier?
105
122
  @@anchor_verifier.add_trust_anchor_with_expiration(k, expiration)
106
123
  end
107
- # Remove the trusted key
124
+ # Remove a configured trust anchor and clear keys learned from local anchors.
108
125
  def Dnssec.remove_trust_anchor(t)
109
126
  @@anchor_verifier.remove_trust_anchor(t)
110
127
  end
111
- # Wipes the cache of trusted keys
128
+ # Remove all local trust anchors and keys learned from them.
129
+ # Built-in root anchors are unaffected.
112
130
  def self.clear_trust_anchors
113
131
  @@anchor_verifier.clear_trust_anchors
114
132
  end
@@ -117,6 +135,8 @@ module Dnsruby
117
135
  return @@anchor_verifier.trust_anchors
118
136
  end
119
137
 
138
+ # Clear learned keys and all DS/DLV anchors, including the built-in root DS
139
+ # anchors. Configured DNSKEY anchors are retained.
120
140
  def self.clear_trusted_keys
121
141
  [@@anchor_verifier, @@root_verifier, @@dlv_verifier].each {|v|
122
142
  v.clear_trusted_keys
@@ -124,10 +144,10 @@ module Dnsruby
124
144
  end
125
145
 
126
146
  def self.reset
147
+ SingleVerifier.set_recursor_hints(nil)
127
148
  @@validation_policy = ValidationPolicy::LOCAL_ANCHORS_THEN_ROOT
128
149
  @@root_verifier = SingleVerifier.new(SingleVerifier::VerifierType::ROOT)
129
- @@root_verifier.add_root_ds(@@root_key)
130
- @@root_verifier.add_root_ds(@@root_key_new)
150
+ ROOT_TRUST_ANCHORS.each {|ds| @@root_verifier.add_root_ds(RR.create(ds))}
131
151
 
132
152
  @@dlv_verifier = SingleVerifier.new(SingleVerifier::VerifierType::DLV)
133
153
 
@@ -138,8 +158,7 @@ module Dnsruby
138
158
  end
139
159
 
140
160
  def self.set_hints(hints)
141
- @@root_verifier.set_hints(hints)
142
- @@anchor_verifier.set_hints(hints)
161
+ SingleVerifier.set_recursor_hints(hints)
143
162
  end
144
163
 
145
164
  def self.no_keys?
@@ -184,10 +203,14 @@ module Dnsruby
184
203
  def self.validate(msg)
185
204
  query = Message.new()
186
205
  query.header.cd=true
187
- return self.validate_with_query(query, msg)
206
+ validate_with_query(query, msg)
188
207
  end
189
208
 
190
209
  def self.validate_with_query(query, msg)
210
+ ValidationBudget.with { validate_with_query_internal(query, msg) }
211
+ end
212
+
213
+ private_class_method def self.validate_with_query_internal(query, msg)
191
214
  if (!msg)
192
215
  return false
193
216
  end
@@ -200,13 +223,19 @@ module Dnsruby
200
223
  }
201
224
  if (found_sigs)
202
225
  begin
203
- if (verify(msg))
226
+ result = verify_result(msg, nil, operational: true)
227
+ if result == :secure
204
228
  msg.security_level = Message::SecurityLevel.SECURE
229
+ msg.security_error = ''
205
230
  return true
206
231
  end
207
- rescue VerifyError => e
208
- msg.security_error = e
209
- msg.security_level = Message::SecurityLevel.BOGUS
232
+ rescue ValidationLimitError
233
+ raise
234
+ rescue VerifyError
235
+ # Missing cached keys and a failed trusted path both arrive here.
236
+ # Full validation discovers the applicable anchors and distinguishes
237
+ # those cases. A cached INSECURE result must go through that policy
238
+ # too: another applicable anchor may establish SECURE or BOGUS.
210
239
  end
211
240
  end
212
241
 
@@ -244,17 +273,21 @@ module Dnsruby
244
273
  Proc.new{|m, q| validate_with_anchors(m, q)}, msg, query)
245
274
  end
246
275
  end
247
- if (last_level != Message::SecurityLevel.SECURE && last_level != Message::SecurityLevel.BOGUS)
276
+ if (last_level != Message::SecurityLevel.SECURE && last_level != Message::SecurityLevel.BOGUS) &&
277
+ [ValidationPolicy::LOCAL_ANCHORS_THEN_ROOT, ValidationPolicy::ROOT_THEN_LOCAL_ANCHORS].include?(@@validation_policy)
248
278
  last_level, last_error, last_error_level = try_validation(last_level, last_error, last_error_level,
249
279
  Proc.new{|m, q| validate_with_dlv(m, q)}, msg, query)
250
280
  end
251
281
  # Set the message security level!
282
+ if last_level == Message::SecurityLevel.SECURE
283
+ last_error = ''
284
+ elsif last_error && !last_error.empty?
285
+ last_level = Message::SecurityLevel.BOGUS
286
+ end
252
287
  msg.security_level = last_level
253
288
  msg.security_error = last_error
254
- if (last_error && last_error.index("ification error"))
255
- msg.security_level = Message::SecurityLevel.BOGUS
256
- end
257
- raise VerifyError.new(last_error) if (last_level < 0)
289
+ msg.header.ad = false if msg.security_level == Message::SecurityLevel.INSECURE
290
+ raise VerifyError.new(last_error) if (msg.security_level < 0)
258
291
  return (msg.security_level.code > Message::SecurityLevel::UNCHECKED)
259
292
  end
260
293
  msg.security_level = Message::SecurityLevel.UNCHECKED
@@ -262,11 +295,33 @@ module Dnsruby
262
295
  end
263
296
 
264
297
  def self.try_validation(last_level, last_error, last_error_level, proc, msg, query) # :nodoc:
298
+ ValidationBudget.with do
299
+ try_validation_internal(last_level, last_error, last_error_level, proc, msg, query)
300
+ end
301
+ end
302
+
303
+ private_class_method def self.try_validation_internal(last_level, last_error, last_error_level, proc, msg, query)
304
+ # Keep the lifetime with the successful result. A later unanchored
305
+ # verifier can return INSECURE without establishing a cacheable proof.
306
+ previous_expiration = if last_level == Message::SecurityLevel.INSECURE
307
+ msg.instance_variable_get(:@dnsruby_authenticated_expiration)
308
+ end
265
309
  begin
266
- proc.call(msg, query)
310
+ result = proc.call(msg, query)
311
+ if !result || msg.security_level == Message::SecurityLevel.BOGUS
312
+ detail = msg.security_error.to_s
313
+ raise VerifyError, detail.empty? ? 'DNSSEC validation failed' : detail
314
+ end
315
+ if previous_expiration && msg.security_level <= Message::SecurityLevel.INSECURE
316
+ current_expiration = msg.instance_variable_get(:@dnsruby_authenticated_expiration)
317
+ msg.instance_variable_set(:@dnsruby_authenticated_expiration,
318
+ [previous_expiration, current_expiration].compact.min)
319
+ end
267
320
  last_level = Message::SecurityLevel.new([msg.security_level.code, last_level.code].max)
268
321
  rescue VerifyError => e
269
- if (last_error_level < last_level)
322
+ # A failed trusted path cannot be erased by a later unanchored or
323
+ # insecure result. A separately authenticated SECURE path may win.
324
+ if last_error.to_s.empty? || last_error_level < last_level
270
325
  last_error = e.to_s
271
326
  last_error_level = last_level
272
327
  end
@@ -275,27 +330,62 @@ module Dnsruby
275
330
  end
276
331
 
277
332
  def self.validate_with_anchors(msg, query)
278
- return @@anchor_verifier.validate(msg, query)
333
+ @@anchor_verifier.validate(msg, query)
279
334
  end
280
335
 
281
336
  def self.validate_with_root(msg, query)
282
- return @@root_verifier.validate(msg, query)
337
+ @@root_verifier.validate(msg, query)
283
338
  end
284
339
 
285
340
  def self.validate_with_dlv(msg, query)
286
- return @@dlv_verifier.validate(msg, query)
341
+ @@dlv_verifier.validate(msg, query)
287
342
  end
288
343
 
289
344
  def self.verify(msg, keys=nil)
290
- begin
291
- return true if @@anchor_verifier.verify(msg, keys)
292
- rescue VerifyError
345
+ verify_result(msg, keys) == :secure
346
+ end
347
+
348
+ # Keep the result separate from mutable Message state: a previous validation
349
+ # must not turn a newly verified Opt-Out response into a secure response.
350
+ private_class_method def self.verify_result(msg, keys=nil, operational: false)
351
+ ValidationBudget.with { verify_internal(msg, keys, operational: operational) }
352
+ end
353
+
354
+ private_class_method def self.verify_internal(msg, keys, operational: false)
355
+ insecure = false
356
+ insecure_expiration = nil
357
+ last_error = nil
358
+ verifiers = if operational
359
+ case @@validation_policy
360
+ when ValidationPolicy::ALWAYS_ROOT_ONLY then [@@root_verifier]
361
+ when ValidationPolicy::ALWAYS_LOCAL_ANCHORS_ONLY then [@@anchor_verifier]
362
+ when ValidationPolicy::ROOT_THEN_LOCAL_ANCHORS then [@@root_verifier, @@anchor_verifier]
363
+ else [@@anchor_verifier, @@root_verifier]
364
+ end
365
+ else
366
+ [@@anchor_verifier, @@root_verifier, @@dlv_verifier]
367
+ end
368
+ verifiers.each do |verifier|
293
369
  begin
294
- return true if @@root_verifier.verify(msg, keys)
295
- rescue VerifyError
296
- return true if @@dlv_verifier.verify(msg, keys) # Will carry error to client
370
+ result = operational ? verifier.verify_for_validation(msg) : verifier.verify_result(msg, keys)
371
+ return :secure if result == :secure
372
+ if result == :insecure
373
+ insecure = true
374
+ expiration = msg.instance_variable_get(:@dnsruby_authenticated_expiration)
375
+ insecure_expiration = [insecure_expiration, expiration].compact.min
376
+ end
377
+ rescue ValidationLimitError
378
+ raise
379
+ rescue VerifyError => error
380
+ last_error = error
297
381
  end
298
382
  end
383
+ if insecure
384
+ msg.instance_variable_set(:@dnsruby_authenticated_expiration, insecure_expiration) if insecure_expiration
385
+ return :insecure
386
+ end
387
+ raise last_error if last_error
388
+ false
299
389
  end
300
390
 
301
391
  def self.anchor_verifier
@@ -312,9 +402,22 @@ module Dnsruby
312
402
 
313
403
 
314
404
  def self.verify_rrset(rrset, keys = nil)
315
- return ((@@anchor_verifier.verify_rrset(rrset, keys) ||
316
- @@root_verifier.verify_rrset(rrset, keys) ||
317
- @@dlv_verifier.verify_rrset(rrset, keys)))
405
+ ValidationBudget.with do
406
+ last_error = nil
407
+ verified = [@@anchor_verifier, @@root_verifier, @@dlv_verifier].any? do |verifier|
408
+ ValidationBudget.current.check!
409
+ begin
410
+ verifier.verify_rrset(rrset, keys)
411
+ rescue ValidationLimitError
412
+ raise
413
+ rescue VerifyError => error
414
+ last_error = error
415
+ false
416
+ end
417
+ end
418
+ raise last_error if !verified && last_error
419
+ verified
420
+ end
318
421
  end
319
422
  end
320
423
  end
data/lib/dnsruby/ipv6.rb CHANGED
@@ -68,6 +68,7 @@ module Dnsruby
68
68
  prefix.scan(/[0-9A-Fa-f]+/) {|hex| a1 << [hex.hex].pack('n')}
69
69
  suffix.scan(/[0-9A-Fa-f]+/) {|hex| a2 << [hex.hex].pack('n')}
70
70
  omitlen = 16 - a1.length - a2.length
71
+ raise ArgumentError, "not numeric IPv6 address: #{arg}" unless omitlen > 0
71
72
  address << a1 << "\0" * omitlen << a2
72
73
  elsif Regex_6Hex4Dec =~ arg
73
74
  prefix, a, b, c, d = $1, $2.to_i, $3.to_i, $4.to_i, $5.to_i
@@ -85,6 +86,7 @@ module Dnsruby
85
86
  prefix.scan(/[0-9A-Fa-f]+/) {|hex| a1 << [hex.hex].pack('n')}
86
87
  suffix.scan(/[0-9A-Fa-f]+/) {|hex| a2 << [hex.hex].pack('n')}
87
88
  omitlen = 12 - a1.length - a2.length
89
+ raise ArgumentError, "not numeric IPv6 address: #{arg}" unless omitlen > 0
88
90
  address << a1 << "\0" * omitlen << a2 << [a, b, c, d].pack('CCCC')
89
91
  else
90
92
  raise ArgumentError.new("not numeric IPv6 address: " + arg)
@@ -108,12 +110,50 @@ module Dnsruby
108
110
  # The raw IPv6 address as a String
109
111
  attr_reader :address
110
112
 
113
+ # The historical Dnsruby representation, retained for callers that compare
114
+ # address strings. Use #to_s_canonical for RFC 5952 formatting.
111
115
  def to_s
112
- address = sprintf("%X:%X:%X:%X:%X:%X:%X:%X", *@address.unpack("nnnnnnnn"))
113
- unless address.sub!(/(^|:)0(:0)+(:|$)/, '::')
114
- address.sub!(/(^|:)0(:|$)/, '::')
116
+ text = sprintf("%X:%X:%X:%X:%X:%X:%X:%X", *@address.unpack("n8"))
117
+ unless text.sub!(/(^|:)0(:0)+(:|$)/, '::')
118
+ text.sub!(/(^|:)0(:|$)/, '::')
115
119
  end
116
- return address
120
+ text
121
+ end
122
+
123
+ # The RFC 5952 canonical text representation of the address, opt-in.
124
+ def to_s_canonical
125
+ fields = @address.unpack("n8")
126
+
127
+ # RFC 5952 section 5: an address carrying an embedded IPv4 address
128
+ # under the well-known ::ffff:0:0/96 prefix is written in the mixed
129
+ # hexadecimal / dotted decimal notation.
130
+ if fields[0, 5].all?(&:zero?) && fields[5] == 0xffff
131
+ return "::ffff:" + fields[6, 2].pack("n2").unpack("C4").join(".")
132
+ end
133
+
134
+ # RFC 5952 section 4.2.3: "::" replaces the longest run of consecutive
135
+ # all-zero fields, and the first such run when several are equally long.
136
+ # Section 4.2.2: a run of a single all-zero field is never replaced.
137
+ run_start = nil
138
+ best_start, best_length = nil, 1
139
+ fields.each_with_index do |field, i|
140
+ if field == 0
141
+ run_start ||= i
142
+ length = i - run_start + 1
143
+ best_start, best_length = run_start, length if length > best_length
144
+ else
145
+ run_start = nil
146
+ end
147
+ end
148
+
149
+ # RFC 5952 sections 4.1 and 4.3: no leading zeros, and lowercase hex
150
+ strings = fields.map {|field| field.to_s(16)}
151
+ if best_start
152
+ strings[best_start, best_length] = ['']
153
+ strings.unshift('') if best_start == 0
154
+ strings.push('') if best_start + best_length == fields.length
155
+ end
156
+ return strings.join(':')
117
157
  end
118
158
 
119
159
  def inspect #:nodoc:
@@ -141,4 +181,4 @@ module Dnsruby
141
181
  return @address.hash
142
182
  end
143
183
  end
144
- end
184
+ end