/*
* 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.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.jitsi.dnssec.SecurityStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xbill.DNS.DNSKEYRecord;
import org.xbill.DNS.DNSSEC;
import org.xbill.DNS.DNSSEC.DNSSECException;
import org.xbill.DNS.RRSIGRecord;
import org.xbill.DNS.RRset;
/**
* A class for performing basic DNSSEC verification. The DNSJAVA package
* contains a similar class. This is a reimplementation that allows us to have
* finer control over the validation process.
*
* @author davidb
* @version $Revision: 361 $
*/
public class DnsSecVerifier {
private static final Logger logger = LoggerFactory.getLogger(DnsSecVerifier.class);
/**
* 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;
}
/**
* 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;
}
/**
* 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;
}
/**
* 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;
}
}