I tried searching the closed issues and didn't see anything obvious which mentioned this.
Describe the bug
dns.tsig.validate() still checks the TSIG time window before verifying the MAC, but RFC 8945 changed that order. RFC 8945 §5.2 requires this check order:
- key
- MAC
- time values
- truncation policy
Under RFC 2845 some implementations checked time first, then used an unvalidated request MAC when signing a BADTIME reply (CVE-2017-3142 / CVE-2017-3143 / CVE-2017-11104). RFC 8945 §10.1 therefore treats the request MAC as invalid until verified, and requires the MAC check before the time check.
Current validate() does roughly:
- peer TSIG error codes
time_signed / fudge (may raise BadTime)
- key name / algorithm
- MAC verify (may raise
BadSignature)
So a message that is both outside the fudge window and has a bad MAC raises BadTime and never reaches MAC verification. Callers that map exceptions to RFC 8945 error replies then cannot tell BADTIME from BADSIG, and cannot know whether the request MAC was validated before signing a BADTIME response (ref: §5.3.2).
Expected behavior: verify the MAC (after the key check) before evaluating time, so a forged/invalid MAC yields BadSignature even when time_signed is also skewed.
To Reproduce
import time
import dns.message
import dns.name
import dns.rdataclass
import dns.rdatatype
import dns.rrset
import dns.tsig
from dns.rdtypes.ANY.TSIG import TSIG
SECRET = "dGVzdC1zZWNyZXQtdHNpZy1rZXktMTIzNA==" # base64
KEY_NAME = dns.name.from_text("test-key.")
KEY = dns.tsig.Key(KEY_NAME, SECRET, algorithm=dns.tsig.HMAC_SHA256)
def forge(time_signed):
"""Build a SOA query whose TSIG has an invalid MAC and the given Time Signed."""
query = dns.message.make_query("example.test.", dns.rdatatype.SOA)
query.tsig = dns.rrset.from_rdata(
KEY_NAME,
0,
TSIG(
dns.rdataclass.ANY,
dns.rdatatype.TSIG,
dns.tsig.HMAC_SHA256,
time_signed,
300, # fudge
b"\x00" * dns.tsig.mac_sizes[dns.tsig.HMAC_SHA256],
query.id,
0, # error
b"",
),
)
return query.to_wire()
for label, time_signed in (("bad MAC, good time", int(time.time())), ("bad MAC, bad time", 0)):
try:
dns.message.from_wire(forge(time_signed), keyring=KEY)
print(f"{label}: accepted (no exception)")
except Exception as exc:
print(f"{label}: {type(exc).__name__}")
Context (please complete the following information):
- dnspython version: current
main (dns/tsig.py validate() still raises BadTime before ctx.verify())
- Python version: 3.12
- OS: macOS
I tried searching the closed issues and didn't see anything obvious which mentioned this.
Describe the bug
dns.tsig.validate()still checks the TSIG time window before verifying the MAC, but RFC 8945 changed that order. RFC 8945 §5.2 requires this check order:Under RFC 2845 some implementations checked time first, then used an unvalidated request MAC when signing a BADTIME reply (CVE-2017-3142 / CVE-2017-3143 / CVE-2017-11104). RFC 8945 §10.1 therefore treats the request MAC as invalid until verified, and requires the MAC check before the time check.
Current
validate()does roughly:time_signed/ fudge (may raiseBadTime)BadSignature)So a message that is both outside the fudge window and has a bad MAC raises
BadTimeand never reaches MAC verification. Callers that map exceptions to RFC 8945 error replies then cannot tell BADTIME from BADSIG, and cannot know whether the request MAC was validated before signing a BADTIME response (ref: §5.3.2).Expected behavior: verify the MAC (after the key check) before evaluating time, so a forged/invalid MAC yields
BadSignatureeven whentime_signedis also skewed.To Reproduce
Context (please complete the following information):
main(dns/tsig.pyvalidate()still raisesBadTimebeforectx.verify())