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 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 # We construct the inverse mapping programmatically to ensure that we 68 # cannot make any mistakes (e.g. omissions, cut-and-paste errors) that 69 # would cause the mapping not to be true inverse. 70 71 _algorithm_by_value = dict([(y, x) for x, y in _algorithm_by_text.iteritems()]) 72
73 -def algorithm_from_text(text):
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
82 -def algorithm_to_text(value):
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
91 -def _to_rdata(record, origin):
92 s = cStringIO.StringIO() 93 record.to_wire(s, origin=origin) 94 return s.getvalue()
95
96 -def key_id(key, origin=None):
97 rdata = _to_rdata(key, origin) 98 if key.algorithm == RSAMD5: 99 return (ord(rdata[-3]) << 8) + ord(rdata[-2]) 100 else: 101 total = 0 102 for i in range(len(rdata) // 2): 103 total += (ord(rdata[2 * i]) << 8) + ord(rdata[2 * i + 1]) 104 if len(rdata) % 2 != 0: 105 total += ord(rdata[len(rdata) - 1]) << 8 106 total += ((total >> 16) & 0xffff); 107 return total & 0xffff
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
129 -def _find_candidate_keys(keys, rrsig):
130 candidate_keys=[] 131 value = keys.get(rrsig.signer) 132 if value is None: 133 return None 134 if isinstance(value, dns.node.Node): 135 try: 136 rdataset = value.find_rdataset(dns.rdataclass.IN, 137 dns.rdatatype.DNSKEY) 138 except KeyError: 139 return None 140 else: 141 rdataset = value 142 for rdata in rdataset: 143 if rdata.algorithm == rrsig.algorithm and \ 144 key_id(rdata) == rrsig.key_tag: 145 candidate_keys.append(rdata) 146 return candidate_keys
147
148 -def _is_rsa(algorithm):
149 return algorithm in (RSAMD5, RSASHA1, 150 RSASHA1NSEC3SHA1, RSASHA256, 151 RSASHA512)
152
153 -def _is_dsa(algorithm):
154 return algorithm in (DSA, DSANSEC3SHA1)
155
156 -def _is_md5(algorithm):
157 return algorithm == RSAMD5
158
159 -def _is_sha1(algorithm):
160 return algorithm in (DSA, RSASHA1, 161 DSANSEC3SHA1, RSASHA1NSEC3SHA1)
162
163 -def _is_sha256(algorithm):
164 return algorithm == RSASHA256
165
166 -def _is_sha512(algorithm):
167 return algorithm == RSASHA512
168
169 -def _make_hash(algorithm):
170 if _is_md5(algorithm): 171 return dns.hash.get('MD5')() 172 if _is_sha1(algorithm): 173 return dns.hash.get('SHA1')() 174 if _is_sha256(algorithm): 175 return dns.hash.get('SHA256')() 176 if _is_sha512(algorithm): 177 return dns.hash.get('SHA512')() 178 raise ValidationFailure, 'unknown hash for algorithm %u' % algorithm
179
180 -def _make_algorithm_id(algorithm):
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
198 -def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None):
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 # For convenience, allow the rrset to be specified as a (name, rdataset) 226 # tuple as well as a proper rrset 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 # PKCS1 algorithm identifier goop 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 # Raise here for code clarity; this won't actually ever happen 309 # since if the algorithm is really unknown we'd already have 310 # raised an exception above 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
363 -def _need_pycrypto(*args, **kwargs):
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