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