1# 2# This file is a part of DNSViz, a tool suite for DNS/DNSSEC monitoring, 3# analysis, and visualization. 4# Created by Casey Deccio (casey@deccio.net) 5# 6# Copyright 2012-2014 Sandia Corporation. Under the terms of Contract 7# DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains 8# certain rights in this software. 9# 10# Copyright 2014-2016 VeriSign, Inc. 11# 12# Copyright 2016-2021 Casey Deccio 13# 14# DNSViz is free software; you can redistribute it and/or modify 15# it under the terms of the GNU General Public License as published by 16# the Free Software Foundation; either version 2 of the License, or 17# (at your option) any later version. 18# 19# DNSViz is distributed in the hope that it will be useful, 20# but WITHOUT ANY WARRANTY; without even the implied warranty of 21# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22# GNU General Public License for more details. 23# 24# You should have received a copy of the GNU General Public License along 25# with DNSViz. If not, see <http://www.gnu.org/licenses/>. 26# 27 28from __future__ import unicode_literals 29 30import base64 31import datetime 32import logging 33 34# minimal support for python2.6 35try: 36 from collections import OrderedDict 37except ImportError: 38 from ordereddict import OrderedDict 39 40# python3/python2 dual compatibility 41try: 42 from html import escape 43except ImportError: 44 from cgi import escape 45 46import dns.name, dns.rdatatype 47 48from dnsviz import base32 49from dnsviz import crypto 50from dnsviz import format as fmt 51from dnsviz.util import tuple_to_dict 52lb2s = fmt.latin1_binary_to_string 53 54from . import errors as Errors 55 56CLOCK_SKEW_WARNING = 300 57 58STATUS_VALID = 0 59STATUS_INDETERMINATE = 1 60STATUS_INVALID = 2 61status_mapping = { 62 STATUS_VALID: 'VALID', 63 True: 'VALID', 64 STATUS_INDETERMINATE: 'INDETERMINATE', 65 None: 'INDETERMINATE', 66 STATUS_INVALID: 'INVALID', 67 False: 'INVALID', 68} 69 70NAME_STATUS_NOERROR = 0 71NAME_STATUS_NXDOMAIN = 1 72NAME_STATUS_INDETERMINATE = 2 73name_status_mapping = { 74 NAME_STATUS_NOERROR: 'NOERROR', 75 NAME_STATUS_NXDOMAIN: 'NXDOMAIN', 76 NAME_STATUS_INDETERMINATE: 'INDETERMINATE', 77} 78 79RRSIG_STATUS_VALID = STATUS_VALID 80RRSIG_STATUS_INDETERMINATE_NO_DNSKEY = 1 81RRSIG_STATUS_INDETERMINATE_MATCH_PRE_REVOKE = 2 82RRSIG_STATUS_INDETERMINATE_UNKNOWN_ALGORITHM = 3 83RRSIG_STATUS_ALGORITHM_IGNORED = 4 84RRSIG_STATUS_EXPIRED = 5 85RRSIG_STATUS_PREMATURE = 6 86RRSIG_STATUS_INVALID_SIG = 7 87RRSIG_STATUS_INVALID = 8 88rrsig_status_mapping = { 89 RRSIG_STATUS_VALID: 'VALID', 90 RRSIG_STATUS_INDETERMINATE_NO_DNSKEY: 'INDETERMINATE_NO_DNSKEY', 91 RRSIG_STATUS_INDETERMINATE_MATCH_PRE_REVOKE: 'INDETERMINATE_MATCH_PRE_REVOKE', 92 RRSIG_STATUS_INDETERMINATE_UNKNOWN_ALGORITHM: 'INDETERMINATE_UNKNOWN_ALGORITHM', 93 RRSIG_STATUS_ALGORITHM_IGNORED: 'ALGORITHM_IGNORED', 94 RRSIG_STATUS_EXPIRED: 'EXPIRED', 95 RRSIG_STATUS_PREMATURE: 'PREMATURE', 96 RRSIG_STATUS_INVALID_SIG: 'INVALID_SIG', 97 RRSIG_STATUS_INVALID: 'INVALID', 98} 99 100DS_STATUS_VALID = STATUS_VALID 101DS_STATUS_INDETERMINATE_NO_DNSKEY = 1 102DS_STATUS_INDETERMINATE_MATCH_PRE_REVOKE = 2 103DS_STATUS_INDETERMINATE_UNKNOWN_ALGORITHM = 3 104DS_STATUS_ALGORITHM_IGNORED = 4 105DS_STATUS_INVALID_DIGEST = 5 106DS_STATUS_INVALID = 6 107ds_status_mapping = { 108 DS_STATUS_VALID: 'VALID', 109 DS_STATUS_INDETERMINATE_NO_DNSKEY: 'INDETERMINATE_NO_DNSKEY', 110 DS_STATUS_INDETERMINATE_MATCH_PRE_REVOKE: 'INDETERMINATE_MATCH_PRE_REVOKE', 111 DS_STATUS_INDETERMINATE_UNKNOWN_ALGORITHM: 'INDETERMINATE_UNKNOWN_ALGORITHM', 112 DS_STATUS_ALGORITHM_IGNORED: 'ALGORITHM_IGNORED', 113 DS_STATUS_INVALID_DIGEST: 'INVALID_DIGEST', 114 DS_STATUS_INVALID: 'INVALID', 115} 116 117DELEGATION_STATUS_SECURE = 0 118DELEGATION_STATUS_INSECURE = 1 119DELEGATION_STATUS_BOGUS = 2 120DELEGATION_STATUS_INCOMPLETE = 3 121DELEGATION_STATUS_LAME = 4 122delegation_status_mapping = { 123 DELEGATION_STATUS_SECURE: 'SECURE', 124 DELEGATION_STATUS_INSECURE: 'INSECURE', 125 DELEGATION_STATUS_BOGUS: 'BOGUS', 126 DELEGATION_STATUS_INCOMPLETE: 'INCOMPLETE', 127 DELEGATION_STATUS_LAME: 'LAME', 128} 129 130RRSET_STATUS_SECURE = 0 131RRSET_STATUS_INSECURE = 1 132RRSET_STATUS_BOGUS = 2 133RRSET_STATUS_NON_EXISTENT = 3 134rrset_status_mapping = { 135 RRSET_STATUS_SECURE: 'SECURE', 136 RRSET_STATUS_INSECURE: 'INSECURE', 137 RRSET_STATUS_BOGUS: 'BOGUS', 138 RRSET_STATUS_NON_EXISTENT: 'NON_EXISTENT', 139} 140 141NSEC_STATUS_VALID = STATUS_VALID 142NSEC_STATUS_INDETERMINATE = STATUS_INDETERMINATE 143NSEC_STATUS_INVALID = 2 144nsec_status_mapping = { 145 NSEC_STATUS_VALID: 'VALID', 146 NSEC_STATUS_INDETERMINATE: 'INDETERMINATE', 147 NSEC_STATUS_INVALID: 'INVALID', 148} 149 150DNAME_STATUS_VALID = STATUS_VALID 151DNAME_STATUS_INDETERMINATE = STATUS_INDETERMINATE 152DNAME_STATUS_INVALID_TARGET = 2 153DNAME_STATUS_INVALID = 3 154dname_status_mapping = { 155 DNAME_STATUS_VALID: 'VALID', 156 DNAME_STATUS_INDETERMINATE: 'INDETERMINATE', 157 DNAME_STATUS_INVALID_TARGET: 'INVALID_TARGET', 158 DNAME_STATUS_INVALID: 'INVALID', 159} 160 161RRSIG_SIG_LENGTHS_BY_ALGORITHM = { 162 12: 512, 13: 512, 14: 768, 15: 512, 16: 912, 163} 164RRSIG_SIG_LENGTH_ERRORS = { 165 12: Errors.RRSIGBadLengthGOST, 13: Errors.RRSIGBadLengthECDSA256, 166 14: Errors.RRSIGBadLengthECDSA384, 15: Errors.RRSIGBadLengthEd25519, 167 16: Errors.RRSIGBadLengthEd448, 168} 169DS_DIGEST_ALGS_STRONGER_THAN_SHA1 = (2, 4) 170DS_DIGEST_ALGS_IGNORING_SHA1 = (2,) 171 172# RFC 8624 Section 3.1 173DNSKEY_ALGS_NOT_RECOMMENDED = (5, 7, 10) 174DNSKEY_ALGS_PROHIBITED = (1, 3, 6, 12) 175DNSKEY_ALGS_VALIDATION_PROHIBITED = (1, 3, 6) 176 177# RFC 8624 Section 3.2 178DS_DIGEST_ALGS_NOT_RECOMMENDED = () 179DS_DIGEST_ALGS_PROHIBITED = (0, 1, 3) 180DS_DIGEST_ALGS_VALIDATION_PROHIBITED = () 181 182class RRSIGStatus(object): 183 def __init__(self, rrset, rrsig, dnskey, zone_name, reference_ts, supported_algs): 184 self.rrset = rrset 185 self.rrsig = rrsig 186 self.dnskey = dnskey 187 self.zone_name = zone_name 188 self.reference_ts = reference_ts 189 self.warnings = [] 190 self.errors = [] 191 192 if self.dnskey is None: 193 self.signature_valid = None 194 else: 195 self.signature_valid = crypto.validate_rrsig(dnskey.rdata.algorithm, rrsig.signature, rrset.message_for_rrsig(rrsig), dnskey.rdata.key) 196 197 self.validation_status = RRSIG_STATUS_VALID 198 if self.signature_valid is None or self.rrsig.algorithm not in supported_algs: 199 # Either we can't validate the cryptographic signature, or we are 200 # explicitly directed to ignore the algorithm. 201 if self.dnskey is None: 202 # In this case, there is no corresponding DNSKEY, so we make 203 # the status "INDETERMINATE". 204 if self.validation_status == RRSIG_STATUS_VALID: 205 self.validation_status = RRSIG_STATUS_INDETERMINATE_NO_DNSKEY 206 207 else: 208 # If there is a DNSKEY, then we look at *why* we are ignoring 209 # the cryptographic signature. 210 if self.dnskey.rdata.algorithm in DNSKEY_ALGS_VALIDATION_PROHIBITED: 211 # In this case, specification dictates that the algorithm 212 # MUST NOT be validated, so we mark it as ignored. 213 if self.validation_status == RRSIG_STATUS_VALID: 214 self.validation_status = RRSIG_STATUS_ALGORITHM_IGNORED 215 else: 216 # In this case, we can't validate this particular 217 # algorithm, either because the code doesn't support it, 218 # or because we have been explicitly directed to ignore it. 219 # In either case, mark it as "UNKNOWN", and warn that it is 220 # not supported. 221 if self.validation_status == RRSIG_STATUS_VALID: 222 self.validation_status = RRSIG_STATUS_INDETERMINATE_UNKNOWN_ALGORITHM 223 self.warnings.append(Errors.AlgorithmNotSupported(algorithm=self.rrsig.algorithm)) 224 225 # Independent of whether or not we considered the cryptographic 226 # validation, issue a warning if we are using an algorithm for which 227 # validation or signing has been prohibited. 228 # 229 # Signing is prohibited 230 if self.rrsig.algorithm in DNSKEY_ALGS_VALIDATION_PROHIBITED: 231 self.warnings.append(Errors.AlgorithmValidationProhibited(algorithm=self.rrsig.algorithm)) 232 # Validation is prohibited or, at least, not recommended 233 if self.rrsig.algorithm in DNSKEY_ALGS_PROHIBITED: 234 self.warnings.append(Errors.AlgorithmProhibited(algorithm=self.rrsig.algorithm)) 235 elif self.rrsig.algorithm in DNSKEY_ALGS_NOT_RECOMMENDED: 236 self.warnings.append(Errors.AlgorithmNotRecommended(algorithm=self.rrsig.algorithm)) 237 238 if self.rrset.ttl_cmp: 239 if self.rrset.rrset.ttl != self.rrset.rrsig_info[self.rrsig].ttl: 240 self.warnings.append(Errors.RRsetTTLMismatch(rrset_ttl=self.rrset.rrset.ttl, rrsig_ttl=self.rrset.rrsig_info[self.rrsig].ttl)) 241 if self.rrset.rrsig_info[self.rrsig].ttl > self.rrsig.original_ttl: 242 self.errors.append(Errors.OriginalTTLExceeded(rrset_ttl=self.rrset.rrset.ttl, original_ttl=self.rrsig.original_ttl)) 243 244 min_ttl = min(self.rrset.rrset.ttl, self.rrset.rrsig_info[self.rrsig].ttl, self.rrsig.original_ttl) 245 246 if (zone_name is not None and self.rrsig.signer != zone_name) or \ 247 (zone_name is None and not self.rrset.rrset.name.is_subdomain(self.rrsig.signer)): 248 if self.validation_status == RRSIG_STATUS_VALID: 249 self.validation_status = RRSIG_STATUS_INVALID 250 if zone_name is None: 251 zn = self.rrsig.signer 252 else: 253 zn = zone_name 254 self.errors.append(Errors.SignerNotZone(zone_name=fmt.humanize_name(zn), signer_name=fmt.humanize_name(self.rrsig.signer))) 255 256 if self.dnskey is not None and \ 257 self.dnskey.rdata.flags & fmt.DNSKEY_FLAGS['revoke'] and self.rrsig.covers() != dns.rdatatype.DNSKEY: 258 if self.rrsig.key_tag != self.dnskey.key_tag: 259 if self.validation_status == RRSIG_STATUS_VALID: 260 self.validation_status = RRSIG_STATUS_INDETERMINATE_MATCH_PRE_REVOKE 261 else: 262 self.errors.append(Errors.DNSKEYRevokedRRSIG()) 263 if self.validation_status == RRSIG_STATUS_VALID: 264 self.validation_status = RRSIG_STATUS_INVALID 265 266 sig_len = len(self.rrsig.signature) << 3 267 if self.rrsig.algorithm in RRSIG_SIG_LENGTHS_BY_ALGORITHM and \ 268 sig_len != RRSIG_SIG_LENGTHS_BY_ALGORITHM[self.rrsig.algorithm]: 269 self.errors.append(RRSIG_SIG_LENGTH_ERRORS[self.rrsig.algorithm](length=sig_len)) 270 271 if self.reference_ts < self.rrsig.inception: 272 if self.validation_status == RRSIG_STATUS_VALID: 273 self.validation_status = RRSIG_STATUS_PREMATURE 274 self.errors.append(Errors.InceptionInFuture(inception=fmt.timestamp_to_datetime(self.rrsig.inception), reference_time=fmt.timestamp_to_datetime(self.reference_ts))) 275 elif self.reference_ts - CLOCK_SKEW_WARNING < self.rrsig.inception: 276 self.warnings.append(Errors.InceptionWithinClockSkew(inception=fmt.timestamp_to_datetime(self.rrsig.inception), reference_time=fmt.timestamp_to_datetime(self.reference_ts))) 277 278 if self.reference_ts >= self.rrsig.expiration: 279 if self.validation_status == RRSIG_STATUS_VALID: 280 self.validation_status = RRSIG_STATUS_EXPIRED 281 self.errors.append(Errors.ExpirationInPast(expiration=fmt.timestamp_to_datetime(self.rrsig.expiration), reference_time=fmt.timestamp_to_datetime(self.reference_ts))) 282 elif self.reference_ts + min_ttl >= self.rrsig.expiration: 283 self.errors.append(Errors.TTLBeyondExpiration(expiration=fmt.timestamp_to_datetime(self.rrsig.expiration), rrsig_ttl=min_ttl, reference_time=fmt.timestamp_to_datetime(self.reference_ts))) 284 elif self.reference_ts + CLOCK_SKEW_WARNING >= self.rrsig.expiration: 285 self.warnings.append(Errors.ExpirationWithinClockSkew(expiration=fmt.timestamp_to_datetime(self.rrsig.expiration), reference_time=fmt.timestamp_to_datetime(self.reference_ts))) 286 287 if self.signature_valid == False and self.dnskey.rdata.algorithm in supported_algs: 288 # only report this if we're not referring to a key revoked post-sign 289 if self.dnskey.key_tag == self.rrsig.key_tag: 290 if self.validation_status == RRSIG_STATUS_VALID: 291 self.validation_status = RRSIG_STATUS_INVALID_SIG 292 self.errors.append(Errors.SignatureInvalid()) 293 294 def __str__(self): 295 return 'RRSIG covering %s/%s' % (fmt.humanize_name(self.rrset.rrset.name), dns.rdatatype.to_text(self.rrset.rrset.rdtype)) 296 297 def serialize(self, consolidate_clients=True, loglevel=logging.DEBUG, html_format=False, map_ip_to_ns_name=None): 298 d = OrderedDict() 299 300 erroneous_status = self.validation_status not in (RRSIG_STATUS_VALID, RRSIG_STATUS_INDETERMINATE_NO_DNSKEY, RRSIG_STATUS_INDETERMINATE_UNKNOWN_ALGORITHM) 301 302 show_id = loglevel <= logging.INFO or \ 303 (self.warnings and loglevel <= logging.WARNING) or \ 304 (self.errors and loglevel <= logging.ERROR) or \ 305 erroneous_status 306 307 if html_format: 308 formatter = lambda x: escape(x, True) 309 else: 310 formatter = lambda x: x 311 312 if show_id: 313 d['id'] = '%s/%d/%d' % (lb2s(self.rrsig.signer.canonicalize().to_text()), self.rrsig.algorithm, self.rrsig.key_tag) 314 315 if loglevel <= logging.DEBUG: 316 d.update(( 317 ('description', formatter(str(self))), 318 ('signer', formatter(lb2s(self.rrsig.signer.canonicalize().to_text()))), 319 ('algorithm', self.rrsig.algorithm), 320 ('key_tag', self.rrsig.key_tag), 321 ('original_ttl', self.rrsig.original_ttl), 322 ('labels', self.rrsig.labels), 323 ('inception', fmt.timestamp_to_str(self.rrsig.inception)), 324 ('expiration', fmt.timestamp_to_str(self.rrsig.expiration)), 325 ('signature', lb2s(base64.b64encode(self.rrsig.signature))), 326 ('ttl', self.rrset.rrsig_info[self.rrsig].ttl), 327 )) 328 329 if html_format: 330 d['algorithm'] = '%d (%s)' % (self.rrsig.algorithm, fmt.DNSKEY_ALGORITHMS.get(self.rrsig.algorithm, self.rrsig.algorithm)) 331 d['original_ttl'] = '%d (%s)' % (self.rrsig.original_ttl, fmt.humanize_time(self.rrsig.original_ttl)) 332 if self.rrset.is_wildcard(self.rrsig): 333 d['labels'] = '%d (wildcard)' % (self.rrsig.labels) 334 else: 335 d['labels'] = '%d (no wildcard)' % (self.rrsig.labels) 336 d['inception'] += ' (%s)' % (fmt.format_diff(fmt.timestamp_to_datetime(self.reference_ts), fmt.timestamp_to_datetime(self.rrsig.inception))) 337 d['expiration'] += ' (%s)' % (fmt.format_diff(fmt.timestamp_to_datetime(self.reference_ts), fmt.timestamp_to_datetime(self.rrsig.expiration))) 338 d['ttl'] = '%d (%s)' % (self.rrset.rrsig_info[self.rrsig].ttl, fmt.humanize_time(self.rrset.rrsig_info[self.rrsig].ttl)) 339 340 if loglevel <= logging.INFO or erroneous_status: 341 d['status'] = rrsig_status_mapping[self.validation_status] 342 343 if loglevel <= logging.INFO: 344 servers = tuple_to_dict(self.rrset.rrsig_info[self.rrsig].servers_clients) 345 if consolidate_clients: 346 servers = list(servers) 347 servers.sort() 348 d['servers'] = servers 349 350 if map_ip_to_ns_name is not None: 351 ns_names = list(set([lb2s(map_ip_to_ns_name(s)[0][0].canonicalize().to_text()) for s in servers])) 352 ns_names.sort() 353 d['ns_names'] = ns_names 354 355 tags = set() 356 nsids = set() 357 for server,client in self.rrset.rrsig_info[self.rrsig].servers_clients: 358 for response in self.rrset.rrsig_info[self.rrsig].servers_clients[(server, client)]: 359 if response is not None: 360 tags.add(response.effective_query_tag()) 361 nsid = response.nsid_val() 362 if nsid is not None: 363 nsids.add(nsid) 364 365 if nsids: 366 d['nsid_values'] = list(nsids) 367 d['nsid_values'].sort() 368 369 d['query_options'] = list(tags) 370 d['query_options'].sort() 371 372 if self.warnings and loglevel <= logging.WARNING: 373 d['warnings'] = [w.serialize(consolidate_clients=consolidate_clients, html_format=html_format) for w in self.warnings] 374 375 if self.errors and loglevel <= logging.ERROR: 376 d['errors'] = [e.serialize(consolidate_clients=consolidate_clients, html_format=html_format) for e in self.errors] 377 378 return d 379 380class DSStatus(object): 381 def __init__(self, ds, ds_meta, dnskey, supported_digest_algs): 382 self.ds = ds 383 self.ds_meta = ds_meta 384 self.dnskey = dnskey 385 self.warnings = [] 386 self.errors = [] 387 388 if self.dnskey is None: 389 self.digest_valid = None 390 else: 391 self.digest_valid = crypto.validate_ds_digest(ds.digest_type, ds.digest, dnskey.message_for_ds()) 392 393 self.validation_status = DS_STATUS_VALID 394 if self.digest_valid is None or self.ds.digest_type not in supported_digest_algs: 395 # Either we cannot reproduce a digest with this type, or we are 396 # explicitly directed to ignore the digest type. 397 if self.dnskey is None: 398 # In this case, there is no corresponding DNSKEY, so we make 399 # the status "INDETERMINATE". 400 if self.validation_status == DS_STATUS_VALID: 401 self.validation_status = DS_STATUS_INDETERMINATE_NO_DNSKEY 402 else: 403 # If there is a DNSKEY, then we look at *why* we are ignoring 404 # the digest of the DNSKEY. 405 if self.ds.digest_type in DS_DIGEST_ALGS_VALIDATION_PROHIBITED: 406 # In this case, specification dictates that the algorithm 407 # MUST NOT be validated, so we mark it as ignored. 408 if self.validation_status == DS_STATUS_VALID: 409 self.validation_status = DS_STATUS_ALGORITHM_IGNORED 410 else: 411 # In this case, we can't validate this particular 412 # digest type, either because the code doesn't support it, 413 # or because we have been explicitly directed to ignore it. 414 # In either case, mark it as "UNKNOWN", and warn that it is 415 # not supported. 416 if self.validation_status == DS_STATUS_VALID: 417 self.validation_status = DS_STATUS_INDETERMINATE_UNKNOWN_ALGORITHM 418 self.warnings.append(Errors.DigestAlgorithmNotSupported(algorithm=self.ds.digest_type)) 419 420 # Independent of whether or not we considered the digest for 421 # validation, issue a warning if we are using a digest type for which 422 # validation or signing has been prohibited. 423 # 424 # Signing is prohibited 425 if self.ds.digest_type in DS_DIGEST_ALGS_VALIDATION_PROHIBITED: 426 self.warnings.append(Errors.DigestAlgorithmValidationProhibited(algorithm=self.ds.digest_type)) 427 # Validation is prohibited or, at least, not recommended 428 if self.ds.digest_type in DS_DIGEST_ALGS_PROHIBITED: 429 self.warnings.append(Errors.DigestAlgorithmProhibited(algorithm=self.ds.digest_type)) 430 elif self.ds.digest_type in DS_DIGEST_ALGS_NOT_RECOMMENDED: 431 self.warnings.append(Errors.DigestAlgorithmNotRecommended(algorithm=self.ds.digest_type)) 432 433 if self.dnskey is not None and \ 434 self.dnskey.rdata.flags & fmt.DNSKEY_FLAGS['revoke']: 435 if self.dnskey.key_tag != self.ds.key_tag: 436 if self.validation_status == DS_STATUS_VALID: 437 self.validation_status = DS_STATUS_INDETERMINATE_MATCH_PRE_REVOKE 438 else: 439 self.errors.append(Errors.DNSKEYRevokedDS()) 440 if self.validation_status == DS_STATUS_VALID: 441 self.validation_status = DS_STATUS_INVALID 442 443 if self.digest_valid == False and self.ds.digest_type in supported_digest_algs: 444 # only report this if we're not referring to a key revoked post-DS 445 if self.dnskey.key_tag == self.ds.key_tag: 446 if self.validation_status == DS_STATUS_VALID: 447 self.validation_status = DS_STATUS_INVALID_DIGEST 448 self.errors.append(Errors.DigestInvalid()) 449 450 # RFC 4509 451 if self.ds.digest_type == 1: 452 stronger_algs_all_ds = set() 453 # Cycle through all other DS records in the DS RRset, and 454 # create a list of digest types that are stronger than SHA1 455 # and are being used by DS records across the *entire* DS. 456 for ds_rdata in self.ds_meta.rrset: 457 if ds_rdata.digest_type in DS_DIGEST_ALGS_STRONGER_THAN_SHA1: 458 stronger_algs_all_ds.add(ds_rdata.digest_type) 459 460 # Consider only digest types that we actually support 461 stronger_algs_all_ds.intersection_update(supported_digest_algs) 462 463 if stronger_algs_all_ds: 464 # If there are DS records in the DS RRset with digest type 465 # stronger than SHA1, then this one MUST be ignored by 466 # validators (RFC 4509). 467 for digest_alg in stronger_algs_all_ds: 468 if digest_alg in DS_DIGEST_ALGS_IGNORING_SHA1: 469 if self.validation_status == DS_STATUS_VALID: 470 self.validation_status = DS_STATUS_ALGORITHM_IGNORED 471 self.warnings.append(Errors.DSDigestAlgorithmIgnored(algorithm=1, new_algorithm=digest_alg)) 472 else: 473 self.warnings.append(Errors.DSDigestAlgorithmMaybeIgnored(algorithm=1, new_algorithm=digest_alg)) 474 475 def __str__(self): 476 return '%s record(s) corresponding to DNSKEY for %s (algorithm %d (%s), key tag %d)' % (dns.rdatatype.to_text(self.ds_meta.rrset.rdtype), fmt.humanize_name(self.ds_meta.rrset.name), self.ds.algorithm, fmt.DNSKEY_ALGORITHMS.get(self.ds.algorithm, self.ds.algorithm), self.ds.key_tag) 477 478 def serialize(self, consolidate_clients=True, loglevel=logging.DEBUG, html_format=False, map_ip_to_ns_name=True): 479 d = OrderedDict() 480 481 erroneous_status = self.validation_status not in (DS_STATUS_VALID, DS_STATUS_INDETERMINATE_NO_DNSKEY, DS_STATUS_INDETERMINATE_UNKNOWN_ALGORITHM) 482 483 show_id = loglevel <= logging.INFO or \ 484 (self.warnings and loglevel <= logging.WARNING) or \ 485 (self.errors and loglevel <= logging.ERROR) or \ 486 erroneous_status 487 488 if html_format: 489 formatter = lambda x: escape(x, True) 490 else: 491 formatter = lambda x: x 492 493 if show_id: 494 d['id'] = '%d/%d/%d' % (self.ds.algorithm, self.ds.key_tag, self.ds.digest_type) 495 496 if loglevel <= logging.DEBUG: 497 d.update(( 498 ('description', formatter(str(self))), 499 ('algorithm', self.ds.algorithm), 500 ('key_tag', self.ds.key_tag), 501 ('digest_type', self.ds.digest_type), 502 ('digest', lb2s(base64.b64encode(self.ds.digest))), 503 )) 504 505 if html_format: 506 d['algorithm'] = '%d (%s)' % (self.ds.algorithm, fmt.DNSKEY_ALGORITHMS.get(self.ds.algorithm, self.ds.algorithm)) 507 d['digest_type'] = '%d (%s)' % (self.ds.digest_type, fmt.DS_DIGEST_TYPES.get(self.ds.digest_type, self.ds.digest_type)) 508 509 d['ttl'] = self.ds_meta.rrset.ttl 510 if html_format: 511 d['ttl'] = '%d (%s)' % (self.ds_meta.rrset.ttl, fmt.humanize_time(self.ds_meta.rrset.ttl)) 512 513 if loglevel <= logging.INFO or erroneous_status: 514 d['status'] = ds_status_mapping[self.validation_status] 515 516 if loglevel <= logging.INFO: 517 servers = tuple_to_dict(self.ds_meta.servers_clients) 518 if consolidate_clients: 519 servers = list(servers) 520 servers.sort() 521 d['servers'] = servers 522 523 if map_ip_to_ns_name is not None: 524 ns_names = list(set([lb2s(map_ip_to_ns_name(s)[0][0].canonicalize().to_text()) for s in servers])) 525 ns_names.sort() 526 d['ns_names'] = ns_names 527 528 tags = set() 529 nsids = set() 530 for server,client in self.ds_meta.servers_clients: 531 for response in self.ds_meta.servers_clients[(server, client)]: 532 if response is not None: 533 tags.add(response.effective_query_tag()) 534 nsid = response.nsid_val() 535 if nsid is not None: 536 nsids.add(nsid) 537 538 if nsids: 539 d['nsid_values'] = list(nsids) 540 d['nsid_values'].sort() 541 542 d['query_options'] = list(tags) 543 d['query_options'].sort() 544 545 if self.warnings and loglevel <= logging.WARNING: 546 d['warnings'] = [w.serialize(consolidate_clients=consolidate_clients, html_format=html_format) for w in self.warnings] 547 548 if self.errors and loglevel <= logging.ERROR: 549 d['errors'] = [e.serialize(consolidate_clients=consolidate_clients, html_format=html_format) for e in self.errors] 550 551 return d 552 553class NSECStatus(object): 554 def __repr__(self): 555 return '<%s: "%s">' % (self.__class__.__name__, self.qname) 556 557 def _get_wildcard(self, qname, nsec_rrset): 558 covering_name = nsec_rrset.name 559 next_name = nsec_rrset[0].next 560 for i in range(len(qname)): 561 j = -(i + 1) 562 if i < len(covering_name) and covering_name[j].lower() == qname[j].lower(): 563 continue 564 elif i < len(next_name) and next_name[j].lower() == qname[j].lower(): 565 continue 566 else: 567 break 568 return dns.name.Name(('*',) + qname[-i:]) 569 570class NSECStatusNXDOMAIN(NSECStatus): 571 def __init__(self, qname, rdtype, origin, is_zone, nsec_set_info): 572 self.qname = qname 573 self.origin = origin 574 self.is_zone = is_zone 575 self.warnings = [] 576 self.errors = [] 577 578 self.wildcard_name = None 579 580 self.nsec_names_covering_qname = {} 581 covering_names = nsec_set_info.nsec_covering_name(self.qname) 582 self.opt_out = None 583 584 if covering_names: 585 self.nsec_names_covering_qname[self.qname] = covering_names 586 587 covering_name = list(covering_names)[0] 588 self.wildcard_name = self._get_wildcard(qname, nsec_set_info.rrsets[covering_name].rrset) 589 590 self.nsec_names_covering_wildcard = {} 591 if self.wildcard_name is not None: 592 covering_names = nsec_set_info.nsec_covering_name(self.wildcard_name) 593 if covering_names: 594 self.nsec_names_covering_wildcard[self.wildcard_name] = covering_names 595 596 # check for covering of the origin 597 self.nsec_names_covering_origin = {} 598 covering_names = nsec_set_info.nsec_covering_name(self.origin) 599 if covering_names: 600 self.nsec_names_covering_origin[self.origin] = covering_names 601 602 self._set_validation_status(nsec_set_info) 603 604 def __eq__(self, other): 605 return isinstance(other, self.__class__) and \ 606 self.qname == other.qname and self.origin == other.origin and self.nsec_set_info == other.nsec_set_info 607 608 def __hash__(self): 609 return hash(id(self)) 610 611 def _set_validation_status(self, nsec_set_info): 612 self.validation_status = NSEC_STATUS_VALID 613 if not self.nsec_names_covering_qname: 614 self.validation_status = NSEC_STATUS_INVALID 615 self.errors.append(Errors.SnameNotCoveredNameError(sname=fmt.humanize_name(self.qname))) 616 if not self.nsec_names_covering_wildcard and self.wildcard_name is not None: 617 self.validation_status = NSEC_STATUS_INVALID 618 self.errors.append(Errors.WildcardNotCoveredNSEC(wildcard=fmt.humanize_name(self.wildcard_name))) 619 if self.nsec_names_covering_origin: 620 self.validation_status = NSEC_STATUS_INVALID 621 qname, nsec_names = list(self.nsec_names_covering_origin.items())[0] 622 nsec_rrset = nsec_set_info.rrsets[list(nsec_names)[0]].rrset 623 self.errors.append(Errors.LastNSECNextNotZone(nsec_owner=fmt.humanize_name(nsec_rrset.name), next_name=fmt.humanize_name(nsec_rrset[0].next), zone_name=fmt.humanize_name(self.origin))) 624 625 # if it validation_status, we project out just the pertinent NSEC records 626 # otherwise clone it by projecting them all 627 if self.validation_status == NSEC_STATUS_VALID: 628 covering_names = set() 629 for names in list(self.nsec_names_covering_qname.values()) + list(self.nsec_names_covering_wildcard.values()): 630 covering_names.update(names) 631 self.nsec_set_info = nsec_set_info.project(*list(covering_names)) 632 else: 633 self.nsec_set_info = nsec_set_info.project(*list(nsec_set_info.rrsets)) 634 635 def __str__(self): 636 return 'NSEC record(s) proving the non-existence (NXDOMAIN) of %s' % (fmt.humanize_name(self.qname)) 637 638 def serialize(self, rrset_info_serializer=None, consolidate_clients=True, loglevel=logging.DEBUG, html_format=False, map_ip_to_ns_name=None): 639 d = OrderedDict() 640 641 nsec_list = [] 642 for nsec_rrset in self.nsec_set_info.rrsets.values(): 643 if rrset_info_serializer is not None: 644 nsec_serialized = rrset_info_serializer(nsec_rrset, consolidate_clients=consolidate_clients, show_servers=False, loglevel=loglevel, html_format=html_format) 645 if nsec_serialized: 646 nsec_list.append(nsec_serialized) 647 elif loglevel <= logging.DEBUG: 648 nsec_list.append(nsec_rrset.serialize(consolidate_clients=consolidate_clients, html_format=html_format)) 649 650 erroneous_status = self.validation_status != STATUS_VALID 651 652 show_id = loglevel <= logging.INFO or \ 653 (self.warnings and loglevel <= logging.WARNING) or \ 654 (self.errors and loglevel <= logging.ERROR) or \ 655 (erroneous_status or nsec_list) 656 657 if html_format: 658 formatter = lambda x: escape(x, True) 659 else: 660 formatter = lambda x: x 661 662 if show_id: 663 d['id'] = 'NSEC' 664 665 if loglevel <= logging.DEBUG: 666 d['description'] = formatter(str(self)) 667 668 if nsec_list: 669 d['nsec'] = nsec_list 670 671 if loglevel <= logging.DEBUG: 672 if self.nsec_names_covering_qname: 673 qname, nsec_names = list(self.nsec_names_covering_qname.items())[0] 674 nsec_name = list(nsec_names)[0] 675 nsec_rr = self.nsec_set_info.rrsets[nsec_name].rrset[0] 676 d['sname_covering'] = OrderedDict(( 677 ('covered_name', formatter(lb2s(qname.canonicalize().to_text()))), 678 ('nsec_owner', formatter(lb2s(nsec_name.canonicalize().to_text()))), 679 ('nsec_next', formatter(lb2s(nsec_rr.next.canonicalize().to_text()))) 680 )) 681 if self.nsec_names_covering_wildcard: 682 wildcard, nsec_names = list(self.nsec_names_covering_wildcard.items())[0] 683 nsec_name = list(nsec_names)[0] 684 nsec_rr = self.nsec_set_info.rrsets[nsec_name].rrset[0] 685 d['wildcard_covering'] = OrderedDict(( 686 ('covered_name', formatter(lb2s(wildcard.canonicalize().to_text()))), 687 ('nsec_owner', formatter(lb2s(nsec_name.canonicalize().to_text()))), 688 ('nsec_next', formatter(lb2s(nsec_rr.next.canonicalize().to_text()))) 689 )) 690 691 if loglevel <= logging.INFO or erroneous_status: 692 d['status'] = nsec_status_mapping[self.validation_status] 693 694 if loglevel <= logging.INFO: 695 servers = tuple_to_dict(self.nsec_set_info.servers_clients) 696 if consolidate_clients: 697 servers = list(servers) 698 servers.sort() 699 d['servers'] = servers 700 701 if map_ip_to_ns_name is not None: 702 ns_names = list(set([lb2s(map_ip_to_ns_name(s)[0][0].canonicalize().to_text()) for s in servers])) 703 ns_names.sort() 704 d['ns_names'] = ns_names 705 706 tags = set() 707 nsids = set() 708 for server,client in self.nsec_set_info.servers_clients: 709 for response in self.nsec_set_info.servers_clients[(server, client)]: 710 if response is not None: 711 tags.add(response.effective_query_tag()) 712 nsid = response.nsid_val() 713 if nsid is not None: 714 nsids.add(nsid) 715 716 if nsids: 717 d['nsid_values'] = list(nsids) 718 d['nsid_values'].sort() 719 720 d['query_options'] = list(tags) 721 d['query_options'].sort() 722 723 if self.warnings and loglevel <= logging.WARNING: 724 d['warnings'] = [w.serialize(consolidate_clients=consolidate_clients, html_format=html_format) for w in self.warnings] 725 726 if self.errors and loglevel <= logging.ERROR: 727 d['errors'] = [e.serialize(consolidate_clients=consolidate_clients, html_format=html_format) for e in self.errors] 728 729 return d 730 731class NSECStatusWildcard(NSECStatusNXDOMAIN): 732 def __init__(self, qname, wildcard_name, rdtype, origin, is_zone, nsec_set_info): 733 self.wildcard_name_from_rrsig = wildcard_name 734 super(NSECStatusWildcard, self).__init__(qname, rdtype, origin, is_zone, nsec_set_info) 735 736 def __eq__(self, other): 737 return isinstance(other, self.__class__) and \ 738 super(NSECStatusWildcard, self).__eq__(other) and self.wildcard_name_from_rrsig == other.wildcard_name_from_rrsig 739 740 def __hash__(self): 741 return hash(id(self)) 742 743 def _next_closest_encloser(self): 744 return dns.name.Name(self.qname.labels[-len(self.wildcard_name):]) 745 746 def _set_validation_status(self, nsec_set_info): 747 self.validation_status = NSEC_STATUS_VALID 748 if self.nsec_names_covering_qname: 749 next_closest_encloser = self._next_closest_encloser() 750 nsec_covering_next_closest_encloser = nsec_set_info.nsec_covering_name(next_closest_encloser) 751 if not nsec_covering_next_closest_encloser: 752 self.validation_status = NSEC_STATUS_INVALID 753 self.errors.append(Errors.WildcardExpansionInvalid(sname=fmt.humanize_name(self.qname), wildcard=fmt.humanize_name(self.wildcard_name), next_closest_encloser=fmt.humanize_name(next_closest_encloser))) 754 else: 755 self.validation_status = NSEC_STATUS_INVALID 756 self.errors.append(Errors.SnameNotCoveredWildcardAnswer(sname=fmt.humanize_name(self.qname))) 757 758 if self.nsec_names_covering_wildcard: 759 self.validation_status = NSEC_STATUS_INVALID 760 self.errors.append(Errors.WildcardCoveredAnswerNSEC(wildcard=fmt.humanize_name(self.wildcard_name))) 761 762 if self.nsec_names_covering_origin: 763 self.validation_status = NSEC_STATUS_INVALID 764 qname, nsec_names = list(self.nsec_names_covering_origin.items())[0] 765 nsec_rrset = nsec_set_info.rrsets[list(nsec_names)[0]].rrset 766 self.errors.append(Errors.LastNSECNextNotZone(nsec_owner=fmt.humanize_name(nsec_rrset.name), next_name=fmt.humanize_name(nsec_rrset[0].next), zone_name=fmt.humanize_name(self.origin))) 767 768 # if it validation_status, we project out just the pertinent NSEC records 769 # otherwise clone it by projecting them all 770 if self.validation_status == NSEC_STATUS_VALID: 771 covering_names = set() 772 for names in self.nsec_names_covering_qname.values(): 773 covering_names.update(names) 774 self.nsec_set_info = nsec_set_info.project(*list(covering_names)) 775 else: 776 self.nsec_set_info = nsec_set_info.project(*list(nsec_set_info.rrsets)) 777 778 def serialize(self, rrset_info_serializer=None, consolidate_clients=True, loglevel=logging.DEBUG, html_format=False, map_ip_to_ns_name=None): 779 d = super(NSECStatusWildcard, self).serialize(rrset_info_serializer, consolidate_clients=consolidate_clients, loglevel=loglevel, html_format=html_format, map_ip_to_ns_name=map_ip_to_ns_name) 780 try: 781 del d['wildcard'] 782 except KeyError: 783 pass 784 return d 785 786class NSECStatusNODATA(NSECStatus): 787 def __init__(self, qname, rdtype, origin, is_zone, nsec_set_info, sname_must_match=False): 788 self.qname = qname 789 self.rdtype = rdtype 790 self.origin = origin 791 self.is_zone = is_zone 792 self.referral = nsec_set_info.referral 793 self.warnings = [] 794 self.errors = [] 795 796 self.wildcard_name = None 797 798 try: 799 self.nsec_for_qname = nsec_set_info.rrsets[self.qname] 800 self.has_rdtype = nsec_set_info.rdtype_exists_in_bitmap(self.qname, self.rdtype) 801 self.has_ns = nsec_set_info.rdtype_exists_in_bitmap(self.qname, dns.rdatatype.NS) 802 self.has_ds = nsec_set_info.rdtype_exists_in_bitmap(self.qname, dns.rdatatype.DS) 803 self.has_soa = nsec_set_info.rdtype_exists_in_bitmap(self.qname, dns.rdatatype.SOA) 804 except KeyError: 805 self.nsec_for_qname = None 806 self.has_rdtype = False 807 self.has_ns = False 808 self.has_ds = False 809 self.has_soa = False 810 811 if not sname_must_match: 812 # If no NSEC exists for the name itself, then look for an NSEC with 813 # an (empty non-terminal) ancestor 814 for nsec_name in nsec_set_info.rrsets: 815 next_name = nsec_set_info.rrsets[nsec_name].rrset[0].next 816 if next_name.is_subdomain(self.qname) and next_name != self.qname: 817 self.nsec_for_qname = nsec_set_info.rrsets[nsec_name] 818 break 819 820 self.nsec_names_covering_qname = {} 821 covering_names = nsec_set_info.nsec_covering_name(self.qname) 822 if covering_names: 823 self.nsec_names_covering_qname[self.qname] = covering_names 824 825 covering_name = list(covering_names)[0] 826 self.wildcard_name = self._get_wildcard(qname, nsec_set_info.rrsets[covering_name].rrset) 827 828 self.nsec_for_wildcard_name = None 829 self.wildcard_has_rdtype = None 830 if self.wildcard_name is not None: 831 try: 832 self.nsec_for_wildcard_name = nsec_set_info.rrsets[self.wildcard_name] 833 self.wildcard_has_rdtype = nsec_set_info.rdtype_exists_in_bitmap(self.wildcard_name, self.rdtype) 834 except KeyError: 835 pass 836 837 # check for covering of the origin 838 self.nsec_names_covering_origin = {} 839 covering_names = nsec_set_info.nsec_covering_name(self.origin) 840 if covering_names: 841 self.nsec_names_covering_origin[self.origin] = covering_names 842 843 self.opt_out = None 844 845 self._set_validation_status(nsec_set_info) 846 847 def __str__(self): 848 return 'NSEC record(s) proving non-existence (NODATA) of %s/%s' % (fmt.humanize_name(self.qname), dns.rdatatype.to_text(self.rdtype)) 849 850 def __eq__(self, other): 851 return isinstance(other, self.__class__) and \ 852 self.qname == other.qname and self.rdtype == other.rdtype and self.origin == other.origin and self.referral == other.referral and self.nsec_set_info == other.nsec_set_info 853 854 def __hash__(self): 855 return hash(id(self)) 856 857 def _set_validation_status(self, nsec_set_info): 858 self.validation_status = NSEC_STATUS_VALID 859 if self.nsec_for_qname is not None: 860 # RFC 4034 5.2, 6840 4.4 861 if self.rdtype == dns.rdatatype.DS or self.referral: 862 if self.is_zone and not self.has_ns: 863 self.errors.append(Errors.ReferralWithoutNSBitNSEC(sname=fmt.humanize_name(self.qname))) 864 self.validation_status = NSEC_STATUS_INVALID 865 if self.has_ds: 866 self.errors.append(Errors.ReferralWithDSBitNSEC(sname=fmt.humanize_name(self.qname))) 867 self.validation_status = NSEC_STATUS_INVALID 868 if self.has_soa: 869 self.errors.append(Errors.ReferralWithSOABitNSEC(sname=fmt.humanize_name(self.qname))) 870 self.validation_status = NSEC_STATUS_INVALID 871 else: 872 if self.has_rdtype: 873 self.errors.append(Errors.StypeInBitmapNODATANSEC(sname=fmt.humanize_name(self.qname), stype=dns.rdatatype.to_text(self.rdtype))) 874 self.validation_status = NSEC_STATUS_INVALID 875 if self.nsec_names_covering_qname: 876 self.errors.append(Errors.SnameCoveredNODATANSEC(sname=fmt.humanize_name(self.qname))) 877 self.validation_status = NSEC_STATUS_INVALID 878 elif self.nsec_for_wildcard_name: # implies wildcard_name, which implies nsec_names_covering_qname 879 if self.wildcard_has_rdtype: 880 self.validation_status = NSEC_STATUS_INVALID 881 self.errors.append(Errors.StypeInBitmapNODATANSEC(sname=fmt.humanize_name(self.wildcard_name), stype=dns.rdatatype.to_text(self.rdtype))) 882 if self.nsec_names_covering_origin: 883 self.validation_status = NSEC_STATUS_INVALID 884 qname, nsec_names = list(self.nsec_names_covering_origin.items())[0] 885 nsec_rrset = nsec_set_info.rrsets[list(nsec_names)[0]].rrset 886 self.errors.append(Errors.LastNSECNextNotZone(nsec_owner=fmt.humanize_name(nsec_rrset.name), next_name=fmt.humanize_name(nsec_rrset[0].next), zone_name=fmt.humanize_name(self.origin))) 887 else: 888 self.validation_status = NSEC_STATUS_INVALID 889 self.errors.append(Errors.NoNSECMatchingSnameNODATA(sname=fmt.humanize_name(self.qname))) 890 891 # if it validation_status, we project out just the pertinent NSEC records 892 # otherwise clone it by projecting them all 893 if self.validation_status == NSEC_STATUS_VALID: 894 covering_names = set() 895 if self.nsec_for_qname is not None: 896 covering_names.add(self.nsec_for_qname.rrset.name) 897 if self.nsec_names_covering_qname: 898 for names in self.nsec_names_covering_qname.values(): 899 covering_names.update(names) 900 if self.nsec_for_wildcard_name is not None: 901 covering_names.add(self.wildcard_name) 902 self.nsec_set_info = nsec_set_info.project(*list(covering_names)) 903 else: 904 self.nsec_set_info = nsec_set_info.project(*list(nsec_set_info.rrsets)) 905 906 def serialize(self, rrset_info_serializer=None, consolidate_clients=True, loglevel=logging.DEBUG, html_format=False, map_ip_to_ns_name=None): 907 d = OrderedDict() 908 909 nsec_list = [] 910 for nsec_rrset in self.nsec_set_info.rrsets.values(): 911 if rrset_info_serializer is not None: 912 nsec_serialized = rrset_info_serializer(nsec_rrset, consolidate_clients=consolidate_clients, show_servers=False, loglevel=loglevel, html_format=html_format) 913 if nsec_serialized: 914 nsec_list.append(nsec_serialized) 915 elif loglevel <= logging.DEBUG: 916 nsec_list.append(nsec_rrset.serialize(consolidate_clients=consolidate_clients, html_format=html_format)) 917 918 erroneous_status = self.validation_status != STATUS_VALID 919 920 show_id = loglevel <= logging.INFO or \ 921 (self.warnings and loglevel <= logging.WARNING) or \ 922 (self.errors and loglevel <= logging.ERROR) or \ 923 (erroneous_status or nsec_list) 924 925 if html_format: 926 formatter = lambda x: escape(x, True) 927 else: 928 formatter = lambda x: x 929 930 if show_id: 931 d['id'] = 'NSEC' 932 933 if loglevel <= logging.DEBUG: 934 d['description'] = formatter(str(self)) 935 936 if nsec_list: 937 d['nsec'] = nsec_list 938 939 if loglevel <= logging.DEBUG: 940 if self.nsec_for_qname is not None: 941 d['sname_nsec_match'] = formatter(lb2s(self.nsec_for_qname.rrset.name.canonicalize().to_text())) 942 943 if self.nsec_names_covering_qname: 944 qname, nsec_names = list(self.nsec_names_covering_qname.items())[0] 945 nsec_name = list(nsec_names)[0] 946 nsec_rr = self.nsec_set_info.rrsets[nsec_name].rrset[0] 947 d['sname_covering'] = OrderedDict(( 948 ('covered_name', formatter(lb2s(qname.canonicalize().to_text()))), 949 ('nsec_owner', formatter(lb2s(nsec_name.canonicalize().to_text()))), 950 ('nsec_next', formatter(lb2s(nsec_rr.next.canonicalize().to_text()))) 951 )) 952 953 if self.nsec_for_wildcard_name is not None: 954 d['wildcard_nsec_match'] = formatter(lb2s(self.wildcard_name.canonicalize().to_text())) 955 956 if loglevel <= logging.INFO or erroneous_status: 957 d['status'] = nsec_status_mapping[self.validation_status] 958 959 if loglevel <= logging.INFO: 960 servers = tuple_to_dict(self.nsec_set_info.servers_clients) 961 if consolidate_clients: 962 servers = list(servers) 963 servers.sort() 964 d['servers'] = servers 965 966 if map_ip_to_ns_name is not None: 967 ns_names = list(set([lb2s(map_ip_to_ns_name(s)[0][0].canonicalize().to_text()) for s in servers])) 968 ns_names.sort() 969 d['ns_names'] = ns_names 970 971 tags = set() 972 nsids = set() 973 for server,client in self.nsec_set_info.servers_clients: 974 for response in self.nsec_set_info.servers_clients[(server, client)]: 975 if response is not None: 976 tags.add(response.effective_query_tag()) 977 nsid = response.nsid_val() 978 if nsid is not None: 979 nsids.add(nsid) 980 981 if nsids: 982 d['nsid_values'] = list(nsids) 983 d['nsid_values'].sort() 984 985 d['query_options'] = list(tags) 986 d['query_options'].sort() 987 988 if self.warnings and loglevel <= logging.WARNING: 989 d['warnings'] = [w.serialize(consolidate_clients=consolidate_clients, html_format=html_format) for w in self.warnings] 990 991 if self.errors and loglevel <= logging.ERROR: 992 d['errors'] = [e.serialize(consolidate_clients=consolidate_clients, html_format=html_format) for e in self.errors] 993 994 return d 995 996class NSEC3Status(object): 997 def __repr__(self): 998 return '<%s: "%s">' % (self.__class__.__name__, self.qname) 999 1000 def _get_next_closest_encloser(self, encloser): 1001 return dns.name.Name(self.qname.labels[-(len(encloser)+1):]) 1002 1003 def get_next_closest_encloser(self): 1004 if self.closest_encloser: 1005 encloser_name, nsec_names = list(self.closest_encloser.items())[0] 1006 return self._get_next_closest_encloser(encloser_name) 1007 return None 1008 1009 def _get_wildcard(self, encloser): 1010 return dns.name.from_text('*', encloser) 1011 1012 def get_wildcard(self): 1013 if self.closest_encloser: 1014 encloser_name, nsec_names = list(self.closest_encloser.items())[0] 1015 return self._get_wildcard(encloser_name) 1016 return None 1017 1018class NSEC3StatusNXDOMAIN(NSEC3Status): 1019 def __init__(self, qname, rdtype, origin, is_zone, nsec_set_info): 1020 self.qname = qname 1021 self.origin = origin 1022 self.is_zone = is_zone 1023 self.warnings = [] 1024 self.errors = [] 1025 1026 self.name_digest_map = {} 1027 1028 self._set_closest_encloser(nsec_set_info) 1029 1030 self.nsec_names_covering_qname = {} 1031 self.nsec_names_covering_wildcard = {} 1032 self.opt_out = None 1033 1034 for (salt, alg, iterations), nsec3_names in nsec_set_info.nsec3_params.items(): 1035 digest_name = nsec_set_info.get_digest_name_for_nsec3(self.qname, self.origin, salt, alg, iterations) 1036 if self.qname not in self.name_digest_map: 1037 self.name_digest_map[self.qname] = {} 1038 self.name_digest_map[self.qname][(salt, alg, iterations)] = digest_name 1039 1040 for encloser in self.closest_encloser: 1041 next_closest_encloser = self._get_next_closest_encloser(encloser) 1042 for salt, alg, iterations in nsec_set_info.nsec3_params: 1043 try: 1044 digest_name = self.name_digest_map[next_closest_encloser][(salt, alg, iterations)] 1045 except KeyError: 1046 digest_name = nsec_set_info.get_digest_name_for_nsec3(next_closest_encloser, self.origin, salt, alg, iterations) 1047 1048 if digest_name is not None: 1049 covering_names = nsec_set_info.nsec3_covering_name(digest_name, salt, alg, iterations) 1050 if covering_names: 1051 self.nsec_names_covering_qname[digest_name] = covering_names 1052 self.opt_out = False 1053 for nsec_name in covering_names: 1054 if nsec_set_info.rrsets[nsec_name].rrset[0].flags & 0x01: 1055 self.opt_out = True 1056 1057 if next_closest_encloser not in self.name_digest_map: 1058 self.name_digest_map[next_closest_encloser] = {} 1059 self.name_digest_map[next_closest_encloser][(salt, alg, iterations)] = digest_name 1060 1061 wildcard_name = self._get_wildcard(encloser) 1062 digest_name = nsec_set_info.get_digest_name_for_nsec3(wildcard_name, self.origin, salt, alg, iterations) 1063 1064 if digest_name is not None: 1065 covering_names = nsec_set_info.nsec3_covering_name(digest_name, salt, alg, iterations) 1066 if covering_names: 1067 self.nsec_names_covering_wildcard[digest_name] = covering_names 1068 1069 if wildcard_name not in self.name_digest_map: 1070 self.name_digest_map[wildcard_name] = {} 1071 self.name_digest_map[wildcard_name][(salt, alg, iterations)] = digest_name 1072 1073 self._set_validation_status(nsec_set_info) 1074 1075 def __str__(self): 1076 return 'NSEC3 record(s) proving the non-existence (NXDOMAIN) of %s' % (fmt.humanize_name(self.qname)) 1077 1078 def __eq__(self, other): 1079 return isinstance(other, self.__class__) and \ 1080 self.qname == other.qname and self.origin == other.origin and self.nsec_set_info == other.nsec_set_info 1081 1082 def __hash__(self): 1083 return hash(id(self)) 1084 1085 def _set_closest_encloser(self, nsec_set_info): 1086 self.closest_encloser = nsec_set_info.get_closest_encloser(self.qname, self.origin) 1087 1088 def _set_validation_status(self, nsec_set_info): 1089 self.validation_status = NSEC_STATUS_VALID 1090 valid_algs, invalid_algs = nsec_set_info.get_algorithm_support() 1091 if invalid_algs: 1092 invalid_alg_err = Errors.UnsupportedNSEC3Algorithm(algorithm=list(invalid_algs)[0]) 1093 else: 1094 invalid_alg_err = None 1095 if not self.closest_encloser: 1096 self.validation_status = NSEC_STATUS_INVALID 1097 if valid_algs: 1098 self.errors.append(Errors.NoClosestEncloserNameError(sname=fmt.humanize_name(self.qname))) 1099 if invalid_algs: 1100 self.errors.append(invalid_alg_err) 1101 else: 1102 if not self.nsec_names_covering_qname: 1103 self.validation_status = NSEC_STATUS_INVALID 1104 if valid_algs: 1105 next_closest_encloser = self.get_next_closest_encloser() 1106 self.errors.append(Errors.NextClosestEncloserNotCoveredNameError(next_closest_encloser=fmt.humanize_name(next_closest_encloser))) 1107 if invalid_algs: 1108 self.errors.append(invalid_alg_err) 1109 if not self.nsec_names_covering_wildcard: 1110 self.validation_status = NSEC_STATUS_INVALID 1111 if valid_algs: 1112 wildcard_name = self.get_wildcard() 1113 self.errors.append(Errors.WildcardNotCoveredNSEC3(wildcard=fmt.humanize_name(wildcard_name))) 1114 if invalid_algs and invalid_alg_err not in self.errors: 1115 self.errors.append(invalid_alg_err) 1116 1117 # if it validation_status, we project out just the pertinent NSEC records 1118 # otherwise clone it by projecting them all 1119 if self.validation_status == NSEC_STATUS_VALID: 1120 covering_names = set() 1121 for names in list(self.closest_encloser.values()) + list(self.nsec_names_covering_qname.values()) + list(self.nsec_names_covering_wildcard.values()): 1122 covering_names.update(names) 1123 self.nsec_set_info = nsec_set_info.project(*list(covering_names)) 1124 else: 1125 self.nsec_set_info = nsec_set_info.project(*list(nsec_set_info.rrsets)) 1126 1127 # Report errors with NSEC3 owner names 1128 for name in self.nsec_set_info.invalid_nsec3_owner: 1129 self.errors.append(Errors.InvalidNSEC3OwnerName(name=fmt.format_nsec3_name(name))) 1130 for name in self.nsec_set_info.invalid_nsec3_hash: 1131 self.errors.append(Errors.InvalidNSEC3Hash(name=fmt.format_nsec3_name(name), nsec3_hash=lb2s(base32.b32encode(self.nsec_set_info.rrsets[name].rrset[0].next)))) 1132 1133 def serialize(self, rrset_info_serializer=None, consolidate_clients=True, loglevel=logging.DEBUG, html_format=False, map_ip_to_ns_name=None): 1134 d = OrderedDict() 1135 1136 nsec3_list = [] 1137 for nsec_rrset in self.nsec_set_info.rrsets.values(): 1138 if rrset_info_serializer is not None: 1139 nsec_serialized = rrset_info_serializer(nsec_rrset, consolidate_clients=consolidate_clients, show_servers=False, loglevel=loglevel, html_format=html_format) 1140 if nsec_serialized: 1141 nsec3_list.append(nsec_serialized) 1142 elif loglevel <= logging.DEBUG: 1143 nsec3_list.append(nsec_rrset.serialize(consolidate_clients=consolidate_clients, html_format=html_format)) 1144 1145 erroneous_status = self.validation_status != STATUS_VALID 1146 1147 show_id = loglevel <= logging.INFO or \ 1148 (self.warnings and loglevel <= logging.WARNING) or \ 1149 (self.errors and loglevel <= logging.ERROR) or \ 1150 (erroneous_status or nsec3_list) 1151 1152 if html_format: 1153 formatter = lambda x: escape(x, True) 1154 else: 1155 formatter = lambda x: x 1156 1157 if show_id: 1158 d['id'] = 'NSEC3' 1159 1160 if loglevel <= logging.DEBUG: 1161 d['description'] = formatter(str(self)) 1162 1163 if nsec3_list: 1164 d['nsec3'] = nsec3_list 1165 1166 if loglevel <= logging.DEBUG: 1167 if self.opt_out is not None: 1168 d['opt_out'] = self.opt_out 1169 1170 if self.closest_encloser: 1171 encloser_name, nsec_names = list(self.closest_encloser.items())[0] 1172 nsec_name = list(nsec_names)[0] 1173 d['closest_encloser'] = formatter(lb2s(encloser_name.canonicalize().to_text())) 1174 # could be inferred from wildcard 1175 if nsec_name is not None: 1176 d['closest_encloser_hash'] = formatter(fmt.format_nsec3_name(nsec_name)) 1177 1178 next_closest_encloser = self._get_next_closest_encloser(encloser_name) 1179 d['next_closest_encloser'] = formatter(lb2s(next_closest_encloser.canonicalize().to_text())) 1180 digest_name = list(self.name_digest_map[next_closest_encloser].items())[0][1] 1181 if digest_name is not None: 1182 d['next_closest_encloser_hash'] = formatter(fmt.format_nsec3_name(digest_name)) 1183 else: 1184 d['next_closest_encloser_hash'] = None 1185 1186 if self.nsec_names_covering_qname: 1187 qname, nsec_names = list(self.nsec_names_covering_qname.items())[0] 1188 nsec_name = list(nsec_names)[0] 1189 next_name = self.nsec_set_info.name_for_nsec3_next(nsec_name) 1190 d['next_closest_encloser_covering'] = OrderedDict(( 1191 ('covered_name', formatter(fmt.format_nsec3_name(qname))), 1192 ('nsec_owner', formatter(fmt.format_nsec3_name(nsec_name))), 1193 ('nsec_next', formatter(fmt.format_nsec3_name(next_name))), 1194 )) 1195 1196 wildcard_name = self._get_wildcard(encloser_name) 1197 wildcard_digest = list(self.name_digest_map[wildcard_name].items())[0][1] 1198 d['wildcard'] = formatter(lb2s(wildcard_name.canonicalize().to_text())) 1199 if wildcard_digest is not None: 1200 d['wildcard_hash'] = formatter(fmt.format_nsec3_name(wildcard_digest)) 1201 else: 1202 d['wildcard_hash'] = None 1203 if self.nsec_names_covering_wildcard: 1204 wildcard, nsec_names = list(self.nsec_names_covering_wildcard.items())[0] 1205 nsec_name = list(nsec_names)[0] 1206 next_name = self.nsec_set_info.name_for_nsec3_next(nsec_name) 1207 d['wildcard_covering'] = OrderedDict(( 1208 ('covered_name', formatter(fmt.format_nsec3_name(wildcard))), 1209 ('nsec3_owner', formatter(fmt.format_nsec3_name(nsec_name))), 1210 ('nsec3_next', formatter(fmt.format_nsec3_name(next_name))), 1211 )) 1212 1213 else: 1214 digest_name = list(self.name_digest_map[self.qname].items())[0][1] 1215 if digest_name is not None: 1216 d['sname_hash'] = formatter(fmt.format_nsec3_name(digest_name)) 1217 else: 1218 d['sname_hash'] = None 1219 1220 if loglevel <= logging.INFO or erroneous_status: 1221 d['status'] = nsec_status_mapping[self.validation_status] 1222 1223 if loglevel <= logging.INFO: 1224 servers = tuple_to_dict(self.nsec_set_info.servers_clients) 1225 if consolidate_clients: 1226 servers = list(servers) 1227 servers.sort() 1228 d['servers'] = servers 1229 1230 if map_ip_to_ns_name is not None: 1231 ns_names = list(set([lb2s(map_ip_to_ns_name(s)[0][0].canonicalize().to_text()) for s in servers])) 1232 ns_names.sort() 1233 d['ns_names'] = ns_names 1234 1235 tags = set() 1236 nsids = set() 1237 for server,client in self.nsec_set_info.servers_clients: 1238 for response in self.nsec_set_info.servers_clients[(server, client)]: 1239 if response is not None: 1240 tags.add(response.effective_query_tag()) 1241 nsid = response.nsid_val() 1242 if nsid is not None: 1243 nsids.add(nsid) 1244 1245 if nsids: 1246 d['nsid_values'] = list(nsids) 1247 d['nsid_values'].sort() 1248 1249 d['query_options'] = list(tags) 1250 d['query_options'].sort() 1251 1252 if self.warnings and loglevel <= logging.WARNING: 1253 d['warnings'] = [w.serialize(consolidate_clients=consolidate_clients, html_format=html_format) for w in self.warnings] 1254 1255 if self.errors and loglevel <= logging.ERROR: 1256 d['errors'] = [e.serialize(consolidate_clients=consolidate_clients, html_format=html_format) for e in self.errors] 1257 1258 return d 1259 1260class NSEC3StatusWildcard(NSEC3StatusNXDOMAIN): 1261 def __init__(self, qname, wildcard_name, rdtype, origin, is_zone, nsec_set_info): 1262 self.wildcard_name = wildcard_name 1263 super(NSEC3StatusWildcard, self).__init__(qname, rdtype, origin, is_zone, nsec_set_info) 1264 1265 def _set_closest_encloser(self, nsec_set_info): 1266 super(NSEC3StatusWildcard, self)._set_closest_encloser(nsec_set_info) 1267 1268 if not self.closest_encloser: 1269 self.closest_encloser = { self.wildcard_name.parent(): set([None]) } 1270 # fill in a dummy value for wildcard_name_digest_map 1271 self.name_digest_map[self.wildcard_name] = { None: self.wildcard_name } 1272 1273 def __eq__(self, other): 1274 return isinstance(other, self.__class__) and \ 1275 super(NSEC3StatusWildcard, self).__eq__(other) and self.wildcard_name == other.wildcard_name 1276 1277 def __hash__(self): 1278 return hash(id(self)) 1279 1280 def _set_validation_status(self, nsec_set_info): 1281 self.validation_status = NSEC_STATUS_VALID 1282 if not self.nsec_names_covering_qname: 1283 self.validation_status = NSEC_STATUS_INVALID 1284 valid_algs, invalid_algs = nsec_set_info.get_algorithm_support() 1285 if invalid_algs: 1286 invalid_alg_err = Errors.UnsupportedNSEC3Algorithm(algorithm=list(invalid_algs)[0]) 1287 else: 1288 invalid_alg_err = None 1289 if valid_algs: 1290 next_closest_encloser = self.get_next_closest_encloser() 1291 self.errors.append(Errors.NextClosestEncloserNotCoveredWildcardAnswer(next_closest_encloser=fmt.humanize_name(next_closest_encloser))) 1292 if invalid_algs: 1293 self.errors.append(invalid_alg_err) 1294 1295 if self.nsec_names_covering_wildcard: 1296 self.validation_status = NSEC_STATUS_INVALID 1297 self.errors.append(Errors.WildcardCoveredAnswerNSEC3(wildcard=fmt.humanize_name(self.wildcard_name))) 1298 1299 # if it validation_status, we project out just the pertinent NSEC records 1300 # otherwise clone it by projecting them all 1301 if self.validation_status == NSEC_STATUS_VALID: 1302 covering_names = set() 1303 for names in list(self.closest_encloser.values()) + list(self.nsec_names_covering_qname.values()): 1304 covering_names.update(names) 1305 self.nsec_set_info = nsec_set_info.project(*[x for x in covering_names if x is not None]) 1306 else: 1307 self.nsec_set_info = nsec_set_info.project(*list(nsec_set_info.rrsets)) 1308 1309 # Report errors with NSEC3 owner names 1310 for name in self.nsec_set_info.invalid_nsec3_owner: 1311 self.errors.append(Errors.InvalidNSEC3OwnerName(name=fmt.format_nsec3_name(name))) 1312 for name in self.nsec_set_info.invalid_nsec3_hash: 1313 self.errors.append(Errors.InvalidNSEC3Hash(name=fmt.format_nsec3_name(name), nsec3_hash=lb2s(base32.b32encode(self.nsec_set_info.rrsets[name].rrset[0].next)))) 1314 1315 def serialize(self, rrset_info_serializer=None, consolidate_clients=True, loglevel=logging.DEBUG, html_format=False, map_ip_to_ns_name=None): 1316 d = super(NSEC3StatusWildcard, self).serialize(rrset_info_serializer, consolidate_clients=consolidate_clients, loglevel=loglevel, html_format=html_format, map_ip_to_ns_name=map_ip_to_ns_name) 1317 try: 1318 del d['wildcard'] 1319 except KeyError: 1320 pass 1321 try: 1322 del d['wildcard_digest'] 1323 except KeyError: 1324 pass 1325 if loglevel <= logging.DEBUG: 1326 if [x for x in list(self.closest_encloser.values())[0] if x is not None]: 1327 d['superfluous_closest_encloser'] = True 1328 return d 1329 1330class NSEC3StatusNODATA(NSEC3Status): 1331 def __init__(self, qname, rdtype, origin, is_zone, nsec_set_info): 1332 self.qname = qname 1333 self.rdtype = rdtype 1334 self.origin = origin 1335 self.is_zone = is_zone 1336 self.referral = nsec_set_info.referral 1337 self.wildcard_name = None 1338 self.warnings = [] 1339 self.errors = [] 1340 1341 self.name_digest_map = {} 1342 1343 self.closest_encloser = nsec_set_info.get_closest_encloser(qname, origin) 1344 1345 self.nsec_names_covering_qname = {} 1346 self.nsec_names_covering_wildcard = {} 1347 self.nsec_for_qname = set() 1348 self.nsec_for_wildcard_name = set() 1349 self.has_rdtype = False 1350 self.has_cname = False 1351 self.has_ns = False 1352 self.has_ds = False 1353 self.has_soa = False 1354 self.opt_out = None 1355 self.wildcard_has_rdtype = False 1356 self.wildcard_has_cname = False 1357 1358 for (salt, alg, iterations), nsec3_names in nsec_set_info.nsec3_params.items(): 1359 digest_name = nsec_set_info.get_digest_name_for_nsec3(self.qname, self.origin, salt, alg, iterations) 1360 if self.qname not in self.name_digest_map: 1361 self.name_digest_map[self.qname] = {} 1362 self.name_digest_map[self.qname][(salt, alg, iterations)] = digest_name 1363 1364 for encloser in self.closest_encloser: 1365 wildcard_name = self._get_wildcard(encloser) 1366 digest_name = nsec_set_info.get_digest_name_for_nsec3(wildcard_name, self.origin, salt, alg, iterations) 1367 if digest_name in nsec3_names: 1368 self.nsec_for_wildcard_name.add(digest_name) 1369 if nsec_set_info.rdtype_exists_in_bitmap(digest_name, rdtype): self.wildcard_has_rdtype = True 1370 if nsec_set_info.rdtype_exists_in_bitmap(digest_name, dns.rdatatype.CNAME): self.wildcard_has_cname = True 1371 1372 if wildcard_name not in self.name_digest_map: 1373 self.name_digest_map[wildcard_name] = {} 1374 self.name_digest_map[wildcard_name][(salt, alg, iterations)] = digest_name 1375 1376 for (salt, alg, iterations), nsec3_names in nsec_set_info.nsec3_params.items(): 1377 digest_name = self.name_digest_map[self.qname][(salt, alg, iterations)] 1378 if digest_name in nsec3_names: 1379 self.nsec_for_qname.add(digest_name) 1380 if nsec_set_info.rdtype_exists_in_bitmap(digest_name, rdtype): self.has_rdtype = True 1381 if nsec_set_info.rdtype_exists_in_bitmap(digest_name, dns.rdatatype.CNAME): self.has_cname = True 1382 if nsec_set_info.rdtype_exists_in_bitmap(digest_name, dns.rdatatype.NS): self.has_ns = True 1383 if nsec_set_info.rdtype_exists_in_bitmap(digest_name, dns.rdatatype.DS): self.has_ds = True 1384 if nsec_set_info.rdtype_exists_in_bitmap(digest_name, dns.rdatatype.SOA): self.has_soa = True 1385 1386 else: 1387 for encloser in self.closest_encloser: 1388 next_closest_encloser = self._get_next_closest_encloser(encloser) 1389 digest_name = nsec_set_info.get_digest_name_for_nsec3(next_closest_encloser, self.origin, salt, alg, iterations) 1390 if next_closest_encloser not in self.name_digest_map: 1391 self.name_digest_map[next_closest_encloser] = {} 1392 self.name_digest_map[next_closest_encloser][(salt, alg, iterations)] = digest_name 1393 1394 if digest_name is not None: 1395 covering_names = nsec_set_info.nsec3_covering_name(digest_name, salt, alg, iterations) 1396 if covering_names: 1397 self.nsec_names_covering_qname[digest_name] = covering_names 1398 self.opt_out = False 1399 for nsec_name in covering_names: 1400 if nsec_set_info.rrsets[nsec_name].rrset[0].flags & 0x01: 1401 self.opt_out = True 1402 1403 self._set_validation_status(nsec_set_info) 1404 1405 def __str__(self): 1406 return 'NSEC3 record(s) proving non-existence (NODATA) of %s/%s' % (fmt.humanize_name(self.qname), dns.rdatatype.to_text(self.rdtype)) 1407 1408 def __eq__(self, other): 1409 return isinstance(other, self.__class__) and \ 1410 self.qname == other.qname and self.rdtype == other.rdtype and self.origin == other.origin and self.referral == other.referral and self.nsec_set_info == other.nsec_set_info 1411 1412 def __hash__(self): 1413 return hash(id(self)) 1414 1415 def _set_validation_status(self, nsec_set_info): 1416 self.validation_status = NSEC_STATUS_VALID 1417 valid_algs, invalid_algs = nsec_set_info.get_algorithm_support() 1418 if invalid_algs: 1419 invalid_alg_err = Errors.UnsupportedNSEC3Algorithm(algorithm=list(invalid_algs)[0]) 1420 else: 1421 invalid_alg_err = None 1422 if self.nsec_for_qname: 1423 # RFC 4035 5.2, 6840 4.4 1424 if self.rdtype == dns.rdatatype.DS or self.referral: 1425 if self.is_zone and not self.has_ns: 1426 self.errors.append(Errors.ReferralWithoutNSBitNSEC3(sname=fmt.humanize_name(self.qname))) 1427 self.validation_status = NSEC_STATUS_INVALID 1428 if self.has_ds: 1429 self.errors.append(Errors.ReferralWithDSBitNSEC3(sname=fmt.humanize_name(self.qname))) 1430 self.validation_status = NSEC_STATUS_INVALID 1431 if self.has_soa: 1432 self.errors.append(Errors.ReferralWithSOABitNSEC3(sname=fmt.humanize_name(self.qname))) 1433 self.validation_status = NSEC_STATUS_INVALID 1434 # RFC 5155, section 8.5, 8.6 1435 else: 1436 if self.has_rdtype: 1437 self.errors.append(Errors.StypeInBitmapNODATANSEC3(sname=fmt.humanize_name(self.qname), stype=dns.rdatatype.to_text(self.rdtype))) 1438 self.validation_status = NSEC_STATUS_INVALID 1439 if self.has_cname: 1440 self.errors.append(Errors.StypeInBitmapNODATANSEC3(sname=fmt.humanize_name(self.qname), stype=dns.rdatatype.to_text(dns.rdatatype.CNAME))) 1441 self.validation_status = NSEC_STATUS_INVALID 1442 elif self.nsec_for_wildcard_name: 1443 if not self.nsec_names_covering_qname: 1444 self.validation_status = NSEC_STATUS_INVALID 1445 if valid_algs: 1446 self.errors.append(Errors.NextClosestEncloserNotCoveredWildcardNODATA(next_closest_encloser=fmt.humanize_name(next_closest_encloser))) 1447 if invalid_algs: 1448 self.errors.append(invalid_alg_err) 1449 if self.wildcard_has_rdtype: 1450 self.validation_status = NSEC_STATUS_INVALID 1451 self.errors.append(Errors.StypeInBitmapWildcardNODATANSEC3(sname=fmt.humanize_name(self.get_wildcard()), stype=dns.rdatatype.to_text(self.rdtype))) 1452 elif self.nsec_names_covering_qname: 1453 if not self.opt_out: 1454 self.validation_status = NSEC_STATUS_INVALID 1455 if valid_algs: 1456 if self.rdtype == dns.rdatatype.DS: 1457 cls = Errors.OptOutFlagNotSetNODATADS 1458 else: 1459 cls = Errors.OptOutFlagNotSetNODATA 1460 next_closest_encloser = self.get_next_closest_encloser() 1461 self.errors.append(cls(next_closest_encloser=fmt.humanize_name(next_closest_encloser))) 1462 if invalid_algs: 1463 self.errors.append(invalid_alg_err) 1464 else: 1465 self.validation_status = NSEC_STATUS_INVALID 1466 if valid_algs: 1467 if self.rdtype == dns.rdatatype.DS: 1468 cls = Errors.NoNSEC3MatchingSnameDSNODATA 1469 else: 1470 cls = Errors.NoNSEC3MatchingSnameNODATA 1471 self.errors.append(cls(sname=fmt.humanize_name(self.qname))) 1472 if invalid_algs: 1473 self.errors.append(invalid_alg_err) 1474 1475 # if it validation_status, we project out just the pertinent NSEC records 1476 # otherwise clone it by projecting them all 1477 if self.validation_status == NSEC_STATUS_VALID: 1478 covering_names = set() 1479 for names in self.closest_encloser.values(): 1480 covering_names.update(names) 1481 if self.nsec_for_qname: 1482 covering_names.update(self.nsec_for_qname) 1483 else: 1484 for names in self.nsec_names_covering_qname.values(): 1485 covering_names.update(names) 1486 if self.nsec_for_wildcard_name is not None: 1487 covering_names.update(self.nsec_for_wildcard_name) 1488 self.nsec_set_info = nsec_set_info.project(*list(covering_names)) 1489 else: 1490 self.nsec_set_info = nsec_set_info.project(*list(nsec_set_info.rrsets)) 1491 1492 # Report errors with NSEC3 owner names 1493 for name in self.nsec_set_info.invalid_nsec3_owner: 1494 self.errors.append(Errors.InvalidNSEC3OwnerName(name=fmt.format_nsec3_name(name))) 1495 for name in self.nsec_set_info.invalid_nsec3_hash: 1496 self.errors.append(Errors.InvalidNSEC3Hash(name=fmt.format_nsec3_name(name), nsec3_hash=lb2s(base32.b32encode(self.nsec_set_info.rrsets[name].rrset[0].next)))) 1497 1498 def serialize(self, rrset_info_serializer=None, consolidate_clients=True, loglevel=logging.DEBUG, html_format=False, map_ip_to_ns_name=None): 1499 d = OrderedDict() 1500 1501 nsec3_list = [] 1502 for nsec_rrset in self.nsec_set_info.rrsets.values(): 1503 if rrset_info_serializer is not None: 1504 nsec_serialized = rrset_info_serializer(nsec_rrset, consolidate_clients=consolidate_clients, show_servers=False, loglevel=loglevel, html_format=html_format) 1505 if nsec_serialized: 1506 nsec3_list.append(nsec_serialized) 1507 elif loglevel <= logging.DEBUG: 1508 nsec3_list.append(nsec_rrset.serialize(consolidate_clients=consolidate_clients, html_format=html_format)) 1509 1510 erroneous_status = self.validation_status != STATUS_VALID 1511 1512 show_id = loglevel <= logging.INFO or \ 1513 (self.warnings and loglevel <= logging.WARNING) or \ 1514 (self.errors and loglevel <= logging.ERROR) or \ 1515 (erroneous_status or nsec3_list) 1516 1517 if html_format: 1518 formatter = lambda x: escape(x, True) 1519 else: 1520 formatter = lambda x: x 1521 1522 if show_id: 1523 d['id'] = 'NSEC3' 1524 1525 if loglevel <= logging.DEBUG: 1526 d['description'] = formatter(str(self)) 1527 1528 if nsec3_list: 1529 d['nsec3'] = nsec3_list 1530 1531 if loglevel <= logging.DEBUG: 1532 if self.opt_out is not None: 1533 d['opt_out'] = self.opt_out 1534 1535 if self.nsec_for_qname: 1536 digest_name = list(self.name_digest_map[self.qname].items())[0][1] 1537 if digest_name is not None: 1538 d['sname_hash'] = formatter(fmt.format_nsec3_name(digest_name)) 1539 else: 1540 d['sname_hash'] = None 1541 d['sname_nsec_match'] = formatter(fmt.format_nsec3_name(list(self.nsec_for_qname)[0])) 1542 1543 if self.closest_encloser: 1544 encloser_name, nsec_names = list(self.closest_encloser.items())[0] 1545 nsec_name = list(nsec_names)[0] 1546 d['closest_encloser'] = formatter(lb2s(encloser_name.canonicalize().to_text())) 1547 d['closest_encloser_digest'] = formatter(fmt.format_nsec3_name(nsec_name)) 1548 1549 next_closest_encloser = self._get_next_closest_encloser(encloser_name) 1550 d['next_closest_encloser'] = formatter(lb2s(next_closest_encloser.canonicalize().to_text())) 1551 digest_name = list(self.name_digest_map[next_closest_encloser].items())[0][1] 1552 if digest_name is not None: 1553 d['next_closest_encloser_hash'] = formatter(fmt.format_nsec3_name(digest_name)) 1554 else: 1555 d['next_closest_encloser_hash'] = None 1556 1557 if self.nsec_names_covering_qname: 1558 qname, nsec_names = list(self.nsec_names_covering_qname.items())[0] 1559 nsec_name = list(nsec_names)[0] 1560 next_name = self.nsec_set_info.name_for_nsec3_next(nsec_name) 1561 d['next_closest_encloser_covering'] = OrderedDict(( 1562 ('covered_name', formatter(fmt.format_nsec3_name(qname))), 1563 ('nsec3_owner', formatter(fmt.format_nsec3_name(nsec_name))), 1564 ('nsec3_next', formatter(fmt.format_nsec3_name(next_name))), 1565 )) 1566 1567 wildcard_name = self._get_wildcard(encloser_name) 1568 wildcard_digest = list(self.name_digest_map[wildcard_name].items())[0][1] 1569 d['wildcard'] = formatter(lb2s(wildcard_name.canonicalize().to_text())) 1570 if wildcard_digest is not None: 1571 d['wildcard_hash'] = formatter(fmt.format_nsec3_name(wildcard_digest)) 1572 else: 1573 d['wildcard_hash'] = None 1574 if self.nsec_for_wildcard_name: 1575 d['wildcard_nsec_match'] = formatter(fmt.format_nsec3_name(list(self.nsec_for_wildcard_name)[0])) 1576 1577 if not self.nsec_for_qname and not self.closest_encloser: 1578 digest_name = list(self.name_digest_map[self.qname].items())[0][1] 1579 if digest_name is not None: 1580 d['sname_hash'] = formatter(fmt.format_nsec3_name(digest_name)) 1581 else: 1582 d['sname_hash'] = None 1583 1584 if loglevel <= logging.INFO or erroneous_status: 1585 d['status'] = nsec_status_mapping[self.validation_status] 1586 1587 if loglevel <= logging.INFO: 1588 servers = tuple_to_dict(self.nsec_set_info.servers_clients) 1589 if consolidate_clients: 1590 servers = list(servers) 1591 servers.sort() 1592 d['servers'] = servers 1593 1594 if map_ip_to_ns_name is not None: 1595 ns_names = list(set([lb2s(map_ip_to_ns_name(s)[0][0].canonicalize().to_text()) for s in servers])) 1596 ns_names.sort() 1597 d['ns_names'] = ns_names 1598 1599 tags = set() 1600 nsids = set() 1601 for server,client in self.nsec_set_info.servers_clients: 1602 for response in self.nsec_set_info.servers_clients[(server, client)]: 1603 if response is not None: 1604 tags.add(response.effective_query_tag()) 1605 nsid = response.nsid_val() 1606 if nsid is not None: 1607 nsids.add(nsid) 1608 1609 if nsids: 1610 d['nsid_values'] = list(nsids) 1611 d['nsid_values'].sort() 1612 1613 d['query_options'] = list(tags) 1614 d['query_options'].sort() 1615 1616 if self.warnings and loglevel <= logging.WARNING: 1617 d['warnings'] = [w.serialize(consolidate_clients=consolidate_clients, html_format=html_format) for w in self.warnings] 1618 1619 if self.errors and loglevel <= logging.ERROR: 1620 d['errors'] = [e.serialize(consolidate_clients=consolidate_clients, html_format=html_format) for e in self.errors] 1621 1622 return d 1623 1624class CNAMEFromDNAMEStatus(object): 1625 def __init__(self, synthesized_cname, included_cname): 1626 self.synthesized_cname = synthesized_cname 1627 self.included_cname = included_cname 1628 self.warnings = [] 1629 self.errors = [] 1630 1631 if self.included_cname is None: 1632 self.validation_status = DNAME_STATUS_INVALID 1633 self.errors.append(Errors.DNAMENoCNAME()) 1634 else: 1635 self.validation_status = DNAME_STATUS_VALID 1636 if self.included_cname.rrset[0].target != self.synthesized_cname.rrset[0].target: 1637 self.errors.append(Errors.DNAMETargetMismatch(included_target=fmt.humanize_name(self.included_cname.rrset[0].target), synthesized_target=fmt.humanize_name(self.synthesized_cname.rrset[0].target))) 1638 self.validation_status = DNAME_STATUS_INVALID_TARGET 1639 if self.included_cname.rrset.ttl != self.synthesized_cname.rrset.ttl: 1640 if self.included_cname.rrset.ttl == 0: 1641 self.warnings.append(Errors.DNAMETTLZero()) 1642 else: 1643 self.warnings.append(Errors.DNAMETTLMismatch(cname_ttl=self.included_cname.rrset.ttl, dname_ttl=self.synthesized_cname.rrset.ttl)) 1644 1645 def __str__(self): 1646 return 'CNAME synthesis for %s from %s/%s' % (fmt.humanize_name(self.synthesized_cname.rrset.name), fmt.humanize_name(self.synthesized_cname.dname_info.rrset.name), dns.rdatatype.to_text(self.synthesized_cname.dname_info.rrset.rdtype)) 1647 1648 def serialize(self, rrset_info_serializer=None, consolidate_clients=True, loglevel=logging.DEBUG, html_format=False, map_ip_to_ns_name=None): 1649 values = [] 1650 d = OrderedDict() 1651 1652 dname_serialized = None 1653 if rrset_info_serializer is not None: 1654 dname_serialized = rrset_info_serializer(self.synthesized_cname.dname_info, consolidate_clients=consolidate_clients, show_servers=False, loglevel=loglevel, html_format=html_format) 1655 elif loglevel <= logging.DEBUG: 1656 dname_serialized = self.synthesized_cname.dname_info.serialize(consolidate_clients=consolidate_clients, html_format=html_format) 1657 1658 erroneous_status = self.validation_status != STATUS_VALID 1659 1660 show_id = loglevel <= logging.INFO or \ 1661 (self.warnings and loglevel <= logging.WARNING) or \ 1662 (self.errors and loglevel <= logging.ERROR) or \ 1663 (erroneous_status or dname_serialized) 1664 1665 if html_format: 1666 formatter = lambda x: escape(x, True) 1667 else: 1668 formatter = lambda x: x 1669 1670 if show_id: 1671 d['id'] = lb2s(self.synthesized_cname.dname_info.rrset.name.canonicalize().to_text()) 1672 1673 if loglevel <= logging.DEBUG: 1674 d['description'] = formatter(str(self)) 1675 1676 if dname_serialized: 1677 d['dname'] = dname_serialized 1678 1679 if loglevel <= logging.DEBUG: 1680 if self.included_cname is not None: 1681 d['cname_owner'] = formatter(lb2s(self.included_cname.rrset.name.canonicalize().to_text())) 1682 d['cname_target'] = formatter(lb2s(self.included_cname.rrset[0].target.canonicalize().to_text())) 1683 1684 if loglevel <= logging.INFO or erroneous_status: 1685 d['status'] = dname_status_mapping[self.validation_status] 1686 1687 if loglevel <= logging.INFO: 1688 servers = tuple_to_dict(self.synthesized_cname.dname_info.servers_clients) 1689 if consolidate_clients: 1690 servers = list(servers) 1691 servers.sort() 1692 d['servers'] = servers 1693 1694 if map_ip_to_ns_name is not None: 1695 ns_names = list(set([lb2s(map_ip_to_ns_name(s)[0][0].canonicalize().to_text()) for s in servers])) 1696 ns_names.sort() 1697 d['ns_names'] = ns_names 1698 1699 tags = set() 1700 nsids = set() 1701 for server,client in self.synthesized_cname.dname_info.servers_clients: 1702 for response in self.synthesized_cname.dname_info.servers_clients[(server, client)]: 1703 if response is not None: 1704 tags.add(response.effective_query_tag()) 1705 nsid = response.nsid_val() 1706 if nsid is not None: 1707 nsids.add(nsid) 1708 1709 if nsids: 1710 d['nsid_values'] = list(nsids) 1711 d['nsid_values'].sort() 1712 1713 d['query_options'] = list(tags) 1714 d['query_options'].sort() 1715 1716 if self.warnings and loglevel <= logging.WARNING: 1717 d['warnings'] = [w.serialize(consolidate_clients=consolidate_clients, html_format=html_format) for w in self.warnings] 1718 1719 if self.errors and loglevel <= logging.ERROR: 1720 d['errors'] = [e.serialize(consolidate_clients=consolidate_clients, html_format=html_format) for e in self.errors] 1721 1722 return d 1723