Package dns :: Module dnssec
[hide private]
[frames] | no frames]

Source Code for Module dns.dnssec

  1  # Copyright (C) 2003-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   
 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   
31 -class UnsupportedAlgorithm(dns.exception.DNSException):
32 """Raised if an algorithm is not supported.""" 33 pass
34
35 -class ValidationFailure(dns.exception.DNSException):
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 ECDSAP256SHA256 = 13 49 ECDSAP384SHA384 = 14 50 INDIRECT = 252 51 PRIVATEDNS = 253 52 PRIVATEOID = 254 53 54 _algorithm_by_text = { 55 'RSAMD5' : RSAMD5, 56 'DH' : DH, 57 'DSA' : DSA, 58 'ECC' : ECC, 59 'RSASHA1' : RSASHA1, 60 'DSANSEC3SHA1' : DSANSEC3SHA1, 61 'RSASHA1NSEC3SHA1' : RSASHA1NSEC3SHA1, 62 'RSASHA256' : RSASHA256, 63 'RSASHA512' : RSASHA512, 64 'INDIRECT' : INDIRECT, 65 'ECDSAP256SHA256' : ECDSAP256SHA256, 66 'ECDSAP384SHA384' : ECDSAP384SHA384, 67 'PRIVATEDNS' : PRIVATEDNS, 68 'PRIVATEOID' : PRIVATEOID, 69 } 70 71 # We construct the inverse mapping programmatically to ensure that we 72 # cannot make any mistakes (e.g. omissions, cut-and-paste errors) that 73 # would cause the mapping not to be true inverse. 74 75 _algorithm_by_value = dict([(y, x) for x, y in _algorithm_by_text.iteritems()]) 76
77 -def algorithm_from_text(text):
78 """Convert text into a DNSSEC algorithm value 79 @rtype: int""" 80 81 value = _algorithm_by_text.get(text.upper()) 82 if value is None: 83 value = int(text) 84 return value
85
86 -def algorithm_to_text(value):
87 """Convert a DNSSEC algorithm value to text 88 @rtype: string""" 89 90 text = _algorithm_by_value.get(value) 91 if text is None: 92 text = str(value) 93 return text
94
95 -def _to_rdata(record, origin):
96 s = cStringIO.StringIO() 97 record.to_wire(s, origin=origin) 98 return s.getvalue()
99
100 -def key_id(key, origin=None):
101 rdata = _to_rdata(key, origin) 102 if key.algorithm == RSAMD5: 103 return (ord(rdata[-3]) << 8) + ord(rdata[-2]) 104 else: 105 total = 0 106 for i in range(len(rdata) // 2): 107 total += (ord(rdata[2 * i]) << 8) + ord(rdata[2 * i + 1]) 108 if len(rdata) % 2 != 0: 109 total += ord(rdata[len(rdata) - 1]) << 8 110 total += ((total >> 16) & 0xffff); 111 return total & 0xffff
112
113 -def make_ds(name, key, algorithm, origin=None):
114 if algorithm.upper() == 'SHA1': 115 dsalg = 1 116 hash = dns.hash.get('SHA1')() 117 elif algorithm.upper() == 'SHA256': 118 dsalg = 2 119 hash = dns.hash.get('SHA256')() 120 else: 121 raise UnsupportedAlgorithm, 'unsupported algorithm "%s"' % algorithm 122 123 if isinstance(name, (str, unicode)): 124 name = dns.name.from_text(name, origin) 125 hash.update(name.canonicalize().to_wire()) 126 hash.update(_to_rdata(key, origin)) 127 digest = hash.digest() 128 129 dsrdata = struct.pack("!HBB", key_id(key), key.algorithm, dsalg) + digest 130 return dns.rdata.from_wire(dns.rdataclass.IN, dns.rdatatype.DS, dsrdata, 0, 131 len(dsrdata))
132
133 -def _find_candidate_keys(keys, rrsig):
134 candidate_keys=[] 135 value = keys.get(rrsig.signer) 136 if value is None: 137 return None 138 if isinstance(value, dns.node.Node): 139 try: 140 rdataset = value.find_rdataset(dns.rdataclass.IN, 141 dns.rdatatype.DNSKEY) 142 except KeyError: 143 return None 144 else: 145 rdataset = value 146 for rdata in rdataset: 147 if rdata.algorithm == rrsig.algorithm and \ 148 key_id(rdata) == rrsig.key_tag: 149 candidate_keys.append(rdata) 150 return candidate_keys
151
152 -def _is_rsa(algorithm):
153 return algorithm in (RSAMD5, RSASHA1, 154 RSASHA1NSEC3SHA1, RSASHA256, 155 RSASHA512)
156
157 -def _is_dsa(algorithm):
158 return algorithm in (DSA, DSANSEC3SHA1)
159
160 -def _is_ecdsa(algorithm):
161 return _have_ecdsa and (algorithm in (ECDSAP256SHA256, ECDSAP384SHA384))
162
163 -def _is_md5(algorithm):
164 return algorithm == RSAMD5
165
166 -def _is_sha1(algorithm):
167 return algorithm in (DSA, RSASHA1, 168 DSANSEC3SHA1, RSASHA1NSEC3SHA1)
169
170 -def _is_sha256(algorithm):
171 return algorithm in (RSASHA256, ECDSAP256SHA256)
172
173 -def _is_sha384(algorithm):
174 return algorithm == ECDSAP384SHA384
175
176 -def _is_sha512(algorithm):
177 return algorithm == RSASHA512
178
179 -def _make_hash(algorithm):
180 if _is_md5(algorithm): 181 return dns.hash.get('MD5')() 182 if _is_sha1(algorithm): 183 return dns.hash.get('SHA1')() 184 if _is_sha256(algorithm): 185 return dns.hash.get('SHA256')() 186 if _is_sha384(algorithm): 187 return dns.hash.get('SHA384')() 188 if _is_sha512(algorithm): 189 return dns.hash.get('SHA512')() 190 raise ValidationFailure, 'unknown hash for algorithm %u' % algorithm
191
192 -def _make_algorithm_id(algorithm):
193 if _is_md5(algorithm): 194 oid = [0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05] 195 elif _is_sha1(algorithm): 196 oid = [0x2b, 0x0e, 0x03, 0x02, 0x1a] 197 elif _is_sha256(algorithm): 198 oid = [0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01] 199 elif _is_sha512(algorithm): 200 oid = [0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03] 201 else: 202 raise ValidationFailure, 'unknown algorithm %u' % algorithm 203 olen = len(oid) 204 dlen = _make_hash(algorithm).digest_size 205 idbytes = [0x30] + [8 + olen + dlen] + \ 206 [0x30, olen + 4] + [0x06, olen] + oid + \ 207 [0x05, 0x00] + [0x04, dlen] 208 return ''.join(map(chr, idbytes))
209
210 -def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None):
211 """Validate an RRset against a single signature rdata 212 213 The owner name of the rrsig is assumed to be the same as the owner name 214 of the rrset. 215 216 @param rrset: The RRset to validate 217 @type rrset: dns.rrset.RRset or (dns.name.Name, dns.rdataset.Rdataset) 218 tuple 219 @param rrsig: The signature rdata 220 @type rrsig: dns.rrset.Rdata 221 @param keys: The key dictionary. 222 @type keys: a dictionary keyed by dns.name.Name with node or rdataset values 223 @param origin: The origin to use for relative names 224 @type origin: dns.name.Name or None 225 @param now: The time to use when validating the signatures. The default 226 is the current time. 227 @type now: int 228 """ 229 230 if isinstance(origin, (str, unicode)): 231 origin = dns.name.from_text(origin, dns.name.root) 232 233 for candidate_key in _find_candidate_keys(keys, rrsig): 234 if not candidate_key: 235 raise ValidationFailure, 'unknown key' 236 237 # For convenience, allow the rrset to be specified as a (name, rdataset) 238 # tuple as well as a proper rrset 239 if isinstance(rrset, tuple): 240 rrname = rrset[0] 241 rdataset = rrset[1] 242 else: 243 rrname = rrset.name 244 rdataset = rrset 245 246 if now is None: 247 now = time.time() 248 if rrsig.expiration < now: 249 raise ValidationFailure, 'expired' 250 if rrsig.inception > now: 251 raise ValidationFailure, 'not yet valid' 252 253 hash = _make_hash(rrsig.algorithm) 254 255 if _is_rsa(rrsig.algorithm): 256 keyptr = candidate_key.key 257 (bytes,) = struct.unpack('!B', keyptr[0:1]) 258 keyptr = keyptr[1:] 259 if bytes == 0: 260 (bytes,) = struct.unpack('!H', keyptr[0:2]) 261 keyptr = keyptr[2:] 262 rsa_e = keyptr[0:bytes] 263 rsa_n = keyptr[bytes:] 264 keylen = len(rsa_n) * 8 265 pubkey = Crypto.PublicKey.RSA.construct( 266 (Crypto.Util.number.bytes_to_long(rsa_n), 267 Crypto.Util.number.bytes_to_long(rsa_e))) 268 sig = (Crypto.Util.number.bytes_to_long(rrsig.signature),) 269 elif _is_dsa(rrsig.algorithm): 270 keyptr = candidate_key.key 271 (t,) = struct.unpack('!B', keyptr[0:1]) 272 keyptr = keyptr[1:] 273 octets = 64 + t * 8 274 dsa_q = keyptr[0:20] 275 keyptr = keyptr[20:] 276 dsa_p = keyptr[0:octets] 277 keyptr = keyptr[octets:] 278 dsa_g = keyptr[0:octets] 279 keyptr = keyptr[octets:] 280 dsa_y = keyptr[0:octets] 281 pubkey = Crypto.PublicKey.DSA.construct( 282 (Crypto.Util.number.bytes_to_long(dsa_y), 283 Crypto.Util.number.bytes_to_long(dsa_g), 284 Crypto.Util.number.bytes_to_long(dsa_p), 285 Crypto.Util.number.bytes_to_long(dsa_q))) 286 (dsa_r, dsa_s) = struct.unpack('!20s20s', rrsig.signature[1:]) 287 sig = (Crypto.Util.number.bytes_to_long(dsa_r), 288 Crypto.Util.number.bytes_to_long(dsa_s)) 289 elif _is_ecdsa(rrsig.algorithm): 290 if rrsig.algorithm == ECDSAP256SHA256: 291 curve = ecdsa.curves.NIST256p 292 key_len = 32 293 digest_len = 32 294 elif rrsig.algorithm == ECDSAP384SHA384: 295 curve = ecdsa.curves.NIST384p 296 key_len = 48 297 digest_len = 48 298 else: 299 # shouldn't happen 300 raise ValidationFailure, 'unknown ECDSA curve' 301 keyptr = candidate_key.key 302 x = Crypto.Util.number.bytes_to_long(keyptr[0:key_len]) 303 y = Crypto.Util.number.bytes_to_long(keyptr[key_len:key_len * 2]) 304 assert ecdsa.ecdsa.point_is_valid(curve.generator, x, y) 305 point = ecdsa.ellipticcurve.Point(curve.curve, x, y, curve.order) 306 verifying_key = ecdsa.keys.VerifyingKey.from_public_point(point, 307 curve) 308 pubkey = ECKeyWrapper(verifying_key, key_len) 309 r = rrsig.signature[:key_len] 310 s = rrsig.signature[key_len:] 311 sig = ecdsa.ecdsa.Signature(Crypto.Util.number.bytes_to_long(r), 312 Crypto.Util.number.bytes_to_long(s)) 313 else: 314 raise ValidationFailure, 'unknown algorithm %u' % rrsig.algorithm 315 316 hash.update(_to_rdata(rrsig, origin)[:18]) 317 hash.update(rrsig.signer.to_digestable(origin)) 318 319 if rrsig.labels < len(rrname) - 1: 320 suffix = rrname.split(rrsig.labels + 1)[1] 321 rrname = dns.name.from_text('*', suffix) 322 rrnamebuf = rrname.to_digestable(origin) 323 rrfixed = struct.pack('!HHI', rdataset.rdtype, rdataset.rdclass, 324 rrsig.original_ttl) 325 rrlist = sorted(rdataset); 326 for rr in rrlist: 327 hash.update(rrnamebuf) 328 hash.update(rrfixed) 329 rrdata = rr.to_digestable(origin) 330 rrlen = struct.pack('!H', len(rrdata)) 331 hash.update(rrlen) 332 hash.update(rrdata) 333 334 digest = hash.digest() 335 336 if _is_rsa(rrsig.algorithm): 337 # PKCS1 algorithm identifier goop 338 digest = _make_algorithm_id(rrsig.algorithm) + digest 339 padlen = keylen // 8 - len(digest) - 3 340 digest = chr(0) + chr(1) + chr(0xFF) * padlen + chr(0) + digest 341 elif _is_dsa(rrsig.algorithm) or _is_ecdsa(rrsig.algorithm): 342 pass 343 else: 344 # Raise here for code clarity; this won't actually ever happen 345 # since if the algorithm is really unknown we'd already have 346 # raised an exception above 347 raise ValidationFailure, 'unknown algorithm %u' % rrsig.algorithm 348 349 if pubkey.verify(digest, sig): 350 return 351 raise ValidationFailure, 'verify failure'
352
353 -def _validate(rrset, rrsigset, keys, origin=None, now=None):
354 """Validate an RRset 355 356 @param rrset: The RRset to validate 357 @type rrset: dns.rrset.RRset or (dns.name.Name, dns.rdataset.Rdataset) 358 tuple 359 @param rrsigset: The signature RRset 360 @type rrsigset: dns.rrset.RRset or (dns.name.Name, dns.rdataset.Rdataset) 361 tuple 362 @param keys: The key dictionary. 363 @type keys: a dictionary keyed by dns.name.Name with node or rdataset values 364 @param origin: The origin to use for relative names 365 @type origin: dns.name.Name or None 366 @param now: The time to use when validating the signatures. The default 367 is the current time. 368 @type now: int 369 """ 370 371 if isinstance(origin, (str, unicode)): 372 origin = dns.name.from_text(origin, dns.name.root) 373 374 if isinstance(rrset, tuple): 375 rrname = rrset[0] 376 else: 377 rrname = rrset.name 378 379 if isinstance(rrsigset, tuple): 380 rrsigname = rrsigset[0] 381 rrsigrdataset = rrsigset[1] 382 else: 383 rrsigname = rrsigset.name 384 rrsigrdataset = rrsigset 385 386 rrname = rrname.choose_relativity(origin) 387 rrsigname = rrname.choose_relativity(origin) 388 if rrname != rrsigname: 389 raise ValidationFailure, "owner names do not match" 390 391 for rrsig in rrsigrdataset: 392 try: 393 _validate_rrsig(rrset, rrsig, keys, origin, now) 394 return 395 except ValidationFailure, e: 396 pass 397 raise ValidationFailure, "no RRSIGs validated"
398
399 -def _need_pycrypto(*args, **kwargs):
400 raise NotImplementedError, "DNSSEC validation requires pycrypto"
401 402 try: 403 import Crypto.PublicKey.RSA 404 import Crypto.PublicKey.DSA 405 import Crypto.Util.number 406 validate = _validate 407 validate_rrsig = _validate_rrsig 408 _have_pycrypto = True 409 except ImportError: 410 validate = _need_pycrypto 411 validate_rrsig = _need_pycrypto 412 _have_pycrypto = False 413 414 try: 415 import ecdsa 416 import ecdsa.ecdsa 417 import ecdsa.ellipticcurve 418 import ecdsa.keys 419 _have_ecdsa = True 420
421 - class ECKeyWrapper(object):
422 - def __init__(self, key, key_len):
423 self.key = key 424 self.key_len = key_len
425 - def verify(self, digest, sig):
426 diglong = Crypto.Util.number.bytes_to_long(digest) 427 return self.key.pubkey.verifies(diglong, sig)
428 429 except ImportError: 430 _have_ecdsa = False 431