tsig.py 7.5 KB

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