More progress, no idea if we are working yet
This commit is contained in:
parent
1f647e3c77
commit
0b91cbfb1f
2
TODO
Normal file
2
TODO
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
* Add way to report errors and validation failure conditions.
|
||||||
|
|
@ -61,35 +61,18 @@ public class CaptiveValidator {
|
|||||||
// ---------------- Module Initialization -------------------
|
// ---------------- Module Initialization -------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the module.
|
* Add a set of trusted keys from a file. The file should be in DNS master
|
||||||
*/
|
* zone file format. Only DNSKEY records will be added.
|
||||||
public void init(Properties config) throws Exception {
|
|
||||||
mVerifier.init(config);
|
|
||||||
|
|
||||||
String s = config.getProperty("dns.trust_anchor_file");
|
|
||||||
if (s != null) {
|
|
||||||
try {
|
|
||||||
loadTrustAnchors(s);
|
|
||||||
} catch (IOException e) {
|
|
||||||
System.err.println("Error loading trust anchors: " + e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load the trust anchor file into the trust anchor store. The trust anchors
|
|
||||||
* are currently stored in a zone file format list of DNSKEY or DS records.
|
|
||||||
*
|
*
|
||||||
* @param filename
|
* @param filename
|
||||||
* The trust anchor file.
|
* The file contains the trusted keys.
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
private void loadTrustAnchors(String filename) throws IOException {
|
@SuppressWarnings("unchecked")
|
||||||
System.err.println("reading trust anchor file file: " + filename);
|
public void addTrustedKeysFromFile(String filename) throws IOException {
|
||||||
|
|
||||||
// First read in the whole trust anchor file.
|
// First read in the whole trust anchor file.
|
||||||
Master master = new Master(filename, Name.root, 0);
|
Master master = new Master(filename, Name.root, 0);
|
||||||
ArrayList records = new ArrayList();
|
ArrayList<Record> records = new ArrayList<Record>();
|
||||||
Record r = null;
|
Record r = null;
|
||||||
|
|
||||||
while ((r = master.nextRecord()) != null) {
|
while ((r = master.nextRecord()) != null) {
|
||||||
@ -102,28 +85,28 @@ public class CaptiveValidator {
|
|||||||
Collections.sort(records);
|
Collections.sort(records);
|
||||||
|
|
||||||
SRRset cur_rrset = new SRRset();
|
SRRset cur_rrset = new SRRset();
|
||||||
for (Iterator i = records.iterator(); i.hasNext();) {
|
for (Record rec : records) {
|
||||||
r = (Record) i.next();
|
// Skip RR types that cannot be used as trusted keys. I.e.,
|
||||||
// Skip RR types that cannot be used as trust anchors.
|
// everything not a key :)
|
||||||
if (r.getType() != Type.DNSKEY && r.getType() != Type.DS) continue;
|
if (rec.getType() != Type.DNSKEY) continue;
|
||||||
|
|
||||||
// If our cur_rrset is empty, we can just add it.
|
// If our cur_rrset is empty, we can just add it.
|
||||||
if (cur_rrset.size() == 0) {
|
if (cur_rrset.size() == 0) {
|
||||||
cur_rrset.addRR(r);
|
cur_rrset.addRR(rec);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// If this record matches our current RRset, we can just add it.
|
// If this record matches our current RRset, we can just add it.
|
||||||
if (cur_rrset.getName().equals(r.getName())
|
if (cur_rrset.getName().equals(rec.getName())
|
||||||
&& cur_rrset.getType() == r.getType()
|
&& cur_rrset.getType() == rec.getType()
|
||||||
&& cur_rrset.getDClass() == r.getDClass()) {
|
&& cur_rrset.getDClass() == rec.getDClass()) {
|
||||||
cur_rrset.addRR(r);
|
cur_rrset.addRR(rec);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, we add the rrset to our set of trust anchors.
|
// Otherwise, we add the rrset to our set of trust anchors.
|
||||||
mTrustedKeys.store(cur_rrset);
|
mTrustedKeys.store(cur_rrset);
|
||||||
cur_rrset = new SRRset();
|
cur_rrset = new SRRset();
|
||||||
cur_rrset.addRR(r);
|
cur_rrset.addRR(rec);
|
||||||
}
|
}
|
||||||
|
|
||||||
// add the last rrset (if it was not empty)
|
// add the last rrset (if it was not empty)
|
||||||
@ -132,14 +115,155 @@ public class CaptiveValidator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addTrustedKeysFromResponse(Message m) {
|
||||||
|
RRset[] rrsets = m.getSectionRRsets(Section.ANSWER);
|
||||||
|
for (int i = 0; i < rrsets.length; ++i) {
|
||||||
|
if (rrsets[i].getType() == Type.DNSKEY) {
|
||||||
|
SRRset srrset = new SRRset(rrsets[i]);
|
||||||
|
mTrustedKeys.store(srrset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------- Validation Support ----------------------
|
// ----------------- Validation Support ----------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This routine normalizes a response. This includes removing "irrelevant"
|
||||||
|
* records from the answer and additional sections and (re)synthesizing
|
||||||
|
* CNAMEs from DNAMEs, if present.
|
||||||
|
*
|
||||||
|
* @param response
|
||||||
|
*/
|
||||||
|
private SMessage normalize(SMessage m) {
|
||||||
|
if (m == null) return m;
|
||||||
|
|
||||||
|
if (m.getRcode() != Rcode.NOERROR && m.getRcode() != Rcode.NXDOMAIN) {
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
Name qname = m.getQuestion().getName();
|
||||||
|
int qtype = m.getQuestion().getType();
|
||||||
|
|
||||||
|
Name sname = qname;
|
||||||
|
|
||||||
|
// For the ANSWER section, remove all "irrelevant" records and add
|
||||||
|
// synthesized CNAMEs from DNAMEs
|
||||||
|
// This will strip out-of-order CNAMEs as well.
|
||||||
|
List<SRRset> rrset_list = m.getSectionList(Section.ANSWER);
|
||||||
|
Set<Name> additional_names = new HashSet<Name>();
|
||||||
|
|
||||||
|
for (ListIterator<SRRset> i = rrset_list.listIterator(); i.hasNext();) {
|
||||||
|
SRRset rrset = i.next();
|
||||||
|
int type = rrset.getType();
|
||||||
|
Name n = rrset.getName();
|
||||||
|
|
||||||
|
// Handle DNAME synthesis; DNAME synthesis does not occur at the
|
||||||
|
// DNAME name itself.
|
||||||
|
if (type == Type.DNAME && ValUtils.strictSubdomain(sname, n)) {
|
||||||
|
if (rrset.size() > 1) {
|
||||||
|
// log.debug("Found DNAME rrset with size > 1: " + rrset);
|
||||||
|
// return Util.errorMessage(m, Rcode.SERVFAIL);
|
||||||
|
return null; // FIXME
|
||||||
|
}
|
||||||
|
DNAMERecord dname = (DNAMERecord) rrset.first();
|
||||||
|
try {
|
||||||
|
Name cname_alias = sname.fromDNAME(dname);
|
||||||
|
// Note that synthesized CNAMEs should have a TTL of zero.
|
||||||
|
|
||||||
|
CNAMERecord cname = new CNAMERecord(sname,
|
||||||
|
dname.getDClass(), 0, cname_alias);
|
||||||
|
SRRset cname_rrset = new SRRset();
|
||||||
|
cname_rrset.addRR(cname);
|
||||||
|
i.add(cname_rrset);
|
||||||
|
|
||||||
|
sname = cname_alias;
|
||||||
|
} catch (NameTooLongException e) {
|
||||||
|
// log.debug("not adding synthesized CNAME -- "
|
||||||
|
// + "generated name is too long", e);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The only records in the ANSWER section not allowed to
|
||||||
|
if (!n.equals(sname)) {
|
||||||
|
// log.debug("normalize: removing irrelevant rrset: " + rrset);
|
||||||
|
i.remove();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Follow the CNAME chain.
|
||||||
|
if (type == Type.CNAME) {
|
||||||
|
if (rrset.size() > 1) {
|
||||||
|
// log.debug("Found CNAME rrset with size > 1: " + rrset);
|
||||||
|
// return Util.errorMessage(m, Rcode.SERVFAIL);
|
||||||
|
return null; // FIXME
|
||||||
|
}
|
||||||
|
|
||||||
|
CNAMERecord cname = (CNAMERecord) rrset.first();
|
||||||
|
sname = cname.getAlias();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, make sure that the RRset matches the qtype.
|
||||||
|
if (qtype != Type.ANY && qtype != type) {
|
||||||
|
// log.debug("normalize: removing irrelevant rrset: " + rrset);
|
||||||
|
i.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, fetch the additional names from the relevant rrset.
|
||||||
|
rrsetAdditionalNames(additional_names, rrset);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get additional names from AUTHORITY
|
||||||
|
rrset_list = m.getSectionList(Section.AUTHORITY);
|
||||||
|
for (SRRset rrset : rrset_list) {
|
||||||
|
rrsetAdditionalNames(additional_names, rrset);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For each record in the additional section, remove it if it is an
|
||||||
|
// address record and not in the collection of additional names found in
|
||||||
|
// ANSWER and AUTHORITY.
|
||||||
|
rrset_list = m.getSectionList(Section.ADDITIONAL);
|
||||||
|
for (Iterator<SRRset> i = rrset_list.iterator(); i.hasNext();) {
|
||||||
|
SRRset rrset = i.next();
|
||||||
|
int type = rrset.getType();
|
||||||
|
if ((type == Type.A || type == Type.AAAA)
|
||||||
|
&& !additional_names.contains(rrset.getName())) {
|
||||||
|
i.remove();
|
||||||
|
}
|
||||||
|
// FIXME: what about other types?
|
||||||
|
}
|
||||||
|
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract additional names from the records in an rrset.
|
||||||
|
*
|
||||||
|
* @param additional_names
|
||||||
|
* The set to add the additional names to, if any.
|
||||||
|
* @param rrset
|
||||||
|
* The rrset to extract from.
|
||||||
|
*/
|
||||||
|
private void rrsetAdditionalNames(Set<Name> additional_names, SRRset rrset) {
|
||||||
|
if (rrset == null) return;
|
||||||
|
|
||||||
|
for (Iterator<Record> i = rrset.rrs(); i.hasNext();) {
|
||||||
|
Record r = i.next();
|
||||||
|
Name add_name = r.getAdditionalName();
|
||||||
|
if (add_name != null) {
|
||||||
|
additional_names.add(add_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private SRRset findKeys(SMessage message) {
|
private SRRset findKeys(SMessage message) {
|
||||||
Name qname = message.getQName();
|
Name qname = message.getQName();
|
||||||
int qclass = message.getQClass();
|
int qclass = message.getQClass();
|
||||||
|
|
||||||
return mTrustedKeys.find(qname, qclass);
|
return mTrustedKeys.find(qname, qclass);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check to see if a given response needs to go through the validation
|
* Check to see if a given response needs to go through the validation
|
||||||
* process. Typical reasons for this routine to return false are: CD bit was
|
* process. Typical reasons for this routine to return false are: CD bit was
|
||||||
@ -156,10 +280,6 @@ public class CaptiveValidator {
|
|||||||
* mean we can actually validate this response).
|
* mean we can actually validate this response).
|
||||||
*/
|
*/
|
||||||
private boolean needsValidation(SMessage message) {
|
private boolean needsValidation(SMessage message) {
|
||||||
|
|
||||||
// FIXME: add check to see if message qname is at or below any of our
|
|
||||||
// configured trust anchors.
|
|
||||||
|
|
||||||
int rcode = message.getRcode();
|
int rcode = message.getRcode();
|
||||||
|
|
||||||
if (rcode != Rcode.NOERROR && rcode != Rcode.NXDOMAIN) {
|
if (rcode != Rcode.NOERROR && rcode != Rcode.NXDOMAIN) {
|
||||||
@ -168,6 +288,9 @@ public class CaptiveValidator {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!mTrustedKeys.isBelowTrustAnchor(message.getQName(), message.getQClass())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,7 +323,7 @@ public class CaptiveValidator {
|
|||||||
Name wc = null;
|
Name wc = null;
|
||||||
boolean wcNSEC_ok = false;
|
boolean wcNSEC_ok = false;
|
||||||
boolean dname = false;
|
boolean dname = false;
|
||||||
List nsec3s = null;
|
List<NSEC3Record> nsec3s = null;
|
||||||
|
|
||||||
for (int i = 0; i < rrsets.length; i++) {
|
for (int i = 0; i < rrsets.length; i++) {
|
||||||
// Skip the CNAME following a (validated) DNAME.
|
// Skip the CNAME following a (validated) DNAME.
|
||||||
@ -217,8 +340,8 @@ public class CaptiveValidator {
|
|||||||
// If the (answer) rrset failed to validate, then this message is
|
// If the (answer) rrset failed to validate, then this message is
|
||||||
// BAD.
|
// BAD.
|
||||||
if (status != SecurityStatus.SECURE) {
|
if (status != SecurityStatus.SECURE) {
|
||||||
// log.debug("Positive response has failed ANSWER rrset: "
|
// log.debug("Positive response has failed ANSWER rrset: "
|
||||||
// + rrsets[i]);
|
// + rrsets[i]);
|
||||||
m.setStatus(SecurityStatus.BOGUS);
|
m.setStatus(SecurityStatus.BOGUS);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -242,8 +365,8 @@ public class CaptiveValidator {
|
|||||||
// a
|
// a
|
||||||
// bad message.
|
// bad message.
|
||||||
if (status != SecurityStatus.SECURE) {
|
if (status != SecurityStatus.SECURE) {
|
||||||
// log.debug("Positive response has failed AUTHORITY rrset: "
|
// log.debug("Positive response has failed AUTHORITY rrset: "
|
||||||
// + rrsets[i]);
|
// + rrsets[i]);
|
||||||
m.setStatus(SecurityStatus.BOGUS);
|
m.setStatus(SecurityStatus.BOGUS);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -258,8 +381,8 @@ public class CaptiveValidator {
|
|||||||
key_rrset.getName())) {
|
key_rrset.getName())) {
|
||||||
Name nsec_wc = ValUtils.nsecWildcard(qname, nsec);
|
Name nsec_wc = ValUtils.nsecWildcard(qname, nsec);
|
||||||
if (!wc.equals(nsec_wc)) {
|
if (!wc.equals(nsec_wc)) {
|
||||||
// log.debug("Postive wildcard response wasn't generated "
|
// log.debug("Positive wildcard response wasn't generated "
|
||||||
// + "by the correct wildcard");
|
// + "by the correct wildcard");
|
||||||
m.setStatus(SecurityStatus.BOGUS);
|
m.setStatus(SecurityStatus.BOGUS);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -270,8 +393,8 @@ public class CaptiveValidator {
|
|||||||
// Otherwise, if this is a positive wildcard response and we have
|
// Otherwise, if this is a positive wildcard response and we have
|
||||||
// NSEC3 records, collect them.
|
// NSEC3 records, collect them.
|
||||||
if (wc != null && rrsets[i].getType() == Type.NSEC3) {
|
if (wc != null && rrsets[i].getType() == Type.NSEC3) {
|
||||||
if (nsec3s == null) nsec3s = new ArrayList();
|
if (nsec3s == null) nsec3s = new ArrayList<NSEC3Record>();
|
||||||
nsec3s.add(rrsets[i].first());
|
nsec3s.add((NSEC3Record) rrsets[i].first());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -288,18 +411,113 @@ public class CaptiveValidator {
|
|||||||
// If after all this, we still haven't proven the positive wildcard
|
// If after all this, we still haven't proven the positive wildcard
|
||||||
// response, fail.
|
// response, fail.
|
||||||
if (wc != null && !wcNSEC_ok) {
|
if (wc != null && !wcNSEC_ok) {
|
||||||
// log.debug("positive response was wildcard expansion and "
|
// log.debug("positive response was wildcard expansion and "
|
||||||
// + "did not prove original data did not exist");
|
// + "did not prove original data did not exist");
|
||||||
m.setStatus(SecurityStatus.BOGUS);
|
m.setStatus(SecurityStatus.BOGUS);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// log.trace("Successfully validated postive response");
|
// log.trace("Successfully validated positive response");
|
||||||
m.setStatus(SecurityStatus.SECURE);
|
m.setStatus(SecurityStatus.SECURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validateReferral(SMessage message, SRRset key_rrset) {
|
private void validateReferral(SMessage message, SRRset key_rrset) {
|
||||||
|
SMessage m = message;
|
||||||
|
|
||||||
|
if (m.getCount(Section.ANSWER) > 0) {
|
||||||
|
// FIXME: fail somehow.
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate the AUTHORITY section.
|
||||||
|
SRRset[] rrsets = m.getSectionRRsets(Section.AUTHORITY);
|
||||||
|
|
||||||
|
boolean secure_delegation = false;
|
||||||
|
Name delegation = null;
|
||||||
|
Name nsec3zone = null;
|
||||||
|
NSECRecord nsec = null;
|
||||||
|
List<NSEC3Record> nsec3s = null;
|
||||||
|
|
||||||
|
// validate the AUTHORITY section as well - this will generally be the
|
||||||
|
// NS rrset, plus proof of a secure delegation or not
|
||||||
|
rrsets = m.getSectionRRsets(Section.AUTHORITY);
|
||||||
|
for (int i = 0; i < rrsets.length; i++) {
|
||||||
|
int status = mValUtils.verifySRRset(rrsets[i], key_rrset);
|
||||||
|
// If anything in the authority section fails to be secure, we have
|
||||||
|
// a bad message.
|
||||||
|
if (status != SecurityStatus.SECURE) {
|
||||||
|
// log.debug("Positive response has failed AUTHORITY rrset: "
|
||||||
|
// + rrsets[i]);
|
||||||
|
m.setStatus(SecurityStatus.BOGUS);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int type = rrsets[i].getType();
|
||||||
|
switch (type) {
|
||||||
|
case Type.DS:
|
||||||
|
secure_delegation = true;
|
||||||
|
break;
|
||||||
|
case Type.NS:
|
||||||
|
delegation = rrsets[i].getName();
|
||||||
|
break;
|
||||||
|
case Type.NSEC:
|
||||||
|
nsec = (NSECRecord) rrsets[i].first();
|
||||||
|
break;
|
||||||
|
case Type.NSEC3:
|
||||||
|
if (nsec3s == null) nsec3s = new ArrayList<NSEC3Record>();
|
||||||
|
NSEC3Record nsec3 = (NSEC3Record) rrsets[i].first();
|
||||||
|
nsec3s.add(nsec3);
|
||||||
|
nsec3zone = rrsets[i].getSignerName(); // this is a hack of sorts.
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// FIXME: should probably whine if we see something else.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point, all validatable RRsets have been validated.
|
||||||
|
// Now to check to see if we have a valid combination of things.
|
||||||
|
if (delegation == null) {
|
||||||
|
// somehow we have a referral without an NS rrset.
|
||||||
|
m.setStatus(SecurityStatus.BOGUS);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (secure_delegation) {
|
||||||
|
if (nsec != null || nsec3s.size() > 0) {
|
||||||
|
// we found both a DS rrset *and* NSEC/NSEC3 rrsets!
|
||||||
|
m.setStatus(SecurityStatus.BOGUS);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// otherwise, we are done.
|
||||||
|
m.setStatus(SecurityStatus.SECURE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: not going to care if both NSEC and NSEC3 rrsets were present.
|
||||||
|
if (nsec != null) {
|
||||||
|
byte status = ValUtils.nsecProvesNoDS(nsec, delegation);
|
||||||
|
if (status != SecurityStatus.SECURE) {
|
||||||
|
// The NSEC *must* prove that there was no DS record. The INSECURE state here is still bogus.
|
||||||
|
m.setStatus(SecurityStatus.BOGUS);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m.setStatus(SecurityStatus.SECURE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nsec3s.size() > 0) {
|
||||||
|
byte status = NSEC3ValUtils.proveNoDS(nsec3s, delegation, nsec3zone);
|
||||||
|
if (status != SecurityStatus.SECURE) {
|
||||||
|
// the NSEC3 RRs MUST prove no DS, so the INDETERMINATE state is actually bogus
|
||||||
|
m.setStatus(SecurityStatus.BOGUS);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m.setStatus(SecurityStatus.SECURE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// failed to find proof either way.
|
||||||
|
m.setStatus(SecurityStatus.BOGUS);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validateCNAMEResponse(SMessage message, SRRset key_rrset) {
|
private void validateCNAMEResponse(SMessage message, SRRset key_rrset) {
|
||||||
@ -345,8 +563,8 @@ public class CaptiveValidator {
|
|||||||
// If the (answer) rrset failed to validate, then this message is
|
// If the (answer) rrset failed to validate, then this message is
|
||||||
// BAD.
|
// BAD.
|
||||||
if (status != SecurityStatus.SECURE) {
|
if (status != SecurityStatus.SECURE) {
|
||||||
// log.debug("Postive response has failed ANSWER rrset: "
|
// log.debug("Positive response has failed ANSWER rrset: "
|
||||||
// + rrsets[i]);
|
// + rrsets[i]);
|
||||||
m.setStatus(SecurityStatus.BOGUS);
|
m.setStatus(SecurityStatus.BOGUS);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -361,14 +579,14 @@ public class CaptiveValidator {
|
|||||||
// a
|
// a
|
||||||
// bad message.
|
// bad message.
|
||||||
if (status != SecurityStatus.SECURE) {
|
if (status != SecurityStatus.SECURE) {
|
||||||
// log.debug("Postive response has failed AUTHORITY rrset: "
|
// log.debug("Positive response has failed AUTHORITY rrset: "
|
||||||
// + rrsets[i]);
|
// + rrsets[i]);
|
||||||
m.setStatus(SecurityStatus.BOGUS);
|
m.setStatus(SecurityStatus.BOGUS);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// log.trace("Successfully validated postive ANY response");
|
// log.trace("Successfully validated positive ANY response");
|
||||||
m.setStatus(SecurityStatus.SECURE);
|
m.setStatus(SecurityStatus.SECURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -406,19 +624,19 @@ public class CaptiveValidator {
|
|||||||
|
|
||||||
boolean hasValidNSEC = false; // If true, then the NODATA has been
|
boolean hasValidNSEC = false; // If true, then the NODATA has been
|
||||||
// proven.
|
// proven.
|
||||||
Name ce = null; // for wildcard nodata responses. This is the proven
|
Name ce = null; // for wildcard NODATA responses. This is the proven
|
||||||
// closest encloser.
|
// closest encloser.
|
||||||
NSECRecord wc = null; // for wildcard nodata responses. This is the
|
NSECRecord wc = null; // for wildcard NODATA responses. This is the
|
||||||
// wildcard NSEC.
|
// wildcard NSEC.
|
||||||
List nsec3s = null; // A collection of NSEC3 RRs found in the authority
|
List<NSEC3Record> nsec3s = null; // A collection of NSEC3 RRs found in the authority
|
||||||
// section.
|
// section.
|
||||||
Name nsec3Signer = null; // The RRSIG signer field for the NSEC3 RRs.
|
Name nsec3Signer = null; // The RRSIG signer field for the NSEC3 RRs.
|
||||||
|
|
||||||
for (int i = 0; i < rrsets.length; i++) {
|
for (int i = 0; i < rrsets.length; i++) {
|
||||||
int status = mValUtils.verifySRRset(rrsets[i], key_rrset);
|
int status = mValUtils.verifySRRset(rrsets[i], key_rrset);
|
||||||
if (status != SecurityStatus.SECURE) {
|
if (status != SecurityStatus.SECURE) {
|
||||||
// log.debug("NODATA response has failed AUTHORITY rrset: "
|
// log.debug("NODATA response has failed AUTHORITY rrset: "
|
||||||
// + rrsets[i]);
|
// + rrsets[i]);
|
||||||
m.setStatus(SecurityStatus.BOGUS);
|
m.setStatus(SecurityStatus.BOGUS);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -440,8 +658,8 @@ public class CaptiveValidator {
|
|||||||
|
|
||||||
// Collect any NSEC3 records present.
|
// Collect any NSEC3 records present.
|
||||||
if (rrsets[i].getType() == Type.NSEC3) {
|
if (rrsets[i].getType() == Type.NSEC3) {
|
||||||
if (nsec3s == null) nsec3s = new ArrayList();
|
if (nsec3s == null) nsec3s = new ArrayList<NSEC3Record>();
|
||||||
nsec3s.add(rrsets[i].first());
|
nsec3s.add((NSEC3Record) rrsets[i].first());
|
||||||
nsec3Signer = rrsets[i].getSignerName();
|
nsec3Signer = rrsets[i].getSignerName();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -458,7 +676,7 @@ public class CaptiveValidator {
|
|||||||
hasValidNSEC = false;
|
hasValidNSEC = false;
|
||||||
}
|
}
|
||||||
} catch (TextParseException e) {
|
} catch (TextParseException e) {
|
||||||
// log.error(e);
|
// log.error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -471,13 +689,13 @@ public class CaptiveValidator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!hasValidNSEC) {
|
if (!hasValidNSEC) {
|
||||||
// log.debug("NODATA response failed to prove NODATA "
|
// log.debug("NODATA response failed to prove NODATA "
|
||||||
// + "status with NSEC/NSEC3");
|
// + "status with NSEC/NSEC3");
|
||||||
// log.trace("Failed NODATA:\n" + m);
|
// log.trace("Failed NODATA:\n" + m);
|
||||||
m.setStatus(SecurityStatus.BOGUS);
|
m.setStatus(SecurityStatus.BOGUS);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// log.trace("sucessfully validated NODATA response.");
|
// log.trace("successfully validated NODATA response.");
|
||||||
m.setStatus(SecurityStatus.SECURE);
|
m.setStatus(SecurityStatus.SECURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -513,14 +731,14 @@ public class CaptiveValidator {
|
|||||||
boolean hasValidNSEC = false;
|
boolean hasValidNSEC = false;
|
||||||
boolean hasValidWCNSEC = false;
|
boolean hasValidWCNSEC = false;
|
||||||
SRRset[] rrsets = m.getSectionRRsets(Section.AUTHORITY);
|
SRRset[] rrsets = m.getSectionRRsets(Section.AUTHORITY);
|
||||||
List nsec3s = null;
|
List<NSEC3Record> nsec3s = null;
|
||||||
Name nsec3Signer = null;
|
Name nsec3Signer = null;
|
||||||
|
|
||||||
for (int i = 0; i < rrsets.length; i++) {
|
for (int i = 0; i < rrsets.length; i++) {
|
||||||
int status = mValUtils.verifySRRset(rrsets[i], key_rrset);
|
int status = mValUtils.verifySRRset(rrsets[i], key_rrset);
|
||||||
if (status != SecurityStatus.SECURE) {
|
if (status != SecurityStatus.SECURE) {
|
||||||
// log.debug("NameError response has failed AUTHORITY rrset: "
|
// log.debug("NameError response has failed AUTHORITY rrset: "
|
||||||
// + rrsets[i]);
|
// + rrsets[i]);
|
||||||
m.setStatus(SecurityStatus.BOGUS);
|
m.setStatus(SecurityStatus.BOGUS);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -537,8 +755,8 @@ public class CaptiveValidator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (rrsets[i].getType() == Type.NSEC3) {
|
if (rrsets[i].getType() == Type.NSEC3) {
|
||||||
if (nsec3s == null) nsec3s = new ArrayList();
|
if (nsec3s == null) nsec3s = new ArrayList<NSEC3Record>();
|
||||||
nsec3s.add(rrsets[i].first());
|
nsec3s.add((NSEC3Record) rrsets[i].first());
|
||||||
nsec3Signer = rrsets[i].getSignerName();
|
nsec3Signer = rrsets[i].getSignerName();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -546,11 +764,11 @@ public class CaptiveValidator {
|
|||||||
NSEC3ValUtils.stripUnknownAlgNSEC3s(nsec3s);
|
NSEC3ValUtils.stripUnknownAlgNSEC3s(nsec3s);
|
||||||
|
|
||||||
if (nsec3s != null && nsec3s.size() > 0) {
|
if (nsec3s != null && nsec3s.size() > 0) {
|
||||||
// log.debug("Validating nxdomain: using NSEC3 records");
|
// log.debug("Validating nxdomain: using NSEC3 records");
|
||||||
// Attempt to prove name error with nsec3 records.
|
// Attempt to prove name error with nsec3 records.
|
||||||
|
|
||||||
if (NSEC3ValUtils.allNSEC3sIgnoreable(nsec3s, key_rrset, mVerifier)) {
|
if (NSEC3ValUtils.allNSEC3sIgnoreable(nsec3s, key_rrset, mVerifier)) {
|
||||||
// log.debug("all NSEC3s were validated but ignored.");
|
// log.debug("all NSEC3s were validated but ignored.");
|
||||||
m.setStatus(SecurityStatus.INSECURE);
|
m.setStatus(SecurityStatus.INSECURE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -565,126 +783,34 @@ public class CaptiveValidator {
|
|||||||
|
|
||||||
// If the message fails to prove either condition, it is bogus.
|
// If the message fails to prove either condition, it is bogus.
|
||||||
if (!hasValidNSEC) {
|
if (!hasValidNSEC) {
|
||||||
// log.debug("NameError response has failed to prove: "
|
// log.debug("NameError response has failed to prove: "
|
||||||
// + "qname does not exist");
|
// + "qname does not exist");
|
||||||
m.setStatus(SecurityStatus.BOGUS);
|
m.setStatus(SecurityStatus.BOGUS);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasValidWCNSEC) {
|
if (!hasValidWCNSEC) {
|
||||||
// log.debug("NameError response has failed to prove: "
|
// log.debug("NameError response has failed to prove: "
|
||||||
// + "covering wildcard does not exist");
|
// + "covering wildcard does not exist");
|
||||||
m.setStatus(SecurityStatus.BOGUS);
|
m.setStatus(SecurityStatus.BOGUS);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, we consider the message secure.
|
// Otherwise, we consider the message secure.
|
||||||
// log.trace("successfully validated NAME ERROR response.");
|
// log.trace("successfully validated NAME ERROR response.");
|
||||||
m.setStatus(SecurityStatus.SECURE);
|
m.setStatus(SecurityStatus.SECURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// /**
|
|
||||||
// * This state is used for validating CNAME-type responses -- i.e., responses
|
|
||||||
// * that have CNAME chains.
|
|
||||||
// *
|
|
||||||
// * It primarily is responsible for breaking down the response into a series
|
|
||||||
// * of separately validated queries & responses.
|
|
||||||
// *
|
|
||||||
// * @param event
|
|
||||||
// * @param state
|
|
||||||
// * @return
|
|
||||||
// */
|
|
||||||
// private boolean processCNAME(DNSEvent event, ValEventState state) {
|
|
||||||
// Request req = event.getRequest();
|
|
||||||
//
|
|
||||||
// Name qname = req.getQName();
|
|
||||||
// int qtype = req.getQType();
|
|
||||||
// int qclass = req.getQClass();
|
|
||||||
//
|
|
||||||
// SMessage m = event.getResponse().getSMessage();
|
|
||||||
//
|
|
||||||
// if (state.cnameSname == null) state.cnameSname = qname;
|
|
||||||
//
|
|
||||||
// // We break the chain down by re-querying for the specific CNAME or
|
|
||||||
// // DNAME
|
|
||||||
// // (or final answer).
|
|
||||||
// SRRset[] rrsets = m.getSectionRRsets(Section.ANSWER);
|
|
||||||
//
|
|
||||||
// while (state.cnameIndex < rrsets.length) {
|
|
||||||
// SRRset rrset = rrsets[state.cnameIndex++];
|
|
||||||
// Name rname = rrset.getName();
|
|
||||||
// int rtype = rrset.getType();
|
|
||||||
//
|
|
||||||
// // Skip DNAMEs -- prefer to query for the generated CNAME,
|
|
||||||
// if (rtype == Type.DNAME && qtype != Type.DNAME) continue;
|
|
||||||
//
|
|
||||||
// // Set the SNAME if we are dealing with a CNAME
|
|
||||||
// if (rtype == Type.CNAME) {
|
|
||||||
// CNAMERecord cname = (CNAMERecord) rrset.first();
|
|
||||||
// state.cnameSname = cname.getTarget();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Note if the current rrset is the answer. In that case, we want to
|
|
||||||
// // set
|
|
||||||
// // the final state differently.
|
|
||||||
// // For non-answers, the response ultimately comes back here.
|
|
||||||
// int final_state = ValEventState.CNAME_RESP_STATE;
|
|
||||||
// if (isAnswerRRset(rrset.getName(), rtype, state.cnameSname, qtype,
|
|
||||||
// Section.ANSWER)) {
|
|
||||||
// // If this is an answer, however, break out of this loop.
|
|
||||||
// final_state = ValEventState.CNAME_ANS_RESP_STATE;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Generate the sub-query.
|
|
||||||
// Request localRequest = generateLocalRequest(rname, rtype, qclass);
|
|
||||||
// DNSEvent localEvent = generateLocalEvent(event, localRequest,
|
|
||||||
// ValEventState.INIT_STATE,
|
|
||||||
// final_state);
|
|
||||||
//
|
|
||||||
// // ...and send it along.
|
|
||||||
// processLocalRequest(localEvent);
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Something odd has happened if we get here.
|
|
||||||
// log.warn("processCNAME: encountered unknown issue handling a CNAME chain.");
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private boolean processCNAMEResponse(DNSEvent event, ValEventState state) {
|
|
||||||
// DNSEvent forEvent = event.forEvent();
|
|
||||||
// ValEventState forState = getModuleState(forEvent);
|
|
||||||
//
|
|
||||||
// SMessage resp = event.getResponse().getSMessage();
|
|
||||||
// if (resp.getStatus() != SecurityStatus.SECURE) {
|
|
||||||
// forEvent.getResponse().getSMessage().setStatus(resp.getStatus());
|
|
||||||
// forState.state = forState.finalState;
|
|
||||||
// handleResponse(forEvent, forState);
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// forState.state = ValEventState.CNAME_STATE;
|
|
||||||
// handleResponse(forEvent, forState);
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private boolean processCNAMEAnswer(DNSEvent event, ValEventState state) {
|
|
||||||
// DNSEvent forEvent = event.forEvent();
|
|
||||||
// ValEventState forState = getModuleState(forEvent);
|
|
||||||
//
|
|
||||||
// SMessage resp = event.getResponse().getSMessage();
|
|
||||||
// SMessage forResp = forEvent.getResponse().getSMessage();
|
|
||||||
//
|
|
||||||
// forResp.setStatus(resp.getStatus());
|
|
||||||
//
|
|
||||||
// forState.state = forState.finalState;
|
|
||||||
// handleResponse(forEvent, forState);
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
public byte validateMessage(SMessage message, Name zone) {
|
public byte validateMessage(SMessage message, Name zone) {
|
||||||
|
|
||||||
|
// FIXME: it is unclear if we should actually normalize our responses
|
||||||
|
// Instead, maybe we should just fail if they are not normal?
|
||||||
|
message = normalize(message);
|
||||||
|
|
||||||
|
if (! needsValidation(message)) {
|
||||||
|
return SecurityStatus.UNCHECKED;
|
||||||
|
}
|
||||||
|
|
||||||
SRRset key_rrset = findKeys(message);
|
SRRset key_rrset = findKeys(message);
|
||||||
if (key_rrset == null) {
|
if (key_rrset == null) {
|
||||||
return SecurityStatus.BOGUS;
|
return SecurityStatus.BOGUS;
|
||||||
@ -701,21 +827,21 @@ public class CaptiveValidator {
|
|||||||
validateReferral(message, key_rrset);
|
validateReferral(message, key_rrset);
|
||||||
break;
|
break;
|
||||||
case NODATA:
|
case NODATA:
|
||||||
// log.trace("Validating a nodata response");
|
// log.trace("Validating a NODATA response");
|
||||||
validateNodataResponse(message, key_rrset);
|
validateNodataResponse(message, key_rrset);
|
||||||
break;
|
break;
|
||||||
case NAMEERROR:
|
case NAMEERROR:
|
||||||
// log.trace("Validating a nxdomain response");
|
// log.trace("Validating a NXDOMAIN response");
|
||||||
validateNameErrorResponse(message, key_rrset);
|
validateNameErrorResponse(message, key_rrset);
|
||||||
break;
|
break;
|
||||||
case CNAME:
|
case CNAME:
|
||||||
// log.trace("Validating a cname response");
|
// log.trace("Validating a CNAME response");
|
||||||
// forward on to the special CNAME state for this.
|
// forward on to the special CNAME state for this.
|
||||||
// state.state = ValEventState.CNAME_STATE;
|
// state.state = ValEventState.CNAME_STATE;
|
||||||
validateCNAMEResponse(message, key_rrset);
|
validateCNAMEResponse(message, key_rrset);
|
||||||
break;
|
break;
|
||||||
case ANY:
|
case ANY:
|
||||||
// log.trace("Validating a postive ANY response");
|
// log.trace("Validating a positive ANY response");
|
||||||
validateAnyResponse(message, key_rrset);
|
validateAnyResponse(message, key_rrset);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -702,7 +702,7 @@ public class NSEC3ValUtils {
|
|||||||
* delegation point, and SecurityStatus.BOGUS if the proofs don't
|
* delegation point, and SecurityStatus.BOGUS if the proofs don't
|
||||||
* work out.
|
* work out.
|
||||||
*/
|
*/
|
||||||
public static int proveNoDS(List<NSEC3Record> nsec3s, Name qname,
|
public static byte proveNoDS(List<NSEC3Record> nsec3s, Name qname,
|
||||||
Name zonename) {
|
Name zonename) {
|
||||||
if (nsec3s == null || nsec3s.size() == 0) return SecurityStatus.BOGUS;
|
if (nsec3s == null || nsec3s.size() == 0) return SecurityStatus.BOGUS;
|
||||||
|
|
||||||
|
@ -36,25 +36,19 @@ import org.xbill.DNS.Name;
|
|||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class TrustAnchorStore
|
public class TrustAnchorStore {
|
||||||
{
|
|
||||||
private Map<String, SRRset> mMap;
|
private Map<String, SRRset> mMap;
|
||||||
|
|
||||||
public TrustAnchorStore()
|
public TrustAnchorStore() {
|
||||||
{
|
|
||||||
mMap = null;
|
mMap = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String key(Name n, int dclass)
|
private String key(Name n, int dclass) {
|
||||||
{
|
|
||||||
return "T" + dclass + "/" + Util.nameToString(n);
|
return "T" + dclass + "/" + Util.nameToString(n);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void store(SRRset rrset) {
|
||||||
public void store(SRRset rrset)
|
if (mMap == null) {
|
||||||
{
|
|
||||||
if (mMap == null)
|
|
||||||
{
|
|
||||||
mMap = new HashMap<String, SRRset>();
|
mMap = new HashMap<String, SRRset>();
|
||||||
}
|
}
|
||||||
String k = key(rrset.getName(), rrset.getDClass());
|
String k = key(rrset.getName(), rrset.getDClass());
|
||||||
@ -63,18 +57,15 @@ public class TrustAnchorStore
|
|||||||
mMap.put(k, rrset);
|
mMap.put(k, rrset);
|
||||||
}
|
}
|
||||||
|
|
||||||
private SRRset lookup(String key)
|
private SRRset lookup(String key) {
|
||||||
{
|
|
||||||
if (mMap == null) return null;
|
if (mMap == null) return null;
|
||||||
return mMap.get(key);
|
return mMap.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SRRset find(Name n, int dclass)
|
public SRRset find(Name n, int dclass) {
|
||||||
{
|
|
||||||
if (mMap == null) return null;
|
if (mMap == null) return null;
|
||||||
|
|
||||||
while (n.labels() > 0)
|
while (n.labels() > 0) {
|
||||||
{
|
|
||||||
String k = key(n, dclass);
|
String k = key(n, dclass);
|
||||||
SRRset r = lookup(k);
|
SRRset r = lookup(k);
|
||||||
if (r != null) return r;
|
if (r != null) return r;
|
||||||
@ -84,4 +75,8 @@ public class TrustAnchorStore
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isBelowTrustAnchor(Name n, int dclass) {
|
||||||
|
return find(n, dclass) != null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -59,27 +59,6 @@ public class ValUtils {
|
|||||||
// a throwaway response (i.e., an error)
|
// a throwaway response (i.e., an error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// /** Not subtyped yet. */
|
|
||||||
// public static final int UNTYPED = 0;
|
|
||||||
//
|
|
||||||
// /** Not a recognized subtype. */
|
|
||||||
// public static final int UNKNOWN = 1;
|
|
||||||
//
|
|
||||||
// /** A postive, direct, response. */
|
|
||||||
// public static final int POSITIVE = 2;
|
|
||||||
//
|
|
||||||
// /** A postive response, with a CNAME/DNAME chain. */
|
|
||||||
// public static final int CNAME = 3;
|
|
||||||
//
|
|
||||||
// /** A NOERROR/NODATA response. */
|
|
||||||
// public static final int NODATA = 4;
|
|
||||||
//
|
|
||||||
// /** A NXDOMAIN response. */
|
|
||||||
// public static final int NAMEERROR = 5;
|
|
||||||
//
|
|
||||||
// /** A response to a qtype=ANY query. */
|
|
||||||
// public static final int ANY = 6;
|
|
||||||
|
|
||||||
/** A local copy of the verifier object. */
|
/** A local copy of the verifier object. */
|
||||||
private DnsSecVerifier mVerifier;
|
private DnsSecVerifier mVerifier;
|
||||||
|
|
||||||
@ -598,7 +577,7 @@ public class ValUtils {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int nsecProvesNoDS(NSECRecord nsec, Name qname) {
|
public static byte nsecProvesNoDS(NSECRecord nsec, Name qname) {
|
||||||
// Could check to make sure the qname is a subdomain of nsec
|
// Could check to make sure the qname is a subdomain of nsec
|
||||||
int[] types = nsec.getTypes();
|
int[] types = nsec.getTypes();
|
||||||
if (typeMapHasType(types, Type.SOA) || typeMapHasType(types, Type.DS)) {
|
if (typeMapHasType(types, Type.SOA) || typeMapHasType(types, Type.DS)) {
|
||||||
|
Loading…
Reference in New Issue
Block a user