1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 """Common DNSSEC-related functions and constants."""
17
18 import cStringIO
19 import struct
20 import time
21
22 import dns.exception
23 import dns.hash
24 import dns.name
25 import dns.node
26 import dns.rdataset
27 import dns.rdata
28 import dns.rdatatype
29 import dns.rdataclass
30
32 """Raised if an algorithm is not supported."""
33 pass
34
36 """The DNSSEC signature is invalid."""
37 pass
38
39 RSAMD5 = 1
40 DH = 2
41 DSA = 3
42 ECC = 4
43 RSASHA1 = 5
44 DSANSEC3SHA1 = 6
45 RSASHA1NSEC3SHA1 = 7
46 RSASHA256 = 8
47 RSASHA512 = 10
48 INDIRECT = 252
49 PRIVATEDNS = 253
50 PRIVATEOID = 254
51
52 _algorithm_by_text = {
53 'RSAMD5' : RSAMD5,
54 'DH' : DH,
55 'DSA' : DSA,
56 'ECC' : ECC,
57 'RSASHA1' : RSASHA1,
58 'DSANSEC3SHA1' : DSANSEC3SHA1,
59 'RSASHA1NSEC3SHA1' : RSASHA1NSEC3SHA1,
60 'RSASHA256' : RSASHA256,
61 'RSASHA512' : RSASHA512,
62 'INDIRECT' : INDIRECT,
63 'PRIVATEDNS' : PRIVATEDNS,
64 'PRIVATEOID' : PRIVATEOID,
65 }
66
67
68
69
70
71 _algorithm_by_value = dict([(y, x) for x, y in _algorithm_by_text.iteritems()])
72
74 """Convert text into a DNSSEC algorithm value
75 @rtype: int"""
76
77 value = _algorithm_by_text.get(text.upper())
78 if value is None:
79 value = int(text)
80 return value
81
83 """Convert a DNSSEC algorithm value to text
84 @rtype: string"""
85
86 text = _algorithm_by_value.get(value)
87 if text is None:
88 text = str(value)
89 return text
90
95
108
109 -def make_ds(name, key, algorithm, origin=None):
110 if algorithm.upper() == 'SHA1':
111 dsalg = 1
112 hash = dns.hash.get('SHA1')()
113 elif algorithm.upper() == 'SHA256':
114 dsalg = 2
115 hash = dns.hash.get('SHA256')()
116 else:
117 raise UnsupportedAlgorithm, 'unsupported algorithm "%s"' % algorithm
118
119 if isinstance(name, (str, unicode)):
120 name = dns.name.from_text(name, origin)
121 hash.update(name.canonicalize().to_wire())
122 hash.update(_to_rdata(key, origin))
123 digest = hash.digest()
124
125 dsrdata = struct.pack("!HBB", key_id(key), key.algorithm, dsalg) + digest
126 return dns.rdata.from_wire(dns.rdataclass.IN, dns.rdatatype.DS, dsrdata, 0,
127 len(dsrdata))
128
147
152
155
158
162
165
168
179
181 if _is_md5(algorithm):
182 oid = [0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05]
183 elif _is_sha1(algorithm):
184 oid = [0x2b, 0x0e, 0x03, 0x02, 0x1a]
185 elif _is_sha256(algorithm):
186 oid = [0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01]
187 elif _is_sha512(algorithm):
188 oid = [0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03]
189 else:
190 raise ValidationFailure, 'unknown algorithm %u' % algorithm
191 olen = len(oid)
192 dlen = _make_hash(algorithm).digest_size
193 idbytes = [0x30] + [8 + olen + dlen] + \
194 [0x30, olen + 4] + [0x06, olen] + oid + \
195 [0x05, 0x00] + [0x04, dlen]
196 return ''.join(map(chr, idbytes))
197
199 """Validate an RRset against a single signature rdata
200
201 The owner name of the rrsig is assumed to be the same as the owner name
202 of the rrset.
203
204 @param rrset: The RRset to validate
205 @type rrset: dns.rrset.RRset or (dns.name.Name, dns.rdataset.Rdataset)
206 tuple
207 @param rrsig: The signature rdata
208 @type rrsig: dns.rrset.Rdata
209 @param keys: The key dictionary.
210 @type keys: a dictionary keyed by dns.name.Name with node or rdataset values
211 @param origin: The origin to use for relative names
212 @type origin: dns.name.Name or None
213 @param now: The time to use when validating the signatures. The default
214 is the current time.
215 @type now: int
216 """
217
218 if isinstance(origin, (str, unicode)):
219 origin = dns.name.from_text(origin, dns.name.root)
220
221 for candidate_key in _find_candidate_keys(keys, rrsig):
222 if not candidate_key:
223 raise ValidationFailure, 'unknown key'
224
225
226
227 if isinstance(rrset, tuple):
228 rrname = rrset[0]
229 rdataset = rrset[1]
230 else:
231 rrname = rrset.name
232 rdataset = rrset
233
234 if now is None:
235 now = time.time()
236 if rrsig.expiration < now:
237 raise ValidationFailure, 'expired'
238 if rrsig.inception > now:
239 raise ValidationFailure, 'not yet valid'
240
241 hash = _make_hash(rrsig.algorithm)
242
243 if _is_rsa(rrsig.algorithm):
244 keyptr = candidate_key.key
245 (bytes,) = struct.unpack('!B', keyptr[0:1])
246 keyptr = keyptr[1:]
247 if bytes == 0:
248 (bytes,) = struct.unpack('!H', keyptr[0:2])
249 keyptr = keyptr[2:]
250 rsa_e = keyptr[0:bytes]
251 rsa_n = keyptr[bytes:]
252 keylen = len(rsa_n) * 8
253 pubkey = Crypto.PublicKey.RSA.construct(
254 (Crypto.Util.number.bytes_to_long(rsa_n),
255 Crypto.Util.number.bytes_to_long(rsa_e)))
256 sig = (Crypto.Util.number.bytes_to_long(rrsig.signature),)
257 elif _is_dsa(rrsig.algorithm):
258 keyptr = candidate_key.key
259 (t,) = struct.unpack('!B', keyptr[0:1])
260 keyptr = keyptr[1:]
261 octets = 64 + t * 8
262 dsa_q = keyptr[0:20]
263 keyptr = keyptr[20:]
264 dsa_p = keyptr[0:octets]
265 keyptr = keyptr[octets:]
266 dsa_g = keyptr[0:octets]
267 keyptr = keyptr[octets:]
268 dsa_y = keyptr[0:octets]
269 pubkey = Crypto.PublicKey.DSA.construct(
270 (Crypto.Util.number.bytes_to_long(dsa_y),
271 Crypto.Util.number.bytes_to_long(dsa_g),
272 Crypto.Util.number.bytes_to_long(dsa_p),
273 Crypto.Util.number.bytes_to_long(dsa_q)))
274 (dsa_r, dsa_s) = struct.unpack('!20s20s', rrsig.signature[1:])
275 sig = (Crypto.Util.number.bytes_to_long(dsa_r),
276 Crypto.Util.number.bytes_to_long(dsa_s))
277 else:
278 raise ValidationFailure, 'unknown algorithm %u' % rrsig.algorithm
279
280 hash.update(_to_rdata(rrsig, origin)[:18])
281 hash.update(rrsig.signer.to_digestable(origin))
282
283 if rrsig.labels < len(rrname) - 1:
284 suffix = rrname.split(rrsig.labels + 1)[1]
285 rrname = dns.name.from_text('*', suffix)
286 rrnamebuf = rrname.to_digestable(origin)
287 rrfixed = struct.pack('!HHI', rdataset.rdtype, rdataset.rdclass,
288 rrsig.original_ttl)
289 rrlist = sorted(rdataset);
290 for rr in rrlist:
291 hash.update(rrnamebuf)
292 hash.update(rrfixed)
293 rrdata = rr.to_digestable(origin)
294 rrlen = struct.pack('!H', len(rrdata))
295 hash.update(rrlen)
296 hash.update(rrdata)
297
298 digest = hash.digest()
299
300 if _is_rsa(rrsig.algorithm):
301
302 digest = _make_algorithm_id(rrsig.algorithm) + digest
303 padlen = keylen // 8 - len(digest) - 3
304 digest = chr(0) + chr(1) + chr(0xFF) * padlen + chr(0) + digest
305 elif _is_dsa(rrsig.algorithm):
306 pass
307 else:
308
309
310
311 raise ValidationFailure, 'unknown algorithm %u' % rrsig.algorithm
312
313 if pubkey.verify(digest, sig):
314 return
315 raise ValidationFailure, 'verify failure'
316
317 -def _validate(rrset, rrsigset, keys, origin=None, now=None):
318 """Validate an RRset
319
320 @param rrset: The RRset to validate
321 @type rrset: dns.rrset.RRset or (dns.name.Name, dns.rdataset.Rdataset)
322 tuple
323 @param rrsigset: The signature RRset
324 @type rrsigset: dns.rrset.RRset or (dns.name.Name, dns.rdataset.Rdataset)
325 tuple
326 @param keys: The key dictionary.
327 @type keys: a dictionary keyed by dns.name.Name with node or rdataset values
328 @param origin: The origin to use for relative names
329 @type origin: dns.name.Name or None
330 @param now: The time to use when validating the signatures. The default
331 is the current time.
332 @type now: int
333 """
334
335 if isinstance(origin, (str, unicode)):
336 origin = dns.name.from_text(origin, dns.name.root)
337
338 if isinstance(rrset, tuple):
339 rrname = rrset[0]
340 else:
341 rrname = rrset.name
342
343 if isinstance(rrsigset, tuple):
344 rrsigname = rrsigset[0]
345 rrsigrdataset = rrsigset[1]
346 else:
347 rrsigname = rrsigset.name
348 rrsigrdataset = rrsigset
349
350 rrname = rrname.choose_relativity(origin)
351 rrsigname = rrname.choose_relativity(origin)
352 if rrname != rrsigname:
353 raise ValidationFailure, "owner names do not match"
354
355 for rrsig in rrsigrdataset:
356 try:
357 _validate_rrsig(rrset, rrsig, keys, origin, now)
358 return
359 except ValidationFailure, e:
360 pass
361 raise ValidationFailure, "no RRSIGs validated"
362
364 raise NotImplementedError, "DNSSEC validation requires pycrypto"
365
366 try:
367 import Crypto.PublicKey.RSA
368 import Crypto.PublicKey.DSA
369 import Crypto.Util.number
370 validate = _validate
371 validate_rrsig = _validate_rrsig
372 except ImportError:
373 validate = _need_pycrypto
374 validate_rrsig = _need_pycrypto
375