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

Source Code for Module dns.dnssec

  1  # Copyright (C) 2003-2007, 2009 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_key(keys, rrsig):
130 value = keys.get(rrsig.signer) 131 if value is None: 132 return None 133 if isinstance(value, dns.node.Node): 134 try: 135 rdataset = node.find_rdataset(dns.rdataclass.IN, 136 dns.rdatatype.DNSKEY) 137 except KeyError: 138 return None 139 else: 140 rdataset = value 141 for rdata in rdataset: 142 if rdata.algorithm == rrsig.algorithm and \ 143 key_id(rdata) == rrsig.key_tag: 144 return rdata 145 return None
146
147 -def _is_rsa(algorithm):
148 return algorithm in (RSAMD5, RSASHA1, 149 RSASHA1NSEC3SHA1, RSASHA256, 150 RSASHA512)
151
152 -def _is_dsa(algorithm):
153 return algorithm in (DSA, DSANSEC3SHA1)
154
155 -def _is_md5(algorithm):
156 return algorithm == RSAMD5
157
158 -def _is_sha1(algorithm):
159 return algorithm in (DSA, RSASHA1, 160 DSANSEC3SHA1, RSASHA1NSEC3SHA1)
161
162 -def _is_sha256(algorithm):
163 return algorithm == RSASHA256
164
165 -def _is_sha512(algorithm):
166 return algorithm == RSASHA512
167
168 -def _make_hash(algorithm):
169 if _is_md5(algorithm): 170 return dns.hash.get('MD5')() 171 if _is_sha1(algorithm): 172 return dns.hash.get('SHA1')() 173 if _is_sha256(algorithm): 174 return dns.hash.get('SHA256')() 175 if _is_sha512(algorithm): 176 return dns.hash.get('SHA512')() 177 raise ValidationFailure, 'unknown hash for algorithm %u' % algorithm
178
179 -def _make_algorithm_id(algorithm):
180 if _is_md5(algorithm): 181 oid = [0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05] 182 elif _is_sha1(algorithm): 183 oid = [0x2b, 0x0e, 0x03, 0x02, 0x1a] 184 elif _is_sha256(algorithm): 185 oid = [0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01] 186 elif _is_sha512(algorithm): 187 oid = [0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03] 188 else: 189 raise ValidationFailure, 'unknown algorithm %u' % algorithm 190 olen = len(oid) 191 dlen = _make_hash(algorithm).digest_size 192 idbytes = [0x30] + [8 + olen + dlen] + \ 193 [0x30, olen + 4] + [0x06, olen] + oid + \ 194 [0x05, 0x00] + [0x04, dlen] 195 return ''.join(map(chr, idbytes))
196
197 -def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None):
198 """Validate an RRset against a single signature rdata 199 200 The owner name of the rrsig is assumed to be the same as the owner name 201 of the rrset. 202 203 @param rrset: The RRset to validate 204 @type rrset: dns.rrset.RRset or (dns.name.Name, dns.rdataset.Rdataset) 205 tuple 206 @param rrsig: The signature rdata 207 @type rrsig: dns.rrset.Rdata 208 @param keys: The key dictionary. 209 @type keys: a dictionary keyed by dns.name.Name with node or rdataset values 210 @param origin: The origin to use for relative names 211 @type origin: dns.name.Name or None 212 @param now: The time to use when validating the signatures. The default 213 is the current time. 214 @type now: int 215 """ 216 217 if isinstance(origin, (str, unicode)): 218 origin = dns.name.from_text(origin, dns.name.root) 219 220 key = _find_key(keys, rrsig) 221 if not key: 222 raise ValidationFailure, 'unknown key' 223 224 # For convenience, allow the rrset to be specified as a (name, rdataset) 225 # tuple as well as a proper rrset 226 if isinstance(rrset, tuple): 227 rrname = rrset[0] 228 rdataset = rrset[1] 229 else: 230 rrname = rrset.name 231 rdataset = rrset 232 233 if now is None: 234 now = time.time() 235 if rrsig.expiration < now: 236 raise ValidationFailure, 'expired' 237 if rrsig.inception > now: 238 raise ValidationFailure, 'not yet valid' 239 240 hash = _make_hash(rrsig.algorithm) 241 242 if _is_rsa(rrsig.algorithm): 243 keyptr = key.key 244 (bytes,) = struct.unpack('!B', keyptr[0:1]) 245 keyptr = keyptr[1:] 246 if bytes == 0: 247 (bytes,) = struct.unpack('!H', keyptr[0:2]) 248 keyptr = keyptr[2:] 249 rsa_e = keyptr[0:bytes] 250 rsa_n = keyptr[bytes:] 251 keylen = len(rsa_n) * 8 252 pubkey = RSA.construct((Crypto.Util.number.bytes_to_long(rsa_n), 253 Crypto.Util.number.bytes_to_long(rsa_e))) 254 sig = (Crypto.Util.number.bytes_to_long(rrsig.signature),) 255 elif _is_dsa(rrsig.algorithm): 256 keyptr = key.key 257 (t,) = struct.unpack('!B', keyptr[0:1]) 258 keyptr = keyptr[1:] 259 octets = 64 + t * 8 260 dsa_q = keyptr[0:20] 261 keyptr = keyptr[20:] 262 dsa_p = keyptr[0:octets] 263 keyptr = keyptr[octets:] 264 dsa_g = keyptr[0:octets] 265 keyptr = keyptr[octets:] 266 dsa_y = keyptr[0:octets] 267 pubkey = DSA.construct((Crypto.Util.number.bytes_to_long(dsa_y), 268 Crypto.Util.number.bytes_to_long(dsa_g), 269 Crypto.Util.number.bytes_to_long(dsa_p), 270 Crypto.Util.number.bytes_to_long(dsa_q))) 271 (dsa_r, dsa_s) = struct.unpack('!20s20s', rrsig.signature[1:]) 272 sig = (Crypto.Util.number.bytes_to_long(dsa_r), 273 Crypto.Util.number.bytes_to_long(dsa_s)) 274 else: 275 raise ValidationFailure, 'unknown algorithm %u' % rrsig.algorithm 276 277 hash.update(_to_rdata(rrsig, origin)[:18]) 278 hash.update(rrsig.signer.to_digestable(origin)) 279 280 if rrsig.labels < len(rrname) - 1: 281 suffix = rrname.split(rrsig.labels + 1)[1] 282 rrname = dns.name.from_text('*', suffix) 283 rrnamebuf = rrname.to_digestable(origin) 284 rrfixed = struct.pack('!HHI', rdataset.rdtype, rdataset.rdclass, 285 rrsig.original_ttl) 286 rrlist = sorted(rdataset); 287 for rr in rrlist: 288 hash.update(rrnamebuf) 289 hash.update(rrfixed) 290 rrdata = rr.to_digestable(origin) 291 rrlen = struct.pack('!H', len(rrdata)) 292 hash.update(rrlen) 293 hash.update(rrdata) 294 295 digest = hash.digest() 296 297 if _is_rsa(rrsig.algorithm): 298 # PKCS1 algorithm identifier goop 299 digest = _make_algorithm_id(rrsig.algorithm) + digest 300 padlen = keylen / 8 - len(digest) - 3 301 digest = chr(0) + chr(1) + chr(0xFF) * padlen + chr(0) + digest 302 elif _is_dsa(rrsig.algorithm): 303 pass 304 else: 305 # Raise here for code clarity; this won't actually ever happen 306 # since if the algorithm is really unknown we'd already have 307 # raised an exception above 308 raise ValidationFailure, 'unknown algorithm %u' % rrsig.algorithm 309 310 if not pubkey.verify(digest, sig): 311 raise ValidationFailure, 'verify failure'
312
313 -def _validate(rrset, rrsigset, keys, origin=None, now=None):
314 """Validate an RRset 315 316 @param rrset: The RRset to validate 317 @type rrset: dns.rrset.RRset or (dns.name.Name, dns.rdataset.Rdataset) 318 tuple 319 @param rrsigset: The signature RRset 320 @type rrsigset: dns.rrset.RRset or (dns.name.Name, dns.rdataset.Rdataset) 321 tuple 322 @param keys: The key dictionary. 323 @type keys: a dictionary keyed by dns.name.Name with node or rdataset values 324 @param origin: The origin to use for relative names 325 @type origin: dns.name.Name or None 326 @param now: The time to use when validating the signatures. The default 327 is the current time. 328 @type now: int 329 """ 330 331 if isinstance(origin, (str, unicode)): 332 origin = dns.name.from_text(origin, dns.name.root) 333 334 if isinstance(rrset, tuple): 335 rrname = rrset[0] 336 else: 337 rrname = rrset.name 338 339 if isinstance(rrsigset, tuple): 340 rrsigname = rrsigset[0] 341 rrsigrdataset = rrsigset[1] 342 else: 343 rrsigname = rrsigset.name 344 rrsigrdataset = rrsigset 345 346 rrname = rrname.choose_relativity(origin) 347 rrsigname = rrname.choose_relativity(origin) 348 if rrname != rrsigname: 349 raise ValidationFailure, "owner names do not match" 350 351 for rrsig in rrsigrdataset: 352 try: 353 _validate_rrsig(rrset, rrsig, keys, origin, now) 354 return 355 except ValidationFailure, e: 356 pass 357 raise ValidationFailure, "no RRSIGs validated"
358
359 -def _need_pycrypto(*args, **kwargs):
360 raise NotImplementedError, "DNSSEC validation requires pycrypto"
361 362 try: 363 from Crypto.PublicKey import RSA,DSA 364 import Crypto.Util.number 365 validate = _validate 366 validate_rrsig = _validate_rrsig 367 except ImportError: 368 validate = _need_pycrypto 369 validate_rrsig = _need_pycrypto 370