--- /dev/null
+#!/usr/bin/python3
+
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+import time
+
+import pytest
+
+pytest.importorskip("dns", minversion="2.7.0") # TSIG parsing without validation
+
+# in FIPs mode md5 fails so we need 4.41.2 or later which does not use md5
+try:
+ import hashlib
+
+ hashlib.md5(b"1234")
+ pytest.importorskip("hypothesis")
+except ValueError:
+ pytest.importorskip("hypothesis", minversion="4.41.2")
+
+import dns.exception
+import dns.message
+import dns.name
+import dns.rdataclass
+import dns.rdatatype
+import dns.rdtypes.ANY.TSIG
+import dns.rrset
+import dns.tsig
+
+from hypothesis import assume, example, given
+from hypothesis.strategies import binary, booleans, composite, just, sampled_from
+
+import isctest
+from isctest.hypothesis.strategies import dns_names, uint
+
+
+pytestmark = pytest.mark.extra_artifacts(
+ [
+ "ans*/ans.run",
+ "ns1/named-fips.conf",
+ ]
+)
+
+
+@composite
+def generate_known_algoritm_and_matching_len_mac(draw):
+ candidates = tuple(dns.tsig.mac_sizes.items())
+ alg, mac_size = draw(sampled_from(candidates))
+ mac = draw(binary(min_size=mac_size, max_size=mac_size))
+ return alg, mac
+
+
+@composite
+def generate_known_algoritm_and_wrong_len_mac(draw):
+ candidates = tuple(dns.tsig.mac_sizes.items())
+ alg, correct_mac_len = draw(sampled_from(candidates))
+ mac = draw(binary())
+ assume(len(mac) != correct_mac_len)
+ return alg, mac
+
+
+@composite
+def generate_unknown_but_likely_algoritm_and_mac(draw):
+ alg = draw(dns_names(min_labels=2, max_labels=2))
+ mac = draw(binary())
+ return alg, mac
+
+
+@composite
+def generate_random_alg_and_mac(draw):
+ alg = draw(dns_names())
+ mac = draw(binary())
+ return alg, mac
+
+
+@given(
+ keyname=dns_names(max_labels=3) | dns_names(),
+ alg_and_mac=generate_known_algoritm_and_matching_len_mac()
+ | generate_known_algoritm_and_wrong_len_mac()
+ | generate_unknown_but_likely_algoritm_and_mac()
+ | generate_random_alg_and_mac(),
+ time_signed=just(int(time.time())) | uint(48),
+ fudge=just(300) | uint(16),
+ mangle_orig_id=booleans(),
+ error=just(0) | uint(12),
+ other=just(b"") | binary(),
+)
+@example(
+ keyname=dns.name.from_text("."),
+ alg_and_mac=(dns.name.from_text("."), b""),
+ time_signed=0,
+ fudge=300,
+ mangle_orig_id=False,
+ error=0,
+ other=b"",
+)
+def test_tsig_fuzz_rdata(
+ keyname,
+ alg_and_mac,
+ time_signed,
+ fudge,
+ error,
+ mangle_orig_id,
+ other,
+ servers,
+ named_port,
+):
+ alg, mac = alg_and_mac
+ ns1 = servers["ns1"]
+ msg = dns.message.make_query("example.com.", "AXFR")
+ msg.keyring = False # don't validate received TSIG
+
+ tsig_orig_id = msg.id
+ if mangle_orig_id:
+ tsig_orig_id = (msg.id - 0xABCD) % 0x10000
+
+ tsig = dns.rdtypes.ANY.TSIG.TSIG(
+ dns.rdataclass.ANY,
+ dns.rdatatype.TSIG,
+ alg,
+ time_signed,
+ fudge,
+ mac,
+ tsig_orig_id,
+ error,
+ other,
+ )
+ rrs = dns.rrset.from_rdata(keyname, 0, tsig)
+ msg.additional.append(rrs)
+
+ try:
+ isctest.query.tcp(msg, ns1.ip, named_port)
+ except dns.tsig.PeerError:
+ pass # any error from named is fine
+ except dns.exception.TooBig:
+ assume(False) # some randomly generated value did not fit into message