* # This is a combination of 6 commits.
# This is the 1st commit message:
Initial changes to prepare for SIG(0):
- Add shared RRSIGBase for code shared between RRSIG and SIG
- Add KEY RR
- Add SIG RR
# This is the commit message #2:
Parse flags mnemonics and symbolic protocol names
# This is the commit message #3:
RFC 2535 section 7.1 says "Note that if the type flags field has the NOKEY value, nothing appears after the algorithm octet."
# This is the commit message #4:
Include sphinx only for Python 3.11 or later (#1225)
* Include sphinx only for Python 3.11 or later
* Use python_version
# This is the commit message #5:
Save token before returning it (for exception handling)
# This is the commit message #6:
Replace get/unget with plain unget and last token
* Initial changes to prepare for SIG(0):
- Add shared RRSIGBase for code shared between RRSIG and SIG
- Add KEY RR
- Add SIG RR
- Parse flags mnemonics and symbolic protocol names
* Make pyright happy
--- /dev/null
+# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
+
+# Copyright (C) 2004-2007, 2009-2011 Nominum, Inc.
+#
+# Permission to use, copy, modify, and distribute this software and its
+# documentation for any purpose with or without fee is hereby granted,
+# provided that the above copyright notice and this permission notice
+# appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import base64
+
+import dns.enum
+import dns.exception
+import dns.immutable
+import dns.rdtypes.dnskeybase # lgtm[py/import-and-import-from]
+
+
+
+class Protocol(dns.enum.IntEnum):
+ NONE = 0
+ TLS = 1
+ EMAIL = 2
+ DNSSEC = 3
+ IPSEC = 4
+ ALL = 255
+
+ @classmethod
+ def _maximum(cls):
+ return 255
+
+
+class LegacyFlag(dns.enum.IntEnum):
+ NOCONF = 0x4000
+ NOAUTH = 0x8000
+ NOKEY = 0xC000
+ FLAG2 = 0x2000
+ EXTEND = 0x1000
+ FLAG4 = 0x0800
+ FLAG5 = 0x0400
+ USER = 0x0000
+ ZONE = 0x0100
+ HOST = 0x0200
+ NTYP3 = 0x0300
+ FLAG8 = 0x0080
+ FLAG9 = 0x0040
+ FLAG10 = 0x0020
+ FLAG11 = 0x0010
+ SIG0 = 0x0000
+ SIG1 = 0x0001
+ SIG2 = 0x0002
+ SIG3 = 0x0003
+ SIG4 = 0x0004
+ SIG5 = 0x0005
+ SIG6 = 0x0006
+ SIG7 = 0x0007
+ SIG8 = 0x0008
+ SIG9 = 0x0009
+ SIG10 = 0x000A
+ SIG11 = 0x000B
+ SIG12 = 0x000C
+ SIG13 = 0x000D
+ SIG14 = 0x000E
+ SIG15 = 0x000F
+
+
+DNS_KEYFLAG_TYPEMASK = LegacyFlag.NOAUTH | LegacyFlag.NOCONF
+
+
+@dns.immutable.immutable
+class KEY(dns.rdtypes.dnskeybase.DNSKEYBase):
+ """KEY record"""
+
+ @classmethod
+ def from_text(
+ cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None
+ ):
+ token = tok.get()
+ try:
+ flags = tok.as_uint16(token)
+ except dns.exception.SyntaxError:
+ flags_str = tok.as_string(token)
+ try:
+ flags = 0
+ for mnemonic in flags_str.split("|"):
+ flags |= LegacyFlag[mnemonic].value
+ except KeyError:
+ raise dns.exception.SyntaxError(f"Invalid flags: {flags_str}")
+
+ token = tok.get()
+ try:
+ protocol = tok.as_uint8(token)
+ except dns.exception.SyntaxError:
+ protocol_str = tok.as_string(token)
+ try:
+ protocol = Protocol[protocol_str].value
+ except KeyError:
+ raise dns.exception.SyntaxError(f"Invalid protocol: {protocol_str}")
+
+ algorithm = tok.get_string()
+
+ # RFC 2535 section 7.1 says "Note that if the type flags field has the
+ # NOKEY value, nothing appears after the algorithm octet."
+ if (flags & DNS_KEYFLAG_TYPEMASK) != LegacyFlag.NOKEY:
+ b64 = tok.concatenate_remaining_identifiers().encode()
+ key = base64.b64decode(b64)
+ else:
+ key = b""
+
+ return cls(rdclass, rdtype, flags, protocol, algorithm, key)
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-import base64
-import calendar
-import struct
-import time
-
-import dns.dnssectypes
-import dns.exception
import dns.immutable
-import dns.rdata
-import dns.rdatatype
-
-
-class BadSigTime(dns.exception.DNSException):
- """Time in DNS SIG or RRSIG resource record cannot be parsed."""
+import dns.rdtypes.rrsigbase # lgtm[py/import-and-import-from]
+# pylint: disable=unused-import
+from dns.rdtypes.rrsigbase import ( # noqa: F401 lgtm[py/unused-import]
+ BadSigTime,
+ posixtime_to_sigtime,
+ sigtime_to_posixtime,
+)
-def sigtime_to_posixtime(what):
- if len(what) <= 10 and what.isdigit():
- return int(what)
- if len(what) != 14:
- raise BadSigTime
- year = int(what[0:4])
- month = int(what[4:6])
- day = int(what[6:8])
- hour = int(what[8:10])
- minute = int(what[10:12])
- second = int(what[12:14])
- return calendar.timegm((year, month, day, hour, minute, second, 0, 0, 0))
-
-
-def posixtime_to_sigtime(what):
- return time.strftime("%Y%m%d%H%M%S", time.gmtime(what))
+# pylint: enable=unused-import
@dns.immutable.immutable
-class RRSIG(dns.rdata.Rdata):
+class RRSIG(dns.rdtypes.rrsigbase.RRSIGBase):
"""RRSIG record"""
-
- __slots__ = [
- "type_covered",
- "algorithm",
- "labels",
- "original_ttl",
- "expiration",
- "inception",
- "key_tag",
- "signer",
- "signature",
- ]
-
- def __init__(
- self,
- rdclass,
- rdtype,
- type_covered,
- algorithm,
- labels,
- original_ttl,
- expiration,
- inception,
- key_tag,
- signer,
- signature,
- ):
- super().__init__(rdclass, rdtype)
- self.type_covered = self._as_rdatatype(type_covered)
- self.algorithm = dns.dnssectypes.Algorithm.make(algorithm)
- self.labels = self._as_uint8(labels)
- self.original_ttl = self._as_ttl(original_ttl)
- self.expiration = self._as_uint32(expiration)
- self.inception = self._as_uint32(inception)
- self.key_tag = self._as_uint16(key_tag)
- self.signer = self._as_name(signer)
- self.signature = self._as_bytes(signature)
-
- def covers(self):
- return self.type_covered
-
- def to_text(self, origin=None, relativize=True, **kw):
- return (
- f"{dns.rdatatype.to_text(self.type_covered)} "
- f"{self.algorithm} {self.labels} {self.original_ttl} "
- f"{posixtime_to_sigtime(self.expiration)} "
- f"{posixtime_to_sigtime(self.inception)} "
- f"{self.key_tag} "
- f"{self.signer.choose_relativity(origin, relativize)} "
- f"{dns.rdata._base64ify(self.signature, **kw)}" # pyright: ignore
- )
-
- @classmethod
- def from_text(
- cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None
- ):
- type_covered = dns.rdatatype.from_text(tok.get_string())
- algorithm = dns.dnssectypes.Algorithm.from_text(tok.get_string())
- labels = tok.get_int()
- original_ttl = tok.get_ttl()
- expiration = sigtime_to_posixtime(tok.get_string())
- inception = sigtime_to_posixtime(tok.get_string())
- key_tag = tok.get_int()
- signer = tok.get_name(origin, relativize, relativize_to)
- b64 = tok.concatenate_remaining_identifiers().encode()
- signature = base64.b64decode(b64)
- return cls(
- rdclass,
- rdtype,
- type_covered,
- algorithm,
- labels,
- original_ttl,
- expiration,
- inception,
- key_tag,
- signer,
- signature,
- )
-
- def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
- header = struct.pack(
- "!HBBIIIH",
- self.type_covered,
- self.algorithm,
- self.labels,
- self.original_ttl,
- self.expiration,
- self.inception,
- self.key_tag,
- )
- file.write(header)
- self.signer.to_wire(file, None, origin, canonicalize)
- file.write(self.signature)
-
- @classmethod
- def from_wire_parser(cls, rdclass, rdtype, parser, origin=None):
- header = parser.get_struct("!HBBIIIH")
- signer = parser.get_name(origin)
- signature = parser.get_remaining()
- return cls(rdclass, rdtype, *header, signer, signature) # pyright: ignore
--- /dev/null
+# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
+
+# Copyright (C) 2004-2007, 2009-2011 Nominum, Inc.
+#
+# Permission to use, copy, modify, and distribute this software and its
+# documentation for any purpose with or without fee is hereby granted,
+# provided that the above copyright notice and this permission notice
+# appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import dns.immutable
+import dns.rdtypes.rrsigbase # lgtm[py/import-and-import-from]
+
+# pylint: disable=unused-import
+from dns.rdtypes.rrsigbase import ( # noqa: F401 lgtm[py/unused-import]
+ BadSigTime,
+ posixtime_to_sigtime,
+ sigtime_to_posixtime,
+)
+
+# pylint: enable=unused-import
+
+
+@dns.immutable.immutable
+class SIG(dns.rdtypes.rrsigbase.RRSIGBase):
+ """SIG record"""
--- /dev/null
+# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
+
+# Copyright (C) 2004-2007, 2009-2011 Nominum, Inc.
+#
+# Permission to use, copy, modify, and distribute this software and its
+# documentation for any purpose with or without fee is hereby granted,
+# provided that the above copyright notice and this permission notice
+# appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import base64
+import calendar
+import struct
+import time
+
+import dns.dnssectypes
+import dns.exception
+import dns.immutable
+import dns.rdata
+import dns.rdatatype
+
+
+class BadSigTime(dns.exception.DNSException):
+
+ """Time in DNS SIG or RRSIG resource record cannot be parsed."""
+
+
+def sigtime_to_posixtime(what):
+ if len(what) <= 10 and what.isdigit():
+ return int(what)
+ if len(what) != 14:
+ raise BadSigTime
+ year = int(what[0:4])
+ month = int(what[4:6])
+ day = int(what[6:8])
+ hour = int(what[8:10])
+ minute = int(what[10:12])
+ second = int(what[12:14])
+ return calendar.timegm((year, month, day, hour, minute, second, 0, 0, 0))
+
+
+def posixtime_to_sigtime(what):
+ return time.strftime("%Y%m%d%H%M%S", time.gmtime(what))
+
+
+@dns.immutable.immutable
+class RRSIGBase(dns.rdata.Rdata):
+
+ """Base class for rdata that is like a RRSIG record"""
+
+ __slots__ = [
+ "type_covered",
+ "algorithm",
+ "labels",
+ "original_ttl",
+ "expiration",
+ "inception",
+ "key_tag",
+ "signer",
+ "signature",
+ ]
+
+ def __init__(
+ self,
+ rdclass,
+ rdtype,
+ type_covered,
+ algorithm,
+ labels,
+ original_ttl,
+ expiration,
+ inception,
+ key_tag,
+ signer,
+ signature,
+ ):
+ super().__init__(rdclass, rdtype)
+ self.type_covered = self._as_rdatatype(type_covered)
+ self.algorithm = dns.dnssectypes.Algorithm.make(algorithm)
+ self.labels = self._as_uint8(labels)
+ self.original_ttl = self._as_ttl(original_ttl)
+ self.expiration = self._as_uint32(expiration)
+ self.inception = self._as_uint32(inception)
+ self.key_tag = self._as_uint16(key_tag)
+ self.signer = self._as_name(signer)
+ self.signature = self._as_bytes(signature)
+
+ def covers(self):
+ return self.type_covered
+
+ def to_text(self, origin=None, relativize=True, **kw):
+ return "%s %d %d %d %s %s %d %s %s" % (
+ dns.rdatatype.to_text(self.type_covered),
+ self.algorithm,
+ self.labels,
+ self.original_ttl,
+ posixtime_to_sigtime(self.expiration),
+ posixtime_to_sigtime(self.inception),
+ self.key_tag,
+ self.signer.choose_relativity(origin, relativize),
+ dns.rdata._base64ify(self.signature, **kw),# pyright: ignore
+ )
+
+ @classmethod
+ def from_text(
+ cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None
+ ):
+ type_covered = dns.rdatatype.from_text(tok.get_string())
+ algorithm = dns.dnssectypes.Algorithm.from_text(tok.get_string())
+ labels = tok.get_int()
+ original_ttl = tok.get_ttl()
+ expiration = sigtime_to_posixtime(tok.get_string())
+ inception = sigtime_to_posixtime(tok.get_string())
+ key_tag = tok.get_int()
+ signer = tok.get_name(origin, relativize, relativize_to)
+ b64 = tok.concatenate_remaining_identifiers().encode()
+ signature = base64.b64decode(b64)
+ return cls(
+ rdclass,
+ rdtype,
+ type_covered,
+ algorithm,
+ labels,
+ original_ttl,
+ expiration,
+ inception,
+ key_tag,
+ signer,
+ signature,
+ )
+
+ def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
+ header = struct.pack(
+ "!HBBIIIH",
+ self.type_covered,
+ self.algorithm,
+ self.labels,
+ self.original_ttl,
+ self.expiration,
+ self.inception,
+ self.key_tag,
+ )
+ file.write(header)
+ self.signer.to_wire(file, None, origin, canonicalize)
+ file.write(self.signature)
+
+ @classmethod
+ def from_wire_parser(cls, rdclass, rdtype, parser, origin=None):
+ header = parser.get_struct("!HBBIIIH")
+ signer = parser.get_name(origin)
+ signature = parser.get_remaining()
+ return cls(rdclass, rdtype, *header, signer, signature)# pyright: ignore
--- /dev/null
+# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
+
+import unittest
+
+import dns.rrset
+
+
+class RdtypeAnyKeyTestCase(unittest.TestCase):
+ def testFlagsRRToText(self): # type: () -> None
+ """Test that RR method returns correct flags."""
+
+ rr = dns.rrset.from_text("foo", 300, "IN", "KEY", "HOST 3 8 KEY=")[0]
+ self.assertEqual(rr.flags, 512)
+
+ rr = dns.rrset.from_text("foo", 300, "IN", "KEY", "257 3 8 KEY=")[0]
+ self.assertEqual(rr.flags, 257)
+
+ rr = dns.rrset.from_text("foo", 300, "IN", "KEY", "ZONE|SIG1 3 8 KEY=")[0]
+ self.assertEqual(rr.flags, 257)
+
+ with self.assertRaises(dns.exception.SyntaxError):
+ _ = dns.rrset.from_text("foo", 300, "IN", "KEY", "ZONE|XYZZY 3 8 KEY=")[0]
+
+ rr = dns.rrset.from_text("foo", 300, "IN", "KEY", "NOKEY 3 8")[0]
+ self.assertEqual(rr.flags, 49152)
+ self.assertEqual(rr.protocol, 3)
+ self.assertEqual(rr.algorithm, 8)
+ self.assertEqual(rr.key, b"")
+
+ def testAlgorithmRRToText(self): # type: () -> None
+ """Test that RR method returns correct flags."""
+
+ rr = dns.rrset.from_text("foo", 300, "IN", "KEY", "257 DNSSEC 8 KEY=")[0]
+ self.assertEqual(rr.protocol, 3)
+
+ rr = dns.rrset.from_text("foo", 300, "IN", "KEY", "257 IPSEC 8 KEY=")[0]
+ self.assertEqual(rr.protocol, 4)
+
+ with self.assertRaises(dns.exception.SyntaxError):
+ _ = dns.rrset.from_text("foo", 300, "IN", "KEY", "257 XYZZY 8 KEY=")[0]
+
+
+if __name__ == "__main__":
+ unittest.main()