1# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license 2 3# Copyright (C) 2003-2017 Nominum, Inc. 4# 5# Permission to use, copy, modify, and distribute this software and its 6# documentation for any purpose with or without fee is hereby granted, 7# provided that the above copyright notice and this permission notice 8# appear in all copies. 9# 10# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES 11# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR 13# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 16# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 18"""Common DNSSEC-related functions and constants.""" 19 20import hashlib 21import struct 22import time 23import base64 24 25import dns.enum 26import dns.exception 27import dns.name 28import dns.node 29import dns.rdataset 30import dns.rdata 31import dns.rdatatype 32import dns.rdataclass 33 34 35class UnsupportedAlgorithm(dns.exception.DNSException): 36 """The DNSSEC algorithm is not supported.""" 37 38 39class ValidationFailure(dns.exception.DNSException): 40 """The DNSSEC signature is invalid.""" 41 42 43class Algorithm(dns.enum.IntEnum): 44 RSAMD5 = 1 45 DH = 2 46 DSA = 3 47 ECC = 4 48 RSASHA1 = 5 49 DSANSEC3SHA1 = 6 50 RSASHA1NSEC3SHA1 = 7 51 RSASHA256 = 8 52 RSASHA512 = 10 53 ECCGOST = 12 54 ECDSAP256SHA256 = 13 55 ECDSAP384SHA384 = 14 56 ED25519 = 15 57 ED448 = 16 58 INDIRECT = 252 59 PRIVATEDNS = 253 60 PRIVATEOID = 254 61 62 @classmethod 63 def _maximum(cls): 64 return 255 65 66 67globals().update(Algorithm.__members__) 68 69 70def algorithm_from_text(text): 71 """Convert text into a DNSSEC algorithm value. 72 73 *text*, a ``str``, the text to convert to into an algorithm value. 74 75 Returns an ``int``. 76 """ 77 78 return Algorithm.from_text(text) 79 80 81def algorithm_to_text(value): 82 """Convert a DNSSEC algorithm value to text 83 84 *value*, an ``int`` a DNSSEC algorithm. 85 86 Returns a ``str``, the name of a DNSSEC algorithm. 87 """ 88 89 return Algorithm.to_text(value) 90 91 92def key_id(key): 93 """Return the key id (a 16-bit number) for the specified key. 94 95 *key*, a ``dns.rdtypes.ANY.DNSKEY.DNSKEY`` 96 97 Returns an ``int`` between 0 and 65535 98 """ 99 100 rdata = key.to_wire() 101 if key.algorithm == Algorithm.RSAMD5: 102 return (rdata[-3] << 8) + rdata[-2] 103 else: 104 total = 0 105 for i in range(len(rdata) // 2): 106 total += (rdata[2 * i] << 8) + \ 107 rdata[2 * i + 1] 108 if len(rdata) % 2 != 0: 109 total += rdata[len(rdata) - 1] << 8 110 total += ((total >> 16) & 0xffff) 111 return total & 0xffff 112 113class DSDigest(dns.enum.IntEnum): 114 """DNSSEC Delgation Signer Digest Algorithm""" 115 116 SHA1 = 1 117 SHA256 = 2 118 SHA384 = 4 119 120 @classmethod 121 def _maximum(cls): 122 return 255 123 124 125def make_ds(name, key, algorithm, origin=None): 126 """Create a DS record for a DNSSEC key. 127 128 *name*, a ``dns.name.Name`` or ``str``, the owner name of the DS record. 129 130 *key*, a ``dns.rdtypes.ANY.DNSKEY.DNSKEY``, the key the DS is about. 131 132 *algorithm*, a ``str`` or ``int`` specifying the hash algorithm. 133 The currently supported hashes are "SHA1", "SHA256", and "SHA384". Case 134 does not matter for these strings. 135 136 *origin*, a ``dns.name.Name`` or ``None``. If `key` is a relative name, 137 then it will be made absolute using the specified origin. 138 139 Raises ``UnsupportedAlgorithm`` if the algorithm is unknown. 140 141 Returns a ``dns.rdtypes.ANY.DS.DS`` 142 """ 143 144 try: 145 if isinstance(algorithm, str): 146 algorithm = DSDigest[algorithm.upper()] 147 except Exception: 148 raise UnsupportedAlgorithm('unsupported algorithm "%s"' % algorithm) 149 150 if algorithm == DSDigest.SHA1: 151 dshash = hashlib.sha1() 152 elif algorithm == DSDigest.SHA256: 153 dshash = hashlib.sha256() 154 elif algorithm == DSDigest.SHA384: 155 dshash = hashlib.sha384() 156 else: 157 raise UnsupportedAlgorithm('unsupported algorithm "%s"' % algorithm) 158 159 if isinstance(name, str): 160 name = dns.name.from_text(name, origin) 161 dshash.update(name.canonicalize().to_wire()) 162 dshash.update(key.to_wire(origin=origin)) 163 digest = dshash.digest() 164 165 dsrdata = struct.pack("!HBB", key_id(key), key.algorithm, algorithm) + \ 166 digest 167 return dns.rdata.from_wire(dns.rdataclass.IN, dns.rdatatype.DS, dsrdata, 0, 168 len(dsrdata)) 169 170 171def _find_candidate_keys(keys, rrsig): 172 candidate_keys = [] 173 value = keys.get(rrsig.signer) 174 if value is None: 175 return None 176 if isinstance(value, dns.node.Node): 177 try: 178 rdataset = value.find_rdataset(dns.rdataclass.IN, 179 dns.rdatatype.DNSKEY) 180 except KeyError: 181 return None 182 else: 183 rdataset = value 184 for rdata in rdataset: 185 if rdata.algorithm == rrsig.algorithm and \ 186 key_id(rdata) == rrsig.key_tag: 187 candidate_keys.append(rdata) 188 return candidate_keys 189 190 191def _is_rsa(algorithm): 192 return algorithm in (Algorithm.RSAMD5, Algorithm.RSASHA1, 193 Algorithm.RSASHA1NSEC3SHA1, Algorithm.RSASHA256, 194 Algorithm.RSASHA512) 195 196 197def _is_dsa(algorithm): 198 return algorithm in (Algorithm.DSA, Algorithm.DSANSEC3SHA1) 199 200 201def _is_ecdsa(algorithm): 202 return algorithm in (Algorithm.ECDSAP256SHA256, Algorithm.ECDSAP384SHA384) 203 204 205def _is_eddsa(algorithm): 206 return algorithm in (Algorithm.ED25519, Algorithm.ED448) 207 208 209def _is_gost(algorithm): 210 return algorithm == Algorithm.ECCGOST 211 212 213def _is_md5(algorithm): 214 return algorithm == Algorithm.RSAMD5 215 216 217def _is_sha1(algorithm): 218 return algorithm in (Algorithm.DSA, Algorithm.RSASHA1, 219 Algorithm.DSANSEC3SHA1, Algorithm.RSASHA1NSEC3SHA1) 220 221 222def _is_sha256(algorithm): 223 return algorithm in (Algorithm.RSASHA256, Algorithm.ECDSAP256SHA256) 224 225 226def _is_sha384(algorithm): 227 return algorithm == Algorithm.ECDSAP384SHA384 228 229 230def _is_sha512(algorithm): 231 return algorithm == Algorithm.RSASHA512 232 233 234def _make_hash(algorithm): 235 if _is_md5(algorithm): 236 return hashes.MD5() 237 if _is_sha1(algorithm): 238 return hashes.SHA1() 239 if _is_sha256(algorithm): 240 return hashes.SHA256() 241 if _is_sha384(algorithm): 242 return hashes.SHA384() 243 if _is_sha512(algorithm): 244 return hashes.SHA512() 245 if algorithm == Algorithm.ED25519: 246 return hashes.SHA512() 247 if algorithm == Algorithm.ED448: 248 return hashes.SHAKE256(114) 249 250 raise ValidationFailure('unknown hash for algorithm %u' % algorithm) 251 252 253def _bytes_to_long(b): 254 return int.from_bytes(b, 'big') 255 256 257def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None): 258 """Validate an RRset against a single signature rdata, throwing an 259 exception if validation is not successful. 260 261 *rrset*, the RRset to validate. This can be a 262 ``dns.rrset.RRset`` or a (``dns.name.Name``, ``dns.rdataset.Rdataset``) 263 tuple. 264 265 *rrsig*, a ``dns.rdata.Rdata``, the signature to validate. 266 267 *keys*, the key dictionary, used to find the DNSKEY associated 268 with a given name. The dictionary is keyed by a 269 ``dns.name.Name``, and has ``dns.node.Node`` or 270 ``dns.rdataset.Rdataset`` values. 271 272 *origin*, a ``dns.name.Name`` or ``None``, the origin to use for relative 273 names. 274 275 *now*, an ``int`` or ``None``, the time, in seconds since the epoch, to 276 use as the current time when validating. If ``None``, the actual current 277 time is used. 278 279 Raises ``ValidationFailure`` if the signature is expired, not yet valid, 280 the public key is invalid, the algorithm is unknown, the verification 281 fails, etc. 282 283 Raises ``UnsupportedAlgorithm`` if the algorithm is recognized by 284 dnspython but not implemented. 285 """ 286 287 if isinstance(origin, str): 288 origin = dns.name.from_text(origin, dns.name.root) 289 290 candidate_keys = _find_candidate_keys(keys, rrsig) 291 if candidate_keys is None: 292 raise ValidationFailure('unknown key') 293 294 for candidate_key in candidate_keys: 295 # For convenience, allow the rrset to be specified as a (name, 296 # rdataset) tuple as well as a proper rrset 297 if isinstance(rrset, tuple): 298 rrname = rrset[0] 299 rdataset = rrset[1] 300 else: 301 rrname = rrset.name 302 rdataset = rrset 303 304 if now is None: 305 now = time.time() 306 if rrsig.expiration < now: 307 raise ValidationFailure('expired') 308 if rrsig.inception > now: 309 raise ValidationFailure('not yet valid') 310 311 if _is_rsa(rrsig.algorithm): 312 keyptr = candidate_key.key 313 (bytes_,) = struct.unpack('!B', keyptr[0:1]) 314 keyptr = keyptr[1:] 315 if bytes_ == 0: 316 (bytes_,) = struct.unpack('!H', keyptr[0:2]) 317 keyptr = keyptr[2:] 318 rsa_e = keyptr[0:bytes_] 319 rsa_n = keyptr[bytes_:] 320 try: 321 public_key = rsa.RSAPublicNumbers( 322 _bytes_to_long(rsa_e), 323 _bytes_to_long(rsa_n)).public_key(default_backend()) 324 except ValueError: 325 raise ValidationFailure('invalid public key') 326 sig = rrsig.signature 327 elif _is_dsa(rrsig.algorithm): 328 keyptr = candidate_key.key 329 (t,) = struct.unpack('!B', keyptr[0:1]) 330 keyptr = keyptr[1:] 331 octets = 64 + t * 8 332 dsa_q = keyptr[0:20] 333 keyptr = keyptr[20:] 334 dsa_p = keyptr[0:octets] 335 keyptr = keyptr[octets:] 336 dsa_g = keyptr[0:octets] 337 keyptr = keyptr[octets:] 338 dsa_y = keyptr[0:octets] 339 try: 340 public_key = dsa.DSAPublicNumbers( 341 _bytes_to_long(dsa_y), 342 dsa.DSAParameterNumbers( 343 _bytes_to_long(dsa_p), 344 _bytes_to_long(dsa_q), 345 _bytes_to_long(dsa_g))).public_key(default_backend()) 346 except ValueError: 347 raise ValidationFailure('invalid public key') 348 sig_r = rrsig.signature[1:21] 349 sig_s = rrsig.signature[21:] 350 sig = utils.encode_dss_signature(_bytes_to_long(sig_r), 351 _bytes_to_long(sig_s)) 352 elif _is_ecdsa(rrsig.algorithm): 353 keyptr = candidate_key.key 354 if rrsig.algorithm == Algorithm.ECDSAP256SHA256: 355 curve = ec.SECP256R1() 356 octets = 32 357 else: 358 curve = ec.SECP384R1() 359 octets = 48 360 ecdsa_x = keyptr[0:octets] 361 ecdsa_y = keyptr[octets:octets * 2] 362 try: 363 public_key = ec.EllipticCurvePublicNumbers( 364 curve=curve, 365 x=_bytes_to_long(ecdsa_x), 366 y=_bytes_to_long(ecdsa_y)).public_key(default_backend()) 367 except ValueError: 368 raise ValidationFailure('invalid public key') 369 sig_r = rrsig.signature[0:octets] 370 sig_s = rrsig.signature[octets:] 371 sig = utils.encode_dss_signature(_bytes_to_long(sig_r), 372 _bytes_to_long(sig_s)) 373 374 elif _is_eddsa(rrsig.algorithm): 375 keyptr = candidate_key.key 376 if rrsig.algorithm == Algorithm.ED25519: 377 loader = ed25519.Ed25519PublicKey 378 else: 379 loader = ed448.Ed448PublicKey 380 try: 381 public_key = loader.from_public_bytes(keyptr) 382 except ValueError: 383 raise ValidationFailure('invalid public key') 384 sig = rrsig.signature 385 elif _is_gost(rrsig.algorithm): 386 raise UnsupportedAlgorithm( 387 'algorithm "%s" not supported by dnspython' % 388 algorithm_to_text(rrsig.algorithm)) 389 else: 390 raise ValidationFailure('unknown algorithm %u' % rrsig.algorithm) 391 392 data = b'' 393 data += rrsig.to_wire(origin=origin)[:18] 394 data += rrsig.signer.to_digestable(origin) 395 396 if rrsig.labels < len(rrname) - 1: 397 suffix = rrname.split(rrsig.labels + 1)[1] 398 rrname = dns.name.from_text('*', suffix) 399 rrnamebuf = rrname.to_digestable(origin) 400 rrfixed = struct.pack('!HHI', rdataset.rdtype, rdataset.rdclass, 401 rrsig.original_ttl) 402 rrlist = sorted(rdataset) 403 for rr in rrlist: 404 data += rrnamebuf 405 data += rrfixed 406 rrdata = rr.to_digestable(origin) 407 rrlen = struct.pack('!H', len(rrdata)) 408 data += rrlen 409 data += rrdata 410 411 chosen_hash = _make_hash(rrsig.algorithm) 412 try: 413 if _is_rsa(rrsig.algorithm): 414 public_key.verify(sig, data, padding.PKCS1v15(), chosen_hash) 415 elif _is_dsa(rrsig.algorithm): 416 public_key.verify(sig, data, chosen_hash) 417 elif _is_ecdsa(rrsig.algorithm): 418 public_key.verify(sig, data, ec.ECDSA(chosen_hash)) 419 elif _is_eddsa(rrsig.algorithm): 420 public_key.verify(sig, data) 421 else: 422 # Raise here for code clarity; this won't actually ever happen 423 # since if the algorithm is really unknown we'd already have 424 # raised an exception above 425 raise ValidationFailure('unknown algorithm %u' % 426 rrsig.algorithm) # pragma: no cover 427 # If we got here, we successfully verified so we can return 428 # without error 429 return 430 except InvalidSignature: 431 # this happens on an individual validation failure 432 continue 433 # nothing verified -- raise failure: 434 raise ValidationFailure('verify failure') 435 436 437def _validate(rrset, rrsigset, keys, origin=None, now=None): 438 """Validate an RRset against a signature RRset, throwing an exception 439 if none of the signatures validate. 440 441 *rrset*, the RRset to validate. This can be a 442 ``dns.rrset.RRset`` or a (``dns.name.Name``, ``dns.rdataset.Rdataset``) 443 tuple. 444 445 *rrsigset*, the signature RRset. This can be a 446 ``dns.rrset.RRset`` or a (``dns.name.Name``, ``dns.rdataset.Rdataset``) 447 tuple. 448 449 *keys*, the key dictionary, used to find the DNSKEY associated 450 with a given name. The dictionary is keyed by a 451 ``dns.name.Name``, and has ``dns.node.Node`` or 452 ``dns.rdataset.Rdataset`` values. 453 454 *origin*, a ``dns.name.Name``, the origin to use for relative names; 455 defaults to None. 456 457 *now*, an ``int`` or ``None``, the time, in seconds since the epoch, to 458 use as the current time when validating. If ``None``, the actual current 459 time is used. 460 461 Raises ``ValidationFailure`` if the signature is expired, not yet valid, 462 the public key is invalid, the algorithm is unknown, the verification 463 fails, etc. 464 """ 465 466 if isinstance(origin, str): 467 origin = dns.name.from_text(origin, dns.name.root) 468 469 if isinstance(rrset, tuple): 470 rrname = rrset[0] 471 else: 472 rrname = rrset.name 473 474 if isinstance(rrsigset, tuple): 475 rrsigname = rrsigset[0] 476 rrsigrdataset = rrsigset[1] 477 else: 478 rrsigname = rrsigset.name 479 rrsigrdataset = rrsigset 480 481 rrname = rrname.choose_relativity(origin) 482 rrsigname = rrsigname.choose_relativity(origin) 483 if rrname != rrsigname: 484 raise ValidationFailure("owner names do not match") 485 486 for rrsig in rrsigrdataset: 487 try: 488 _validate_rrsig(rrset, rrsig, keys, origin, now) 489 return 490 except (ValidationFailure, UnsupportedAlgorithm): 491 pass 492 raise ValidationFailure("no RRSIGs validated") 493 494 495class NSEC3Hash(dns.enum.IntEnum): 496 """NSEC3 hash algorithm""" 497 498 SHA1 = 1 499 500 @classmethod 501 def _maximum(cls): 502 return 255 503 504def nsec3_hash(domain, salt, iterations, algorithm): 505 """ 506 Calculate the NSEC3 hash, according to 507 https://tools.ietf.org/html/rfc5155#section-5 508 509 *domain*, a ``dns.name.Name`` or ``str``, the name to hash. 510 511 *salt*, a ``str``, ``bytes``, or ``None``, the hash salt. If a 512 string, it is decoded as a hex string. 513 514 *iterations*, an ``int``, the number of iterations. 515 516 *algorithm*, a ``str`` or ``int``, the hash algorithm. 517 The only defined algorithm is SHA1. 518 519 Returns a ``str``, the encoded NSEC3 hash. 520 """ 521 522 b32_conversion = str.maketrans( 523 "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567", "0123456789ABCDEFGHIJKLMNOPQRSTUV" 524 ) 525 526 try: 527 if isinstance(algorithm, str): 528 algorithm = NSEC3Hash[algorithm.upper()] 529 except Exception: 530 raise ValueError("Wrong hash algorithm (only SHA1 is supported)") 531 532 if algorithm != NSEC3Hash.SHA1: 533 raise ValueError("Wrong hash algorithm (only SHA1 is supported)") 534 535 salt_encoded = salt 536 if salt is None: 537 salt_encoded = b'' 538 elif isinstance(salt, str): 539 if len(salt) % 2 == 0: 540 salt_encoded = bytes.fromhex(salt) 541 else: 542 raise ValueError("Invalid salt length") 543 544 if not isinstance(domain, dns.name.Name): 545 domain = dns.name.from_text(domain) 546 domain_encoded = domain.canonicalize().to_wire() 547 548 digest = hashlib.sha1(domain_encoded + salt_encoded).digest() 549 for i in range(iterations): 550 digest = hashlib.sha1(digest + salt_encoded).digest() 551 552 output = base64.b32encode(digest).decode("utf-8") 553 output = output.translate(b32_conversion) 554 555 return output 556 557 558def _need_pyca(*args, **kwargs): 559 raise ImportError("DNSSEC validation requires " + 560 "python cryptography") # pragma: no cover 561 562 563try: 564 from cryptography.exceptions import InvalidSignature 565 from cryptography.hazmat.backends import default_backend 566 from cryptography.hazmat.primitives import hashes 567 from cryptography.hazmat.primitives.asymmetric import padding 568 from cryptography.hazmat.primitives.asymmetric import utils 569 from cryptography.hazmat.primitives.asymmetric import dsa 570 from cryptography.hazmat.primitives.asymmetric import ec 571 from cryptography.hazmat.primitives.asymmetric import ed25519 572 from cryptography.hazmat.primitives.asymmetric import ed448 573 from cryptography.hazmat.primitives.asymmetric import rsa 574except ImportError: # pragma: no cover 575 validate = _need_pyca 576 validate_rrsig = _need_pyca 577 _have_pyca = False 578else: 579 validate = _validate # type: ignore 580 validate_rrsig = _validate_rrsig # type: ignore 581 _have_pyca = True 582