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 java.util.ArrayList;
014import java.util.Collections;
015import java.util.List;
016import java.util.Set;
017
018import org.minidns.constants.DnssecConstants.DigestAlgorithm;
019import org.minidns.dnsmessage.Question;
020import org.minidns.dnsname.DnsName;
021import org.minidns.record.DNSKEY;
022import org.minidns.record.Data;
023import org.minidns.record.RRSIG;
024import org.minidns.record.Record;
025import org.minidns.record.Record.TYPE;
026
027public abstract class DnssecUnverifiedReason {
028    public abstract String getReasonString();
029
030    @Override
031    public String toString() {
032        return getReasonString();
033    }
034
035    @Override
036    public int hashCode() {
037        return getReasonString().hashCode();
038    }
039
040    @Override
041    public boolean equals(Object obj) {
042        return obj instanceof DnssecUnverifiedReason && ((DnssecUnverifiedReason) obj).getReasonString().equals(getReasonString());
043    }
044
045    public static class AlgorithmNotSupportedReason extends DnssecUnverifiedReason {
046        private final String algorithm;
047        private final TYPE type;
048        private final Record<? extends Data> record;
049
050        public AlgorithmNotSupportedReason(byte algorithm, TYPE type, Record<? extends Data> record) {
051            this.algorithm = Integer.toString(algorithm & 0xff);
052            this.type = type;
053            this.record = record;
054        }
055
056        @Override
057        public String getReasonString() {
058            return type.name() + " algorithm " + algorithm + " required to verify " + record.name + " is unknown or not supported by platform";
059        }
060    }
061
062    public static class AlgorithmExceptionThrownReason extends DnssecUnverifiedReason {
063        private final int algorithmNumber;
064        private final String kind;
065        private final Exception reason;
066        private final Record<? extends Data> record;
067
068        public AlgorithmExceptionThrownReason(DigestAlgorithm algorithm, String kind, Record<? extends Data> record, Exception reason) {
069            this.algorithmNumber = algorithm.value;
070            this.kind = kind;
071            this.record = record;
072            this.reason = reason;
073        }
074
075        @Override
076        public String getReasonString() {
077            return kind + " algorithm " + algorithmNumber + " threw exception while verifying " + record.name + ": " + reason;
078        }
079    }
080
081    public static class ConflictsWithSep extends DnssecUnverifiedReason {
082        private final Record<DNSKEY> record;
083
084        public ConflictsWithSep(Record<DNSKEY> record) {
085            this.record = record;
086        }
087
088        @Override
089        public String getReasonString() {
090            return "Zone " + record.name.ace + " is in list of known SEPs, but DNSKEY from response mismatches!";
091        }
092    }
093
094    public static class NoTrustAnchorReason extends DnssecUnverifiedReason {
095        private final DnsName zone;
096
097        public NoTrustAnchorReason(DnsName zone) {
098            this.zone = zone;
099        }
100
101        @Override
102        public String getReasonString() {
103            return "No trust anchor was found for zone " + zone + ". Try enabling DLV";
104        }
105    }
106
107    public static class NoSecureEntryPointReason extends DnssecUnverifiedReason {
108        private final DnsName zone;
109
110        public NoSecureEntryPointReason(DnsName zone) {
111            this.zone = zone;
112        }
113
114        @Override
115        public String getReasonString() {
116            return "No secure entry point was found for zone " + zone;
117        }
118    }
119
120    public static class NoRootSecureEntryPointReason extends DnssecUnverifiedReason {
121        public NoRootSecureEntryPointReason() {
122        }
123
124        @Override
125        public String getReasonString() {
126            return "No secure entry point was found for the root zone (\"Did you forget to configure a root SEP?\")";
127        }
128    }
129
130    public static class CrossSignedDnskeyRrsetReason extends DnssecUnverifiedReason {
131        private final DnsName zone;
132        private final Set<DnsName> signerNames;
133
134        public CrossSignedDnskeyRrsetReason(DnsName zone, Set<DnsName> signerNames) {
135            this.zone = zone;
136            this.signerNames = Collections.unmodifiableSet(signerNames);
137        }
138
139        /**
140         * The names of the zones whose keys signed the DNSKEY RRset instead of a key of the zone itself.
141         *
142         * @return the cross-signing zones.
143         */
144        public Set<DnsName> getSignerNames() {
145            return signerNames;
146        }
147
148        @Override
149        public String getReasonString() {
150            List<String> sortedSignerNames = new ArrayList<>(signerNames.size());
151            for (DnsName signerName : signerNames) {
152                sortedSignerNames.add(signerName.toString());
153            }
154            Collections.sort(sortedSignerNames);
155            return "The DNSKEY RRset of zone " + zone + " is not self-signed and hence can not be anchored. It is only "
156                            + "signed by a key of the following zone(s): " + sortedSignerNames;
157        }
158    }
159
160    public static class NoSignaturesReason extends DnssecUnverifiedReason {
161        private final Question question;
162
163        public NoSignaturesReason(Question question) {
164            this.question = question;
165        }
166
167        @Override
168        public String getReasonString() {
169            return "No signatures were attached to answer on question for " + question.type + " at " + question.name;
170        }
171    }
172
173    public static class NoActiveSignaturesReason extends DnssecUnverifiedReason {
174        private final Question question;
175        private final List<RRSIG> outdatedRrSigs;
176
177        public NoActiveSignaturesReason(Question question, List<RRSIG> outdatedRrSigs) {
178            this.question = question;
179            assert !outdatedRrSigs.isEmpty();
180            this.outdatedRrSigs = Collections.unmodifiableList(outdatedRrSigs);
181        }
182
183        @Override
184        public String getReasonString() {
185            return "No currently active signatures were attached to answer on question for " + question.type + " at " + question.name;
186        }
187
188        public List<RRSIG> getOutdatedRrSigs() {
189            return outdatedRrSigs;
190        }
191    }
192
193    public static class NSECDoesNotMatchReason extends DnssecUnverifiedReason {
194        private final Question question;
195        private final Record<? extends Data> record;
196
197        public NSECDoesNotMatchReason(Question question, Record<? extends Data> record) {
198            this.question = question;
199            this.record = record;
200        }
201
202        @Override
203        public String getReasonString() {
204            return "NSEC " + record.name + " does nat match question for " + question.type + " at " + question.name;
205        }
206    }
207}