/* * dnssecjava - a DNSSEC validating stub resolver for Java * Copyright (c) 2013-2015 Ingo Bauersachs * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * This file is based on work under the following copyright and permission * notice: * * Copyright (c) 2005 VeriSign. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote * products derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package org.jitsi.dnssec.validator; import java.util.Iterator; import java.util.Properties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.jitsi.dnssec.R; import org.jitsi.dnssec.SMessage; import org.jitsi.dnssec.SRRset; import org.jitsi.dnssec.SecurityStatus; import org.xbill.DNS.DClass; import org.xbill.DNS.DNSKEYRecord; import org.xbill.DNS.DNSSEC.Algorithm; import org.xbill.DNS.DSRecord; import org.xbill.DNS.DSRecord.Digest; import org.xbill.DNS.Message; import org.xbill.DNS.NSECRecord; import org.xbill.DNS.Name; import org.xbill.DNS.NameTooLongException; import org.xbill.DNS.RRSIGRecord; import org.xbill.DNS.RRset; import org.xbill.DNS.Rcode; import org.xbill.DNS.Record; import org.xbill.DNS.Section; import org.xbill.DNS.Type; /** * This is a collection of routines encompassing the logic of validating * different message types. * * @author davidb */ public class ValUtils { public static final String DIGEST_PREFERENCE = "org.jitsi.dnssec.digest_preference"; private static final Logger logger = LoggerFactory.getLogger(ValUtils.class); private static final Name WILDCARD = Name.fromConstantString("*"); /** A local copy of the verifier object. */ private DnsSecVerifier verifier; private int[] digestPreference = null; /** * Creates a new instance of this class. */ public ValUtils() { this.verifier = new DnsSecVerifier(); } /** * Initialize the module. The only recognized configuration value is * {@link #DIGEST_PREFERENCE}. * * @param config The configuration data for this module. */ public void init(Properties config) { String dp = config.getProperty(DIGEST_PREFERENCE); if (dp != null) { String[] dpdata = dp.split(","); this.digestPreference = new int[dpdata.length]; for (int i = 0; i < dpdata.length; i++) { this.digestPreference[i] = Integer.parseInt(dpdata[i]); if (!isDigestSupported(this.digestPreference[i])) { throw new IllegalArgumentException("Unsupported digest ID in digest preferences"); } } } } /** * Given a response, classify ANSWER responses into a subtype. * * @param m The response to classify. * * @return A subtype ranging from UNKNOWN to NAMEERROR. */ public static ResponseClassification classifyResponse(SMessage m) { // Normal Name Error's are easy to detect -- but don't mistake a CNAME // chain ending in NXDOMAIN. if (m.getRcode() == Rcode.NXDOMAIN && m.getCount(Section.ANSWER) == 0) { return ResponseClassification.NAMEERROR; } // Next is NODATA if (m.getCount(Section.ANSWER) == 0) { return ResponseClassification.NODATA; } // We distinguish between CNAME response and other positive/negative // responses because CNAME answers require extra processing. int qtype = m.getQuestion().getType(); // We distinguish between ANY and CNAME or POSITIVE because ANY // responses are validated differently. if (qtype == Type.ANY) { return ResponseClassification.ANY; } boolean hadCname = false; for (RRset set : m.getSectionRRsets(Section.ANSWER)) { if (set.getType() == qtype) { return ResponseClassification.POSITIVE; } if (set.getType() == Type.CNAME || set.getType() == Type.DNAME) { hadCname = true; if (qtype == Type.DS) { return ResponseClassification.CNAME; } } } if (hadCname) { if (m.getRcode() == Rcode.NXDOMAIN) { return ResponseClassification.CNAME_NAMEERROR; } else { return ResponseClassification.CNAME_NODATA; } } logger.warn("Failed to classify response message:\n" + m); return ResponseClassification.UNKNOWN; } /** * Given a DS rrset and a DNSKEY rrset, match the DS to a DNSKEY and verify * the DNSKEY rrset with that key. * * @param dnskeyRrset The DNSKEY rrset to match against. The security status * of this rrset will be updated on a successful verification. * @param dsRrset The DS rrset to match with. This rrset must already be * trusted. * @param badKeyTTL The TTL [s] for keys determined to be bad. * * @return a KeyEntry. This will either contain the now trusted dnskey * RRset, a "null" key entry indicating that this DS rrset/DNSKEY * pair indicate an secure end to the island of trust (i.e., unknown * algorithms), or a "bad" KeyEntry if the dnskey RRset fails to * verify. Note that the "null" response should generally only occur * in a private algorithm scenario: normally this sort of thing is * checked before fetching the matching DNSKEY rrset. */ public KeyEntry verifyNewDNSKEYs(SRRset dnskeyRrset, SRRset dsRrset, long badKeyTTL) { if (!atLeastOneDigestSupported(dsRrset)) { KeyEntry ke = KeyEntry.newNullKeyEntry(dsRrset.getName(), dsRrset.getDClass(), dsRrset.getTTL()); ke.setBadReason(R.get("failed.ds.nodigest", dsRrset.getName())); return ke; } if (!atLeastOneSupportedAlgorithm(dsRrset)) { KeyEntry ke = KeyEntry.newNullKeyEntry(dsRrset.getName(), dsRrset.getDClass(), dsRrset.getTTL()); ke.setBadReason(R.get("failed.ds.noalg", dsRrset.getName())); return ke; } int favoriteDigestID = this.favoriteDSDigestID(dsRrset); for (Iterator<?> i = dsRrset.rrs(); i.hasNext();) { DSRecord ds = (DSRecord)i.next(); if (ds.getDigestID() != favoriteDigestID) { continue; } DNSKEY: for (Iterator<?> j = dnskeyRrset.rrs(); j.hasNext();) { DNSKEYRecord dnskey = (DNSKEYRecord)j.next(); // Skip DNSKEYs that don't match the basic criteria. if (ds.getFootprint() != dnskey.getFootprint() || ds.getAlgorithm() != dnskey.getAlgorithm()) { continue; } // Convert the candidate DNSKEY into a hash using the same DS // hash algorithm. DSRecord keyDigest = new DSRecord(Name.root, ds.getDClass(), 0, ds.getDigestID(), dnskey); byte[] keyHash = keyDigest.getDigest(); byte[] dsHash = ds.getDigest(); // see if there is a length mismatch (unlikely) if (keyHash.length != dsHash.length) { continue; } for (int k = 0; k < keyHash.length; k++) { if (keyHash[k] != dsHash[k]) { continue DNSKEY; } } // Otherwise, we have a match! Make sure that the DNSKEY // verifies *with this key*. SecurityStatus res = this.verifier.verify(dnskeyRrset, dnskey); if (res == SecurityStatus.SECURE) { logger.trace("DS matched DNSKEY."); dnskeyRrset.setSecurityStatus(SecurityStatus.SECURE); return KeyEntry.newKeyEntry(dnskeyRrset); } // If it didn't validate with the DNSKEY, try the next one! } } // If any were understandable, then it is bad. KeyEntry badKey = KeyEntry.newBadKeyEntry(dsRrset.getName(), dsRrset.getDClass(), badKeyTTL); badKey.setBadReason(R.get("dnskey.no_ds_match")); return badKey; } /** * Gets the digest ID for the favorite (best) algorithm that is support in a * given DS set. * * The order of preference can be configured with the property * {@value #DIGEST_PREFERENCE}. If the property is not set, the highest * supported number is returned. * * @param dsset The DS set to check for the favorite algorithm. * @return The favorite digest ID or 0 if none is supported. 0 is not a * known digest ID. */ int favoriteDSDigestID(SRRset dsset) { if (this.digestPreference == null) { int max = 0; for (Iterator<?> rrs = dsset.rrs(); rrs.hasNext();) { DSRecord r = (DSRecord)rrs.next(); if (r.getDigestID() > max && isDigestSupported(r.getDigestID()) && isAlgorithmSupported(r.getAlgorithm())) { max = r.getDigestID(); } } return max; } else { for (int i = 0; i < this.digestPreference.length; i++) { for (Iterator<?> rrs = dsset.rrs(); rrs.hasNext();) { DSRecord r = (DSRecord)rrs.next(); if (r.getDigestID() == this.digestPreference[i]) { return r.getDigestID(); } } } } return 0; } /** * Given an SRRset that is signed by a DNSKEY found in the key_rrset, verify * it. This will return the status (either BOGUS or SECURE) and set that * status in rrset. * * @param rrset The SRRset to verify. * @param keyRrset The set of keys to verify against. * @return The status (BOGUS or SECURE). */ public SecurityStatus verifySRRset(SRRset rrset, SRRset keyRrset) { String rrsetName = rrset.getName() + "/" + Type.string(rrset.getType()) + "/" + DClass.string(rrset.getDClass()); if (rrset.getSecurityStatus() == SecurityStatus.SECURE) { logger.trace("verifySRRset: rrset <" + rrsetName + "> previously found to be SECURE"); return SecurityStatus.SECURE; } SecurityStatus status = this.verifier.verify(rrset, keyRrset); if (status != SecurityStatus.SECURE) { logger.debug("verifySRRset: rrset <" + rrsetName + "> found to be BAD"); status = SecurityStatus.BOGUS; } else { logger.trace("verifySRRset: rrset <" + rrsetName + "> found to be SECURE"); } rrset.setSecurityStatus(status); return status; } /** * Determine by looking at a signed RRset whether or not the RRset name was * the result of a wildcard expansion. If so, return the name of the * generating wildcard. * * @param rrset The rrset to chedck. * @return the wildcard name, if the rrset was synthesized from a wildcard. * null if not. */ public static Name rrsetWildcard(RRset rrset) { @SuppressWarnings("unchecked") Iterator<RRSIGRecord> it = (Iterator<RRSIGRecord>)rrset.sigs(); RRSIGRecord rrsig = it.next(); // check rest of signatures have identical label count while (it.hasNext()) { if (it.next().getLabels() != rrsig.getLabels()) { throw new RuntimeException("failed.wildcard.label_count_mismatch"); } } // if the RRSIG label count is shorter than the number of actual labels, // then this rrset was synthesized from a wildcard. // Note that the RRSIG label count doesn't count the root label. Name wn = rrset.getName(); // skip a leading wildcard label in the dname (RFC4035 2.2) if (rrset.getName().isWild()) { wn = new Name(wn, 1); } int labelDiff = (wn.labels() - 1) - rrsig.getLabels(); if (labelDiff > 0) { return wn.wild(labelDiff); } return null; } /** * Finds the longest domain name in common with the given name. * * @param domain1 The first domain to process. * @param domain2 The second domain to process. * @return The longest label in common of domain1 and domain2. The least * common name is the root. */ public static Name longestCommonName(Name domain1, Name domain2) { int l = Math.min(domain1.labels(), domain2.labels()); domain1 = new Name(domain1, domain1.labels() - l); domain2 = new Name(domain2, domain2.labels() - l); for (int i = 0; i < l - 1; i++) { Name ns1 = new Name(domain1, i); if (ns1.equals(new Name(domain2, i))) { return ns1; } } return Name.root; } /** * Is the first Name strictly a subdomain of the second name (i.e., below * but not equal to). * * @param domain1 The first domain to process. * @param domain2 The second domain to process. * @return True when domain1 is a strict subdomain of domain2. */ public static boolean strictSubdomain(Name domain1, Name domain2) { if (domain1.labels() <= domain2.labels()) { return false; } return new Name(domain1, domain1.labels() - domain2.labels()).equals(domain2); } /** * Determines the 'closest encloser' - the name that has the most common * labels between <code>domain</code> and ({@link NSECRecord#getName()} or * {@link NSECRecord#getNext()}). * * @param domain The name for which the closest encloser is queried. * @param nsec The covering {@link NSECRecord} to check. * @return The closest encloser name of <code>domain</code> as defined by * <code>nsec</code>. */ public static Name closestEncloser(Name domain, NSECRecord nsec) { Name n1 = longestCommonName(domain, nsec.getName()); Name n2 = longestCommonName(domain, nsec.getNext()); return (n1.labels() > n2.labels()) ? n1 : n2; } /** * Gets the closest encloser of <code>domain</code> prepended with a * wildcard label. * * @param domain The name for which the wildcard closest encloser is * demanded. * @param nsec The covering NSEC that defines the encloser. * @return The wildcard closest encloser name of <code>domain</code> as * defined by <code>nsec</code>. * @throws NameTooLongException If adding the wildcard label to the closest * encloser results in an invalid name. */ public static Name nsecWildcard(Name domain, NSECRecord nsec) throws NameTooLongException { Name origin = closestEncloser(domain, nsec); return Name.concatenate(WILDCARD, origin); } /** * Determine if the given NSEC proves a NameError (NXDOMAIN) for a given * qname. * * @param nsec The NSEC to check. * @param qname The qname to check against. * @param signerName The signer of the NSEC RRset. * @return true if the NSEC proves the condition. */ public static boolean nsecProvesNameError(NSECRecord nsec, Name qname, Name signerName) { Name owner = nsec.getName(); Name next = nsec.getNext(); // If NSEC owner == qname, then this NSEC proves that qname exists. if (qname.equals(owner)) { return false; } // deny overreaching NSECs if (!next.subdomain(signerName)) { return false; } // If NSEC is a parent of qname, we need to check the type map // If the parent name has a DNAME or is a delegation point, then this // NSEC is being misused. if (qname.subdomain(owner)) { if (nsec.hasType(Type.DNAME)) { return false; } if (nsec.hasType(Type.NS) && !nsec.hasType(Type.SOA)) { return false; } } if (owner.equals(next)) { // this nsec is the only nsec: zone.name NSEC zone.name // it disproves everything else but only for subdomains of that zone if (strictSubdomain(qname, next)) { return true; } } else if (owner.compareTo(next) > 0) { // this is the last nsec, ....(bigger) NSEC zonename(smaller) // the names after the last (owner) name do not exist // there are no names before the zone name in the zone // but the qname must be a subdomain of the zone name(next). if (owner.compareTo(qname) < 0 && strictSubdomain(qname, next)) { return true; } } else { // regular NSEC, (smaller) NSEC (larger) if (owner.compareTo(qname) < 0 && qname.compareTo(next) < 0) { return true; } } return false; } /** * Determine if a NSEC record proves the non-existence of a wildcard that * could have produced qname. * * @param nsec The nsec to check. * @param qname The qname to check against. * @param signerName The signer of the NSEC RRset. * @return true if the NSEC proves the condition. */ public static boolean nsecProvesNoWC(NSECRecord nsec, Name qname, Name signerName) { int qnameLabels = qname.labels(); Name ce = closestEncloser(qname, nsec); int ceLabels = ce.labels(); for (int i = qnameLabels - ceLabels; i > 0; i--) { Name wcName = qname.wild(i); if (nsecProvesNameError(nsec, wcName, signerName)) { return true; } } return false; } /** * Container for responses of * {@link ValUtils#nsecProvesNodata(NSECRecord, Name, int)}. */ public static class NsecProvesNodataResponse { boolean result; Name wc; } /** * Determine if a NSEC proves the NOERROR/NODATA conditions. This will also * handle the empty non-terminal (ENT) case and partially handle the * wildcard case. If the ownername of 'nsec' is a wildcard, the validator * must still be provided proof that qname did not directly exist and that * the wildcard is, in fact, *.closest_encloser. * * @param nsec The NSEC to check * @param qname The query name to check against. * @param qtype The query type to check against. * @return true if the NSEC proves the condition. */ public static NsecProvesNodataResponse nsecProvesNodata(NSECRecord nsec, Name qname, int qtype) { NsecProvesNodataResponse result = new NsecProvesNodataResponse(); if (!nsec.getName().equals(qname)) { // empty-non-terminal checking. // Done before wildcard, because this is an exact match, // and would prevent a wildcard from matching. // If the nsec is proving that qname is an ENT, the nsec owner will // be less than qname, and the next name will be a child domain of // the qname. if (strictSubdomain(nsec.getNext(), qname) && nsec.getName().compareTo(qname) < 0) { result.result = true; return result; } // Wildcard checking: // If this is a wildcard NSEC, make sure that a) it was possible to // have generated qname from the wildcard and b) the type map does // not contain qtype. Note that this does NOT prove that this // wildcard was the applicable wildcard. if (nsec.getName().isWild()) { // the is the purported closest encloser. Name ce = new Name(nsec.getName(), 1); // The qname must be a strict subdomain of the closest encloser, // and the qtype must be absent from the type map. if (strictSubdomain(qname, ce)) { if (nsec.hasType(Type.CNAME)) { // should have gotten the wildcard CNAME result.result = false; return result; } if (nsec.hasType(Type.NS) && !nsec.hasType(Type.SOA)) { // wrong parentside (wildcard) NSEC used, and it really // should not exist anyway: // http://tools.ietf.org/html/rfc4592#section-4.2 result.result = false; return result; } if (nsec.hasType(qtype)) { result.result = false; return result; } } result.wc = ce; result.result = true; return result; } // Otherwise, this NSEC does not prove ENT, so it does not prove // NODATA. result.result = false; return result; } // If the qtype exists, then we should have gotten it. if (nsec.hasType(qtype)) { result.result = false; return result; } // if the name is a CNAME node, then we should have gotten the CNAME if (nsec.hasType(Type.CNAME)) { result.result = false; return result; } // If an NS set exists at this name, and NOT a SOA (so this is a zone // cut, not a zone apex), then we should have gotten a referral (or we // just got the wrong NSEC). // The reverse of this check is used when qtype is DS, since that // must use the NSEC from above the zone cut. if (qtype != Type.DS && nsec.hasType(Type.NS) && !nsec.hasType(Type.SOA)) { result.result = false; return result; } else if (qtype == Type.DS && nsec.hasType(Type.SOA) && !Name.root.equals(qname)) { result.result = false; return result; } result.result = true; return result; } /** * Check DS absence. There is a NODATA reply to a DS that needs checking. * NSECs can prove this is not a delegation point, or successfully prove * that there is no DS. Or this fails. * * @param request The request that generated this response. * @param response The response to validate. * @param keyRrset The key that validate the NSECs. * @return The NODATA proof along with the reason of the result. */ public JustifiedSecStatus nsecProvesNodataDsReply(Message request, SMessage response, SRRset keyRrset) { Name qname = request.getQuestion().getName(); int qclass = request.getQuestion().getDClass(); // If we have a NSEC at the same name, it must prove one of two // things // -- // 1) this is a delegation point and there is no DS // 2) this is not a delegation point SRRset nsecRrset = response.findRRset(qname, Type.NSEC, qclass, Section.AUTHORITY); if (nsecRrset != null) { // The NSEC must verify, first of all. SecurityStatus status = this.verifySRRset(nsecRrset, keyRrset); if (status != SecurityStatus.SECURE) { return new JustifiedSecStatus(SecurityStatus.BOGUS, R.get("failed.ds.nsec")); } NSECRecord nsec = (NSECRecord)nsecRrset.first(); status = ValUtils.nsecProvesNoDS(nsec, qname); switch (status) { case INSECURE: // this wasn't a delegation point. return new JustifiedSecStatus(status, R.get("failed.ds.nodelegation")); case SECURE: // this proved no DS. return new JustifiedSecStatus(status, R.get("insecure.ds.nsec")); default: // something was wrong. return new JustifiedSecStatus(status, R.get("failed.ds.nsec.hasdata")); } } // Otherwise, there is no NSEC at qname. This could be an ENT. // If not, this is broken. NsecProvesNodataResponse ndp = new NsecProvesNodataResponse(); Name ce = null; boolean hasValidNSEC = false; NSECRecord wcNsec = null; for (SRRset set : response.getSectionRRsets(Section.AUTHORITY, Type.NSEC)) { SecurityStatus status = this.verifySRRset(set, keyRrset); if (status != SecurityStatus.SECURE) { return new JustifiedSecStatus(status, R.get("failed.ds.nsec.ent")); } NSECRecord nsec = (NSECRecord)set.first(); ndp = ValUtils.nsecProvesNodata(nsec, qname, Type.DS); if (ndp.result) { hasValidNSEC = true; if (ndp.wc != null && nsec.getName().isWild()) { wcNsec = nsec; } } if (ValUtils.nsecProvesNameError(nsec, qname, set.getSignerName())) { ce = closestEncloser(qname, nsec); } } // The wildcard NODATA is 1 NSEC proving that qname does not exists (and // also proving what the closest encloser is), and 1 NSEC showing the // matching wildcard, which must be *.closest_encloser. if (ndp.wc != null && (ce == null || !ce.equals(ndp.wc))) { hasValidNSEC = false; } if (hasValidNSEC) { if (ndp.wc != null) { SecurityStatus status = nsecProvesNoDS(wcNsec, qname); return new JustifiedSecStatus(status, R.get("failed.ds.nowildcardproof")); } return new JustifiedSecStatus(SecurityStatus.INSECURE, R.get("insecure.ds.nsec.ent")); } return new JustifiedSecStatus(SecurityStatus.UNCHECKED, R.get("failed.ds.nonconclusive")); } /** * Checks if the authority section of a message contains at least one signed * NSEC or NSEC3 record. * * @param message The message to inspect. * @return True if at least one record is found, false otherwise. */ public boolean hasSignedNsecs(SMessage message) { for (SRRset set : message.getSectionRRsets(Section.AUTHORITY)) { if (set.getType() == Type.NSEC || set.getType() == Type.NSEC3) { if (set.sigs().hasNext()) { return true; } } } return false; } /** * Determines whether the given {@link NSECRecord} proves that there is no * {@link DSRecord} for <code>qname</code>. * * @param nsec The NSEC that should prove the non-existence. * @param qname The name for which the prove is made. * @return {@link SecurityStatus#BOGUS} when the NSEC is from the child * domain or indicates that there indeed is a DS record, * {@link SecurityStatus#INSECURE} when there is not even a prove * for a NS record, {@link SecurityStatus#SECURE} when there is no * DS record. */ public static SecurityStatus nsecProvesNoDS(NSECRecord nsec, Name qname) { // Could check to make sure the qname is a subdomain of nsec if ((nsec.hasType(Type.SOA) && !Name.root.equals(qname)) || nsec.hasType(Type.DS)) { // SOA present means that this is the NSEC from the child, not the // parent (so it is the wrong one) -> cannot happen because the // keyset is always from the parent zone and doesn't validate the // NSEC // DS present means that there should have been a positive response // to the DS query, so there is something wrong. return SecurityStatus.BOGUS; } if (!nsec.hasType(Type.NS)) { // If there is no NS at this point at all, then this doesn't prove // anything one way or the other. return SecurityStatus.INSECURE; } // Otherwise, this proves no DS. return SecurityStatus.SECURE; } /** * Determines if at least one of the DS records in the RRset has a supported * algorithm. * * @param dsRRset The RR set to search in. * @return True when at least one DS record uses a supported algorithm, * false otherwise. */ static boolean atLeastOneSupportedAlgorithm(RRset dsRRset) { Iterator<?> it = dsRRset.rrs(); while (it.hasNext()) { Record r = (Record)it.next(); if (r.getType() == Type.DS) { if (isAlgorithmSupported(((DSRecord)r).getAlgorithm())) { return true; } // do nothing, there could be another DS we understand } } return false; } /** * Determines if the algorithm is supported. * * @param alg The algorithm to check. * @return True when the algorithm is supported, false otherwise. */ static boolean isAlgorithmSupported(int alg) { switch (alg) { case Algorithm.RSAMD5: return false; // obsoleted by rfc6944 case Algorithm.DSA: case Algorithm.DSA_NSEC3_SHA1: case Algorithm.RSASHA1: case Algorithm.RSA_NSEC3_SHA1: case Algorithm.RSASHA256: case Algorithm.RSASHA512: case Algorithm.ECDSAP256SHA256: case Algorithm.ECDSAP384SHA384: return true; default: return false; } } /** * Determines if at least one of the DS records in the RRset has a supported * digest algorithm. * * @param dsRRset The RR set to search in. * @return True when at least one DS record uses a supported digest * algorithm, false otherwise. */ static boolean atLeastOneDigestSupported(RRset dsRRset) { Iterator<?> it = dsRRset.rrs(); while (it.hasNext()) { Record r = (Record)it.next(); if (r.getType() == Type.DS) { if (isDigestSupported(((DSRecord)r).getDigestID())) { return true; } // do nothing, there could be another DS we understand } } return false; } /** * Determines if the digest algorithm is supported. * * @param digestID the algorithm to check. * @return True when the digest algorithm is supported, false otherwise. */ static boolean isDigestSupported(int digestID) { switch (digestID) { case Digest.SHA1: case Digest.SHA256: case Digest.SHA384: return true; default: return false; } } }