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.

236 lines
7.6 KiB

4 years ago
  1. # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
  2. # Copyright (C) 2001-2007, 2009-2011 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. """DNS TSIG support."""
  17. import hashlib
  18. import hmac
  19. import struct
  20. import dns.exception
  21. import dns.rdataclass
  22. import dns.name
  23. from ._compat import long, string_types, text_type
  24. class BadTime(dns.exception.DNSException):
  25. """The current time is not within the TSIG's validity time."""
  26. class BadSignature(dns.exception.DNSException):
  27. """The TSIG signature fails to verify."""
  28. class PeerError(dns.exception.DNSException):
  29. """Base class for all TSIG errors generated by the remote peer"""
  30. class PeerBadKey(PeerError):
  31. """The peer didn't know the key we used"""
  32. class PeerBadSignature(PeerError):
  33. """The peer didn't like the signature we sent"""
  34. class PeerBadTime(PeerError):
  35. """The peer didn't like the time we sent"""
  36. class PeerBadTruncation(PeerError):
  37. """The peer didn't like amount of truncation in the TSIG we sent"""
  38. # TSIG Algorithms
  39. HMAC_MD5 = dns.name.from_text("HMAC-MD5.SIG-ALG.REG.INT")
  40. HMAC_SHA1 = dns.name.from_text("hmac-sha1")
  41. HMAC_SHA224 = dns.name.from_text("hmac-sha224")
  42. HMAC_SHA256 = dns.name.from_text("hmac-sha256")
  43. HMAC_SHA384 = dns.name.from_text("hmac-sha384")
  44. HMAC_SHA512 = dns.name.from_text("hmac-sha512")
  45. _hashes = {
  46. HMAC_SHA224: hashlib.sha224,
  47. HMAC_SHA256: hashlib.sha256,
  48. HMAC_SHA384: hashlib.sha384,
  49. HMAC_SHA512: hashlib.sha512,
  50. HMAC_SHA1: hashlib.sha1,
  51. HMAC_MD5: hashlib.md5,
  52. }
  53. default_algorithm = HMAC_MD5
  54. BADSIG = 16
  55. BADKEY = 17
  56. BADTIME = 18
  57. BADTRUNC = 22
  58. def sign(wire, keyname, secret, time, fudge, original_id, error,
  59. other_data, request_mac, ctx=None, multi=False, first=True,
  60. algorithm=default_algorithm):
  61. """Return a (tsig_rdata, mac, ctx) tuple containing the HMAC TSIG rdata
  62. for the input parameters, the HMAC MAC calculated by applying the
  63. TSIG signature algorithm, and the TSIG digest context.
  64. @rtype: (string, string, hmac.HMAC object)
  65. @raises ValueError: I{other_data} is too long
  66. @raises NotImplementedError: I{algorithm} is not supported
  67. """
  68. if isinstance(other_data, text_type):
  69. other_data = other_data.encode()
  70. (algorithm_name, digestmod) = get_algorithm(algorithm)
  71. if first:
  72. ctx = hmac.new(secret, digestmod=digestmod)
  73. ml = len(request_mac)
  74. if ml > 0:
  75. ctx.update(struct.pack('!H', ml))
  76. ctx.update(request_mac)
  77. id = struct.pack('!H', original_id)
  78. ctx.update(id)
  79. ctx.update(wire[2:])
  80. if first:
  81. ctx.update(keyname.to_digestable())
  82. ctx.update(struct.pack('!H', dns.rdataclass.ANY))
  83. ctx.update(struct.pack('!I', 0))
  84. long_time = time + long(0)
  85. upper_time = (long_time >> 32) & long(0xffff)
  86. lower_time = long_time & long(0xffffffff)
  87. time_mac = struct.pack('!HIH', upper_time, lower_time, fudge)
  88. pre_mac = algorithm_name + time_mac
  89. ol = len(other_data)
  90. if ol > 65535:
  91. raise ValueError('TSIG Other Data is > 65535 bytes')
  92. post_mac = struct.pack('!HH', error, ol) + other_data
  93. if first:
  94. ctx.update(pre_mac)
  95. ctx.update(post_mac)
  96. else:
  97. ctx.update(time_mac)
  98. mac = ctx.digest()
  99. mpack = struct.pack('!H', len(mac))
  100. tsig_rdata = pre_mac + mpack + mac + id + post_mac
  101. if multi:
  102. ctx = hmac.new(secret, digestmod=digestmod)
  103. ml = len(mac)
  104. ctx.update(struct.pack('!H', ml))
  105. ctx.update(mac)
  106. else:
  107. ctx = None
  108. return (tsig_rdata, mac, ctx)
  109. def hmac_md5(wire, keyname, secret, time, fudge, original_id, error,
  110. other_data, request_mac, ctx=None, multi=False, first=True,
  111. algorithm=default_algorithm):
  112. return sign(wire, keyname, secret, time, fudge, original_id, error,
  113. other_data, request_mac, ctx, multi, first, algorithm)
  114. def validate(wire, keyname, secret, now, request_mac, tsig_start, tsig_rdata,
  115. tsig_rdlen, ctx=None, multi=False, first=True):
  116. """Validate the specified TSIG rdata against the other input parameters.
  117. @raises FormError: The TSIG is badly formed.
  118. @raises BadTime: There is too much time skew between the client and the
  119. server.
  120. @raises BadSignature: The TSIG signature did not validate
  121. @rtype: hmac.HMAC object"""
  122. (adcount,) = struct.unpack("!H", wire[10:12])
  123. if adcount == 0:
  124. raise dns.exception.FormError
  125. adcount -= 1
  126. new_wire = wire[0:10] + struct.pack("!H", adcount) + wire[12:tsig_start]
  127. current = tsig_rdata
  128. (aname, used) = dns.name.from_wire(wire, current)
  129. current = current + used
  130. (upper_time, lower_time, fudge, mac_size) = \
  131. struct.unpack("!HIHH", wire[current:current + 10])
  132. time = ((upper_time + long(0)) << 32) + (lower_time + long(0))
  133. current += 10
  134. mac = wire[current:current + mac_size]
  135. current += mac_size
  136. (original_id, error, other_size) = \
  137. struct.unpack("!HHH", wire[current:current + 6])
  138. current += 6
  139. other_data = wire[current:current + other_size]
  140. current += other_size
  141. if current != tsig_rdata + tsig_rdlen:
  142. raise dns.exception.FormError
  143. if error != 0:
  144. if error == BADSIG:
  145. raise PeerBadSignature
  146. elif error == BADKEY:
  147. raise PeerBadKey
  148. elif error == BADTIME:
  149. raise PeerBadTime
  150. elif error == BADTRUNC:
  151. raise PeerBadTruncation
  152. else:
  153. raise PeerError('unknown TSIG error code %d' % error)
  154. time_low = time - fudge
  155. time_high = time + fudge
  156. if now < time_low or now > time_high:
  157. raise BadTime
  158. (junk, our_mac, ctx) = sign(new_wire, keyname, secret, time, fudge,
  159. original_id, error, other_data,
  160. request_mac, ctx, multi, first, aname)
  161. if our_mac != mac:
  162. raise BadSignature
  163. return ctx
  164. def get_algorithm(algorithm):
  165. """Returns the wire format string and the hash module to use for the
  166. specified TSIG algorithm
  167. @rtype: (string, hash constructor)
  168. @raises NotImplementedError: I{algorithm} is not supported
  169. """
  170. if isinstance(algorithm, string_types):
  171. algorithm = dns.name.from_text(algorithm)
  172. try:
  173. return (algorithm.to_digestable(), _hashes[algorithm])
  174. except KeyError:
  175. raise NotImplementedError("TSIG algorithm " + str(algorithm) +
  176. " is not supported")
  177. def get_algorithm_and_mac(wire, tsig_rdata, tsig_rdlen):
  178. """Return the tsig algorithm for the specified tsig_rdata
  179. @raises FormError: The TSIG is badly formed.
  180. """
  181. current = tsig_rdata
  182. (aname, used) = dns.name.from_wire(wire, current)
  183. current = current + used
  184. (upper_time, lower_time, fudge, mac_size) = \
  185. struct.unpack("!HIHH", wire[current:current + 10])
  186. current += 10
  187. mac = wire[current:current + mac_size]
  188. current += mac_size
  189. if current > tsig_rdata + tsig_rdlen:
  190. raise dns.exception.FormError
  191. return (aname, mac)