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/lib/dnsruby/resource/CAA.rb
CHANGED
|
@@ -16,26 +16,28 @@
|
|
|
16
16
|
module Dnsruby
|
|
17
17
|
class RR
|
|
18
18
|
# Class for CAA resource records.
|
|
19
|
-
# RFC
|
|
19
|
+
# RFC 8659
|
|
20
20
|
class CAA < RR
|
|
21
21
|
ClassValue = nil #:nodoc: all
|
|
22
22
|
TypeValue= Types::CAA #:nodoc: all
|
|
23
23
|
|
|
24
|
-
# The property tag
|
|
24
|
+
# The property tag. Tags are extensible ASCII letters and digits.
|
|
25
25
|
attr_accessor :property_tag
|
|
26
26
|
# The value for the property_tag
|
|
27
27
|
attr_accessor :property_value
|
|
28
28
|
# The value for the flag
|
|
29
|
-
|
|
29
|
+
def flag=(value)
|
|
30
|
+
@flag = value.to_i
|
|
31
|
+
end
|
|
30
32
|
|
|
31
33
|
def from_hash(hash) #:nodoc: all
|
|
32
|
-
|
|
33
|
-
@property_value = hash[:property_value]
|
|
34
|
-
@flag = hash[:flag]
|
|
34
|
+
from_data([hash[:flag], hash[:property_tag], hash[:property_value]])
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def from_data(data) #:nodoc: all
|
|
38
|
-
|
|
38
|
+
flag, @property_tag, @property_value = data
|
|
39
|
+
self.flag = flag
|
|
40
|
+
validate_fields
|
|
39
41
|
end
|
|
40
42
|
|
|
41
43
|
def flag
|
|
@@ -43,20 +45,31 @@ module Dnsruby
|
|
|
43
45
|
end
|
|
44
46
|
|
|
45
47
|
def from_string(input) #:nodoc: all
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
# A CAA value is one character-string, with no 255-octet limit.
|
|
49
|
+
matches = /\A\s*(\d+)\s+([A-Za-z0-9]+)\s+("(?:\\.|[^"\\])*"|(?:\\.|[^\s"\\])+)\s*\z/m.match(input.b)
|
|
50
|
+
raise DecodeError, "Cannot parse CAA record: #{input[0...1000]}" unless matches
|
|
51
|
+
|
|
52
|
+
value = matches[3]
|
|
53
|
+
value = value[1...-1] if value.start_with?('"')
|
|
54
|
+
from_data([matches[1], matches[2], decode_value(value)])
|
|
53
55
|
end
|
|
54
56
|
|
|
55
57
|
def rdata_to_string #:nodoc: all
|
|
56
|
-
|
|
58
|
+
value = +''.b
|
|
59
|
+
@property_value.each_byte do |octet|
|
|
60
|
+
if octet == 34 || octet == 92
|
|
61
|
+
value << 92 << octet
|
|
62
|
+
elsif octet < 32 || octet > 126
|
|
63
|
+
value << format('\\%03d', octet)
|
|
64
|
+
else
|
|
65
|
+
value << octet
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
"#{flag} #{@property_tag} \"#{value}\""
|
|
57
69
|
end
|
|
58
70
|
|
|
59
71
|
def encode_rdata(msg, canonical=false) #:nodoc: all
|
|
72
|
+
validate_fields
|
|
60
73
|
msg.put_pack('C', flag)
|
|
61
74
|
msg.put_string(@property_tag)
|
|
62
75
|
# We don't put a length byte on the final string.
|
|
@@ -68,8 +81,46 @@ module Dnsruby
|
|
|
68
81
|
property_tag = msg.get_string
|
|
69
82
|
# The final string has no length byte - its length is implicit as the remainder of the packet length
|
|
70
83
|
property_value = msg.get_bytes
|
|
71
|
-
|
|
84
|
+
record = self.new([flag, property_tag, property_value])
|
|
85
|
+
# Keep the public construction-time rdata String used by Comparable
|
|
86
|
+
# and older serializers, without reparsing arbitrary wire value bytes.
|
|
87
|
+
record.rdata = record.rdata_to_string
|
|
88
|
+
record
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def validate_fields
|
|
92
|
+
unless (0..255).cover?(flag) && @property_tag.is_a?(String) &&
|
|
93
|
+
@property_tag.bytesize.between?(1, 255) && /\A[A-Za-z0-9]+\z/n.match?(@property_tag.b) &&
|
|
94
|
+
@property_value.is_a?(String)
|
|
95
|
+
raise DecodeError, 'CAA requires an octet flag, an alphanumeric tag and a string value'
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
private :validate_fields
|
|
99
|
+
|
|
100
|
+
def decode_value(value)
|
|
101
|
+
decoded = +''.b
|
|
102
|
+
index = 0
|
|
103
|
+
while index < value.bytesize
|
|
104
|
+
octet = value.getbyte(index)
|
|
105
|
+
if octet == 92
|
|
106
|
+
index += 1
|
|
107
|
+
octet = value.getbyte(index)
|
|
108
|
+
raise DecodeError, 'Incomplete CAA value escape' unless octet
|
|
109
|
+
if (48..57).cover?(octet)
|
|
110
|
+
digits = value.byteslice(index, 3)
|
|
111
|
+
unless /\A[0-9]{3}\z/.match?(digits) && digits.to_i <= 255
|
|
112
|
+
raise DecodeError, 'CAA decimal escapes must specify one octet with three digits'
|
|
113
|
+
end
|
|
114
|
+
octet = digits.to_i
|
|
115
|
+
index += 2
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
decoded << octet
|
|
119
|
+
index += 1
|
|
120
|
+
end
|
|
121
|
+
decoded
|
|
72
122
|
end
|
|
123
|
+
private :decode_value
|
|
73
124
|
end
|
|
74
125
|
end
|
|
75
126
|
end
|
data/lib/dnsruby/resource/DLV.rb
CHANGED
|
@@ -22,6 +22,12 @@ module Dnsruby
|
|
|
22
22
|
class DLV < RR::DS
|
|
23
23
|
ClassValue = nil #:nodoc: all
|
|
24
24
|
TypeValue = Types::DLV #:nodoc: all
|
|
25
|
+
|
|
26
|
+
# DLV owners are names within the lookaside registry, rather than the
|
|
27
|
+
# DNSKEY owner used in the digest. Keep that legacy distinction explicit.
|
|
28
|
+
def check_key(key)
|
|
29
|
+
key.type == Types.DNSKEY && key.klass == klass && matching_key_digest?(key)
|
|
30
|
+
end
|
|
25
31
|
end
|
|
26
32
|
end
|
|
27
33
|
end
|
|
@@ -77,6 +77,7 @@ module Dnsruby
|
|
|
77
77
|
begin
|
|
78
78
|
alg = Algorithms.new(a)
|
|
79
79
|
@algorithm = alg
|
|
80
|
+
@public_key = @key_length = nil
|
|
80
81
|
rescue ArgumentError => e
|
|
81
82
|
raise DecodeError.new(e)
|
|
82
83
|
end
|
|
@@ -207,13 +208,13 @@ module Dnsruby
|
|
|
207
208
|
|
|
208
209
|
def encode_rdata(msg, canonical=false) #:nodoc: all
|
|
209
210
|
# 2 octets, then 2 sets of 1 octet
|
|
210
|
-
msg.put_pack('
|
|
211
|
+
msg.put_pack('nCC', @flags, @protocol, @algorithm.code)
|
|
211
212
|
msg.put_bytes(@key)
|
|
212
213
|
end
|
|
213
214
|
|
|
214
215
|
def self.decode_rdata(msg) #:nodoc: all
|
|
215
216
|
# 2 octets, then 2 sets of 1 octet
|
|
216
|
-
flags, protocol, algorithm = msg.get_unpack('
|
|
217
|
+
flags, protocol, algorithm = msg.get_unpack('nCC')
|
|
217
218
|
key = msg.get_bytes
|
|
218
219
|
return self.new(
|
|
219
220
|
[flags, protocol, algorithm, key])
|
|
@@ -292,9 +293,8 @@ module Dnsruby
|
|
|
292
293
|
|
|
293
294
|
def key=(key_text)
|
|
294
295
|
begin
|
|
295
|
-
key_text.
|
|
296
|
-
|
|
297
|
-
@key=Base64.decode64(key_text)
|
|
296
|
+
@key=Base64.decode64(key_text.delete("\n "))
|
|
297
|
+
@public_key = @key_length = nil
|
|
298
298
|
public_key
|
|
299
299
|
get_new_key_tag
|
|
300
300
|
rescue Exception => e
|
|
@@ -315,31 +315,121 @@ module Dnsruby
|
|
|
315
315
|
@public_key = dsa_key
|
|
316
316
|
elsif [Algorithms.ECDSAP256SHA256, Algorithms.ECDSAP384SHA384].include?(@algorithm)
|
|
317
317
|
@public_key = ec_key(Algorithms.ECDSAP256SHA256 == @algorithm ? 'prime256v1' : 'secp384r1')
|
|
318
|
+
elsif @algorithm == Algorithms.ED25519 && DNSKEY.ed25519_supported?
|
|
319
|
+
@public_key = ed25519_key
|
|
320
|
+
elsif @algorithm == Algorithms.ED448 && DNSKEY.ed448_supported?
|
|
321
|
+
@public_key = ed448_key
|
|
318
322
|
end
|
|
319
323
|
end
|
|
320
324
|
# @TODO@ Support other key encodings!
|
|
321
325
|
return @public_key
|
|
322
326
|
end
|
|
323
327
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
+
# Check the actual provider as well as Ruby's OpenSSL API. Parsing a
|
|
329
|
+
# public key alone does not guarantee that signature verification works.
|
|
330
|
+
def self.ed25519_supported? #:nodoc:
|
|
331
|
+
return @ed25519_supported if defined?(@ed25519_supported)
|
|
332
|
+
return false unless defined?(OpenSSL::PKey)
|
|
333
|
+
public_bytes = ['d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a'].pack('H*')
|
|
334
|
+
signature = ['e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e06522490155' \
|
|
335
|
+
'5fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b'].pack('H*')
|
|
336
|
+
# RFC 8032 section 7.1, test 1 (the empty message).
|
|
337
|
+
key = OpenSSL::PKey.read(ed25519_subject_public_key(public_bytes))
|
|
338
|
+
@ed25519_supported = key.verify(nil, signature, ''.b)
|
|
339
|
+
rescue OpenSSL::OpenSSLError, NotImplementedError, TypeError, ArgumentError
|
|
340
|
+
@ed25519_supported = false
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
def self.ed25519_subject_public_key(bytes) #:nodoc:
|
|
344
|
+
# RFC 8410 SubjectPublicKeyInfo: Ed25519's algorithm has no parameters.
|
|
345
|
+
OpenSSL::ASN1::Sequence.new([
|
|
346
|
+
OpenSSL::ASN1::Sequence.new([OpenSSL::ASN1::ObjectId.new('1.3.101.112')]),
|
|
347
|
+
OpenSSL::ASN1::BitString.new(bytes)
|
|
348
|
+
]).to_der
|
|
349
|
+
end
|
|
350
|
+
private_class_method :ed25519_subject_public_key
|
|
351
|
+
|
|
352
|
+
def ed25519_key
|
|
353
|
+
unless @key.bytesize == 32
|
|
354
|
+
raise OpenSSL::PKey::PKeyError, 'Ed25519 DNSKEY must contain 32 octets'
|
|
355
|
+
end
|
|
356
|
+
@key_length = 256
|
|
357
|
+
OpenSSL::PKey.read(DNSKEY.send(:ed25519_subject_public_key, @key))
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
def self.ed448_supported? #:nodoc:
|
|
361
|
+
return @ed448_supported if defined?(@ed448_supported)
|
|
362
|
+
return false unless defined?(OpenSSL::PKey)
|
|
363
|
+
# RFC 8032 section 7.4, the empty-message test vector.
|
|
364
|
+
public_bytes = ['5fd7449b59b461fd2ce787ec616ad46a' \
|
|
365
|
+
'1da1342485a70e1f8a0ea75d80e96778' \
|
|
366
|
+
'edf124769b46c7061bd6783df1e50f6c' \
|
|
367
|
+
'd1fa1abeafe8256180'].pack('H*')
|
|
368
|
+
signature = ['533a37f6bbe457251f023c0d88f976ae' \
|
|
369
|
+
'2dfb504a843e34d2074fd823d41a591f' \
|
|
370
|
+
'2b233f034f628281f2fd7a22ddd47d78' \
|
|
371
|
+
'28c59bd0a21bfd3980ff0d2028d4b18a' \
|
|
372
|
+
'9df63e006c5d1c2d345b925d8dc00b41' \
|
|
373
|
+
'04852db99ac5c7cdda8530a113a0f4db' \
|
|
374
|
+
'b61149f05a7363268c71d95808ff2e65' \
|
|
375
|
+
'2600'].pack('H*')
|
|
376
|
+
key = OpenSSL::PKey.read(ed448_subject_public_key(public_bytes))
|
|
377
|
+
@ed448_supported = key.verify(nil, signature, ''.b)
|
|
378
|
+
rescue OpenSSL::OpenSSLError, NotImplementedError, TypeError, ArgumentError
|
|
379
|
+
@ed448_supported = false
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
def self.ed448_subject_public_key(bytes) #:nodoc:
|
|
383
|
+
# RFC 8410: Ed448, like Ed25519, has no algorithm parameters.
|
|
384
|
+
OpenSSL::ASN1::Sequence.new([
|
|
385
|
+
OpenSSL::ASN1::Sequence.new([OpenSSL::ASN1::ObjectId.new('1.3.101.113')]),
|
|
386
|
+
OpenSSL::ASN1::BitString.new(bytes)
|
|
387
|
+
]).to_der
|
|
388
|
+
end
|
|
389
|
+
private_class_method :ed448_subject_public_key
|
|
390
|
+
|
|
391
|
+
def ed448_key
|
|
392
|
+
unless @key.bytesize == 57
|
|
393
|
+
raise OpenSSL::PKey::PKeyError, 'Ed448 DNSKEY must contain 57 octets'
|
|
328
394
|
end
|
|
395
|
+
@key_length = 448
|
|
396
|
+
OpenSSL::PKey.read(DNSKEY.send(:ed448_subject_public_key, @key))
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
def rsa_key
|
|
400
|
+
exponentLength = @key.getbyte(0)
|
|
329
401
|
pos = 1
|
|
330
402
|
if (exponentLength == 0)
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
403
|
+
if @key.bytesize < 3
|
|
404
|
+
raise OpenSSL::PKey::PKeyError, "Truncated RSA DNSKEY exponent length"
|
|
405
|
+
end
|
|
406
|
+
exponentLength = @key.byteslice(1, 2).unpack1('n')
|
|
407
|
+
if exponentLength < 256
|
|
408
|
+
raise OpenSSL::PKey::PKeyError, "Noncanonical RSA DNSKEY exponent length"
|
|
334
409
|
end
|
|
335
|
-
exponentLength = (key1<<8) + key1
|
|
336
410
|
pos += 2
|
|
337
411
|
end
|
|
412
|
+
if !exponentLength || exponentLength == 0 || @key.bytesize <= pos + exponentLength
|
|
413
|
+
raise OpenSSL::PKey::PKeyError, "Truncated RSA DNSKEY exponent or modulus"
|
|
414
|
+
end
|
|
415
|
+
# RFC 3110 section 2 limits both integers to 4096 bits and forbids
|
|
416
|
+
# leading zero octets. Check bytes before constructing OpenSSL BNs.
|
|
417
|
+
modulus_length = @key.bytesize - pos - exponentLength
|
|
418
|
+
if exponentLength > 512 || modulus_length > 512 ||
|
|
419
|
+
@key.getbyte(pos) == 0 || @key.getbyte(pos + exponentLength) == 0
|
|
420
|
+
raise OpenSSL::PKey::PKeyError, "Invalid RSA DNSKEY integer encoding or size"
|
|
421
|
+
end
|
|
422
|
+
bits = (modulus_length - 1) * 8 + @key.getbyte(pos + exponentLength).bit_length
|
|
423
|
+
# RFC 3110 section 3 and RFC 5702 section 2 define modulus minima.
|
|
424
|
+
minimum = @algorithm == Algorithms.RSASHA512 ? 1024 : 512
|
|
425
|
+
if bits < minimum
|
|
426
|
+
raise OpenSSL::PKey::PKeyError, "RSA DNSKEY modulus must be at least #{minimum} bits"
|
|
427
|
+
end
|
|
338
428
|
exponent = RR::get_num(@key[pos, exponentLength])
|
|
339
429
|
pos += exponentLength
|
|
340
430
|
|
|
341
431
|
modulus = RR::get_num(@key[pos, @key.length])
|
|
342
|
-
@key_length =
|
|
432
|
+
@key_length = bits
|
|
343
433
|
|
|
344
434
|
data_sequence = OpenSSL::ASN1::Sequence([
|
|
345
435
|
OpenSSL::ASN1::Integer(modulus),
|
|
@@ -351,8 +441,10 @@ module Dnsruby
|
|
|
351
441
|
end
|
|
352
442
|
|
|
353
443
|
def dsa_key
|
|
354
|
-
t = @key
|
|
355
|
-
t
|
|
444
|
+
t = @key.getbyte(0)
|
|
445
|
+
if !t || t > 8 || @key.bytesize != 21 + 3 * (t * 8 + 64)
|
|
446
|
+
raise OpenSSL::PKey::PKeyError, "Invalid DSA DNSKEY length"
|
|
447
|
+
end
|
|
356
448
|
pgy_len = t * 8 + 64
|
|
357
449
|
pos = 1
|
|
358
450
|
q = RR::get_num(@key[pos, 20])
|
data/lib/dnsruby/resource/DS.rb
CHANGED
|
@@ -75,13 +75,10 @@ module Dnsruby
|
|
|
75
75
|
end
|
|
76
76
|
|
|
77
77
|
def DS.get_digest_type(d)
|
|
78
|
-
if
|
|
79
|
-
if (d.length == 1)
|
|
80
|
-
d = d.to_i
|
|
81
|
-
end
|
|
82
|
-
end
|
|
78
|
+
d = d.to_i if d.is_a?(String) && /\A[0-9]+\z/.match?(d)
|
|
83
79
|
begin
|
|
84
80
|
digest = DigestTypes.new(d)
|
|
81
|
+
raise ArgumentError, 'DS digest type must fit in an unsigned octet' unless (0..255).cover?(digest.code)
|
|
85
82
|
return digest
|
|
86
83
|
rescue ArgumentError => e
|
|
87
84
|
raise DecodeError.new(e)
|
|
@@ -89,13 +86,10 @@ module Dnsruby
|
|
|
89
86
|
end
|
|
90
87
|
|
|
91
88
|
def algorithm=(a)
|
|
92
|
-
if
|
|
93
|
-
if (a.length < 3)
|
|
94
|
-
a = a.to_i
|
|
95
|
-
end
|
|
96
|
-
end
|
|
89
|
+
a = a.to_i if a.is_a?(String) && /\A[0-9]+\z/.match?(a)
|
|
97
90
|
begin
|
|
98
91
|
alg = Algorithms.new(a)
|
|
92
|
+
raise ArgumentError, 'DS algorithm must fit in an unsigned octet' unless (0..255).cover?(alg.code)
|
|
99
93
|
@algorithm = alg
|
|
100
94
|
rescue ArgumentError => e
|
|
101
95
|
raise DecodeError.new(e)
|
|
@@ -130,24 +124,30 @@ module Dnsruby
|
|
|
130
124
|
|
|
131
125
|
end
|
|
132
126
|
|
|
133
|
-
#
|
|
127
|
+
# A DS binds both the DNSKEY's identity and its public-key digest.
|
|
134
128
|
def check_key(key)
|
|
135
|
-
|
|
129
|
+
key.type == Types.DNSKEY && key.klass == klass && key.name.canonical == name.canonical &&
|
|
130
|
+
matching_key_digest?(key)
|
|
131
|
+
end
|
|
136
132
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
else
|
|
141
|
-
return true
|
|
142
|
-
end
|
|
143
|
-
else
|
|
144
|
-
end
|
|
145
|
-
end
|
|
146
|
-
return false
|
|
133
|
+
def matching_key_digest?(key)
|
|
134
|
+
key.zone_key? && key.key_tag == @key_tag && key.algorithm == @algorithm &&
|
|
135
|
+
!@digestbin.nil? && @digestbin == digest_key(key)
|
|
147
136
|
end
|
|
137
|
+
protected :matching_key_digest?
|
|
148
138
|
|
|
149
139
|
|
|
150
|
-
|
|
140
|
+
# Create a DS for current delegation use. Historical records can still
|
|
141
|
+
# be decoded and checked with digest_key/check_key, but RFC 9905 prohibits
|
|
142
|
+
# new SHA-1 delegations and DS records for RSA/SHA-1 signing keys.
|
|
143
|
+
def DS.from_key(key, digest_type = 2)
|
|
144
|
+
if [Algorithms.RSASHA1, Algorithms.RSASHA1_NSEC3_SHA1].include?(key.algorithm)
|
|
145
|
+
raise ArgumentError, "RFC 9905 prohibits creating DS records for RSA/SHA-1 keys"
|
|
146
|
+
end
|
|
147
|
+
digest_type = get_digest_type(digest_type)
|
|
148
|
+
if digest_type.code == 1
|
|
149
|
+
raise ArgumentError, "RFC 9905 prohibits SHA-1 for new DS delegations; use SHA-256 or SHA-384"
|
|
150
|
+
end
|
|
151
151
|
# # The key must not be a NULL key.
|
|
152
152
|
# if ((key.flags & 0xc000 ) == 0xc000 )
|
|
153
153
|
# puts "\nCreating a DS record for a NULL key is illegal"
|
|
@@ -175,9 +175,8 @@ module Dnsruby
|
|
|
175
175
|
# return
|
|
176
176
|
# end
|
|
177
177
|
#
|
|
178
|
-
digest_type = get_digest_type(digest_type)
|
|
179
178
|
# Create a new DS record from the specified key
|
|
180
|
-
ds = RR.create(:name => key.name, :type => "DS", :ttl => key.ttl,
|
|
179
|
+
ds = RR.create(:name => key.name, :type => "DS", :klass => key.klass, :ttl => key.ttl,
|
|
181
180
|
:key_tag => key.key_tag,
|
|
182
181
|
:digest_type => digest_type, :algorithm => key.algorithm)
|
|
183
182
|
|
|
@@ -238,16 +237,16 @@ module Dnsruby
|
|
|
238
237
|
end
|
|
239
238
|
|
|
240
239
|
def encode_rdata(msg, canonical=false) #:nodoc: all
|
|
241
|
-
msg.put_pack("
|
|
240
|
+
msg.put_pack("nCC", @key_tag, @algorithm.code, @digest_type.code)
|
|
242
241
|
msg.put_bytes(@digestbin)
|
|
243
242
|
end
|
|
244
243
|
|
|
245
244
|
def self.decode_rdata(msg) #:nodoc: all
|
|
246
|
-
key_tag, algorithm, digest_type = msg.get_unpack("
|
|
245
|
+
key_tag, algorithm, digest_type = msg.get_unpack("nCC")
|
|
247
246
|
digest = msg.get_bytes
|
|
248
247
|
return self.new(
|
|
249
248
|
[key_tag, algorithm, digest_type, digest])
|
|
250
249
|
end
|
|
251
250
|
end
|
|
252
251
|
end
|
|
253
|
-
end
|
|
252
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require 'dnsruby/resource/SVCB'
|
|
2
|
+
|
|
3
|
+
module Dnsruby
|
|
4
|
+
class RR
|
|
5
|
+
# Class for DNS HTTPS resource records.
|
|
6
|
+
#
|
|
7
|
+
# RFC 9460 sec 9
|
|
8
|
+
#
|
|
9
|
+
# The HTTPS-specific instantiation of the SVCB record, sharing its wire and
|
|
10
|
+
# presentation formats, for the "https" and "http" URI schemes.
|
|
11
|
+
class HTTPS < SVCB
|
|
12
|
+
ClassValue = nil #:nodoc: all
|
|
13
|
+
TypeValue = Types::HTTPS #:nodoc: all
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
data/lib/dnsruby/resource/IN.rb
CHANGED
|
@@ -74,6 +74,25 @@ module Dnsruby
|
|
|
74
74
|
self.const_set(s.name.sub(/.*::/, ''), c)
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
# RFC 9460 defines these formats only for class IN. Other ordinary
|
|
78
|
+
# classes must retain their opaque RDATA through the generic fallback.
|
|
79
|
+
class SVCB < RR::SVCB
|
|
80
|
+
ClassHash[[TypeValue = Types::SVCB, ClassValue = ClassValue]] = self #:nodoc: all
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
class HTTPS < RR::HTTPS
|
|
84
|
+
ClassHash[[TypeValue = Types::HTTPS, ClassValue = ClassValue]] = self #:nodoc: all
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# RFC 2136 uses ANY/NONE as UPDATE markers, not as an RDATA format.
|
|
88
|
+
# Keep the IN codec (including empty deletes/prerequisites), with the
|
|
89
|
+
# actual class stored on the record, just as Update#delete does.
|
|
90
|
+
[SVCB, HTTPS].each do |record_class|
|
|
91
|
+
[Classes::ANY, Classes::NONE].each do |update_class|
|
|
92
|
+
ClassHash[[record_class::TypeValue, update_class]] = record_class
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
77
96
|
# RFC 1035, Section 3.4.2 (deprecated)
|
|
78
97
|
class WKS < RR
|
|
79
98
|
ClassHash[[TypeValue = Types::WKS, ClassValue = ClassValue]] = self #:nodoc: all
|
data/lib/dnsruby/resource/MX.rb
CHANGED
|
@@ -37,7 +37,7 @@ module Dnsruby
|
|
|
37
37
|
|
|
38
38
|
def from_string(input) #:nodoc: all
|
|
39
39
|
if (input.length > 0)
|
|
40
|
-
names =
|
|
40
|
+
names = Presentation.tokens(input)
|
|
41
41
|
if(names.size != 2)
|
|
42
42
|
raise DecodeError.new("MX record expects preference and domain")
|
|
43
43
|
end
|
|
@@ -66,4 +66,4 @@ module Dnsruby
|
|
|
66
66
|
end
|
|
67
67
|
end
|
|
68
68
|
end
|
|
69
|
-
end
|
|
69
|
+
end
|
|
@@ -236,14 +236,10 @@ module Dnsruby
|
|
|
236
236
|
|
|
237
237
|
def from_string(input)
|
|
238
238
|
if input.length > 0
|
|
239
|
-
data =
|
|
240
|
-
self.next_domain = data
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
len += data[1].length
|
|
244
|
-
end
|
|
245
|
-
self.types = input[len, input.length-len]
|
|
246
|
-
@types = NSEC.get_types(input[len, input.length-len])
|
|
239
|
+
data = Presentation.tokens(input)
|
|
240
|
+
self.next_domain = data.shift
|
|
241
|
+
data.shift if data.first == '('
|
|
242
|
+
self.types = data.join(' ')
|
|
247
243
|
end
|
|
248
244
|
end
|
|
249
245
|
|
|
@@ -260,7 +256,7 @@ module Dnsruby
|
|
|
260
256
|
|
|
261
257
|
def encode_rdata(msg, canonical=false) #:nodoc: all
|
|
262
258
|
# Canonical
|
|
263
|
-
msg.put_name(@next_domain,
|
|
259
|
+
msg.put_name(@next_domain, true, false) # dnssec-bis-updates says NSEC should not be downcased
|
|
264
260
|
types = encode_types
|
|
265
261
|
msg.put_bytes(types)
|
|
266
262
|
end
|
|
@@ -272,4 +268,4 @@ module Dnsruby
|
|
|
272
268
|
end
|
|
273
269
|
end
|
|
274
270
|
end
|
|
275
|
-
end
|
|
271
|
+
end
|
|
@@ -90,14 +90,46 @@ module Dnsruby
|
|
|
90
90
|
# NSEC RR's owner name
|
|
91
91
|
attr_reader :types
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
# A hash interval proves absence, while equality proves existence. Keep
|
|
94
|
+
# these operations separate: an interval's bitmap describes its owner,
|
|
95
|
+
# never a name merely covered by the interval (RFC 5155 sections 8.3-8.7).
|
|
96
|
+
def supported_parameters? # :nodoc:
|
|
97
|
+
@hash_alg == Nsec3HashAlgorithms.SHA_1 && [0, 1].include?(@flags)
|
|
96
98
|
end
|
|
97
99
|
|
|
98
|
-
def
|
|
99
|
-
|
|
100
|
-
|
|
100
|
+
def usable_hash? # :nodoc:
|
|
101
|
+
supported_parameters? &&
|
|
102
|
+
@iterations.is_a?(Integer) && @iterations.between?(0, 65_535) &&
|
|
103
|
+
@next_hashed.is_a?(String) && @next_hashed.bytesize == 20 &&
|
|
104
|
+
/\A[0-9a-v]{32}\z/.match?(owner_hash)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def owner_hash # :nodoc:
|
|
108
|
+
@name.labels.first.to_s.downcase
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def hash_name(name) # :nodoc:
|
|
112
|
+
NSEC3.calculate_hash(name, @iterations, @salt, @hash_alg)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def covers_hash?(hashed) # :nodoc:
|
|
116
|
+
return false unless usable_hash?
|
|
117
|
+
first, following = owner_hash, NSEC3.encode_next_hashed(@next_hashed)
|
|
118
|
+
return false if hashed == first || hashed == following
|
|
119
|
+
first < following ? first < hashed && hashed < following : first < hashed || hashed < following
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def check_name_in_range(name)
|
|
123
|
+
candidate = Name.create(name)
|
|
124
|
+
zone = @name.strip_label
|
|
125
|
+
usable_hash? && (candidate == zone || candidate.subdomain_of?(zone)) && covers_hash?(hash_name(candidate))
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# The closest encloser must be established by the complete proof before
|
|
129
|
+
# checking its literal wildcard name; one NSEC3 cannot discover it alone.
|
|
130
|
+
def check_name_in_wildcard_range(name)
|
|
131
|
+
candidate = Name.create(name)
|
|
132
|
+
candidate.wild? && check_name_in_range(candidate)
|
|
101
133
|
end
|
|
102
134
|
|
|
103
135
|
def calculate_hash
|
|
@@ -138,7 +170,12 @@ module Dnsruby
|
|
|
138
170
|
n = Name.create(name)
|
|
139
171
|
out = n.canonical
|
|
140
172
|
begin
|
|
141
|
-
|
|
173
|
+
budget = ValidationBudget.current
|
|
174
|
+
budget&.check!(iterations + 1)
|
|
175
|
+
(iterations + 1).times do |iteration|
|
|
176
|
+
budget&.check! if iteration % 64 == 0
|
|
177
|
+
out = NSEC3.h(out + salt, hash_alg)
|
|
178
|
+
end
|
|
142
179
|
return Base32.encode32hex(out).downcase
|
|
143
180
|
rescue ArgumentError
|
|
144
181
|
TheLog.error("Unknown hash algorithm #{hash_alg} used for NSEC3 hash")
|
|
@@ -181,11 +218,12 @@ module Dnsruby
|
|
|
181
218
|
|
|
182
219
|
OPT_OUT = 1
|
|
183
220
|
def flags=(f)
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
else
|
|
187
|
-
raise DecodeError.new("Unknown NSEC3 flags field - #{f}")
|
|
221
|
+
unless f.is_a?(Integer) && f.between?(0, 255)
|
|
222
|
+
raise DecodeError, "NSEC3 flags must be an octet: #{f}"
|
|
188
223
|
end
|
|
224
|
+
# Preserve future flags on decode. Validators ignore unsupported
|
|
225
|
+
# records under RFC 5155 section 8.2 instead of rejecting the message.
|
|
226
|
+
@flags = f
|
|
189
227
|
end
|
|
190
228
|
|
|
191
229
|
# If the Opt-Out flag is set, the NSEC3 record covers zero or more
|
|
@@ -216,6 +254,7 @@ module Dnsruby
|
|
|
216
254
|
# self.salt_length=(salt_length)
|
|
217
255
|
# self.salt=(salt)
|
|
218
256
|
@salt = salt
|
|
257
|
+
@salt_length = salt.bytesize
|
|
219
258
|
self.hash_length = hash_length
|
|
220
259
|
self.next_hashed = next_hashed
|
|
221
260
|
self.types = types
|
|
@@ -301,27 +340,24 @@ module Dnsruby
|
|
|
301
340
|
# s = salt()
|
|
302
341
|
s = @salt
|
|
303
342
|
sl = s.length
|
|
304
|
-
|
|
305
|
-
sl = 0
|
|
306
|
-
end
|
|
307
|
-
msg.put_pack('ccnc', @hash_alg.code, @flags, @iterations, sl)
|
|
343
|
+
msg.put_pack('CCnC', @hash_alg.code, @flags, @iterations, sl)
|
|
308
344
|
if sl > 0
|
|
309
345
|
msg.put_bytes(s)
|
|
310
346
|
end
|
|
311
|
-
msg.put_pack('
|
|
347
|
+
msg.put_pack('C', @hash_length)
|
|
312
348
|
msg.put_bytes(@next_hashed)
|
|
313
349
|
types = NSEC.encode_types(self)
|
|
314
350
|
msg.put_bytes(types)
|
|
315
351
|
end
|
|
316
352
|
|
|
317
353
|
def self.decode_rdata(msg) #:nodoc: all
|
|
318
|
-
hash_alg, flags, iterations, salt_length = msg.get_unpack('
|
|
354
|
+
hash_alg, flags, iterations, salt_length = msg.get_unpack('CCnC')
|
|
319
355
|
# Salt may be omitted
|
|
320
|
-
salt =
|
|
356
|
+
salt = ''.b
|
|
321
357
|
if salt_length > 0
|
|
322
358
|
salt = msg.get_bytes(salt_length)
|
|
323
359
|
end
|
|
324
|
-
hash_length, = msg.get_unpack('
|
|
360
|
+
hash_length, = msg.get_unpack('C')
|
|
325
361
|
next_hashed = msg.get_bytes(hash_length)
|
|
326
362
|
types = NSEC.decode_types(msg.get_bytes)
|
|
327
363
|
return self.new(
|
|
@@ -92,6 +92,7 @@ module Dnsruby
|
|
|
92
92
|
# self.salt_length=(salt_length)
|
|
93
93
|
# self.salt=(salt)
|
|
94
94
|
@salt=salt
|
|
95
|
+
@salt_length = salt.bytesize
|
|
95
96
|
end
|
|
96
97
|
|
|
97
98
|
def from_string(input)
|
|
@@ -114,10 +115,7 @@ module Dnsruby
|
|
|
114
115
|
# s = salt()
|
|
115
116
|
s = @salt
|
|
116
117
|
sl = s.length()
|
|
117
|
-
|
|
118
|
-
sl == 0
|
|
119
|
-
end
|
|
120
|
-
msg.put_pack("ccnc", @hash_alg.code, @flags, @iterations, sl)
|
|
118
|
+
msg.put_pack("CCnC", @hash_alg.code, @flags, @iterations, sl)
|
|
121
119
|
|
|
122
120
|
if (sl > 0)
|
|
123
121
|
msg.put_bytes(s)
|
|
@@ -125,11 +123,11 @@ module Dnsruby
|
|
|
125
123
|
end
|
|
126
124
|
|
|
127
125
|
def self.decode_rdata(msg) #:nodoc: all
|
|
128
|
-
hash_alg, flags, iterations, salt_length = msg.get_unpack("
|
|
126
|
+
hash_alg, flags, iterations, salt_length = msg.get_unpack("CCnC")
|
|
129
127
|
salt = msg.get_bytes(salt_length)
|
|
130
128
|
return self.new(
|
|
131
129
|
[hash_alg, flags, iterations, salt_length, salt])
|
|
132
130
|
end
|
|
133
131
|
end
|
|
134
132
|
end
|
|
135
|
-
end
|
|
133
|
+
end
|