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}