Skip to content

Commit ad0803c

Browse files
nab-iha3rthalley
andcommitted
check the RRSIG type covered and class against the RRset (#1388)
_validate_rrsig() never compared the RRSIG type covered field or class with the RRset, which RFC 4035 section 5.3.1 requires. The revoked key check in _find_candidate_keys() trusts type_covered, so a revoked key could validate any RRset with an RRSIG claiming to cover DNSKEY. Co-authored-by: Bob Halley <[email protected]> (cherry picked from commit 57ed817)
1 parent 8c38a04 commit ad0803c

3 files changed

Lines changed: 39 additions & 0 deletions

File tree

‎dns/dnssec.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,11 @@ def _validate_rrsig(
358358
if policy is None:
359359
policy = default_policy
360360

361+
# RFC 4035 section 5.3.1: the RRSIG must cover the RRset's type and class.
362+
_, rdataset = _get_rrname_rdataset(rrset)
363+
if rrsig.type_covered != rdataset.rdtype or rrsig.rdclass != rdataset.rdclass:
364+
raise ValidationFailure("RRSIG does not cover the RRset")
365+
361366
candidate_keys = _find_candidate_keys(keys, rrsig)
362367
if candidate_keys is None:
363368
raise ValidationFailure("unknown key")

‎doc/whatsnew.rst‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ What's New in dnspython
66
2.9.1 (in development)
77
----------------------
88

9+
* DNSSEC validation now rejects an RRSIG whose type covered or class does not
10+
match the RRset, as RFC 4035 section 5.3.1 requires. This was not checked,
11+
so a revoked DNSKEY, which may only validate the DNSKEY RRset, still
12+
validated any RRset signed with its private key if the RRSIG claimed to
13+
cover DNSKEY.
14+
915
* ``dns.tokenizer.Tokenizer`` now ends a line at a carriage return, or at a
1016
carriage return followed by a line feed, unless the carriage return is in a
1117
quoted string. The carriage return of CRLF line ends used to stay in the

‎tests/test_dnssec.py‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1168,6 +1168,34 @@ def testRevokedKeyOnlyValidatesDNSKEY(self):
11681168
with self.assertRaises(dns.dnssec.ValidationFailure):
11691169
dns.dnssec.validate(a_rrset, dns.rrset.from_rdata(signer, 300, rrsig), keys)
11701170

1171+
def testRRSIGMustCoverRRsetType(self):
1172+
# RFC 4035 section 5.3.1: the RRSIG type covered and class must match
1173+
# the RRset. Otherwise a revoked key could sign any RRset with an
1174+
# RRSIG claiming to cover DNSKEY.
1175+
private_key = ed25519.Ed25519PrivateKey.generate()
1176+
dnskey = dns.dnssec.make_dnskey(
1177+
public_key=private_key.public_key(),
1178+
algorithm=dns.dnssec.Algorithm.ED25519,
1179+
flags=Flag.ZONE | Flag.SEP | Flag.REVOKE,
1180+
)
1181+
signer = dns.name.from_text("example")
1182+
keys = {signer: dns.rrset.from_rdata(signer, 300, dnskey)}
1183+
a_rrset = dns.rrset.from_text(signer, 300, "IN", "A", "10.0.0.1")
1184+
rrsig = dns.dnssec.sign(a_rrset, private_key, signer, dnskey, lifetime=300)
1185+
rrsig = rrsig.replace(type_covered=dns.rdatatype.DNSKEY)
1186+
data = dns.dnssec._make_rrsig_signature_data(a_rrset, rrsig)
1187+
rrsig = rrsig.replace(signature=private_key.sign(data))
1188+
with self.assertRaises(dns.dnssec.ValidationFailure):
1189+
dns.dnssec.validate(a_rrset, dns.rrset.from_rdata(signer, 300, rrsig), keys)
1190+
1191+
txt_rrset = dns.rrset.from_text(signer, 300, "IN", "TXT", "foo")
1192+
rrsig = dns.dnssec.sign(txt_rrset, private_key, signer, dnskey, lifetime=300)
1193+
ch_rrset = dns.rrset.from_text(signer, 300, "CH", "TXT", "foo")
1194+
with self.assertRaises(dns.dnssec.ValidationFailure):
1195+
dns.dnssec.validate(
1196+
ch_rrset, dns.rrset.from_rdata(signer, 300, rrsig), keys
1197+
)
1198+
11711199

11721200
@unittest.skipUnless(dns.dnssec._have_pyca, "Python Cryptography cannot be imported")
11731201
class DNSSECMiscTestCase(unittest.TestCase):

0 commit comments

Comments
 (0)