From d9d25555efb3fdddfbb534342e137ef1bfe24b17 Mon Sep 17 00:00:00 2001 From: Remi Jouannet Date: Sat, 27 Jun 2026 20:02:43 +0200 Subject: [PATCH] Open tsig key file (#1271) * add tsigkeyring.from_file to use key file generate by tsig-keygen * add tsigkeyring.from_file to use key file generate by tsig-keygen --- dns/tsigkeyring.py | 37 ++++++++++++++++++++++- tests/test_tsigkeyring.py | 63 ++++++++++++++++++++++++++++++++++++++- tests/tsigkeys/1.key | 1 + tests/tsigkeys/2.key | 1 + tests/tsigkeys/3.key | 3 ++ tests/tsigkeys/4.key | 4 +++ tests/tsigkeys/5.key | 1 + 7 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 tests/tsigkeys/1.key create mode 100644 tests/tsigkeys/2.key create mode 100644 tests/tsigkeys/3.key create mode 100644 tests/tsigkeys/4.key create mode 100644 tests/tsigkeys/5.key diff --git a/dns/tsigkeyring.py b/dns/tsigkeyring.py index d4f2dcbf..2f18bed6 100644 --- a/dns/tsigkeyring.py +++ b/dns/tsigkeyring.py @@ -18,11 +18,46 @@ """A place to store TSIG keys.""" import base64 +import re from typing import Any - +from pathlib import Path import dns.name import dns.tsig +TSIG_KEY_FILE_RE = re.compile( + r"key\s*" + r'(?:"([^"]*)"' # group 1: quoted name + r"|([^{\s]+))" # group 2: unquoted name + r"\s*\{\s*" + r"(?:algorithm\s*" # --- algorithm block (optional) --- + r'(?:"([^"]*)"' # group 3: quoted algo + r"|([^;\s}]+))" # group 4: unquoted algo + r"\s*;\s*)?" # --- end optional block --- + r"secret\s*" + r'(?:"([^"]*)"' # group 5: quoted secret + r"|([^;\s}]+))" # group 6: unquoted secret + r"\s*;?\s*\}?" +) + + +def from_file(key_file: str) -> dict[dns.name.Name, Any]: + """Open a tsig key file generate by tsig-keygen + and parse to retrieve key, algo and secret + @rtype: dict""" + + content = Path(key_file).read_text().replace("\n", "").strip() + + if m := TSIG_KEY_FILE_RE.match(content): + key_name = m.group(1) or m.group(2) + algorithm = m.group(3) or m.group(4) + secret = m.group(5) or m.group(6) + if algorithm: + return from_text({key_name: (algorithm, secret)}) + else: + return from_text({key_name: secret}) + else: + raise Exception(f"Error while parsing {key_file}") + def from_text(textring: dict[str, Any]) -> dict[dns.name.Name, Any]: """Convert a dictionary containing (textual DNS name, base64 secret) diff --git a/tests/test_tsigkeyring.py b/tests/test_tsigkeyring.py index f8c889e8..e29e2d24 100644 --- a/tests/test_tsigkeyring.py +++ b/tests/test_tsigkeyring.py @@ -2,9 +2,10 @@ import base64 import unittest - +import os import dns.tsig import dns.tsigkeyring +import pytest text_keyring = {"keyname.": ("hmac-sha256.", "NjHwPsMKjdN++dOfE5iAiQ==")} @@ -18,6 +19,39 @@ rich_keyring = {key.name: key} old_rich_keyring = {key.name: key.secret} +keyring_file_1 = { + dns.name.from_text("aaa-test"): dns.tsig.Key( + dns.name.from_text("aaa-test"), + "WklIGweRJDBzgYJAfFAt3ln7NJAyz66W56/fpoeepVn5yqs3i3iiXFIS6GXpuFw0TyRfADBcnap8Vvl7TokfYA==", + "hmac-sha512", + ) +} + +keyring_file_2 = { + dns.name.from_text("b-key"): dns.tsig.Key( + dns.name.from_text("b-key"), + "KykTutSQNZYc+o8rqfMPH7ce2a9SruvKgPAqfn8EER8=", + "hmac-sha256", + ) +} + +keyring_file_3 = { + dns.name.from_text("example.com"): dns.tsig.Key( + dns.name.from_text("example.com"), + "WklIGweRJDBzgYJAfFAt3ln7NJAyz66W56/fpoeepVn5yqs3i3iiXFIS6GXpuFw0TyRfADBcnap8Vvl7TokfYA==", + ).secret +} + +keyring_file_4 = { + dns.name.from_text("bh-"): dns.tsig.Key( + dns.name.from_text("bh-"), + "WklIGweRJDBzgYJAfFAt3ln7NJAyz66W56/fpoeepVn5yqs3i3iiXFIS6GXpuFw0TyRfADBcnap8Vvl7TokfYA==", + "hmac-sha512", + ) +} + +TEST_DIR = os.path.dirname(os.path.abspath(__file__)) + class TSIGKeyRingTestCase(unittest.TestCase): def test_from_text(self): @@ -56,3 +90,30 @@ class TSIGKeyRingTestCase(unittest.TestCase): rkeyring = dns.tsigkeyring.from_text(old_text_keyring) tkeyring = dns.tsigkeyring.to_text(rkeyring) self.assertEqual(tkeyring, old_text_keyring) + + def test_from_file_1(self): + """test to parse key file 1.key""" + keyring = dns.tsigkeyring.from_file(os.path.join(TEST_DIR, "./tsigkeys/1.key")) + self.assertEqual(keyring, keyring_file_1) + + def test_from_file_2(self): + """test to parse key file 2.key""" + keyring = dns.tsigkeyring.from_file(os.path.join(TEST_DIR, "./tsigkeys/2.key")) + self.assertEqual(keyring, keyring_file_2) + + def test_from_file_3(self): + """test to parse key file 3.key""" + keyring = dns.tsigkeyring.from_file(os.path.join(TEST_DIR, "./tsigkeys/3.key")) + self.assertEqual(keyring, keyring_file_3) + + def test_from_file_4(self): + """test to parse key file 4.key""" + keyring = dns.tsigkeyring.from_file(os.path.join(TEST_DIR, "./tsigkeys/4.key")) + self.assertEqual(keyring, keyring_file_4) + + def test_from_file_error(self): + """test to parse key file 5.key""" + with pytest.raises(Exception): + keyring = dns.tsigkeyring.from_file( + os.path.join(TEST_DIR, "./tsigkeys/5.key") + ) diff --git a/tests/tsigkeys/1.key b/tests/tsigkeys/1.key new file mode 100644 index 00000000..8415d948 --- /dev/null +++ b/tests/tsigkeys/1.key @@ -0,0 +1 @@ +key "aaa-test" {algorithm hmac-sha512;secret "WklIGweRJDBzgYJAfFAt3ln7NJAyz66W56/fpoeepVn5yqs3i3iiXFIS6GXpuFw0TyRfADBcnap8Vvl7TokfYA==";}; diff --git a/tests/tsigkeys/2.key b/tests/tsigkeys/2.key new file mode 100644 index 00000000..5526322c --- /dev/null +++ b/tests/tsigkeys/2.key @@ -0,0 +1 @@ +key"b-key"{algorithm"hmac-sha256";secret"KykTutSQNZYc+o8rqfMPH7ce2a9SruvKgPAqfn8EER8=";}; diff --git a/tests/tsigkeys/3.key b/tests/tsigkeys/3.key new file mode 100644 index 00000000..49cfee9c --- /dev/null +++ b/tests/tsigkeys/3.key @@ -0,0 +1,3 @@ +key example.com { + secret "WklIGweRJDBzgYJAfFAt3ln7NJAyz66W56/fpoeepVn5yqs3i3iiXFIS6GXpuFw0TyRfADBcnap8Vvl7TokfYA=="; +}; diff --git a/tests/tsigkeys/4.key b/tests/tsigkeys/4.key new file mode 100644 index 00000000..149b3792 --- /dev/null +++ b/tests/tsigkeys/4.key @@ -0,0 +1,4 @@ +key bh- { + algorithm hmac-sha512; + secret "WklIGweRJDBzgYJAfFAt3ln7NJAyz66W56/fpoeepVn5yqs3i3iiXFIS6GXpuFw0TyRfADBcnap8Vvl7TokfYA=="; +}; diff --git a/tests/tsigkeys/5.key b/tests/tsigkeys/5.key new file mode 100644 index 00000000..b964aa65 --- /dev/null +++ b/tests/tsigkeys/5.key @@ -0,0 +1 @@ +key bh -- 2.47.3