dnssec.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  1. # Copyright (C) 2003-2007, 2009, 2011 Nominum, Inc.
  2. #
  3. # Permission to use, copy, modify, and distribute this software and its
  4. # documentation for any purpose with or without fee is hereby granted,
  5. # provided that the above copyright notice and this permission notice
  6. # appear in all copies.
  7. #
  8. # THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
  9. # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  10. # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
  11. # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  12. # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  13. # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
  14. # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  15. """Common DNSSEC-related functions and constants."""
  16. from io import BytesIO
  17. import struct
  18. import time
  19. import dns.exception
  20. import dns.hash
  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 = 1
  33. DH = 2
  34. DSA = 3
  35. ECC = 4
  36. RSASHA1 = 5
  37. DSANSEC3SHA1 = 6
  38. RSASHA1NSEC3SHA1 = 7
  39. RSASHA256 = 8
  40. RSASHA512 = 10
  41. ECDSAP256SHA256 = 13
  42. ECDSAP384SHA384 = 14
  43. INDIRECT = 252
  44. PRIVATEDNS = 253
  45. PRIVATEOID = 254
  46. _algorithm_by_text = {
  47. 'RSAMD5': RSAMD5,
  48. 'DH': DH,
  49. 'DSA': DSA,
  50. 'ECC': ECC,
  51. 'RSASHA1': RSASHA1,
  52. 'DSANSEC3SHA1': DSANSEC3SHA1,
  53. 'RSASHA1NSEC3SHA1': RSASHA1NSEC3SHA1,
  54. 'RSASHA256': RSASHA256,
  55. 'RSASHA512': RSASHA512,
  56. 'INDIRECT': INDIRECT,
  57. 'ECDSAP256SHA256': ECDSAP256SHA256,
  58. 'ECDSAP384SHA384': ECDSAP384SHA384,
  59. 'PRIVATEDNS': PRIVATEDNS,
  60. 'PRIVATEOID': PRIVATEOID,
  61. }
  62. # We construct the inverse mapping programmatically to ensure that we
  63. # cannot make any mistakes (e.g. omissions, cut-and-paste errors) that
  64. # would cause the mapping not to be true inverse.
  65. _algorithm_by_value = dict((y, x) for x, y in _algorithm_by_text.items())
  66. def algorithm_from_text(text):
  67. """Convert text into a DNSSEC algorithm value
  68. @rtype: int"""
  69. value = _algorithm_by_text.get(text.upper())
  70. if value is None:
  71. value = int(text)
  72. return value
  73. def algorithm_to_text(value):
  74. """Convert a DNSSEC algorithm value to text
  75. @rtype: string"""
  76. text = _algorithm_by_value.get(value)
  77. if text is None:
  78. text = str(value)
  79. return text
  80. def _to_rdata(record, origin):
  81. s = BytesIO()
  82. record.to_wire(s, origin=origin)
  83. return s.getvalue()
  84. def key_id(key, origin=None):
  85. rdata = _to_rdata(key, origin)
  86. rdata = bytearray(rdata)
  87. if key.algorithm == RSAMD5:
  88. return (rdata[-3] << 8) + rdata[-2]
  89. else:
  90. total = 0
  91. for i in range(len(rdata) // 2):
  92. total += (rdata[2 * i] << 8) + \
  93. rdata[2 * i + 1]
  94. if len(rdata) % 2 != 0:
  95. total += rdata[len(rdata) - 1] << 8
  96. total += ((total >> 16) & 0xffff)
  97. return total & 0xffff
  98. def make_ds(name, key, algorithm, origin=None):
  99. if algorithm.upper() == 'SHA1':
  100. dsalg = 1
  101. hash = dns.hash.hashes['SHA1']()
  102. elif algorithm.upper() == 'SHA256':
  103. dsalg = 2
  104. hash = dns.hash.hashes['SHA256']()
  105. else:
  106. raise UnsupportedAlgorithm('unsupported algorithm "%s"' % algorithm)
  107. if isinstance(name, string_types):
  108. name = dns.name.from_text(name, origin)
  109. hash.update(name.canonicalize().to_wire())
  110. hash.update(_to_rdata(key, origin))
  111. digest = hash.digest()
  112. dsrdata = struct.pack("!HBB", key_id(key), key.algorithm, dsalg) + digest
  113. return dns.rdata.from_wire(dns.rdataclass.IN, dns.rdatatype.DS, dsrdata, 0,
  114. len(dsrdata))
  115. def _find_candidate_keys(keys, rrsig):
  116. candidate_keys = []
  117. value = keys.get(rrsig.signer)
  118. if value is None:
  119. return None
  120. if isinstance(value, dns.node.Node):
  121. try:
  122. rdataset = value.find_rdataset(dns.rdataclass.IN,
  123. dns.rdatatype.DNSKEY)
  124. except KeyError:
  125. return None
  126. else:
  127. rdataset = value
  128. for rdata in rdataset:
  129. if rdata.algorithm == rrsig.algorithm and \
  130. key_id(rdata) == rrsig.key_tag:
  131. candidate_keys.append(rdata)
  132. return candidate_keys
  133. def _is_rsa(algorithm):
  134. return algorithm in (RSAMD5, RSASHA1,
  135. RSASHA1NSEC3SHA1, RSASHA256,
  136. RSASHA512)
  137. def _is_dsa(algorithm):
  138. return algorithm in (DSA, DSANSEC3SHA1)
  139. def _is_ecdsa(algorithm):
  140. return _have_ecdsa and (algorithm in (ECDSAP256SHA256, ECDSAP384SHA384))
  141. def _is_md5(algorithm):
  142. return algorithm == RSAMD5
  143. def _is_sha1(algorithm):
  144. return algorithm in (DSA, RSASHA1,
  145. DSANSEC3SHA1, RSASHA1NSEC3SHA1)
  146. def _is_sha256(algorithm):
  147. return algorithm in (RSASHA256, ECDSAP256SHA256)
  148. def _is_sha384(algorithm):
  149. return algorithm == ECDSAP384SHA384
  150. def _is_sha512(algorithm):
  151. return algorithm == RSASHA512
  152. def _make_hash(algorithm):
  153. if _is_md5(algorithm):
  154. return dns.hash.hashes['MD5']()
  155. if _is_sha1(algorithm):
  156. return dns.hash.hashes['SHA1']()
  157. if _is_sha256(algorithm):
  158. return dns.hash.hashes['SHA256']()
  159. if _is_sha384(algorithm):
  160. return dns.hash.hashes['SHA384']()
  161. if _is_sha512(algorithm):
  162. return dns.hash.hashes['SHA512']()
  163. raise ValidationFailure('unknown hash for algorithm %u' % algorithm)
  164. def _make_algorithm_id(algorithm):
  165. if _is_md5(algorithm):
  166. oid = [0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05]
  167. elif _is_sha1(algorithm):
  168. oid = [0x2b, 0x0e, 0x03, 0x02, 0x1a]
  169. elif _is_sha256(algorithm):
  170. oid = [0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01]
  171. elif _is_sha512(algorithm):
  172. oid = [0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03]
  173. else:
  174. raise ValidationFailure('unknown algorithm %u' % algorithm)
  175. olen = len(oid)
  176. dlen = _make_hash(algorithm).digest_size
  177. idbytes = [0x30] + [8 + olen + dlen] + \
  178. [0x30, olen + 4] + [0x06, olen] + oid + \
  179. [0x05, 0x00] + [0x04, dlen]
  180. return struct.pack('!%dB' % len(idbytes), *idbytes)
  181. def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None):
  182. """Validate an RRset against a single signature rdata
  183. The owner name of the rrsig is assumed to be the same as the owner name
  184. of the rrset.
  185. @param rrset: The RRset to validate
  186. @type rrset: dns.rrset.RRset or (dns.name.Name, dns.rdataset.Rdataset)
  187. tuple
  188. @param rrsig: The signature rdata
  189. @type rrsig: dns.rrset.Rdata
  190. @param keys: The key dictionary.
  191. @type keys: a dictionary keyed by dns.name.Name with node or rdataset
  192. values
  193. @param origin: The origin to use for relative names
  194. @type origin: dns.name.Name or None
  195. @param now: The time to use when validating the signatures. The default
  196. is the current time.
  197. @type now: int
  198. """
  199. if isinstance(origin, string_types):
  200. origin = dns.name.from_text(origin, dns.name.root)
  201. for candidate_key in _find_candidate_keys(keys, rrsig):
  202. if not candidate_key:
  203. raise ValidationFailure('unknown key')
  204. # For convenience, allow the rrset to be specified as a (name,
  205. # rdataset) tuple as well as a proper rrset
  206. if isinstance(rrset, tuple):
  207. rrname = rrset[0]
  208. rdataset = rrset[1]
  209. else:
  210. rrname = rrset.name
  211. rdataset = rrset
  212. if now is None:
  213. now = time.time()
  214. if rrsig.expiration < now:
  215. raise ValidationFailure('expired')
  216. if rrsig.inception > now:
  217. raise ValidationFailure('not yet valid')
  218. hash = _make_hash(rrsig.algorithm)
  219. if _is_rsa(rrsig.algorithm):
  220. keyptr = candidate_key.key
  221. (bytes_,) = struct.unpack('!B', keyptr[0:1])
  222. keyptr = keyptr[1:]
  223. if bytes_ == 0:
  224. (bytes_,) = struct.unpack('!H', keyptr[0:2])
  225. keyptr = keyptr[2:]
  226. rsa_e = keyptr[0:bytes_]
  227. rsa_n = keyptr[bytes_:]
  228. keylen = len(rsa_n) * 8
  229. pubkey = Crypto.PublicKey.RSA.construct(
  230. (Crypto.Util.number.bytes_to_long(rsa_n),
  231. Crypto.Util.number.bytes_to_long(rsa_e)))
  232. sig = (Crypto.Util.number.bytes_to_long(rrsig.signature),)
  233. elif _is_dsa(rrsig.algorithm):
  234. keyptr = candidate_key.key
  235. (t,) = struct.unpack('!B', keyptr[0:1])
  236. keyptr = keyptr[1:]
  237. octets = 64 + t * 8
  238. dsa_q = keyptr[0:20]
  239. keyptr = keyptr[20:]
  240. dsa_p = keyptr[0:octets]
  241. keyptr = keyptr[octets:]
  242. dsa_g = keyptr[0:octets]
  243. keyptr = keyptr[octets:]
  244. dsa_y = keyptr[0:octets]
  245. pubkey = Crypto.PublicKey.DSA.construct(
  246. (Crypto.Util.number.bytes_to_long(dsa_y),
  247. Crypto.Util.number.bytes_to_long(dsa_g),
  248. Crypto.Util.number.bytes_to_long(dsa_p),
  249. Crypto.Util.number.bytes_to_long(dsa_q)))
  250. (dsa_r, dsa_s) = struct.unpack('!20s20s', rrsig.signature[1:])
  251. sig = (Crypto.Util.number.bytes_to_long(dsa_r),
  252. Crypto.Util.number.bytes_to_long(dsa_s))
  253. elif _is_ecdsa(rrsig.algorithm):
  254. if rrsig.algorithm == ECDSAP256SHA256:
  255. curve = ecdsa.curves.NIST256p
  256. key_len = 32
  257. elif rrsig.algorithm == ECDSAP384SHA384:
  258. curve = ecdsa.curves.NIST384p
  259. key_len = 48
  260. else:
  261. # shouldn't happen
  262. raise ValidationFailure('unknown ECDSA curve')
  263. keyptr = candidate_key.key
  264. x = Crypto.Util.number.bytes_to_long(keyptr[0:key_len])
  265. y = Crypto.Util.number.bytes_to_long(keyptr[key_len:key_len * 2])
  266. assert ecdsa.ecdsa.point_is_valid(curve.generator, x, y)
  267. point = ecdsa.ellipticcurve.Point(curve.curve, x, y, curve.order)
  268. verifying_key = ecdsa.keys.VerifyingKey.from_public_point(point,
  269. curve)
  270. pubkey = ECKeyWrapper(verifying_key, key_len)
  271. r = rrsig.signature[:key_len]
  272. s = rrsig.signature[key_len:]
  273. sig = ecdsa.ecdsa.Signature(Crypto.Util.number.bytes_to_long(r),
  274. Crypto.Util.number.bytes_to_long(s))
  275. else:
  276. raise ValidationFailure('unknown algorithm %u' % rrsig.algorithm)
  277. hash.update(_to_rdata(rrsig, origin)[:18])
  278. hash.update(rrsig.signer.to_digestable(origin))
  279. if rrsig.labels < len(rrname) - 1:
  280. suffix = rrname.split(rrsig.labels + 1)[1]
  281. rrname = dns.name.from_text('*', suffix)
  282. rrnamebuf = rrname.to_digestable(origin)
  283. rrfixed = struct.pack('!HHI', rdataset.rdtype, rdataset.rdclass,
  284. rrsig.original_ttl)
  285. rrlist = sorted(rdataset)
  286. for rr in rrlist:
  287. hash.update(rrnamebuf)
  288. hash.update(rrfixed)
  289. rrdata = rr.to_digestable(origin)
  290. rrlen = struct.pack('!H', len(rrdata))
  291. hash.update(rrlen)
  292. hash.update(rrdata)
  293. digest = hash.digest()
  294. if _is_rsa(rrsig.algorithm):
  295. # PKCS1 algorithm identifier goop
  296. digest = _make_algorithm_id(rrsig.algorithm) + digest
  297. padlen = keylen // 8 - len(digest) - 3
  298. digest = struct.pack('!%dB' % (2 + padlen + 1),
  299. *([0, 1] + [0xFF] * padlen + [0])) + digest
  300. elif _is_dsa(rrsig.algorithm) or _is_ecdsa(rrsig.algorithm):
  301. pass
  302. else:
  303. # Raise here for code clarity; this won't actually ever happen
  304. # since if the algorithm is really unknown we'd already have
  305. # raised an exception above
  306. raise ValidationFailure('unknown algorithm %u' % rrsig.algorithm)
  307. if pubkey.verify(digest, sig):
  308. return
  309. raise ValidationFailure('verify failure')
  310. def _validate(rrset, rrsigset, keys, origin=None, now=None):
  311. """Validate an RRset
  312. @param rrset: The RRset to validate
  313. @type rrset: dns.rrset.RRset or (dns.name.Name, dns.rdataset.Rdataset)
  314. tuple
  315. @param rrsigset: The signature RRset
  316. @type rrsigset: dns.rrset.RRset or (dns.name.Name, dns.rdataset.Rdataset)
  317. tuple
  318. @param keys: The key dictionary.
  319. @type keys: a dictionary keyed by dns.name.Name with node or rdataset
  320. values
  321. @param origin: The origin to use for relative names
  322. @type origin: dns.name.Name or None
  323. @param now: The time to use when validating the signatures. The default
  324. is the current time.
  325. @type now: int
  326. """
  327. if isinstance(origin, string_types):
  328. origin = dns.name.from_text(origin, dns.name.root)
  329. if isinstance(rrset, tuple):
  330. rrname = rrset[0]
  331. else:
  332. rrname = rrset.name
  333. if isinstance(rrsigset, tuple):
  334. rrsigname = rrsigset[0]
  335. rrsigrdataset = rrsigset[1]
  336. else:
  337. rrsigname = rrsigset.name
  338. rrsigrdataset = rrsigset
  339. rrname = rrname.choose_relativity(origin)
  340. rrsigname = rrname.choose_relativity(origin)
  341. if rrname != rrsigname:
  342. raise ValidationFailure("owner names do not match")
  343. for rrsig in rrsigrdataset:
  344. try:
  345. _validate_rrsig(rrset, rrsig, keys, origin, now)
  346. return
  347. except ValidationFailure:
  348. pass
  349. raise ValidationFailure("no RRSIGs validated")
  350. def _need_pycrypto(*args, **kwargs):
  351. raise NotImplementedError("DNSSEC validation requires pycrypto")
  352. try:
  353. import Crypto.PublicKey.RSA
  354. import Crypto.PublicKey.DSA
  355. import Crypto.Util.number
  356. validate = _validate
  357. validate_rrsig = _validate_rrsig
  358. _have_pycrypto = True
  359. except ImportError:
  360. validate = _need_pycrypto
  361. validate_rrsig = _need_pycrypto
  362. _have_pycrypto = False
  363. try:
  364. import ecdsa
  365. import ecdsa.ecdsa
  366. import ecdsa.ellipticcurve
  367. import ecdsa.keys
  368. _have_ecdsa = True
  369. class ECKeyWrapper(object):
  370. def __init__(self, key, key_len):
  371. self.key = key
  372. self.key_len = key_len
  373. def verify(self, digest, sig):
  374. diglong = Crypto.Util.number.bytes_to_long(digest)
  375. return self.key.pubkey.verifies(diglong, sig)
  376. except ImportError:
  377. _have_ecdsa = False