dnssec.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594
  1. # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
  2. # Copyright (C) 2003-2017 Nominum, Inc.
  3. #
  4. # Permission to use, copy, modify, and distribute this software and its
  5. # documentation for any purpose with or without fee is hereby granted,
  6. # provided that the above copyright notice and this permission notice
  7. # appear in all copies.
  8. #
  9. # THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
  10. # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  11. # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
  12. # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  13. # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  14. # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
  15. # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  16. """Common DNSSEC-related functions and constants."""
  17. import hashlib
  18. import struct
  19. import time
  20. import base64
  21. import dns.enum
  22. import dns.exception
  23. import dns.name
  24. import dns.node
  25. import dns.rdataset
  26. import dns.rdata
  27. import dns.rdatatype
  28. import dns.rdataclass
  29. class UnsupportedAlgorithm(dns.exception.DNSException):
  30. """The DNSSEC algorithm is not supported."""
  31. class ValidationFailure(dns.exception.DNSException):
  32. """The DNSSEC signature is invalid."""
  33. class Algorithm(dns.enum.IntEnum):
  34. RSAMD5 = 1
  35. DH = 2
  36. DSA = 3
  37. ECC = 4
  38. RSASHA1 = 5
  39. DSANSEC3SHA1 = 6
  40. RSASHA1NSEC3SHA1 = 7
  41. RSASHA256 = 8
  42. RSASHA512 = 10
  43. ECCGOST = 12
  44. ECDSAP256SHA256 = 13
  45. ECDSAP384SHA384 = 14
  46. ED25519 = 15
  47. ED448 = 16
  48. INDIRECT = 252
  49. PRIVATEDNS = 253
  50. PRIVATEOID = 254
  51. @classmethod
  52. def _maximum(cls):
  53. return 255
  54. def algorithm_from_text(text):
  55. """Convert text into a DNSSEC algorithm value.
  56. *text*, a ``str``, the text to convert to into an algorithm value.
  57. Returns an ``int``.
  58. """
  59. return Algorithm.from_text(text)
  60. def algorithm_to_text(value):
  61. """Convert a DNSSEC algorithm value to text
  62. *value*, an ``int`` a DNSSEC algorithm.
  63. Returns a ``str``, the name of a DNSSEC algorithm.
  64. """
  65. return Algorithm.to_text(value)
  66. def key_id(key):
  67. """Return the key id (a 16-bit number) for the specified key.
  68. *key*, a ``dns.rdtypes.ANY.DNSKEY.DNSKEY``
  69. Returns an ``int`` between 0 and 65535
  70. """
  71. rdata = key.to_wire()
  72. if key.algorithm == Algorithm.RSAMD5:
  73. return (rdata[-3] << 8) + rdata[-2]
  74. else:
  75. total = 0
  76. for i in range(len(rdata) // 2):
  77. total += (rdata[2 * i] << 8) + \
  78. rdata[2 * i + 1]
  79. if len(rdata) % 2 != 0:
  80. total += rdata[len(rdata) - 1] << 8
  81. total += ((total >> 16) & 0xffff)
  82. return total & 0xffff
  83. class DSDigest(dns.enum.IntEnum):
  84. """DNSSEC Delegation Signer Digest Algorithm"""
  85. SHA1 = 1
  86. SHA256 = 2
  87. SHA384 = 4
  88. @classmethod
  89. def _maximum(cls):
  90. return 255
  91. def make_ds(name, key, algorithm, origin=None):
  92. """Create a DS record for a DNSSEC key.
  93. *name*, a ``dns.name.Name`` or ``str``, the owner name of the DS record.
  94. *key*, a ``dns.rdtypes.ANY.DNSKEY.DNSKEY``, the key the DS is about.
  95. *algorithm*, a ``str`` or ``int`` specifying the hash algorithm.
  96. The currently supported hashes are "SHA1", "SHA256", and "SHA384". Case
  97. does not matter for these strings.
  98. *origin*, a ``dns.name.Name`` or ``None``. If `key` is a relative name,
  99. then it will be made absolute using the specified origin.
  100. Raises ``UnsupportedAlgorithm`` if the algorithm is unknown.
  101. Returns a ``dns.rdtypes.ANY.DS.DS``
  102. """
  103. try:
  104. if isinstance(algorithm, str):
  105. algorithm = DSDigest[algorithm.upper()]
  106. except Exception:
  107. raise UnsupportedAlgorithm('unsupported algorithm "%s"' % algorithm)
  108. if algorithm == DSDigest.SHA1:
  109. dshash = hashlib.sha1()
  110. elif algorithm == DSDigest.SHA256:
  111. dshash = hashlib.sha256()
  112. elif algorithm == DSDigest.SHA384:
  113. dshash = hashlib.sha384()
  114. else:
  115. raise UnsupportedAlgorithm('unsupported algorithm "%s"' % algorithm)
  116. if isinstance(name, str):
  117. name = dns.name.from_text(name, origin)
  118. dshash.update(name.canonicalize().to_wire())
  119. dshash.update(key.to_wire(origin=origin))
  120. digest = dshash.digest()
  121. dsrdata = struct.pack("!HBB", key_id(key), key.algorithm, algorithm) + \
  122. digest
  123. return dns.rdata.from_wire(dns.rdataclass.IN, dns.rdatatype.DS, dsrdata, 0,
  124. len(dsrdata))
  125. def _find_candidate_keys(keys, rrsig):
  126. value = keys.get(rrsig.signer)
  127. if isinstance(value, dns.node.Node):
  128. rdataset = value.get_rdataset(dns.rdataclass.IN, dns.rdatatype.DNSKEY)
  129. else:
  130. rdataset = value
  131. if rdataset is None:
  132. return None
  133. return [rd for rd in rdataset if
  134. rd.algorithm == rrsig.algorithm and key_id(rd) == rrsig.key_tag]
  135. def _is_rsa(algorithm):
  136. return algorithm in (Algorithm.RSAMD5, Algorithm.RSASHA1,
  137. Algorithm.RSASHA1NSEC3SHA1, Algorithm.RSASHA256,
  138. Algorithm.RSASHA512)
  139. def _is_dsa(algorithm):
  140. return algorithm in (Algorithm.DSA, Algorithm.DSANSEC3SHA1)
  141. def _is_ecdsa(algorithm):
  142. return algorithm in (Algorithm.ECDSAP256SHA256, Algorithm.ECDSAP384SHA384)
  143. def _is_eddsa(algorithm):
  144. return algorithm in (Algorithm.ED25519, Algorithm.ED448)
  145. def _is_gost(algorithm):
  146. return algorithm == Algorithm.ECCGOST
  147. def _is_md5(algorithm):
  148. return algorithm == Algorithm.RSAMD5
  149. def _is_sha1(algorithm):
  150. return algorithm in (Algorithm.DSA, Algorithm.RSASHA1,
  151. Algorithm.DSANSEC3SHA1, Algorithm.RSASHA1NSEC3SHA1)
  152. def _is_sha256(algorithm):
  153. return algorithm in (Algorithm.RSASHA256, Algorithm.ECDSAP256SHA256)
  154. def _is_sha384(algorithm):
  155. return algorithm == Algorithm.ECDSAP384SHA384
  156. def _is_sha512(algorithm):
  157. return algorithm == Algorithm.RSASHA512
  158. def _make_hash(algorithm):
  159. if _is_md5(algorithm):
  160. return hashes.MD5()
  161. if _is_sha1(algorithm):
  162. return hashes.SHA1()
  163. if _is_sha256(algorithm):
  164. return hashes.SHA256()
  165. if _is_sha384(algorithm):
  166. return hashes.SHA384()
  167. if _is_sha512(algorithm):
  168. return hashes.SHA512()
  169. if algorithm == Algorithm.ED25519:
  170. return hashes.SHA512()
  171. if algorithm == Algorithm.ED448:
  172. return hashes.SHAKE256(114)
  173. raise ValidationFailure('unknown hash for algorithm %u' % algorithm)
  174. def _bytes_to_long(b):
  175. return int.from_bytes(b, 'big')
  176. def _validate_signature(sig, data, key, chosen_hash):
  177. if _is_rsa(key.algorithm):
  178. keyptr = key.key
  179. (bytes_,) = struct.unpack('!B', keyptr[0:1])
  180. keyptr = keyptr[1:]
  181. if bytes_ == 0:
  182. (bytes_,) = struct.unpack('!H', keyptr[0:2])
  183. keyptr = keyptr[2:]
  184. rsa_e = keyptr[0:bytes_]
  185. rsa_n = keyptr[bytes_:]
  186. try:
  187. public_key = rsa.RSAPublicNumbers(
  188. _bytes_to_long(rsa_e),
  189. _bytes_to_long(rsa_n)).public_key(default_backend())
  190. except ValueError:
  191. raise ValidationFailure('invalid public key')
  192. public_key.verify(sig, data, padding.PKCS1v15(), chosen_hash)
  193. elif _is_dsa(key.algorithm):
  194. keyptr = key.key
  195. (t,) = struct.unpack('!B', keyptr[0:1])
  196. keyptr = keyptr[1:]
  197. octets = 64 + t * 8
  198. dsa_q = keyptr[0:20]
  199. keyptr = keyptr[20:]
  200. dsa_p = keyptr[0:octets]
  201. keyptr = keyptr[octets:]
  202. dsa_g = keyptr[0:octets]
  203. keyptr = keyptr[octets:]
  204. dsa_y = keyptr[0:octets]
  205. try:
  206. public_key = dsa.DSAPublicNumbers(
  207. _bytes_to_long(dsa_y),
  208. dsa.DSAParameterNumbers(
  209. _bytes_to_long(dsa_p),
  210. _bytes_to_long(dsa_q),
  211. _bytes_to_long(dsa_g))).public_key(default_backend())
  212. except ValueError:
  213. raise ValidationFailure('invalid public key')
  214. public_key.verify(sig, data, chosen_hash)
  215. elif _is_ecdsa(key.algorithm):
  216. keyptr = key.key
  217. if key.algorithm == Algorithm.ECDSAP256SHA256:
  218. curve = ec.SECP256R1()
  219. octets = 32
  220. else:
  221. curve = ec.SECP384R1()
  222. octets = 48
  223. ecdsa_x = keyptr[0:octets]
  224. ecdsa_y = keyptr[octets:octets * 2]
  225. try:
  226. public_key = ec.EllipticCurvePublicNumbers(
  227. curve=curve,
  228. x=_bytes_to_long(ecdsa_x),
  229. y=_bytes_to_long(ecdsa_y)).public_key(default_backend())
  230. except ValueError:
  231. raise ValidationFailure('invalid public key')
  232. public_key.verify(sig, data, ec.ECDSA(chosen_hash))
  233. elif _is_eddsa(key.algorithm):
  234. keyptr = key.key
  235. if key.algorithm == Algorithm.ED25519:
  236. loader = ed25519.Ed25519PublicKey
  237. else:
  238. loader = ed448.Ed448PublicKey
  239. try:
  240. public_key = loader.from_public_bytes(keyptr)
  241. except ValueError:
  242. raise ValidationFailure('invalid public key')
  243. public_key.verify(sig, data)
  244. elif _is_gost(key.algorithm):
  245. raise UnsupportedAlgorithm(
  246. 'algorithm "%s" not supported by dnspython' %
  247. algorithm_to_text(key.algorithm))
  248. else:
  249. raise ValidationFailure('unknown algorithm %u' % key.algorithm)
  250. def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None):
  251. """Validate an RRset against a single signature rdata, throwing an
  252. exception if validation is not successful.
  253. *rrset*, the RRset to validate. This can be a
  254. ``dns.rrset.RRset`` or a (``dns.name.Name``, ``dns.rdataset.Rdataset``)
  255. tuple.
  256. *rrsig*, a ``dns.rdata.Rdata``, the signature to validate.
  257. *keys*, the key dictionary, used to find the DNSKEY associated
  258. with a given name. The dictionary is keyed by a
  259. ``dns.name.Name``, and has ``dns.node.Node`` or
  260. ``dns.rdataset.Rdataset`` values.
  261. *origin*, a ``dns.name.Name`` or ``None``, the origin to use for relative
  262. names.
  263. *now*, an ``int`` or ``None``, the time, in seconds since the epoch, to
  264. use as the current time when validating. If ``None``, the actual current
  265. time is used.
  266. Raises ``ValidationFailure`` if the signature is expired, not yet valid,
  267. the public key is invalid, the algorithm is unknown, the verification
  268. fails, etc.
  269. Raises ``UnsupportedAlgorithm`` if the algorithm is recognized by
  270. dnspython but not implemented.
  271. """
  272. if isinstance(origin, str):
  273. origin = dns.name.from_text(origin, dns.name.root)
  274. candidate_keys = _find_candidate_keys(keys, rrsig)
  275. if candidate_keys is None:
  276. raise ValidationFailure('unknown key')
  277. # For convenience, allow the rrset to be specified as a (name,
  278. # rdataset) tuple as well as a proper rrset
  279. if isinstance(rrset, tuple):
  280. rrname = rrset[0]
  281. rdataset = rrset[1]
  282. else:
  283. rrname = rrset.name
  284. rdataset = rrset
  285. if now is None:
  286. now = time.time()
  287. if rrsig.expiration < now:
  288. raise ValidationFailure('expired')
  289. if rrsig.inception > now:
  290. raise ValidationFailure('not yet valid')
  291. if _is_dsa(rrsig.algorithm):
  292. sig_r = rrsig.signature[1:21]
  293. sig_s = rrsig.signature[21:]
  294. sig = utils.encode_dss_signature(_bytes_to_long(sig_r),
  295. _bytes_to_long(sig_s))
  296. elif _is_ecdsa(rrsig.algorithm):
  297. if rrsig.algorithm == Algorithm.ECDSAP256SHA256:
  298. octets = 32
  299. else:
  300. octets = 48
  301. sig_r = rrsig.signature[0:octets]
  302. sig_s = rrsig.signature[octets:]
  303. sig = utils.encode_dss_signature(_bytes_to_long(sig_r),
  304. _bytes_to_long(sig_s))
  305. else:
  306. sig = rrsig.signature
  307. data = b''
  308. data += rrsig.to_wire(origin=origin)[:18]
  309. data += rrsig.signer.to_digestable(origin)
  310. # Derelativize the name before considering labels.
  311. rrname = rrname.derelativize(origin)
  312. if len(rrname) - 1 < rrsig.labels:
  313. raise ValidationFailure('owner name longer than RRSIG labels')
  314. elif rrsig.labels < len(rrname) - 1:
  315. suffix = rrname.split(rrsig.labels + 1)[1]
  316. rrname = dns.name.from_text('*', suffix)
  317. rrnamebuf = rrname.to_digestable()
  318. rrfixed = struct.pack('!HHI', rdataset.rdtype, rdataset.rdclass,
  319. rrsig.original_ttl)
  320. rdatas = [rdata.to_digestable(origin) for rdata in rdataset]
  321. for rdata in sorted(rdatas):
  322. data += rrnamebuf
  323. data += rrfixed
  324. rrlen = struct.pack('!H', len(rdata))
  325. data += rrlen
  326. data += rdata
  327. chosen_hash = _make_hash(rrsig.algorithm)
  328. for candidate_key in candidate_keys:
  329. try:
  330. _validate_signature(sig, data, candidate_key, chosen_hash)
  331. return
  332. except (InvalidSignature, ValidationFailure):
  333. # this happens on an individual validation failure
  334. continue
  335. # nothing verified -- raise failure:
  336. raise ValidationFailure('verify failure')
  337. def _validate(rrset, rrsigset, keys, origin=None, now=None):
  338. """Validate an RRset against a signature RRset, throwing an exception
  339. if none of the signatures validate.
  340. *rrset*, the RRset to validate. This can be a
  341. ``dns.rrset.RRset`` or a (``dns.name.Name``, ``dns.rdataset.Rdataset``)
  342. tuple.
  343. *rrsigset*, the signature RRset. This can be a
  344. ``dns.rrset.RRset`` or a (``dns.name.Name``, ``dns.rdataset.Rdataset``)
  345. tuple.
  346. *keys*, the key dictionary, used to find the DNSKEY associated
  347. with a given name. The dictionary is keyed by a
  348. ``dns.name.Name``, and has ``dns.node.Node`` or
  349. ``dns.rdataset.Rdataset`` values.
  350. *origin*, a ``dns.name.Name``, the origin to use for relative names;
  351. defaults to None.
  352. *now*, an ``int`` or ``None``, the time, in seconds since the epoch, to
  353. use as the current time when validating. If ``None``, the actual current
  354. time is used.
  355. Raises ``ValidationFailure`` if the signature is expired, not yet valid,
  356. the public key is invalid, the algorithm is unknown, the verification
  357. fails, etc.
  358. """
  359. if isinstance(origin, str):
  360. origin = dns.name.from_text(origin, dns.name.root)
  361. if isinstance(rrset, tuple):
  362. rrname = rrset[0]
  363. else:
  364. rrname = rrset.name
  365. if isinstance(rrsigset, tuple):
  366. rrsigname = rrsigset[0]
  367. rrsigrdataset = rrsigset[1]
  368. else:
  369. rrsigname = rrsigset.name
  370. rrsigrdataset = rrsigset
  371. rrname = rrname.choose_relativity(origin)
  372. rrsigname = rrsigname.choose_relativity(origin)
  373. if rrname != rrsigname:
  374. raise ValidationFailure("owner names do not match")
  375. for rrsig in rrsigrdataset:
  376. try:
  377. _validate_rrsig(rrset, rrsig, keys, origin, now)
  378. return
  379. except (ValidationFailure, UnsupportedAlgorithm):
  380. pass
  381. raise ValidationFailure("no RRSIGs validated")
  382. class NSEC3Hash(dns.enum.IntEnum):
  383. """NSEC3 hash algorithm"""
  384. SHA1 = 1
  385. @classmethod
  386. def _maximum(cls):
  387. return 255
  388. def nsec3_hash(domain, salt, iterations, algorithm):
  389. """
  390. Calculate the NSEC3 hash, according to
  391. https://tools.ietf.org/html/rfc5155#section-5
  392. *domain*, a ``dns.name.Name`` or ``str``, the name to hash.
  393. *salt*, a ``str``, ``bytes``, or ``None``, the hash salt. If a
  394. string, it is decoded as a hex string.
  395. *iterations*, an ``int``, the number of iterations.
  396. *algorithm*, a ``str`` or ``int``, the hash algorithm.
  397. The only defined algorithm is SHA1.
  398. Returns a ``str``, the encoded NSEC3 hash.
  399. """
  400. b32_conversion = str.maketrans(
  401. "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567", "0123456789ABCDEFGHIJKLMNOPQRSTUV"
  402. )
  403. try:
  404. if isinstance(algorithm, str):
  405. algorithm = NSEC3Hash[algorithm.upper()]
  406. except Exception:
  407. raise ValueError("Wrong hash algorithm (only SHA1 is supported)")
  408. if algorithm != NSEC3Hash.SHA1:
  409. raise ValueError("Wrong hash algorithm (only SHA1 is supported)")
  410. salt_encoded = salt
  411. if salt is None:
  412. salt_encoded = b''
  413. elif isinstance(salt, str):
  414. if len(salt) % 2 == 0:
  415. salt_encoded = bytes.fromhex(salt)
  416. else:
  417. raise ValueError("Invalid salt length")
  418. if not isinstance(domain, dns.name.Name):
  419. domain = dns.name.from_text(domain)
  420. domain_encoded = domain.canonicalize().to_wire()
  421. digest = hashlib.sha1(domain_encoded + salt_encoded).digest()
  422. for _ in range(iterations):
  423. digest = hashlib.sha1(digest + salt_encoded).digest()
  424. output = base64.b32encode(digest).decode("utf-8")
  425. output = output.translate(b32_conversion)
  426. return output
  427. def _need_pyca(*args, **kwargs):
  428. raise ImportError("DNSSEC validation requires " +
  429. "python cryptography") # pragma: no cover
  430. try:
  431. from cryptography.exceptions import InvalidSignature
  432. from cryptography.hazmat.backends import default_backend
  433. from cryptography.hazmat.primitives import hashes
  434. from cryptography.hazmat.primitives.asymmetric import padding
  435. from cryptography.hazmat.primitives.asymmetric import utils
  436. from cryptography.hazmat.primitives.asymmetric import dsa
  437. from cryptography.hazmat.primitives.asymmetric import ec
  438. from cryptography.hazmat.primitives.asymmetric import ed25519
  439. from cryptography.hazmat.primitives.asymmetric import ed448
  440. from cryptography.hazmat.primitives.asymmetric import rsa
  441. except ImportError: # pragma: no cover
  442. validate = _need_pyca
  443. validate_rrsig = _need_pyca
  444. _have_pyca = False
  445. else:
  446. validate = _validate # type: ignore
  447. validate_rrsig = _validate_rrsig # type: ignore
  448. _have_pyca = True
  449. ### BEGIN generated Algorithm constants
  450. RSAMD5 = Algorithm.RSAMD5
  451. DH = Algorithm.DH
  452. DSA = Algorithm.DSA
  453. ECC = Algorithm.ECC
  454. RSASHA1 = Algorithm.RSASHA1
  455. DSANSEC3SHA1 = Algorithm.DSANSEC3SHA1
  456. RSASHA1NSEC3SHA1 = Algorithm.RSASHA1NSEC3SHA1
  457. RSASHA256 = Algorithm.RSASHA256
  458. RSASHA512 = Algorithm.RSASHA512
  459. ECCGOST = Algorithm.ECCGOST
  460. ECDSAP256SHA256 = Algorithm.ECDSAP256SHA256
  461. ECDSAP384SHA384 = Algorithm.ECDSAP384SHA384
  462. ED25519 = Algorithm.ED25519
  463. ED448 = Algorithm.ED448
  464. INDIRECT = Algorithm.INDIRECT
  465. PRIVATEDNS = Algorithm.PRIVATEDNS
  466. PRIVATEOID = Algorithm.PRIVATEOID
  467. ### END generated Algorithm constants