You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

519 lines
16 KiB

4 years ago
  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. from io import BytesIO
  18. import struct
  19. import time
  20. import dns.exception
  21. import dns.name
  22. import dns.node
  23. import dns.rdataset
  24. import dns.rdata
  25. import dns.rdatatype
  26. import dns.rdataclass
  27. from ._compat import string_types
  28. class UnsupportedAlgorithm(dns.exception.DNSException):
  29. """The DNSSEC algorithm is not supported."""
  30. class ValidationFailure(dns.exception.DNSException):
  31. """The DNSSEC signature is invalid."""
  32. #: RSAMD5
  33. RSAMD5 = 1
  34. #: DH
  35. DH = 2
  36. #: DSA
  37. DSA = 3
  38. #: ECC
  39. ECC = 4
  40. #: RSASHA1
  41. RSASHA1 = 5
  42. #: DSANSEC3SHA1
  43. DSANSEC3SHA1 = 6
  44. #: RSASHA1NSEC3SHA1
  45. RSASHA1NSEC3SHA1 = 7
  46. #: RSASHA256
  47. RSASHA256 = 8
  48. #: RSASHA512
  49. RSASHA512 = 10
  50. #: ECDSAP256SHA256
  51. ECDSAP256SHA256 = 13
  52. #: ECDSAP384SHA384
  53. ECDSAP384SHA384 = 14
  54. #: INDIRECT
  55. INDIRECT = 252
  56. #: PRIVATEDNS
  57. PRIVATEDNS = 253
  58. #: PRIVATEOID
  59. PRIVATEOID = 254
  60. _algorithm_by_text = {
  61. 'RSAMD5': RSAMD5,
  62. 'DH': DH,
  63. 'DSA': DSA,
  64. 'ECC': ECC,
  65. 'RSASHA1': RSASHA1,
  66. 'DSANSEC3SHA1': DSANSEC3SHA1,
  67. 'RSASHA1NSEC3SHA1': RSASHA1NSEC3SHA1,
  68. 'RSASHA256': RSASHA256,
  69. 'RSASHA512': RSASHA512,
  70. 'INDIRECT': INDIRECT,
  71. 'ECDSAP256SHA256': ECDSAP256SHA256,
  72. 'ECDSAP384SHA384': ECDSAP384SHA384,
  73. 'PRIVATEDNS': PRIVATEDNS,
  74. 'PRIVATEOID': PRIVATEOID,
  75. }
  76. # We construct the inverse mapping programmatically to ensure that we
  77. # cannot make any mistakes (e.g. omissions, cut-and-paste errors) that
  78. # would cause the mapping not to be true inverse.
  79. _algorithm_by_value = {y: x for x, y in _algorithm_by_text.items()}
  80. def algorithm_from_text(text):
  81. """Convert text into a DNSSEC algorithm value.
  82. Returns an ``int``.
  83. """
  84. value = _algorithm_by_text.get(text.upper())
  85. if value is None:
  86. value = int(text)
  87. return value
  88. def algorithm_to_text(value):
  89. """Convert a DNSSEC algorithm value to text
  90. Returns a ``str``.
  91. """
  92. text = _algorithm_by_value.get(value)
  93. if text is None:
  94. text = str(value)
  95. return text
  96. def _to_rdata(record, origin):
  97. s = BytesIO()
  98. record.to_wire(s, origin=origin)
  99. return s.getvalue()
  100. def key_id(key, origin=None):
  101. """Return the key id (a 16-bit number) for the specified key.
  102. Note the *origin* parameter of this function is historical and
  103. is not needed.
  104. Returns an ``int`` between 0 and 65535.
  105. """
  106. rdata = _to_rdata(key, origin)
  107. rdata = bytearray(rdata)
  108. if key.algorithm == RSAMD5:
  109. return (rdata[-3] << 8) + rdata[-2]
  110. else:
  111. total = 0
  112. for i in range(len(rdata) // 2):
  113. total += (rdata[2 * i] << 8) + \
  114. rdata[2 * i + 1]
  115. if len(rdata) % 2 != 0:
  116. total += rdata[len(rdata) - 1] << 8
  117. total += ((total >> 16) & 0xffff)
  118. return total & 0xffff
  119. def make_ds(name, key, algorithm, origin=None):
  120. """Create a DS record for a DNSSEC key.
  121. *name* is the owner name of the DS record.
  122. *key* is a ``dns.rdtypes.ANY.DNSKEY``.
  123. *algorithm* is a string describing which hash algorithm to use. The
  124. currently supported hashes are "SHA1" and "SHA256". Case does not
  125. matter for these strings.
  126. *origin* is a ``dns.name.Name`` and will be used as the origin
  127. if *key* is a relative name.
  128. Returns a ``dns.rdtypes.ANY.DS``.
  129. """
  130. if algorithm.upper() == 'SHA1':
  131. dsalg = 1
  132. hash = SHA1.new()
  133. elif algorithm.upper() == 'SHA256':
  134. dsalg = 2
  135. hash = SHA256.new()
  136. else:
  137. raise UnsupportedAlgorithm('unsupported algorithm "%s"' % algorithm)
  138. if isinstance(name, string_types):
  139. name = dns.name.from_text(name, origin)
  140. hash.update(name.canonicalize().to_wire())
  141. hash.update(_to_rdata(key, origin))
  142. digest = hash.digest()
  143. dsrdata = struct.pack("!HBB", key_id(key), key.algorithm, dsalg) + digest
  144. return dns.rdata.from_wire(dns.rdataclass.IN, dns.rdatatype.DS, dsrdata, 0,
  145. len(dsrdata))
  146. def _find_candidate_keys(keys, rrsig):
  147. candidate_keys = []
  148. value = keys.get(rrsig.signer)
  149. if value is None:
  150. return None
  151. if isinstance(value, dns.node.Node):
  152. try:
  153. rdataset = value.find_rdataset(dns.rdataclass.IN,
  154. dns.rdatatype.DNSKEY)
  155. except KeyError:
  156. return None
  157. else:
  158. rdataset = value
  159. for rdata in rdataset:
  160. if rdata.algorithm == rrsig.algorithm and \
  161. key_id(rdata) == rrsig.key_tag:
  162. candidate_keys.append(rdata)
  163. return candidate_keys
  164. def _is_rsa(algorithm):
  165. return algorithm in (RSAMD5, RSASHA1,
  166. RSASHA1NSEC3SHA1, RSASHA256,
  167. RSASHA512)
  168. def _is_dsa(algorithm):
  169. return algorithm in (DSA, DSANSEC3SHA1)
  170. def _is_ecdsa(algorithm):
  171. return _have_ecdsa and (algorithm in (ECDSAP256SHA256, ECDSAP384SHA384))
  172. def _is_md5(algorithm):
  173. return algorithm == RSAMD5
  174. def _is_sha1(algorithm):
  175. return algorithm in (DSA, RSASHA1,
  176. DSANSEC3SHA1, RSASHA1NSEC3SHA1)
  177. def _is_sha256(algorithm):
  178. return algorithm in (RSASHA256, ECDSAP256SHA256)
  179. def _is_sha384(algorithm):
  180. return algorithm == ECDSAP384SHA384
  181. def _is_sha512(algorithm):
  182. return algorithm == RSASHA512
  183. def _make_hash(algorithm):
  184. if _is_md5(algorithm):
  185. return MD5.new()
  186. if _is_sha1(algorithm):
  187. return SHA1.new()
  188. if _is_sha256(algorithm):
  189. return SHA256.new()
  190. if _is_sha384(algorithm):
  191. return SHA384.new()
  192. if _is_sha512(algorithm):
  193. return SHA512.new()
  194. raise ValidationFailure('unknown hash for algorithm %u' % algorithm)
  195. def _make_algorithm_id(algorithm):
  196. if _is_md5(algorithm):
  197. oid = [0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05]
  198. elif _is_sha1(algorithm):
  199. oid = [0x2b, 0x0e, 0x03, 0x02, 0x1a]
  200. elif _is_sha256(algorithm):
  201. oid = [0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01]
  202. elif _is_sha512(algorithm):
  203. oid = [0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03]
  204. else:
  205. raise ValidationFailure('unknown algorithm %u' % algorithm)
  206. olen = len(oid)
  207. dlen = _make_hash(algorithm).digest_size
  208. idbytes = [0x30] + [8 + olen + dlen] + \
  209. [0x30, olen + 4] + [0x06, olen] + oid + \
  210. [0x05, 0x00] + [0x04, dlen]
  211. return struct.pack('!%dB' % len(idbytes), *idbytes)
  212. def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None):
  213. """Validate an RRset against a single signature rdata
  214. The owner name of *rrsig* is assumed to be the same as the owner name
  215. of *rrset*.
  216. *rrset* is the RRset to validate. It can be a ``dns.rrset.RRset`` or
  217. a ``(dns.name.Name, dns.rdataset.Rdataset)`` tuple.
  218. *rrsig* is a ``dns.rdata.Rdata``, the signature to validate.
  219. *keys* is the key dictionary, used to find the DNSKEY associated with
  220. a given name. The dictionary is keyed by a ``dns.name.Name``, and has
  221. ``dns.node.Node`` or ``dns.rdataset.Rdataset`` values.
  222. *origin* is a ``dns.name.Name``, the origin to use for relative names.
  223. *now* is an ``int``, the time to use when validating the signatures,
  224. in seconds since the UNIX epoch. The default is the current time.
  225. """
  226. if isinstance(origin, string_types):
  227. origin = dns.name.from_text(origin, dns.name.root)
  228. candidate_keys = _find_candidate_keys(keys, rrsig)
  229. if candidate_keys is None:
  230. raise ValidationFailure('unknown key')
  231. for candidate_key in candidate_keys:
  232. # For convenience, allow the rrset to be specified as a (name,
  233. # rdataset) tuple as well as a proper rrset
  234. if isinstance(rrset, tuple):
  235. rrname = rrset[0]
  236. rdataset = rrset[1]
  237. else:
  238. rrname = rrset.name
  239. rdataset = rrset
  240. if now is None:
  241. now = time.time()
  242. if rrsig.expiration < now:
  243. raise ValidationFailure('expired')
  244. if rrsig.inception > now:
  245. raise ValidationFailure('not yet valid')
  246. hash = _make_hash(rrsig.algorithm)
  247. if _is_rsa(rrsig.algorithm):
  248. keyptr = candidate_key.key
  249. (bytes_,) = struct.unpack('!B', keyptr[0:1])
  250. keyptr = keyptr[1:]
  251. if bytes_ == 0:
  252. (bytes_,) = struct.unpack('!H', keyptr[0:2])
  253. keyptr = keyptr[2:]
  254. rsa_e = keyptr[0:bytes_]
  255. rsa_n = keyptr[bytes_:]
  256. try:
  257. pubkey = CryptoRSA.construct(
  258. (number.bytes_to_long(rsa_n),
  259. number.bytes_to_long(rsa_e)))
  260. except ValueError:
  261. raise ValidationFailure('invalid public key')
  262. sig = rrsig.signature
  263. elif _is_dsa(rrsig.algorithm):
  264. keyptr = candidate_key.key
  265. (t,) = struct.unpack('!B', keyptr[0:1])
  266. keyptr = keyptr[1:]
  267. octets = 64 + t * 8
  268. dsa_q = keyptr[0:20]
  269. keyptr = keyptr[20:]
  270. dsa_p = keyptr[0:octets]
  271. keyptr = keyptr[octets:]
  272. dsa_g = keyptr[0:octets]
  273. keyptr = keyptr[octets:]
  274. dsa_y = keyptr[0:octets]
  275. pubkey = CryptoDSA.construct(
  276. (number.bytes_to_long(dsa_y),
  277. number.bytes_to_long(dsa_g),
  278. number.bytes_to_long(dsa_p),
  279. number.bytes_to_long(dsa_q)))
  280. sig = rrsig.signature[1:]
  281. elif _is_ecdsa(rrsig.algorithm):
  282. # use ecdsa for NIST-384p -- not currently supported by pycryptodome
  283. keyptr = candidate_key.key
  284. if rrsig.algorithm == ECDSAP256SHA256:
  285. curve = ecdsa.curves.NIST256p
  286. key_len = 32
  287. elif rrsig.algorithm == ECDSAP384SHA384:
  288. curve = ecdsa.curves.NIST384p
  289. key_len = 48
  290. x = number.bytes_to_long(keyptr[0:key_len])
  291. y = number.bytes_to_long(keyptr[key_len:key_len * 2])
  292. if not ecdsa.ecdsa.point_is_valid(curve.generator, x, y):
  293. raise ValidationFailure('invalid ECDSA key')
  294. point = ecdsa.ellipticcurve.Point(curve.curve, x, y, curve.order)
  295. verifying_key = ecdsa.keys.VerifyingKey.from_public_point(point,
  296. curve)
  297. pubkey = ECKeyWrapper(verifying_key, key_len)
  298. r = rrsig.signature[:key_len]
  299. s = rrsig.signature[key_len:]
  300. sig = ecdsa.ecdsa.Signature(number.bytes_to_long(r),
  301. number.bytes_to_long(s))
  302. else:
  303. raise ValidationFailure('unknown algorithm %u' % rrsig.algorithm)
  304. hash.update(_to_rdata(rrsig, origin)[:18])
  305. hash.update(rrsig.signer.to_digestable(origin))
  306. if rrsig.labels < len(rrname) - 1:
  307. suffix = rrname.split(rrsig.labels + 1)[1]
  308. rrname = dns.name.from_text('*', suffix)
  309. rrnamebuf = rrname.to_digestable(origin)
  310. rrfixed = struct.pack('!HHI', rdataset.rdtype, rdataset.rdclass,
  311. rrsig.original_ttl)
  312. rrlist = sorted(rdataset)
  313. for rr in rrlist:
  314. hash.update(rrnamebuf)
  315. hash.update(rrfixed)
  316. rrdata = rr.to_digestable(origin)
  317. rrlen = struct.pack('!H', len(rrdata))
  318. hash.update(rrlen)
  319. hash.update(rrdata)
  320. try:
  321. if _is_rsa(rrsig.algorithm):
  322. verifier = pkcs1_15.new(pubkey)
  323. # will raise ValueError if verify fails:
  324. verifier.verify(hash, sig)
  325. elif _is_dsa(rrsig.algorithm):
  326. verifier = DSS.new(pubkey, 'fips-186-3')
  327. verifier.verify(hash, sig)
  328. elif _is_ecdsa(rrsig.algorithm):
  329. digest = hash.digest()
  330. if not pubkey.verify(digest, sig):
  331. raise ValueError
  332. else:
  333. # Raise here for code clarity; this won't actually ever happen
  334. # since if the algorithm is really unknown we'd already have
  335. # raised an exception above
  336. raise ValidationFailure('unknown algorithm %u' % rrsig.algorithm)
  337. # If we got here, we successfully verified so we can return without error
  338. return
  339. except ValueError:
  340. # this happens on an individual validation failure
  341. continue
  342. # nothing verified -- raise failure:
  343. raise ValidationFailure('verify failure')
  344. def _validate(rrset, rrsigset, keys, origin=None, now=None):
  345. """Validate an RRset.
  346. *rrset* is the RRset to validate. It can be a ``dns.rrset.RRset`` or
  347. a ``(dns.name.Name, dns.rdataset.Rdataset)`` tuple.
  348. *rrsigset* is the signature RRset to be validated. It can be a
  349. ``dns.rrset.RRset`` or a ``(dns.name.Name, dns.rdataset.Rdataset)`` tuple.
  350. *keys* is the key dictionary, used to find the DNSKEY associated with
  351. a given name. The dictionary is keyed by a ``dns.name.Name``, and has
  352. ``dns.node.Node`` or ``dns.rdataset.Rdataset`` values.
  353. *origin* is a ``dns.name.Name``, the origin to use for relative names.
  354. *now* is an ``int``, the time to use when validating the signatures,
  355. in seconds since the UNIX epoch. The default is the current time.
  356. """
  357. if isinstance(origin, string_types):
  358. origin = dns.name.from_text(origin, dns.name.root)
  359. if isinstance(rrset, tuple):
  360. rrname = rrset[0]
  361. else:
  362. rrname = rrset.name
  363. if isinstance(rrsigset, tuple):
  364. rrsigname = rrsigset[0]
  365. rrsigrdataset = rrsigset[1]
  366. else:
  367. rrsigname = rrsigset.name
  368. rrsigrdataset = rrsigset
  369. rrname = rrname.choose_relativity(origin)
  370. rrsigname = rrsigname.choose_relativity(origin)
  371. if rrname != rrsigname:
  372. raise ValidationFailure("owner names do not match")
  373. for rrsig in rrsigrdataset:
  374. try:
  375. _validate_rrsig(rrset, rrsig, keys, origin, now)
  376. return
  377. except ValidationFailure:
  378. pass
  379. raise ValidationFailure("no RRSIGs validated")
  380. def _need_pycrypto(*args, **kwargs):
  381. raise NotImplementedError("DNSSEC validation requires pycryptodome/pycryptodomex")
  382. try:
  383. try:
  384. # test we're using pycryptodome, not pycrypto (which misses SHA1 for example)
  385. from Crypto.Hash import MD5, SHA1, SHA256, SHA384, SHA512
  386. from Crypto.PublicKey import RSA as CryptoRSA, DSA as CryptoDSA
  387. from Crypto.Signature import pkcs1_15, DSS
  388. from Crypto.Util import number
  389. except ImportError:
  390. from Cryptodome.Hash import MD5, SHA1, SHA256, SHA384, SHA512
  391. from Cryptodome.PublicKey import RSA as CryptoRSA, DSA as CryptoDSA
  392. from Cryptodome.Signature import pkcs1_15, DSS
  393. from Cryptodome.Util import number
  394. except ImportError:
  395. validate = _need_pycrypto
  396. validate_rrsig = _need_pycrypto
  397. _have_pycrypto = False
  398. _have_ecdsa = False
  399. else:
  400. validate = _validate
  401. validate_rrsig = _validate_rrsig
  402. _have_pycrypto = True
  403. try:
  404. import ecdsa
  405. import ecdsa.ecdsa
  406. import ecdsa.ellipticcurve
  407. import ecdsa.keys
  408. except ImportError:
  409. _have_ecdsa = False
  410. else:
  411. _have_ecdsa = True
  412. class ECKeyWrapper(object):
  413. def __init__(self, key, key_len):
  414. self.key = key
  415. self.key_len = key_len
  416. def verify(self, digest, sig):
  417. diglong = number.bytes_to_long(digest)
  418. return self.key.pubkey.verifies(diglong, sig)