]> git.ipfire.org Git - thirdparty/dnspython.git/commitdiff
add support for more TSIG algorithms
authorBob Halley <halley@nominum.com>
Thu, 12 Nov 2009 19:06:54 +0000 (04:06 +0900)
committerBob Halley <halley@nominum.com>
Thu, 12 Nov 2009 19:06:54 +0000 (04:06 +0900)
ChangeLog
dns/message.py
dns/query.py
dns/renderer.py
dns/resolver.py
dns/tsig.py
dns/update.py

index 6b9cc5004b673d2249925f585f4d5c8d0f67d0d0..496d21d03da0e6e9a481f8c49958588228752f90 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,12 +1,14 @@
-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.
 
index eec9acb589b23cbe7afa6fa19e58173e3f814838..20769d4860b78fc83495b695f08b0710f72827d1 100644 (file)
@@ -92,6 +92,9 @@ class Message(object):
     @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.
@@ -149,6 +152,7 @@ class Message(object):
         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
@@ -410,12 +414,14 @@ class Message(object):
         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.
 
@@ -436,6 +442,8 @@ class Message(object):
         @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
@@ -445,6 +453,7 @@ class Message(object):
             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
index 09e39d58718861a770317ffe9bb6f6a36f7af0bf..ff353747b3e15a2c5e0bf783a30742e171c7d8cf 100644 (file)
@@ -258,7 +258,7 @@ def tcp(q, where, timeout=None, port=53, af=None, source=None, source_port=0,
 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
@@ -303,6 +303,9 @@ def xfr(where, zone, rdtype=dns.rdatatype.AXFR, rdclass=dns.rdataclass.IN,
     @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)):
@@ -315,7 +318,7 @@ def xfr(where, zone, rdtype=dns.rdatatype.AXFR, rdclass=dns.rdataclass.IN,
                                     '. . %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:
index db827f1ff8313385985871a4a47288d1414a3296..c4453d072c9542988340c6251fa88dace88d1f61 100644 (file)
@@ -250,7 +250,7 @@ class Renderer(object):
         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
@@ -267,6 +267,7 @@ class Renderer(object):
         @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
         """
 
@@ -281,7 +282,8 @@ class Renderer(object):
                                                         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))
index 70156c1d1366be2a132544d091d9c87a0cace753..2da94cc16d26b62d35027e5f6b99b850610503cf 100644 (file)
@@ -265,6 +265,9 @@ class Resolver(object):
     @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
@@ -307,6 +310,7 @@ class Resolver(object):
         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
@@ -589,7 +593,7 @@ class Resolver(object):
                     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
             #
@@ -670,7 +674,8 @@ class Resolver(object):
             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.
@@ -680,12 +685,16 @@ class Resolver(object):
         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.
index 3f579d10ae9db701f934c8e43200f66a7866185c..a94cae09176cfdd62ba5e56c42ae51401f7c9c5e 100644 (file)
@@ -50,7 +50,7 @@ class PeerBadTruncation(PeerError):
     """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
@@ -58,16 +58,19 @@ BADTIME = 18
 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))
@@ -83,7 +86,7 @@ def hmac_md5(wire, keyname, secret, time, fudge, original_id, error,
     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'
@@ -153,7 +156,54 @@ def validate(wire, keyname, secret, now, request_mac, tsig_start, tsig_rdata,
         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"
index 5bd289f7d10f2f64eaca83bf0faa8ada9c598f97..7cf13f687c6e636b05793cdb469c79abe067c778 100644 (file)
@@ -24,7 +24,7 @@ import dns.rdataset
 
 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.
@@ -41,6 +41,9 @@ class Update(dns.message.Message):
         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)
@@ -53,7 +56,7 @@ class Update(dns.message.Message):
         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."""