-2009-11-13 Bob Halley <halley@nominum.com>
+2009-11-13 Bob Halley <halley@dnspython.org>
+
+ * Support has been added for hmac-sha1, hmac-sha224, hmac-sha256,
+ hmac-sha384 and hmac-sha512. Thanks to Kevin Chen for a
+ thoughtful, high quality patch.
* dns/update.py (Update::present): A zero TTL was not added if
present() was called with a single rdata, causing _add() to be
unhappy. Thanks to Eugene Kim for reporting the problem and
submitting a patch.
-2009-11-13 Bob Halley <halley@dnspython.org>
-
* dns/entropy.py: Use os.urandom() if present. Don't seed until
someone wants randomness.
@type keyring: dict
@ivar keyname: The TSIG keyname to use. The default is None.
@type keyname: dns.name.Name object
+ @ivar keyalgorithm: The TSIG key algorithm to use. The default is
+ dns.tsig.default_algorithm.
+ @type keyalgorithm: string
@ivar request_mac: The TSIG MAC of the request message associated with
this message; used when validating TSIG signatures. @see: RFC 2845 for
more information on TSIG fields.
self.request_payload = 0
self.keyring = None
self.keyname = None
+ self.keyalgorithm = dns.tsig.default_algorithm
self.request_mac = ''
self.other_data = ''
self.tsig_error = 0
if not self.keyname is None:
r.add_tsig(self.keyname, self.keyring[self.keyname],
self.fudge, self.original_id, self.tsig_error,
- self.other_data, self.request_mac)
+ self.other_data, self.request_mac,
+ self.keyalgorithm)
self.mac = r.mac
return r.get_wire()
- def use_tsig(self, keyring, keyname=None, fudge=300, original_id=None,
- tsig_error=0, other_data=''):
+ def use_tsig(self, keyring, keyname=None, fudge=300,
+ original_id=None, tsig_error=0, other_data='',
+ algorithm=dns.tsig.default_algorithm):
"""When sending, a TSIG signature using the specified keyring
and keyname should be added.
@type tsig_error: int
@param other_data: TSIG other data.
@type other_data: string
+ @param algorithm: The TSIG algorithm to use; defaults to
+ dns.tsig.default_algorithm
"""
self.keyring = keyring
if isinstance(keyname, (str, unicode)):
keyname = dns.name.from_text(keyname)
self.keyname = keyname
+ self.keyalgorithm = algorithm
self.fudge = fudge
if original_id is None:
self.original_id = self.id
def xfr(where, zone, rdtype=dns.rdatatype.AXFR, rdclass=dns.rdataclass.IN,
timeout=None, port=53, keyring=None, keyname=None, relativize=True,
af=None, lifetime=None, source=None, source_port=0, serial=0,
- use_udp=False):
+ use_udp=False, keyalgorithm=dns.tsig.default_algorithm):
"""Return a generator for the responses to a zone transfer.
@param where: where to send the message
@type serial: int
@param use_udp: Use UDP (only meaningful for IXFR)
@type use_udp: bool
+ @param keyalgorithm: The TSIG algorithm to use; defaults to
+ dns.tsig.default_algorithm
+ @type keyalgorithm: string
"""
if isinstance(zone, (str, unicode)):
'. . %u 0 0 0 0' % serial)
q.authority.append(rrset)
if not keyring is None:
- q.use_tsig(keyring, keyname)
+ q.use_tsig(keyring, keyname, keyalgorithm)
wire = q.to_wire()
if af is None:
try:
self.counts[ADDITIONAL] += 1
def add_tsig(self, keyname, secret, fudge, id, tsig_error, other_data,
- request_mac):
+ request_mac, algorithm=dns.tsig.default_algorithm):
"""Add a TSIG signature to the message.
@param keyname: the TSIG key name
@type other_data: string
@param request_mac: This message is a response to the request which
had the specified MAC.
+ @param algorithm: the TSIG algorithm to use
@type request_mac: string
"""
id,
tsig_error,
other_data,
- request_mac)
+ request_mac,
+ algorithm=algorithm)
keyname.to_wire(self.output, self.compress, self.origin)
self.output.write(struct.pack('!HHIH', dns.rdatatype.TSIG,
dns.rdataclass.ANY, 0, 0))
@type keyring: dict
@ivar keyname: The TSIG keyname to use. The default is None.
@type keyname: dns.name.Name object
+ @ivar keyalgorithm: The TSIG key algorithm to use. The default is
+ dns.tsig.default_algorithm.
+ @type keyalgorithm: string
@ivar edns: The EDNS level to use. The default is -1, no Edns.
@type edns: int
@ivar ednsflags: The EDNS flags
self.lifetime = 30.0
self.keyring = None
self.keyname = None
+ self.keyalgorithm = dns.tsig.default_algorithm
self.edns = -1
self.ednsflags = 0
self.payload = 0
return answer
request = dns.message.make_query(qname, rdtype, rdclass)
if not self.keyname is None:
- request.use_tsig(self.keyring, self.keyname)
+ request.use_tsig(self.keyring, self.keyname, self.keyalgorithm)
request.use_edns(self.edns, self.ednsflags, self.payload)
response = None
#
self.cache.put((qname, rdtype, rdclass), answer)
return answer
- def use_tsig(self, keyring, keyname=None):
+ def use_tsig(self, keyring, keyname=None,
+ algorithm=dns.tsig.default_algorithm):
"""Add a TSIG signature to the query.
@param keyring: The TSIG keyring to use; defaults to None.
but a keyname is not, then the key used will be the first key in the
keyring. Note that the order of keys in a dictionary is not defined,
so applications should supply a keyname when a keyring is used, unless
- they know the keyring contains only one key."""
+ they know the keyring contains only one key.
+ @param algorithm: The TSIG key algorithm to use. The default
+ is dns.tsig.default_algorithm.
+ @type algorithm: string"""
self.keyring = keyring
if keyname is None:
self.keyname = self.keyring.keys()[0]
else:
self.keyname = keyname
+ self.keyalgorithm = algorithm
def use_edns(self, edns, ednsflags, payload):
"""Configure Edns.
"""Raised if the peer didn't like amount of truncation in the TSIG we sent"""
pass
-_alg_name = dns.name.from_text('HMAC-MD5.SIG-ALG.REG.INT.').to_digestable()
+default_algorithm = "HMAC-MD5.SIG-ALG.REG.INT"
BADSIG = 16
BADKEY = 17
BADTRUNC = 22
def hmac_md5(wire, keyname, secret, time, fudge, original_id, error,
- other_data, request_mac, ctx=None, multi=False, first=True):
- """Return a (tsig_rdata, mac, ctx) tuple containing the HMAC-MD5 TSIG rdata
- for the input parameters, the HMAC-MD5 MAC calculated by applying the
+ other_data, request_mac, ctx=None, multi=False, first=True,
+ algorithm=default_algorithm):
+ """Return a (tsig_rdata, mac, ctx) tuple containing the HMAC TSIG rdata
+ for the input parameters, the HMAC MAC calculated by applying the
TSIG signature algorithm, and the TSIG digest context.
@rtype: (string, string, hmac.HMAC object)
@raises ValueError: I{other_data} is too long
+ @raises NotImplementedError: I{algorithm} is not supported
"""
+ (algorithm_name, digestmod) = get_algorithm(algorithm)
if first:
- ctx = hmac.new(secret)
+ ctx = hmac.new(secret, digestmod=digestmod)
ml = len(request_mac)
if ml > 0:
ctx.update(struct.pack('!H', ml))
upper_time = (long_time >> 32) & 0xffffL
lower_time = long_time & 0xffffffffL
time_mac = struct.pack('!HIH', upper_time, lower_time, fudge)
- pre_mac = _alg_name + time_mac
+ pre_mac = algorithm_name + time_mac
ol = len(other_data)
if ol > 65535:
raise ValueError, 'TSIG Other Data is > 65535 bytes'
raise BadTime
(junk, our_mac, ctx) = hmac_md5(new_wire, keyname, secret, time, fudge,
original_id, error, other_data,
- request_mac, ctx, multi, first)
+ request_mac, ctx, multi, first, aname)
if (our_mac != mac):
raise BadSignature
return ctx
+
+def get_algorithm(algorithm):
+ """Returns the wire format string and the hash module to use for the
+ specified TSIG algorithm"
+ @rtype: (string, hash module)
+ @raises NotImplementedError: I{algorithm} is not supported
+ """
+
+ hashes = {}
+ try:
+ import hashlib
+ hashes[dns.name.from_text('hmac-sha224')] = hashlib.sha224
+ hashes[dns.name.from_text('hmac-sha256')] = hashlib.sha256
+ hashes[dns.name.from_text('hmac-sha384')] = hashlib.sha384
+ hashes[dns.name.from_text('hmac-sha512')] = hashlib.sha512
+
+ import sys
+ if sys.hexversion < 0x02050000:
+ # hashlib doesn't conform to PEP 247: API for
+ # Cryptographic Hash Functions, which hmac before python
+ # 2.5 requires, so add the necessary items.
+ class HashlibWrapper:
+ def __init__(self, basehash):
+ self.basehash = basehash
+ self.digest_size = self.basehash().digest_size
+
+ def new(self, *args, **kwargs):
+ return self.basehash(*args, **kwargs)
+
+ for name in hashes:
+ hashes[name] = HashlibWrapper(hashes[name])
+
+ except ImportError:
+ pass
+
+ import md5, sha
+ hashes[dns.name.from_text('HMAC-MD5.SIG-ALG.REG.INT')] = md5
+ hashes[dns.name.from_text('hmac-sha1')] = sha
+
+ if isinstance(algorithm, (str, unicode)):
+ algorithm = dns.name.from_text(algorithm)
+
+ if algorithm in hashes:
+ return (algorithm.to_digestable(), hashes[algorithm])
+
+ raise NotImplementedError, "TSIG algorithm " + str(algorithm) + \
+ " is not supported"
class Update(dns.message.Message):
def __init__(self, zone, rdclass=dns.rdataclass.IN, keyring=None,
- keyname=None):
+ keyname=None, keyalgorithm=dns.tsig.default_algorithm):
"""Initialize a new DNS Update object.
@param zone: The zone which is being updated.
so applications should supply a keyname when a keyring is used, unless
they know the keyring contains only one key.
@type keyname: dns.name.Name or string
+ @param keyalgorithm: The TSIG algorithm to use; defaults to
+ dns.tsig.default_algorithm
+ @type keyalgorithm: string
"""
super(Update, self).__init__()
self.flags |= dns.opcode.to_flags(dns.opcode.UPDATE)
self.find_rrset(self.question, self.origin, rdclass, dns.rdatatype.SOA,
create=True, force_unique=True)
if not keyring is None:
- self.use_tsig(keyring, keyname)
+ self.use_tsig(keyring, keyname, keyalgorithm)
def _add_rr(self, name, ttl, rd, deleting=None, section=None):
"""Add a single RR to the update section."""