private RRset makeDelegationSignerSet(DomainResource domain) { RRset signerSet = new RRset(); for (DelegationSignerData signerData : domain.getDsData()) { DSRecord dsRecord = new DSRecord( toAbsoluteName(domain.getFullyQualifiedDomainName()), DClass.IN, dnsDefaultDsTtl.getStandardSeconds(), signerData.getKeyTag(), signerData.getAlgorithm(), signerData.getDigestType(), signerData.getDigest()); signerSet.addRR(dsRecord); } return signerSet; }
private Message getCached(Message query) { Cache cache = getCache(); if (cache == null) return null; Record question = query.getQuestion(); RRset[] rrsets = cache.findAnyRecords(question.getName(), question.getType()); if (rrsets == null) return null; Message msg = new Message(); for (RRset rrset : rrsets) { @SuppressWarnings("unchecked") Iterator<Record> recordsIter = rrset.rrs(); while (recordsIter.hasNext()) { msg.addRecord(recordsIter.next(), Section.ANSWER); } } return msg; }
/** * Find the matching DNSKEY(s) to an RRSIG within a DNSKEY rrset. Normally * this will only return one DNSKEY. It can return more than one, since * KeyID/Footprints are not guaranteed to be unique. * * @param dnskeyRrset The DNSKEY rrset to search. * @param signature The RRSIG to match against. * @return A List contains a one or more DNSKEYRecord objects, or null if a * matching DNSKEY could not be found. */ private List<DNSKEYRecord> findKey(RRset dnskeyRrset, RRSIGRecord signature) { if (!signature.getSigner().equals(dnskeyRrset.getName())) { logger.trace("findKey: could not find appropriate key because incorrect keyset was supplied. Wanted: " + signature.getSigner() + ", got: " + dnskeyRrset.getName()); return null; } int keyid = signature.getFootprint(); int alg = signature.getAlgorithm(); List<DNSKEYRecord> res = new ArrayList<DNSKEYRecord>(dnskeyRrset.size()); for (Iterator<?> i = dnskeyRrset.rrs(); i.hasNext();) { DNSKEYRecord r = (DNSKEYRecord)i.next(); if (r.getAlgorithm() == alg && r.getFootprint() == keyid) { res.add(r); } } if (res.size() == 0) { logger.trace("findKey: could not find a key matching the algorithm and footprint in supplied keyset. "); return null; } return res; }
/** * Verifies an RRset. This routine does not modify the RRset. This RRset is * presumed to be verifiable, and the correct DNSKEY rrset is presumed to * have been found. * * @param rrset The RRset to verify. * @param keyRrset The keys to verify the signatures in the RRset to check. * @return SecurityStatus.SECURE if the rrest verified positively, * SecurityStatus.BOGUS otherwise. */ public SecurityStatus verify(RRset rrset, RRset keyRrset) { Iterator<?> i = rrset.sigs(); if (!i.hasNext()) { logger.info("RRset failed to verify due to lack of signatures"); return SecurityStatus.BOGUS; } while (i.hasNext()) { RRSIGRecord sigrec = (RRSIGRecord)i.next(); SecurityStatus res = this.verifySignature(rrset, sigrec, keyRrset); if (res == SecurityStatus.SECURE) { return res; } } logger.info("RRset failed to verify: all signatures were BOGUS"); return SecurityStatus.BOGUS; }
/** * 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 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; }
protected String getReason(Message m) { for (RRset set : m.getSectionRRsets(Section.ADDITIONAL)) { if (set.getName().equals(Name.root) && set.getType() == Type.TXT && set.getDClass() == ValidatingResolver.VALIDATION_REASON_QCLASS) { StringBuilder sb = new StringBuilder(); @SuppressWarnings("unchecked") List<String> strings = (List<String>)((TXTRecord)set.first()).getStrings(); for (String part : strings){ sb.append(part); } return sb.toString(); } } return null; }
public String rrSetsToString(RRset[] rrsets) { StringBuffer ansBuffer = new StringBuffer(); Iterator it; int i; for (i = 0; i < rrsets.length; i++) { RRset rrset = rrsets[i]; it = rrset.rrs(); while (it.hasNext()) { Record r = (Record) it.next(); //Log.i(TAG, "rrsetstostring: type=" + r.getType()); ansBuffer.append(r.toString()); ansBuffer.append("\n"); } //RRSIGs final Iterator<Record> sigIter = rrset.sigs(); while (sigIter.hasNext()) { final Record sigRec = sigIter.next(); ansBuffer.append(sigRec.toString()); ansBuffer.append("\n"); } } //replace tabs String ret = ansBuffer.toString().replace('\t',' '); return ret; }
private RRset makeNameServerSet(DomainResource domain) { RRset nameServerSet = new RRset(); for (String hostName : domain.loadNameserverFullyQualifiedHostNames()) { NSRecord record = new NSRecord( toAbsoluteName(domain.getFullyQualifiedDomainName()), DClass.IN, dnsDefaultNsTtl.getStandardSeconds(), toAbsoluteName(hostName)); nameServerSet.addRR(record); } return nameServerSet; }
private RRset makeAddressSet(HostResource host) { RRset addressSet = new RRset(); for (InetAddress address : host.getInetAddresses()) { if (address instanceof Inet4Address) { ARecord record = new ARecord( toAbsoluteName(host.getFullyQualifiedHostName()), DClass.IN, dnsDefaultATtl.getStandardSeconds(), address); addressSet.addRR(record); } } return addressSet; }
private RRset makeV6AddressSet(HostResource host) { RRset addressSet = new RRset(); for (InetAddress address : host.getInetAddresses()) { if (address instanceof Inet6Address) { AAAARecord record = new AAAARecord( toAbsoluteName(host.getFullyQualifiedHostName()), DClass.IN, dnsDefaultATtl.getStandardSeconds(), address); addressSet.addRR(record); } } return addressSet; }
private ImmutableList<Record> findUpdateRecords( Update update, String resourceName, int recordType) { for (RRset set : update.getSectionRRsets(Section.UPDATE)) { if (set.getName().toString().equals(resourceName) && set.getType() == recordType) { return fixIterator(Record.class, set.rrs()); } } assert_().fail( "No record set found for resource '%s' type '%s'", resourceName, Type.string(recordType)); throw new AssertionError(); }
/** * Verify an RRset against a particular signature. * * @param rrset The RRset to verify. * @param sigrec The signature record that signs the RRset. * @param keyRrset The keys used to create the signature record. * * @return {@link SecurityStatus#SECURE} if the signature verified, * {@link SecurityStatus#BOGUS} if it did not verify (for any * reason), and {@link SecurityStatus#UNCHECKED} if verification * could not be completed (usually because the public key was not * available). */ private SecurityStatus verifySignature(RRset rrset, RRSIGRecord sigrec, RRset keyRrset) { List<DNSKEYRecord> keys = this.findKey(keyRrset, sigrec); if (keys == null) { logger.trace("could not find appropriate key"); return SecurityStatus.BOGUS; } SecurityStatus status = SecurityStatus.UNCHECKED; for (DNSKEYRecord key : keys) { try { if (!rrset.getName().subdomain(keyRrset.getName())) { logger.debug("signer name is off-tree"); status = SecurityStatus.BOGUS; continue; } DNSSEC.verify(rrset, sigrec, key); return SecurityStatus.SECURE; } catch (DNSSECException e) { logger.error("Failed to validate RRset", e); status = SecurityStatus.BOGUS; } } return status; }
/** * Verify an RRset against a single DNSKEY. Use this when you must be * certain that an RRset signed and verifies with a particular DNSKEY (as * opposed to a particular DNSKEY rrset). * * @param rrset The rrset to verify. * @param dnskey The DNSKEY to verify with. * @return SecurityStatus.SECURE if the rrset verified, BOGUS otherwise. */ public SecurityStatus verify(RRset rrset, DNSKEYRecord dnskey) { Iterator<?> i = rrset.sigs(); if (!i.hasNext()) { logger.info("RRset failed to verify due to lack of signatures"); return SecurityStatus.BOGUS; } while (i.hasNext()) { RRSIGRecord sigrec = (RRSIGRecord)i.next(); // Skip RRSIGs that do not match our given key's footprint. if (sigrec.getFootprint() != dnskey.getFootprint()) { continue; } try { DNSSEC.verify(rrset, sigrec, dnskey); return SecurityStatus.SECURE; } catch (DNSSECException e) { logger.error("Failed to validate RRset", e); } } logger.info("RRset failed to verify: all signatures were BOGUS"); return SecurityStatus.BOGUS; }
/** * Creates a new instance of this class. * * @param m The DNS message to wrap. */ public SMessage(Message m) { this(m.getHeader()); this.question = m.getQuestion(); this.oPTRecord = m.getOPT(); for (int i = Section.ANSWER; i <= Section.ADDITIONAL; i++) { RRset[] rrsets = m.getSectionRRsets(i); for (int j = 0; j < rrsets.length; j++) { this.addRRset(new SRRset(rrsets[j]), i); } } }
/** * Create a new SRRset from an existing RRset. This SRRset will contain that * same internal Record objects as the original RRset. * * @param r The RRset to copy. */ public SRRset(RRset r) { this(); for (Iterator<?> i = r.rrs(); i.hasNext();) { addRR((Record)i.next()); } for (Iterator<?> i = r.sigs(); i.hasNext();) { addRR((Record)i.next()); } }
private Message stripAdditional(Message m) { if (m.getQuestion().getType() == Type.RRSIG) { return m; } Message copy = new Message(); copy.setHeader(m.getHeader()); for (int i = 0; i < Section.ADDITIONAL; i++) { for (RRset set : m.getSectionRRsets(i)) { if (set.getType() == Type.NS && m.getQuestion().getType() != Type.NS) { continue; } Iterator<?> rrs = set.rrs(); while (rrs.hasNext()) { copy.addRecord((Record)rrs.next(), i); } Iterator<?> sigs = set.sigs(); while (sigs.hasNext()) { copy.addRecord((Record)sigs.next(), i); } } } return copy; }
@Test public void testDNameDirectQueryIsValid() throws IOException { Message response = resolver.send(createMessage("alias.ingotronic.ch./DNAME")); assertTrue("AD flag must not set", response.getHeader().getFlag(Flags.AD)); assertEquals(Rcode.NOERROR, response.getRcode()); assertNull(getReason(response)); for (RRset set : response.getSectionRRsets(Section.ANSWER)) { if (set.getType() == Type.DNAME) { DNAMERecord r = (DNAMERecord)set.first(); assertEquals(Name.fromString("ingotronic.ch."), r.getTarget()); } } }
@SuppressWarnings("unchecked") @Test public void testDNameInNsecIsUnderstood_Rfc6672_5_3_4_1() throws IOException { Message nsecs = resolver.send(createMessage("alias.ingotronic.ch./NS")); RRset nsecSet = null; for (RRset set : nsecs.getSectionRRsets(Section.AUTHORITY)) { if (set.getName().equals(Name.fromString("alias.ingotronic.ch."))) { nsecSet = set; break; } } Message message = new Message(); message.getHeader().setRcode(Rcode.NXDOMAIN); message.addRecord(Record.newRecord(Name.fromString("www.alias.ingotronic.ch."), Type.A, DClass.IN), Section.QUESTION); Iterator<Record> rrs = nsecSet.rrs(); while (rrs.hasNext()) { message.addRecord(rrs.next(), Section.AUTHORITY); } Iterator<Record> sigs = nsecSet.sigs(); while (sigs.hasNext()) { message.addRecord(sigs.next(), Section.AUTHORITY); } add("www.alias.ingotronic.ch./A", message); Message response = resolver.send(createMessage("www.alias.ingotronic.ch./A")); assertFalse("AD flag must not be set", response.getHeader().getFlag(Flags.AD)); assertEquals(Rcode.SERVFAIL, response.getRcode()); assertEquals("failed.nxdomain.exists:www.alias.ingotronic.ch.", getReason(response)); }
@Test @AlwaysOffline public void testNoDSProofCanExistForRoot() throws IOException { // ./DS can exist resolver.getTrustAnchors().clear(); resolver.getTrustAnchors().store(new SRRset(new RRset(toRecord(". 300 IN DS 16758 7 1 EC88DF5E2902FD4AB9E9C246BEEA9B822BD7BCF7")))); Message response = resolver.send(createMessage("./DS")); assertTrue("AD flag must be set", response.getHeader().getFlag(Flags.AD)); assertEquals(Rcode.NOERROR, response.getRcode()); assertNull(getReason(response)); }
@Test @AlwaysOffline public void testNodataNsec3ForDSMustNotHaveSOA() throws IOException { // bogus./DS cannot coexist with bogus./SOA resolver.getTrustAnchors().clear(); resolver.getTrustAnchors().store(new SRRset(new RRset(toRecord("bogus. 300 IN DS 16758 7 1 A5D56841416AB42DC39629E42D12C98B0E94232A")))); Message response = resolver.send(createMessage("bogus./DS")); assertTrue("AD flag must be set", response.getHeader().getFlag(Flags.AD)); assertEquals(Rcode.NOERROR, response.getRcode()); assertNull(getReason(response)); }
@Test public void testVerifyWithoutSignaturesIsBogus() { DnsSecVerifier verifier = new DnsSecVerifier(); ARecord record = new ARecord(Name.root, DClass.IN, 120, localhost); RRset set = new RRset(record); RRset keys = new RRset(); SecurityStatus result = verifier.verify(set, keys); assertEquals(SecurityStatus.BOGUS, result); }
@Test public void testLoadRootTrustAnchorWithDNSKEY() throws IOException { Message keys = resolver.send(createMessage("./DNSKEY")); ByteArrayOutputStream bos = new ByteArrayOutputStream(); OutputStreamWriter osw = new OutputStreamWriter(bos); for (RRset set : keys.getSectionRRsets(Section.ANSWER)) { if (set.getType() == Type.DNSKEY) { @SuppressWarnings("unchecked") Iterator<DNSKEYRecord> it = set.rrs(); while (it.hasNext()) { DNSKEYRecord r = it.next(); osw.write(r.toString()); osw.write('\n'); } } } osw.close(); resolver.getTrustAnchors().clear(); resolver.loadTrustAnchors(new ByteArrayInputStream(bos.toByteArray())); assertNotNull(resolver.getTrustAnchors().find(Name.root, DClass.IN)); Message response = resolver.send(createMessage("www.ingotronic.ch./A")); assertTrue("AD flag must be set", response.getHeader().getFlag(Flags.AD)); assertEquals(Rcode.NOERROR, response.getRcode()); assertNull(getReason(response)); }
@SuppressWarnings("unchecked") protected String firstA(Message response) { RRset[] sectionRRsets = response.getSectionRRsets(Section.ANSWER); if (sectionRRsets.length > 0) { Iterator<Record> rrs = sectionRRsets[0].rrs(); while (rrs.hasNext()) { Record r = rrs.next(); if (r.getType() == Type.A) { return ((ARecord)r).getAddress().getHostAddress(); } } } return null; }
@Test public void testKeyWhenNameUnderAnchorDS() throws TextParseException { SRRset set = new SRRset(new RRset(new DSRecord(Name.fromString("bla."), DClass.IN, 0, 0, 0, 0, new byte[]{0}))); TrustAnchorStore tas = new TrustAnchorStore(); tas.store(set); SRRset anchor = tas.find(Name.fromString("asdf.bla."), DClass.IN); assertEquals(set, anchor); }
@Test public void testKeyWhenNameUnderAnchorDNSKEY() throws TextParseException { SRRset set = new SRRset(new RRset(new DNSKEYRecord(Name.fromString("bla."), DClass.IN, 0, 0, 0, 0, new byte[]{0}))); TrustAnchorStore tas = new TrustAnchorStore(); tas.store(set); SRRset anchor = tas.find(Name.fromString("asdf.bla."), DClass.IN); assertEquals(set.getName(), anchor.getName()); }
@Test public void testClear() throws TextParseException { SRRset set = new SRRset(new RRset(new DNSKEYRecord(Name.fromString("bla."), DClass.IN, 0, 0, 0, 0, new byte[]{0}))); TrustAnchorStore tas = new TrustAnchorStore(); tas.store(set); SRRset anchor = tas.find(Name.fromString("asdf.bla."), DClass.IN); assertNotNull(anchor); tas.clear(); assertNull(tas.find(Name.fromString("asdf.bla."), DClass.IN)); }
@Test public void testCacheOnlySecureDNSKEYs() throws TextParseException { KeyCache kc = new KeyCache(); DNSKEYRecord rA = new DNSKEYRecord(Name.fromString("a."), DClass.IN, 60, 0, 0, 0, new byte[]{0}); SRRset setA = new SRRset(new RRset(rA)); setA.setSecurityStatus(SecurityStatus.SECURE); KeyEntry nkeA = KeyEntry.newKeyEntry(setA); kc.store(nkeA); DSRecord rB = new DSRecord(Name.fromString("b."), DClass.IN, 60, 0, 0, 0, new byte[]{0}); SRRset setB = new SRRset(new RRset(rB)); KeyEntry nkeB = KeyEntry.newKeyEntry(setB); kc.store(nkeB); DNSKEYRecord rC = new DNSKEYRecord(Name.fromString("c."), DClass.IN, 60, 0, 0, 0, new byte[]{0}); SRRset setC = new SRRset(new RRset(rC)); KeyEntry nkeC = KeyEntry.newKeyEntry(setC); kc.store(nkeC); KeyEntry fromCacheA = kc.find(Name.fromString("a."), DClass.IN); assertEquals(nkeA, fromCacheA); KeyEntry fromCacheB = kc.find(Name.fromString("b."), DClass.IN); assertNull(fromCacheB); KeyEntry fromCacheC = kc.find(Name.fromString("c."), DClass.IN); assertNull(fromCacheC); }
/** * Validate the DNSSEC trust chain against the provided domain name (i.e. <code>Fqdn</code>). * * @param name A <code>Fqdn</code> representing the validating domain * @param resolver A DNS <code>Resovler</code> to be used in this validation * @param rType An integer representing the record type * * @return <code>true</code> iff the DNSSEC is valid * * @throws LookupException * Containing the specific <code>StatusCode</code> defining the error that has been raised. */ public static boolean checkDnsSec(Fqdn name, Resolver resolver, int rType) throws LookupException { try { ValidatingResolver validating = (ValidatingResolver) resolver; Record toValidate = Record.newRecord(Name.fromConstantString(name.fqdn()), rType, DClass.IN); Message dnsResponse = validating.send(Message.newQuery(toValidate)); RRset[] rrSets = dnsResponse.getSectionRRsets(Section.ADDITIONAL); StringBuilder reason = new StringBuilder(""); for (RRset rrset : rrSets) { if (rrset.getName().equals(Name.root) && rrset.getType() == Type.TXT && rrset.getDClass() == ValidatingResolver.VALIDATION_REASON_QCLASS) { reason.append(TextRecord.build((TXTRecord) rrset.first()).getRData()); } } StatusCode outcome = StatusCode.SUCCESSFUL_OPERATION; if (dnsResponse.getRcode() == Rcode.SERVFAIL) { if (reason.toString().toLowerCase().contains(CHAIN_OF_TRUST) || reason.toString().toLowerCase().contains(INSECURE)) { outcome = StatusCode.RESOURCE_INSECURE_ERROR; } else if (reason.toString().toLowerCase().contains(NO_DATA)) { outcome = StatusCode.NETWORK_ERROR; } else if (reason.toString().toLowerCase().contains(NO_SIGNATURE) || reason.toString().toLowerCase().contains(MISSING_KEY)) { outcome = StatusCode.RESOLUTION_NAME_ERROR; } } else if (dnsResponse.getRcode() == Rcode.NXDOMAIN) { if (reason.toString().toLowerCase().contains(NSEC3_NO_DS)) { outcome = StatusCode.RESOURCE_INSECURE_ERROR; } else { outcome = StatusCode.RESOLUTION_NAME_ERROR; } } else if (dnsResponse.getRcode() == Rcode.NOERROR && !dnsResponse.getHeader().getFlag(Flags.AD)) { outcome = StatusCode.RESOURCE_INSECURE_ERROR; } if (outcome != StatusCode.SUCCESSFUL_OPERATION) { throw ExceptionsUtil.build(outcome, "DNSSEC Validation Failed", new LinkedHashMap<String, StatusCode>()); } } catch (IOException e) { // it might be a transient error network: retry with next Resolver return false; } return true; }
/** * 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; }
@Test public void testAtLeastOneSupportedAlgorithmWithOnlyNonDSRecords() { RRset set = new RRset(new NSECRecord(Name.root, DClass.IN, 0, Name.root, new int[] { Type.A })); boolean result = ValUtils.atLeastOneSupportedAlgorithm(set); assertFalse(result); }
@Test public void testAtLeastOneDigestSupportedWithOnlyNonDSRecords() { RRset set = new RRset(new NSECRecord(Name.root, DClass.IN, 0, Name.root, new int[] { Type.A })); boolean result = ValUtils.atLeastOneDigestSupported(set); assertFalse(result); }
protected boolean isEmptyAnswer(Message response) { RRset[] sectionRRsets = response.getSectionRRsets(Section.ANSWER); return sectionRRsets.length == 0; }
@Test(expected = IllegalArgumentException.class) public void testInvalidAnchorRecord() throws TextParseException { SRRset set = new SRRset(new RRset(new TXTRecord(Name.fromString("bla."), DClass.IN, 0, "root"))); TrustAnchorStore tas = new TrustAnchorStore(); tas.store(set); }
public synchronized void addRRset(RRset arg0, int arg1) { throw new UnsupportedOperationException("ZoneCache is a mock used only for testing purpose"); }
public RRset[] findAnyRecords(Name arg0, int arg1) { throw new UnsupportedOperationException("ZoneCache is a mock used only for testing purpose"); }
public RRset[] findRecords(Name arg0, int arg1) { throw new UnsupportedOperationException("ZoneCache is a mock used only for testing purpose"); }