Skip to content

TSIG validation raises BadTime before verifying MAC (RFC 8945 §5.2) #1287

Description

@nrnvgh

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:

  1. key
  2. MAC
  3. time values
  4. 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:

  1. peer TSIG error codes
  2. time_signed / fudge (may raise BadTime)
  3. key name / algorithm
  4. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions