001/* 002 * Copyright 2015-2024 the original author or authors 003 * 004 * This software is licensed under the Apache License, Version 2.0, 005 * the GNU Lesser General Public License version 2 or later ("LGPL") 006 * and the WTFPL. 007 * You may choose either license to govern your use of this software only 008 * upon the condition that you accept all of the terms of either 009 * the Apache License 2.0, the LGPL 2.1+ or the WTFPL. 010 */ 011package org.minidns.dnssec; 012 013import org.minidns.DnsCache; 014import org.minidns.constants.DnssecConstants.DigestAlgorithm; 015import org.minidns.dnsmessage.DnsMessage; 016import org.minidns.dnsmessage.Question; 017import org.minidns.dnsname.DnsName; 018import org.minidns.dnsname.InvalidDnsNameException; 019import org.minidns.dnsqueryresult.DnsQueryResult; 020import org.minidns.dnssec.DnssecUnverifiedReason.CrossSignedDnskeyRrsetReason; 021import org.minidns.dnssec.DnssecUnverifiedReason.NoActiveSignaturesReason; 022import org.minidns.dnssec.DnssecUnverifiedReason.NoSecureEntryPointReason; 023import org.minidns.dnssec.DnssecUnverifiedReason.NoSignaturesReason; 024import org.minidns.dnssec.DnssecUnverifiedReason.NoTrustAnchorReason; 025import org.minidns.dnssec.DnssecValidationFailedException.AuthorityDoesNotContainSoa; 026import org.minidns.dnssec.algorithms.AlgorithmMap; 027import org.minidns.iterative.ReliableDnsClient; 028import org.minidns.record.CNAME; 029import org.minidns.record.DLV; 030import org.minidns.record.DNAME; 031import org.minidns.record.DNSKEY; 032import org.minidns.record.DS; 033import org.minidns.record.Data; 034import org.minidns.record.DelegatingDnssecRR; 035import org.minidns.record.NSEC; 036import org.minidns.record.NSEC3; 037import org.minidns.record.RRSIG; 038import org.minidns.record.Record; 039import org.minidns.record.Record.CLASS; 040import org.minidns.record.Record.TYPE; 041 042import java.io.IOException; 043import java.math.BigInteger; 044import java.util.ArrayList; 045import java.util.Collection; 046import java.util.Collections; 047import java.util.Date; 048import java.util.HashSet; 049import java.util.Iterator; 050import java.util.LinkedHashMap; 051import java.util.List; 052import java.util.Map; 053import java.util.Set; 054import java.util.concurrent.ConcurrentHashMap; 055import java.util.concurrent.ConcurrentMap; 056import java.util.concurrent.CopyOnWriteArrayList; 057 058public class DnssecClient extends ReliableDnsClient { 059 060 /** 061 * The root zone's KSK-2017, whose ID is "Klajeyz" and whose key tag value is "20326". 062 * 063 * @see <a href="https://data.iana.org/root-anchors/root-anchors.xml">IANA root zone trust anchors</a> 064 */ 065 private static final BigInteger ROOT_KSK_2017 = new BigInteger("1628686155461064465348252249725010996177649738666492500572664444461532807739744536029771810659241049343994038053541290419968870563183856865780916376571550372513476957870843322273120879361960335192976656756972171258658400305760429696147778001233984421619267530978084631948434496468785021389956803104620471232008587410372348519229650742022804219634190734272506220018657920136902014393834092648785514548876370028925405557661759399901378816916683122474038734912535425670533237815676134840739565610963796427401855723026687073600445461090736240030247906095053875491225879656640052743394090544036297390104110989318819106653199917493"); 066 067 /** 068 * The root zone's KSK-2024, whose ID is "Kmyv6jo" and whose key tag value is "38696". The root zone's DNSKEY RRset 069 * is signed with this key since 2026-10-11. 070 * 071 * @see <a href="https://data.iana.org/root-anchors/root-anchors.xml">IANA root zone trust anchors</a> 072 */ 073 private static final BigInteger ROOT_KSK_2024 = new BigInteger("1628686155774121212168081024680239155604420268691380415887810174716442156063993152758837847983942799877886941977560662680203835048611462974430324555256015052877501366226310539062711393346584350551693842362076723827358391786883611293998126527175865775276768514791204165196787699241676676814251122639194103258915247638509608611180547418668353298742571235995455990809680031413088313234415379381686894805351607921243348741522050630785576836709808997878870113887781468394260370502860846217704015926043569970756428556654452203219129363526322839180253672785807974524503640093519709242302125704407718029753636688205844737179386330503"); 074 075 private static final DnsName DEFAULT_DLV = DnsName.from("dlv.isc.org"); 076 077 /** 078 * Create a new DNSSEC aware DNS client using the global default cache. 079 */ 080 public DnssecClient() { 081 this(DEFAULT_CACHE); 082 } 083 084 /** 085 * Create a new DNSSEC aware DNS client with the given DNS cache. 086 * 087 * @param cache The backend DNS cache. 088 */ 089 public DnssecClient(DnsCache cache) { 090 super(cache); 091 addSecureEntryPoint(DnsName.ROOT, ROOT_KSK_2017.toByteArray()); 092 addSecureEntryPoint(DnsName.ROOT, ROOT_KSK_2024.toByteArray()); 093 } 094 095 /** 096 * Known secure entry points (SEPs). 097 */ 098 private final ConcurrentMap<DnsName, List<byte[]>> knownSeps = new ConcurrentHashMap<>(); 099 100 private boolean stripSignatureRecords = true; 101 102 /** 103 * The active DNSSEC Look-aside Validation Registry. May be <code>null</code>. 104 */ 105 private DnsName dlv; 106 107 @Override 108 public DnsQueryResult query(Question q) throws IOException { 109 DnssecQueryResult dnssecQueryResult = queryDnssec(q); 110 if (!dnssecQueryResult.isAuthenticData()) { 111 // TODO: Refine exception. 112 throw new IOException(); 113 } 114 return dnssecQueryResult.dnsQueryResult; 115 } 116 117 public DnssecQueryResult queryDnssec(CharSequence name, TYPE type) throws IOException { 118 Question q = new Question(name, type, CLASS.IN); 119 return queryDnssec(q); 120 } 121 122 public DnssecQueryResult queryDnssec(Question q) throws IOException { 123 DnsQueryResult dnsQueryResult = super.query(q); 124 DnssecQueryResult dnssecQueryResult = performVerification(dnsQueryResult); 125 return dnssecQueryResult; 126 } 127 128 private DnssecQueryResult performVerification(DnsQueryResult dnsQueryResult) throws IOException { 129 if (dnsQueryResult == null) return null; 130 131 DnsMessage dnsMessage = dnsQueryResult.response; 132 DnsMessage.Builder messageBuilder = dnsMessage.asBuilder(); 133 134 Set<DnssecUnverifiedReason> unverifiedReasons = verify(dnsMessage); 135 136 messageBuilder.setAuthenticData(unverifiedReasons.isEmpty()); 137 138 List<Record<? extends Data>> answers = dnsMessage.answerSection; 139 List<Record<? extends Data>> nameserverRecords = dnsMessage.authoritySection; 140 List<Record<? extends Data>> additionalResourceRecords = dnsMessage.additionalSection; 141 Set<Record<RRSIG>> signatures = new HashSet<>(); 142 Record.filter(signatures, RRSIG.class, answers); 143 Record.filter(signatures, RRSIG.class, nameserverRecords); 144 Record.filter(signatures, RRSIG.class, additionalResourceRecords); 145 146 if (stripSignatureRecords) { 147 messageBuilder.setAnswers(stripSignatureRecords(answers)); 148 messageBuilder.setNameserverRecords(stripSignatureRecords(nameserverRecords)); 149 messageBuilder.setAdditionalResourceRecords(stripSignatureRecords(additionalResourceRecords)); 150 } 151 152 return new DnssecQueryResult(messageBuilder.build(), dnsQueryResult, signatures, unverifiedReasons); 153 } 154 155 private static List<Record<? extends Data>> stripSignatureRecords(List<Record<? extends Data>> records) { 156 if (records.isEmpty()) return records; 157 List<Record<? extends Data>> recordList = new ArrayList<>(records.size()); 158 for (Record<? extends Data> record : records) { 159 if (record.type != TYPE.RRSIG) { 160 recordList.add(record); 161 } 162 } 163 return recordList; 164 } 165 166 private Set<DnssecUnverifiedReason> verify(DnsMessage dnsMessage) throws IOException { 167 if (!dnsMessage.answerSection.isEmpty()) { 168 return verifyAnswer(dnsMessage); 169 } else { 170 return verifyNsec(dnsMessage); 171 } 172 } 173 174 private Set<DnssecUnverifiedReason> verifyAnswer(DnsMessage dnsMessage) throws IOException { 175 Question q = dnsMessage.questions.get(0); 176 List<Record<? extends Data>> answers = dnsMessage.answerSection; 177 List<Record<? extends Data>> toBeVerified = dnsMessage.copyAnswers(); 178 VerifySignaturesResult verifiedSignatures = verifySignatures(q, answers, toBeVerified); 179 Set<DnssecUnverifiedReason> result = verifiedSignatures.reasons; 180 if (!result.isEmpty()) { 181 return result; 182 } 183 184 // A DNSKEY RRset owned by the queried name is authenticated only through a secure entry point (SEP), i.e., a 185 // key of the RRset that validly signs the RRset and that is a trust anchor or is referred to by a DS RR in the 186 // parent zone (RFC 4035 § 5.2). It is never authenticated by a signature from another zone. Hence, if the 187 // answer carries such a DNSKEY RRset, a SEP has to be found below, or the answer is not authentic. 188 boolean dnskeyRrsetRequiresSep = false; 189 for (Record<? extends Data> record : answers) { 190 if (record.type == TYPE.DNSKEY && record.name.equals(q.name)) { 191 dnskeyRrsetRequiresSep = true; 192 break; 193 } 194 } 195 196 // We only need one valid SEP. 197 boolean sepSignatureValid = false; 198 Set<DnssecUnverifiedReason> sepReasons = new HashSet<>(); 199 Set<DNSKEY> selfSigningKeys = verifiedSignatures.selfSigningKeys; 200 for (Iterator<Record<? extends Data>> iterator = toBeVerified.iterator(); iterator.hasNext(); ) { 201 Record<DNSKEY> record = iterator.next().ifPossibleAs(DNSKEY.class); 202 if (record == null) { 203 continue; 204 } 205 iterator.remove(); 206 207 // Only a DNSKEY that validly signs the DNSKEY RRset can establish the chain of trust to the RRset. 208 if (!selfSigningKeys.contains(record.payloadData)) { 209 LOGGER.finer("Not considering DNSKEY " + record.payloadData.getKeyTag() + " of " + record.name 210 + " as secure entry point, as it does not validly sign the DNSKEY RRset"); 211 continue; 212 } 213 214 // Verify the DNSKEY as if it was a SEP. If we find a single SEP we are safe. 215 Set<DnssecUnverifiedReason> reasons = verifySecureEntryPoint(record); 216 if (reasons.isEmpty()) { 217 sepSignatureValid = true; 218 } else { 219 sepReasons.addAll(reasons); 220 } 221 } 222 223 if (dnskeyRrsetRequiresSep && !sepSignatureValid) { 224 if (!sepReasons.isEmpty()) { 225 result.addAll(sepReasons); 226 } else if (!verifiedSignatures.dnskeyRrsetCrossSigners.isEmpty()) { 227 // The DNSKEY RRset is not self-signed; it is only signed by a key from another zone. 228 result.add(new CrossSignedDnskeyRrsetReason(q.name, verifiedSignatures.dnskeyRrsetCrossSigners)); 229 } else { 230 result.add(new NoSecureEntryPointReason(q.name)); 231 } 232 } 233 removeDnameSynthesizedCnames(answers, toBeVerified); 234 if (!toBeVerified.isEmpty()) { 235 if (toBeVerified.size() != answers.size()) { 236 throw new DnssecValidationFailedException(q, "Only some records are signed!"); 237 } else { 238 result.add(new NoSignaturesReason(q)); 239 } 240 } 241 return result; 242 } 243 244 /** 245 * Remove the CNAME RRs synthesized from validated DNAME RRs from the RRs to be verified. Synthesized CNAME RRs are 246 * not signed, instead a validator has to check that they are consistent with a validated DNAME RR (RFC 6672 247 * § 5.3.1). 248 * 249 * @param answers the RRs of the answer section. 250 * @param toBeVerified the RRs of the answer section that have not been verified yet. 251 */ 252 private static void removeDnameSynthesizedCnames(List<Record<? extends Data>> answers, List<Record<? extends Data>> toBeVerified) { 253 List<Record<DNAME>> validatedDnames = new ArrayList<>(); 254 for (Record<? extends Data> record : answers) { 255 Record<DNAME> dnameRecord = record.ifPossibleAs(DNAME.class); 256 // The RRs that are no longer to be verified have been validated by verifySignatures(). 257 if (dnameRecord == null || toBeVerified.contains(dnameRecord)) continue; 258 validatedDnames.add(dnameRecord); 259 } 260 if (validatedDnames.isEmpty()) return; 261 262 for (Iterator<Record<? extends Data>> iterator = toBeVerified.iterator(); iterator.hasNext(); ) { 263 Record<CNAME> cnameRecord = iterator.next().ifPossibleAs(CNAME.class); 264 if (cnameRecord == null) continue; 265 266 for (Record<DNAME> dnameRecord : validatedDnames) { 267 if (isSynthesizedFrom(cnameRecord, dnameRecord)) { 268 iterator.remove(); 269 break; 270 } 271 } 272 } 273 } 274 275 private static boolean isSynthesizedFrom(Record<CNAME> cnameRecord, Record<DNAME> dnameRecord) { 276 if (cnameRecord.clazz != dnameRecord.clazz) return false; 277 278 DnsName owner = cnameRecord.name; 279 DnsName dnameOwner = dnameRecord.name; 280 // A DNAME RR does not redirect its owner name, only the names below it (RFC 6672 § 2.3). 281 if (!owner.isChildOf(dnameOwner) || owner.equals(dnameOwner)) return false; 282 283 DnsName synthesizedTarget; 284 try { 285 synthesizedTarget = owner.replaceSuffix(dnameOwner, dnameRecord.payloadData.target); 286 } catch (InvalidDnsNameException.DNSNameTooLongException e) { 287 // The substitution overflows, hence no CNAME is synthesized (RFC 6672 § 2.2). 288 return false; 289 } 290 return cnameRecord.payloadData.target.equals(synthesizedTarget); 291 } 292 293 private Set<DnssecUnverifiedReason> verifyNsec(DnsMessage dnsMessage) throws IOException { 294 Set<DnssecUnverifiedReason> result = new HashSet<>(); 295 Question q = dnsMessage.questions.get(0); 296 boolean validNsec = false; 297 boolean nsecPresent = false; 298 299 // Get the SOA RR that has to be in the authority section. Note that we will verify its signature later, after 300 // we have verified the NSEC3 RR. And although the data form the SOA RR is only required for NSEC3 we check for 301 // its existence here, since it would be invalid if there is none. 302 // TODO: Add a reference to the relevant RFC parts which specify that there has to be a SOA RR in X. 303 DnsName zone = null; 304 List<Record<? extends Data>> authoritySection = dnsMessage.authoritySection; 305 for (Record<? extends Data> authorityRecord : authoritySection) { 306 if (authorityRecord.type == TYPE.SOA) { 307 zone = authorityRecord.name; 308 break; 309 } 310 } 311 if (zone == null) 312 throw new AuthorityDoesNotContainSoa(dnsMessage); 313 314 // TODO Examine if it is better to verify the RRs in the authority section *before* we verify NSEC(3). We 315 // currently do it the other way around. 316 317 // TODO: This whole logic needs to be changed. It currently checks one NSEC(3) record after another, when it 318 // should first determine if we are dealing with NSEC or NSEC3 and the verify the whole response. 319 for (Record<? extends Data> record : authoritySection) { 320 DnssecUnverifiedReason reason; 321 322 switch (record.type) { 323 case NSEC: 324 nsecPresent = true; 325 Record<NSEC> nsecRecord = record.as(NSEC.class); 326 reason = Verifier.verifyNsec(nsecRecord, q); 327 break; 328 case NSEC3: 329 nsecPresent = true; 330 Record<NSEC3> nsec3Record = record.as(NSEC3.class); 331 reason = Verifier.verifyNsec3(zone, nsec3Record, q); 332 break; 333 default: 334 continue; 335 } 336 337 if (reason != null) { 338 result.add(reason); 339 } else { 340 validNsec = true; 341 } 342 } 343 344 // TODO: Shouldn't we also throw if !nsecPresent? 345 if (nsecPresent && !validNsec) { 346 throw new DnssecValidationFailedException(q, "Invalid NSEC!"); 347 } 348 349 List<Record<? extends Data>> toBeVerified = dnsMessage.copyAuthority(); 350 VerifySignaturesResult verifiedSignatures = verifySignatures(q, authoritySection, toBeVerified); 351 if (validNsec && verifiedSignatures.reasons.isEmpty()) { 352 result.clear(); 353 } else { 354 result.addAll(verifiedSignatures.reasons); 355 } 356 357 if (!toBeVerified.isEmpty() && toBeVerified.size() != authoritySection.size()) { 358 // TODO Refine this exception and include the missing toBeVerified RRs and the whole DnsMessage into it. 359 throw new DnssecValidationFailedException(q, "Only some resource records from the authority section are signed!"); 360 } 361 362 return result; 363 } 364 365 private static final class VerifySignaturesResult { 366 /** 367 * The DNSKEYs of the queried name that validly sign the DNSKEY RRset. If empty, then the DNSKEY RRset has no 368 * valid self-signature. 369 */ 370 Set<DNSKEY> selfSigningKeys = new HashSet<>(); 371 /** 372 * The names of the zones whose keys cross-sign the DNSKEY RRset of the queried name, that is, keys that are 373 * neither of the queried name nor of one of its parents. Used to explain why a cross-signed DNSKEY RRset is 374 * not anchored. 375 */ 376 Set<DnsName> dnskeyRrsetCrossSigners = new HashSet<>(); 377 Set<DnssecUnverifiedReason> reasons = new HashSet<>(); 378 } 379 380 private static final class RrSetId { 381 final DnsName name; 382 final int typeCoveredValue; 383 384 RrSetId(DnsName name, int typeCoveredValue) { 385 this.name = name; 386 this.typeCoveredValue = typeCoveredValue; 387 } 388 389 @Override 390 public boolean equals(Object other) { 391 if (this == other) return true; 392 if (!(other instanceof RrSetId)) return false; 393 RrSetId otherRrSetId = (RrSetId) other; 394 return typeCoveredValue == otherRrSetId.typeCoveredValue && name.equals(otherRrSetId.name); 395 } 396 397 @Override 398 public int hashCode() { 399 return 37 * name.hashCode() + typeCoveredValue; 400 } 401 } 402 403 @SuppressWarnings("JavaUtilDate") 404 private VerifySignaturesResult verifySignatures(Question q, Collection<Record<? extends Data>> reference, List<Record<? extends Data>> toBeVerified) throws IOException { 405 final Date now = new Date(); 406 final List<RRSIG> outdatedRrSigs = new ArrayList<>(); 407 VerifySignaturesResult result = new VerifySignaturesResult(); 408 final List<Record<RRSIG>> rrsigs = new ArrayList<>(toBeVerified.size()); 409 410 for (Record<? extends Data> recordToBeVerified : toBeVerified) { 411 Record<RRSIG> record = recordToBeVerified.ifPossibleAs(RRSIG.class); 412 if (record == null) continue; 413 414 RRSIG rrsig = record.payloadData; 415 if (rrsig.signatureExpiration.compareTo(now) < 0 || rrsig.signatureInception.compareTo(now) > 0) { 416 // This RRSIG is out of date, but there might be one that is not. 417 outdatedRrSigs.add(rrsig); 418 continue; 419 } 420 rrsigs.add(record); 421 } 422 423 if (rrsigs.isEmpty()) { 424 if (!outdatedRrSigs.isEmpty()) { 425 result.reasons.add(new NoActiveSignaturesReason(q, outdatedRrSigs)); 426 } else { 427 // TODO: Check if QNAME results should have signatures and add a different reason if there are RRSIGs 428 // expected compared to when not. 429 result.reasons.add(new NoSignaturesReason(q)); 430 } 431 return result; 432 } 433 434 // Group the RRSIGs by the RRset they cover, i.e., by owner name and covered type. 435 Map<RrSetId, List<Record<RRSIG>>> rrsigsByRrSet = new LinkedHashMap<>(); 436 for (Record<RRSIG> sigRecord : rrsigs) { 437 RrSetId rrSetId = new RrSetId(sigRecord.name, sigRecord.payloadData.typeCoveredValue); 438 List<Record<RRSIG>> rrSetRrsigs = rrsigsByRrSet.get(rrSetId); 439 if (rrSetRrsigs == null) { 440 rrSetRrsigs = new ArrayList<>(); 441 rrsigsByRrSet.put(rrSetId, rrSetRrsigs); 442 } 443 rrSetRrsigs.add(sigRecord); 444 } 445 446 for (Map.Entry<RrSetId, List<Record<RRSIG>>> entry : rrsigsByRrSet.entrySet()) { 447 RrSetId rrSetId = entry.getKey(); 448 List<Record<RRSIG>> rrSetRrsigs = entry.getValue(); 449 toBeVerified.removeAll(rrSetRrsigs); 450 451 List<Record<? extends Data>> records = new ArrayList<>(reference.size()); 452 for (Record<? extends Data> record : reference) { 453 if (record.typeValue == rrSetId.typeCoveredValue && record.name.equals(rrSetId.name)) { 454 records.add(record); 455 } 456 } 457 458 // An RRset is verified if at least one of its RRSIGs is valid (RFC 4035 § 5.3.3, RFC 6840 § 5.11). For 459 // example, during a key or algorithm rollover, the RRset may also carry RRSIGs that we are unable to 460 // verify. 461 boolean rrSetVerified = false; 462 boolean rrSetSelfSigned = false; 463 Set<DnssecUnverifiedReason> rrSetReasons = new HashSet<>(); 464 DnssecValidationFailedException validationFailedException = null; 465 for (Record<RRSIG> sigRecord : rrSetRrsigs) { 466 RRSIG rrsig = sigRecord.payloadData; 467 468 if (!sigRecord.name.isChildOf(rrsig.signerName)) { 469 LOGGER.finer("Records at " + sigRecord.name + " are cross-signed with a key from " + rrsig.signerName); 470 if (rrsig.typeCovered == TYPE.DNSKEY && sigRecord.name.equals(q.name)) { 471 result.dnskeyRrsetCrossSigners.add(rrsig.signerName); 472 } 473 continue; 474 } 475 476 boolean selfSignedDnskeys = q.name.equals(rrsig.signerName) && rrsig.typeCovered == TYPE.DNSKEY; 477 rrSetSelfSigned |= selfSignedDnskeys; 478 479 VerifySignedRecordsResult verifySignedRecordsResult; 480 try { 481 verifySignedRecordsResult = verifySignedRecords(q, rrsig, records); 482 } catch (DnssecValidationFailedException e) { 483 if (validationFailedException == null) { 484 validationFailedException = e; 485 } 486 continue; 487 } 488 489 if (!verifySignedRecordsResult.reasons.isEmpty()) { 490 rrSetReasons.addAll(verifySignedRecordsResult.reasons); 491 continue; 492 } 493 494 rrSetVerified = true; 495 if (!selfSignedDnskeys) { 496 break; 497 } 498 // Continue verifying the remaining RRSIGs, as we need to know all DNSKEYs that validly sign the 499 // DNSKEY RRset in order to find the one that is a secure entry point. 500 result.selfSigningKeys.add(verifySignedRecordsResult.signingKey); 501 } 502 503 if (!rrSetVerified) { 504 if (validationFailedException != null) { 505 throw validationFailedException; 506 } 507 if (rrSetReasons.isEmpty()) { 508 // All RRSIGs of this RRset are cross-signed, hence the records remain to be verified. 509 continue; 510 } 511 result.reasons.addAll(rrSetReasons); 512 } 513 514 if (rrSetSelfSigned) { 515 // DNSKEYs are verified separately, so don't mark them verified now. 516 continue; 517 } 518 toBeVerified.removeAll(records); 519 } 520 return result; 521 } 522 523 private static final class VerifySignedRecordsResult { 524 final Set<DnssecUnverifiedReason> reasons = new HashSet<>(); 525 DNSKEY signingKey; 526 } 527 528 private VerifySignedRecordsResult verifySignedRecords(Question q, RRSIG rrsig, List<Record<? extends Data>> records) throws IOException { 529 VerifySignedRecordsResult result = new VerifySignedRecordsResult(); 530 List<Record<DNSKEY>> dnskeyRrs; 531 532 if (rrsig.typeCovered == TYPE.DNSKEY) { 533 // Key must be present 534 dnskeyRrs = Record.filter(DNSKEY.class, records); 535 } else if (q.type == TYPE.DS && rrsig.signerName.equals(q.name)) { 536 // We should not probe for the self signed DS negative response, as it will be an endless loop. 537 result.reasons.add(new NoTrustAnchorReason(q.name)); 538 return result; 539 } else { 540 DnssecQueryResult dnskeyRes = queryDnssec(rrsig.signerName, TYPE.DNSKEY); 541 result.reasons.addAll(dnskeyRes.getUnverifiedReasons()); 542 dnskeyRrs = dnskeyRes.dnsQueryResult.response.filterAnswerSectionBy(DNSKEY.class); 543 } 544 545 // Key tags are not unique, hence try every DNSKEY with a matching key tag (RFC 4034 § 5.3.1). 546 boolean keyFound = false; 547 DnssecUnverifiedReason unverifiedReason = null; 548 for (Record<DNSKEY> dnsKeyRecord : dnskeyRrs) { 549 DNSKEY dnskey = dnsKeyRecord.payloadData; 550 if (dnskey.getKeyTag() != rrsig.keyTag) { 551 continue; 552 } 553 keyFound = true; 554 555 unverifiedReason = Verifier.verify(records, rrsig, dnskey); 556 if (unverifiedReason == null) { 557 result.signingKey = dnskey; 558 return result; 559 } 560 } 561 562 if (!keyFound) { 563 throw new DnssecValidationFailedException(q, records.size() + " " + rrsig.typeCovered + " record(s) are signed using an unknown key."); 564 } 565 566 result.reasons.add(unverifiedReason); 567 return result; 568 } 569 570 private Set<DnssecUnverifiedReason> verifySecureEntryPoint(final Record<DNSKEY> sepRecord) throws IOException { 571 final DNSKEY dnskey = sepRecord.payloadData; 572 573 Set<DnssecUnverifiedReason> unverifiedReasons = new HashSet<>(); 574 Set<DnssecUnverifiedReason> activeReasons = new HashSet<>(); 575 List<byte[]> knownSepKeys = knownSeps.get(sepRecord.name); 576 if (knownSepKeys != null) { 577 for (byte[] knownSepKey : knownSepKeys) { 578 if (dnskey.keyEquals(knownSepKey)) { 579 return unverifiedReasons; 580 } 581 } 582 unverifiedReasons.add(new DnssecUnverifiedReason.ConflictsWithSep(sepRecord)); 583 return unverifiedReasons; 584 } 585 586 // If we are looking for the SEP of the root zone at this point, then the client was not 587 // configured with one. Hence we can abort and state the reason why we aborted. 588 if (sepRecord.name.isRootLabel()) { 589 unverifiedReasons.add(new DnssecUnverifiedReason.NoRootSecureEntryPointReason()); 590 return unverifiedReasons; 591 } 592 593 DnssecQueryResult dsResp = queryDnssec(sepRecord.name, TYPE.DS); 594 unverifiedReasons.addAll(dsResp.getUnverifiedReasons()); 595 596 List<DS> dsRrs = new ArrayList<>(); 597 for (Record<DS> dsRecord : dsResp.dnsQueryResult.response.filterAnswerSectionBy(DS.class)) { 598 dsRrs.add(dsRecord.payloadData); 599 } 600 List<DelegatingDnssecRR> delegations = getDelegationsFor(dnskey, dsRrs); 601 if (!delegations.isEmpty()) { 602 activeReasons = dsResp.getUnverifiedReasons(); 603 } else { 604 LOGGER.fine("There is no DS record for \'" + sepRecord.name + "\', server gives empty result"); 605 } 606 607 if (delegations.isEmpty() && dlv != null && !dlv.isChildOf(sepRecord.name)) { 608 DnssecQueryResult dlvResp = queryDnssec(DnsName.from(sepRecord.name, dlv), TYPE.DLV); 609 unverifiedReasons.addAll(dlvResp.getUnverifiedReasons()); 610 611 List<DLV> dlvRrs = new ArrayList<>(); 612 for (Record<DLV> dlvRecord : dlvResp.dnsQueryResult.response.filterAnswerSectionBy(DLV.class)) { 613 dlvRrs.add(dlvRecord.payloadData); 614 } 615 delegations = getDelegationsFor(dnskey, dlvRrs); 616 if (!delegations.isEmpty()) { 617 LOGGER.fine("Found DLV for " + sepRecord.name + ", awesome."); 618 activeReasons = dlvResp.getUnverifiedReasons(); 619 } 620 } 621 622 if (!delegations.isEmpty()) { 623 Set<DnssecUnverifiedReason> delegationReasons = verifyDelegations(sepRecord, delegations); 624 if (delegationReasons.isEmpty()) { 625 unverifiedReasons = activeReasons; 626 } else { 627 unverifiedReasons.addAll(delegationReasons); 628 } 629 } else if (unverifiedReasons.isEmpty()) { 630 unverifiedReasons.add(new NoTrustAnchorReason(sepRecord.name)); 631 } 632 return unverifiedReasons; 633 } 634 635 /** 636 * Get the delegating RRs, i.e., DS or DLV RRs, that refer to the given DNSKEY. A delegating RR refers to a DNSKEY 637 * if its key tag and algorithm match the ones of the DNSKEY (RFC 4035 § 5.2). If there is a delegating RR with a 638 * supported digest type other than SHA-1, then the ones with SHA-1 are ignored (RFC 4509 § 3). 639 * 640 * @param dnskey the DNSKEY. 641 * @param delegatingRrs the delegating RRs. 642 * @return the delegating RRs that refer to the DNSKEY. 643 */ 644 private static List<DelegatingDnssecRR> getDelegationsFor(DNSKEY dnskey, List<? extends DelegatingDnssecRR> delegatingRrs) { 645 List<DelegatingDnssecRR> delegations = new ArrayList<>(delegatingRrs.size()); 646 boolean strongerThanSha1Present = false; 647 for (DelegatingDnssecRR delegatingRr : delegatingRrs) { 648 if (delegatingRr.keyTag != dnskey.getKeyTag() || delegatingRr.algorithmByte != dnskey.algorithmByte) { 649 continue; 650 } 651 delegations.add(delegatingRr); 652 653 DigestAlgorithm digestType = delegatingRr.digestType; 654 if (digestType != null && digestType != DigestAlgorithm.SHA1 655 && AlgorithmMap.INSTANCE.getDsDigestCalculator(digestType) != null) { 656 strongerThanSha1Present = true; 657 } 658 } 659 660 if (strongerThanSha1Present) { 661 for (Iterator<DelegatingDnssecRR> iterator = delegations.iterator(); iterator.hasNext(); ) { 662 if (iterator.next().digestType == DigestAlgorithm.SHA1) { 663 iterator.remove(); 664 } 665 } 666 } 667 return delegations; 668 } 669 670 /** 671 * Verify the given SEP using the given delegating RRs. The SEP is verified if at least one of the delegating RRs 672 * verifies it. 673 * 674 * @param sepRecord the SEP. 675 * @param delegations the delegating RRs that refer to the SEP. 676 * @return the reasons why the SEP could not be verified, which is empty if the SEP is verified. 677 * @throws DnssecValidationFailedException if the SEP could not be verified because a digest did not match. 678 */ 679 private static Set<DnssecUnverifiedReason> verifyDelegations(Record<DNSKEY> sepRecord, List<DelegatingDnssecRR> delegations) 680 throws DnssecValidationFailedException { 681 Set<DnssecUnverifiedReason> reasons = new HashSet<>(); 682 DnssecValidationFailedException validationFailedException = null; 683 for (DelegatingDnssecRR delegation : delegations) { 684 DnssecUnverifiedReason unverifiedReason; 685 try { 686 unverifiedReason = Verifier.verify(sepRecord, delegation); 687 } catch (DnssecValidationFailedException e) { 688 if (validationFailedException == null) { 689 validationFailedException = e; 690 } 691 continue; 692 } 693 694 if (unverifiedReason == null) { 695 return Collections.emptySet(); 696 } 697 reasons.add(unverifiedReason); 698 } 699 700 if (validationFailedException != null) { 701 throw validationFailedException; 702 } 703 return reasons; 704 } 705 706 @Override 707 protected DnsMessage.Builder newQuestion(DnsMessage.Builder message) { 708 message.getEdnsBuilder().setUdpPayloadSize(dataSource.getUdpPayloadSize()).setDnssecOk(); 709 message.setCheckingDisabled(true); 710 return super.newQuestion(message); 711 } 712 713 @Override 714 protected String isResponseAcceptable(DnsMessage response) { 715 boolean dnssecOk = response.isDnssecOk(); 716 if (!dnssecOk) { 717 // This is a deliberate violation of RFC 6840 § 5.6. I doubt that 718 // "resolvers MUST ignore the DO bit in responses" does any good. Also we basically ignore the DO bit after 719 // the fall back to iterative mode. 720 return "DNSSEC OK (DO) flag not set in response"; 721 } 722 boolean checkingDisabled = response.checkingDisabled; 723 if (!checkingDisabled) { 724 return "CHECKING DISABLED (CD) flag not set in response"; 725 } 726 return super.isResponseAcceptable(response); 727 } 728 729 /** 730 * Add a new secure entry point to the list of known secure entry points. 731 * 732 * A secure entry point acts as a trust anchor. By default, the secure entry points are the key signing keys of the 733 * root zone. A domain name may have multiple secure entry points, e.g., during a key rollover, in which case a 734 * DNSKEY that matches any of them is accepted. 735 * 736 * @param name The domain name originating the key. Once the secure entry point for this domain is requested, 737 * the resolver will use this key without further verification instead of using the DNS system to 738 * verify the key. 739 * @param key The secure entry point corresponding to the domain name. This key can be retrieved by requesting 740 * the DNSKEY record for the domain and using the key with first flags bit set 741 * (also called key signing key) 742 */ 743 public final void addSecureEntryPoint(DnsName name, byte[] key) { 744 List<byte[]> keys = knownSeps.get(name); 745 if (keys == null) { 746 keys = new CopyOnWriteArrayList<>(); 747 List<byte[]> previousKeys = knownSeps.putIfAbsent(name, keys); 748 if (previousKeys != null) { 749 keys = previousKeys; 750 } 751 } 752 keys.add(key); 753 } 754 755 /** 756 * Remove the secure entry points stored for a domain name. 757 * 758 * @param name The domain name of which the corresponding secure entry points shall be removed. For the root zone, 759 * use the empty string here. 760 */ 761 public void removeSecureEntryPoint(DnsName name) { 762 knownSeps.remove(name); 763 } 764 765 /** 766 * Clears the list of known secure entry points. 767 * 768 * This will also remove the secure entry point of the root zone and 769 * thus render this instance useless until a new secure entry point is added. 770 */ 771 public void clearSecureEntryPoints() { 772 knownSeps.clear(); 773 } 774 775 /** 776 * Whether signature records (RRSIG) are stripped from the resulting {@link DnsMessage}. 777 * 778 * Default is {@code true}. 779 * 780 * @return Whether signature records are stripped. 781 */ 782 public boolean isStripSignatureRecords() { 783 return stripSignatureRecords; 784 } 785 786 /** 787 * Enable or disable stripping of signature records (RRSIG) from the result {@link DnsMessage}. 788 * @param stripSignatureRecords Whether signature records shall be stripped. 789 */ 790 public void setStripSignatureRecords(boolean stripSignatureRecords) { 791 this.stripSignatureRecords = stripSignatureRecords; 792 } 793 794 /** 795 * Enables DNSSEC Lookaside Validation (DLV) using the default DLV service at dlv.isc.org. 796 */ 797 public void enableLookasideValidation() { 798 configureLookasideValidation(DEFAULT_DLV); 799 } 800 801 /** 802 * Disables DNSSEC Lookaside Validation (DLV). 803 * DLV is disabled by default, this is only required if {@link #enableLookasideValidation()} was used before. 804 */ 805 public void disableLookasideValidation() { 806 configureLookasideValidation(null); 807 } 808 809 /** 810 * Enables DNSSEC Lookaside Validation (DLV) using the given DLV service. 811 * 812 * @param dlv The domain name of the DLV service to be used or {@code null} to disable DLV. 813 */ 814 public void configureLookasideValidation(DnsName dlv) { 815 this.dlv = dlv; 816 } 817}