2 Commits

Author SHA1 Message Date
4478d7e3af WIP on refactoring signzone, verifyzone 2024-07-18 08:56:12 -04:00
1406cd2e68 remove ECC-GOST, add new DS algorithms (#18) 2024-04-13 11:39:18 -04:00
12 changed files with 647 additions and 153 deletions

View File

@@ -1,3 +1,12 @@
2024-04-13 David Blacka <david@blacka.com>
* Remove support for ECC_GOST
* Create a new DSAlgorithm class, move DS creation into that
* Add support for DS algorithms 3 and 4 -- bouncycastle crypto
provider used for DS algoirthm 3 (GOST R 34.11-94)
* Moved support for loading the bouncycastle provider to the new
DSAlgorithm class
2024-04-07 David Blacka <david@blacka.com>
* Released version 0.20

View File

@@ -1,3 +1,3 @@
build.deprecation=true
build.debug=false
build.debug=true
build.java_version=17

BIN
lib/bcprov-jdk18on-1.78.jar Normal file

Binary file not shown.

View File

@@ -22,16 +22,14 @@ import java.io.IOException;
import java.io.PrintWriter;
import org.apache.commons.cli.Option;
import org.xbill.DNS.CDSRecord;
import org.xbill.DNS.DLVRecord;
import org.xbill.DNS.DNSKEYRecord;
import org.xbill.DNS.DNSSEC;
import org.xbill.DNS.DSRecord;
import org.xbill.DNS.Record;
import com.verisignlabs.dnssec.security.BINDKeyUtils;
import com.verisignlabs.dnssec.security.DSAlgorithm;
import com.verisignlabs.dnssec.security.DnsKeyPair;
import com.verisignlabs.dnssec.security.SignUtils;
/**
* This class forms the command line implementation of a DNSSEC DS/DLV generator
@@ -59,7 +57,6 @@ public class DSTool extends CLBase {
* state.
*/
/**
* Set up the command line options.
*
@@ -68,8 +65,11 @@ public class DSTool extends CLBase {
protected void setupOptions() {
opts.addOption(Option.builder("D").longOpt("dlv").desc("Generate a DLV record instead.").build());
opts.addOption(Option.builder("C").longOpt("cds").desc("Generate a CDS record instead").build());
String[] algStrings = DSAlgorithm.getInstance().supportedAlgorithmMnemonics();
String algStringSet = String.join(" | ", algStrings);
opts.addOption(
Option.builder("d").hasArg().argName("id").longOpt("digest").desc("The digest algorithm to use").build());
Option.builder("d").hasArg().argName("id").longOpt("digest").desc(algStringSet + ": default is SHA256")
.build());
opts.addOption(Option.builder("f").hasArg().argName("file").longOpt("output").desc("output to file").build());
opts.addOption(Option.builder("T").longOpt("ttl").hasArg().desc("TTL to use for generated DS/CDS record").build());
}
@@ -99,6 +99,7 @@ public class DSTool extends CLBase {
}
public void createDS(String keyname) throws IOException {
DSAlgorithm dsAlgorithm = DSAlgorithm.getInstance();
DnsKeyPair key = BINDKeyUtils.loadKey(keyname, null);
DNSKEYRecord dnskey = key.getDNSKEYRecord();
@@ -107,21 +108,17 @@ public class DSTool extends CLBase {
}
long ttl = dsTTL < 0 ? dnskey.getTTL() : dsTTL;
DSRecord ds = SignUtils.calculateDSRecord(dnskey, digestId, ttl);
DSRecord ds = dsAlgorithm.calculateDSRecord(dnskey, digestId, ttl);
Record res;
switch (createType) {
case DLV:
log.fine("creating DLV.");
DLVRecord dlv = new DLVRecord(ds.getName(), ds.getDClass(), ds.getTTL(), ds.getFootprint(), ds.getAlgorithm(),
ds.getDigestID(), ds.getDigest());
res = dlv;
res = dsAlgorithm.dsToDLV(ds);
break;
case CDS:
log.fine("creating CDS.");
CDSRecord cds = new CDSRecord(ds.getName(), ds.getDClass(), ds.getTTL(), ds.getFootprint(), ds.getAlgorithm(),
ds.getDClass(), ds.getDigest());
res = cds;
res = dsAlgorithm.dstoCDS(ds);
break;
default:
res = ds;
@@ -138,7 +135,7 @@ public class DSTool extends CLBase {
}
public void execute() throws Exception {
for (String keyname : keynames){
for (String keyname : keynames) {
createDS(keyname);
}
}

View File

@@ -67,7 +67,7 @@ public class KeyGen extends CLBase {
String[] algStrings = DnsKeyAlgorithm.getInstance().supportedAlgMnemonics();
String algStringSet = String.join(" | ", algStrings);
opts.addOption(Option.builder("a").hasArg().argName("algorithm")
.desc(algStringSet + " | alias, ECDSAP256SHA256 is default.").build());
.desc(algStringSet + " | aliases, ECDSAP256SHA256 is default.").build());
opts.addOption(Option.builder("b").hasArg().argName("size").desc(
"key size, in bits (default 2048). RSA: [512..4096], DSA: [512..1024], DH: [128..4096], ECDSA: ignored, EdDSA: ignored")

View File

@@ -134,8 +134,7 @@ public class BINDKeyUtils {
public static DnsKeyPair loadKeyPair(String keyFileBasePath, File inDirectory)
throws IOException {
keyFileBasePath = fixKeyFileBasePath(keyFileBasePath);
// FIXME: should we throw the IOException when one of the files
// cannot be found, or just when both cannot be found?
File publicKeyFile = new File(inDirectory, keyFileBasePath + ".key");
File privateKeyFile = new File(inDirectory, keyFileBasePath + ".private");
@@ -251,8 +250,6 @@ public class BINDKeyUtils {
if (privateKeyString == null)
return null;
// FIXME: should this swallow exceptions or actually propagate
// them?
try {
DnsKeyConverter conv = new DnsKeyConverter();
return conv.parsePrivateKeyString(privateKeyString);

View File

@@ -0,0 +1,167 @@
/*
* Copyright (c) 2006, 2022 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 com.verisignlabs.dnssec.security;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.Security;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.logging.Logger;
import org.xbill.DNS.CDSRecord;
import org.xbill.DNS.DLVRecord;
import org.xbill.DNS.DNSKEYRecord;
import org.xbill.DNS.DNSOutput;
import org.xbill.DNS.DNSSEC;
import org.xbill.DNS.DSRecord;
/**
* This class handles the implementation behind converting DNSKEYs into
* DSRecords. It primarily exists to bootstrap whatever crypto libraries we
* might need to do so.
*
* @author David Blacka
*/
public class DSAlgorithm {
private Logger log = Logger.getLogger(this.getClass().toString());
HashSet<Integer> mSupportedAlgorithms = null;
private static DSAlgorithm mInstance = null;
public DSAlgorithm() {
mSupportedAlgorithms = new HashSet<>();
mSupportedAlgorithms.add(DNSSEC.Digest.SHA1);
mSupportedAlgorithms.add(DNSSEC.Digest.SHA256);
mSupportedAlgorithms.add(DNSSEC.Digest.SHA384);
// Attempt to add the bouncycastle provider. This is so we can use this
// provider if it is available, but not require the user to add it as one of
// the java.security providers.
try {
Class<?> bcProviderClass = Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider");
Provider bcProvider = (Provider) bcProviderClass.getDeclaredConstructor().newInstance();
Security.addProvider(bcProvider);
log.fine("bouncycastle crypto provider loaded");
mSupportedAlgorithms.add(DNSSEC.Digest.GOST3411);
} catch (ReflectiveOperationException e) {
// do nothing, this is the normal case
}
}
public String[] supportedAlgorithmMnemonics() {
ArrayList<String> algs = new ArrayList<>();
for (int digestId : mSupportedAlgorithms) {
algs.add(DNSSEC.Digest.string(digestId));
}
String[] result = new String[algs.size()];
return algs.toArray(result);
}
/**
* Given a DNSKEY record, generate the DS record from it.
*
* @param keyrec the KEY record in question.
* @param digestAlg The digest algorithm (SHA-1, SHA-256, etc.).
* @param ttl the desired TTL for the generated DS record. If zero, or
* negative, the original KEY RR's TTL will be used.
* @return the corresponding {@link org.xbill.DNS.DSRecord}
*/
public DSRecord calculateDSRecord(DNSKEYRecord keyrec, int digestAlg, long ttl) {
if (keyrec == null)
return null;
if (ttl <= 0)
ttl = keyrec.getTTL();
DNSOutput os = new DNSOutput();
os.writeByteArray(keyrec.getName().toWireCanonical());
os.writeByteArray(keyrec.rdataToWireCanonical());
try {
byte[] digest;
MessageDigest md;
switch (digestAlg) {
case DNSSEC.Digest.SHA1:
md = MessageDigest.getInstance("SHA");
digest = md.digest(os.toByteArray());
break;
case DNSSEC.Digest.SHA256:
md = MessageDigest.getInstance("SHA-256");
digest = md.digest(os.toByteArray());
break;
case DNSSEC.Digest.GOST3411:
// The standard Java crypto providers don't have this, but bouncycastle does
if (java.security.Security.getProviders("MessageDigest.GOST3411") != null) {
md = MessageDigest.getInstance("GOST3411");
digest = md.digest(os.toByteArray());
} else {
throw new IllegalArgumentException("Unsupported digest id: " + digestAlg);
}
break;
case DNSSEC.Digest.SHA384:
md = MessageDigest.getInstance("SHA-384");
digest = md.digest(os.toByteArray());
break;
default:
throw new IllegalArgumentException("Unknown digest id: " + digestAlg);
}
return new DSRecord(keyrec.getName(), keyrec.getDClass(), ttl,
keyrec.getFootprint(), keyrec.getAlgorithm(), digestAlg,
digest);
} catch (NoSuchAlgorithmException e) {
log.severe(e.toString());
return null;
}
}
public DLVRecord dsToDLV(DSRecord ds) {
return new DLVRecord(ds.getName(), ds.getDClass(), ds.getTTL(), ds.getFootprint(), ds.getAlgorithm(),
ds.getDigestID(), ds.getDigest());
}
public CDSRecord dstoCDS(DSRecord ds) {
return new CDSRecord(ds.getName(), ds.getDClass(), ds.getTTL(), ds.getFootprint(), ds.getAlgorithm(),
ds.getDClass(), ds.getDigest());
}
public static DSAlgorithm getInstance() {
if (mInstance == null) {
mInstance = new DSAlgorithm();
}
return mInstance;
}
}

View File

@@ -27,20 +27,14 @@
package com.verisignlabs.dnssec.security;
import java.math.BigInteger;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.Security;
import java.security.Signature;
import java.security.spec.ECFieldFp;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.EllipticCurve;
import java.security.spec.InvalidParameterSpecException;
import java.security.spec.NamedParameterSpec;
import java.security.spec.RSAKeyGenParameterSpec;
@@ -73,7 +67,6 @@ public class DnsKeyAlgorithm {
RSA,
DH,
DSA,
ECC_GOST,
ECDSA,
EDDSA;
}
@@ -129,12 +122,8 @@ public class DnsKeyAlgorithm {
private KeyPairGenerator mRSAKeyGenerator;
/** This is a cached key pair generator for DSA keys. */
private KeyPairGenerator mDSAKeyGenerator;
/** This is a cached key pair generator for ECC GOST keys. */
private KeyPairGenerator mECGOSTKeyGenerator;
/** This is a cached key pair generator for ECDSA_P256 keys. */
private KeyPairGenerator mECKeyGenerator;
/** This is a cached key pair generator for EdDSA keys. */
private KeyPairGenerator mEdKeyGenerator;
private Logger log = Logger.getLogger(this.getClass().toString());
@@ -142,17 +131,6 @@ public class DnsKeyAlgorithm {
private static DnsKeyAlgorithm mInstance = null;
public DnsKeyAlgorithm() {
// Attempt to add the bouncycastle provider. This is so we can use this
// provider if it is available, but not require the user to add it as one of
// the java.security providers.
try {
Class<?> bcProviderClass = Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider");
Provider bcProvider = (Provider) bcProviderClass.getDeclaredConstructor().newInstance();
Security.addProvider(bcProvider);
} catch (ReflectiveOperationException e) {
log.fine("Unable to load BC provider");
}
initialize();
}
@@ -189,14 +167,6 @@ public class DnsKeyAlgorithm {
addAlgorithm(DNSSEC.Algorithm.RSASHA512, "SHA512withRSA", BaseAlgorithm.RSA);
addMnemonic("RSASHA512", DNSSEC.Algorithm.RSASHA512);
// ECC-GOST is not supported by Java 1.8's Sun crypto provider. The
// bouncycastle.org provider, however, does support it.
// GostR3410-2001-CryptoPro-A is the named curve in the BC provider, but we
// will get the parameters directly.
addAlgorithm(DNSSEC.Algorithm.ECC_GOST, "GOST3411withECGOST3410", BaseAlgorithm.ECC_GOST, null);
addMnemonic("ECCGOST", DNSSEC.Algorithm.ECC_GOST);
addMnemonic("ECC-GOST", DNSSEC.Algorithm.ECC_GOST);
addAlgorithm(DNSSEC.Algorithm.ECDSAP256SHA256, "SHA256withECDSA", BaseAlgorithm.ECDSA, "secp256r1");
addMnemonic("ECDSAP256SHA256", DNSSEC.Algorithm.ECDSAP256SHA256);
addMnemonic("ECDSA-P256", DNSSEC.Algorithm.ECDSAP256SHA256);
@@ -226,9 +196,7 @@ public class DnsKeyAlgorithm {
* library (SunEC).
*/
private void addECDSAAlgorithm(int algorithm, String sigName, String curveName) {
ECParameterSpec ecSpec = ECSpecFromAlgorithm(algorithm);
if (ecSpec == null)
ecSpec = ECSpecFromName(curveName);
ECParameterSpec ecSpec = ECSpecFromName(curveName);
if (ecSpec == null)
return;
@@ -278,10 +246,15 @@ public class DnsKeyAlgorithm {
* @param curveName the name of the curve.
*/
private void addAlgorithm(int algorithm, String sigName, BaseAlgorithm baseType, String curveName) {
if (baseType == BaseAlgorithm.ECDSA) {
addECDSAAlgorithm(algorithm, sigName, curveName);
} else if (baseType == BaseAlgorithm.EDDSA) {
addEdDSAAlgorithm(algorithm, sigName, curveName);
switch (baseType) {
case ECDSA:
addECDSAAlgorithm(algorithm, sigName, curveName);
break;
case EDDSA:
addEdDSAAlgorithm(algorithm, sigName, curveName);
break;
default:
throw new IllegalArgumentException("Non-Ellipic curve algorithm passed.");
}
}
@@ -324,25 +297,6 @@ public class DnsKeyAlgorithm {
return mAlgorithmMap.get(alg);
}
// For curves where we don't (or can't) get the parameters from a standard
// name, we can construct the parameters here. For now, we only do this for
// the ECC-GOST curve.
private ECParameterSpec ECSpecFromAlgorithm(int algorithm) {
if (algorithm == DNSSEC.Algorithm.ECC_GOST) {
// From RFC 4357 Section 11.4
BigInteger p = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD97", 16);
BigInteger a = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD94", 16);
BigInteger b = new BigInteger("A6", 16);
BigInteger gx = new BigInteger("1", 16);
BigInteger gy = new BigInteger("8D91E471E0989CDA27DF505A453F2B7635294F2DDF23E3B122ACC99C9E9F1E14", 16);
BigInteger n = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6C611070995AD10045841B09B761B893", 16);
EllipticCurve curve = new EllipticCurve(new ECFieldFp(p), a, b);
return new ECParameterSpec(curve, new ECPoint(gx, gy), n, 1);
}
return null;
}
// Fetch the curve parameters from a named ECDSA curve.
private ECParameterSpec ECSpecFromName(String stdName) {
try {
@@ -529,22 +483,6 @@ public class DnsKeyAlgorithm {
pair = mDSAKeyGenerator.generateKeyPair();
break;
}
case ECC_GOST: {
if (mECGOSTKeyGenerator == null) {
mECGOSTKeyGenerator = KeyPairGenerator.getInstance("ECGOST3410");
}
ECParameterSpec ecSpec = getEllipticCurveParams(algorithm);
try {
mECGOSTKeyGenerator.initialize(ecSpec);
} catch (InvalidAlgorithmParameterException e) {
// Fold the InvalidAlgorithmParameterException into our existing
// thrown exception. Ugly, but requires less code change.
throw new NoSuchAlgorithmException("invalid key parameter spec");
}
pair = mECGOSTKeyGenerator.generateKeyPair();
break;
}
case ECDSA: {
if (mECKeyGenerator == null) {
mECKeyGenerator = KeyPairGenerator.getInstance("EC");
@@ -563,9 +501,9 @@ public class DnsKeyAlgorithm {
}
case EDDSA: {
EdAlgEntry entry = (EdAlgEntry) getEntry(algorithm);
mEdKeyGenerator = KeyPairGenerator.getInstance(entry.curveName);
KeyPairGenerator edKeyGenerator = KeyPairGenerator.getInstance(entry.curveName);
pair = mEdKeyGenerator.generateKeyPair();
pair = edKeyGenerator.generateKeyPair();
break;
}
default:

View File

@@ -201,8 +201,6 @@ public class DnsKeyConverter {
return parsePrivateDSA(lines);
case DH:
return parsePrivateDH(lines);
case ECC_GOST:
return parsePrivateECDSA(lines, alg);
case ECDSA:
return parsePrivateECDSA(lines, alg);
case EDDSA:

View File

@@ -61,7 +61,7 @@ public class JCEDnsSecSigner {
}
public JCEDnsSecSigner(boolean verboseSigning) {
this.mKeyConverter = null;
super();
this.mVerboseSigning = verboseSigning;
}
@@ -334,8 +334,8 @@ public class JCEDnsSecSigner {
// Remove duplicate records
SignUtils.removeDuplicateRecords(records);
// Generate DS records. This replaces any non-zone-apex DNSKEY RRs with DS
// RRs.
// Generate DS records. This replaces any non-zone-apex DNSKEY RRs with DS RRs.
// This is not a common practice, so this step may be dropped in the future.
SignUtils.generateDSRecords(zonename, records, dsDigestAlg);
// Generate the NSEC or NSEC3 records based on 'mode'

View File

@@ -31,6 +31,7 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
@@ -61,11 +62,11 @@ public class SignUtils {
private static final int ASN1_INT = 0x02;
private static final int ASN1_SEQ = 0x30;
public static final int RR_NORMAL = 0;
public static final int RR_DELEGATION = 1;
public static final int RR_GLUE = 2;
public static final int RR_INVALID = 3;
public static final int RR_DNAME = 4;
// public static final int RR_NORMAL = 0;
// public static final int RR_DELEGATION = 1;
// public static final int RR_GLUE = 2;
// public static final int RR_INVALID = 3;
// public static final int RR_DNAME = 4;
private static Logger log;
@@ -714,6 +715,41 @@ public class SignUtils {
}
}
public static void generateNSECRecord(ZoneData zone) {
Name lastCut = null;
Name lastDname = null;
Map.Entry<Name, Set<Integer>> lastNode = null;
int backup;
long nsecTTL = 0;
SOARecord soa = (SOARecord) zone.getSOA();
nsecTTL = Math.min(soa.getMinimum(), soa.getTTL());
for (Map.Entry<Name, Set<Integer>> entry : zone.nodeMap().entrySet()) {
ZoneData.NodeType nodeType = zone.determineNodeType(entry.getKey(), entry.getValue(), lastCut, lastDname);
switch (nodeType) {
case INVALID:
case GLUE:
// we can skip these
continue;
case DELEGATION:
lastCut = entry.getKey();
break;
case DNAME:
lastDname = entry.getKey();
break;
case NORMAL:
default:
break;
}
if (lastNode != null) {
NSECRecord nsec = new NSECRecord(lastNode.getKey(), DClass.IN, nsecTTL, entry.getKey(), lastNode.getValue().toArray());
}
NSECRecord nsec =
}
}
/**
* Given a canonical (by name) ordered list of records in a zone, generate the
* NSEC records in place.
@@ -1307,7 +1343,7 @@ public class SignUtils {
}
/**
* Given a zone with DNSKEY records at delegation points, convert those KEY
* Given a zone with DNSKEY records at delegation points, convert those DNSKEY
* records into their corresponding DS records in place.
*
* @param zonename the name of the zone, used to reliably distinguish the
@@ -1317,6 +1353,8 @@ public class SignUtils {
*/
public static void generateDSRecords(Name zonename, List<Record> records, int digestAlg) {
DSAlgorithm dsAlgorithm = DSAlgorithm.getInstance();
for (ListIterator<Record> i = records.listIterator(); i.hasNext();) {
Record r = i.next();
if (r == null)
@@ -1328,7 +1366,7 @@ public class SignUtils {
// Convert non-zone level KEY records into DS records.
if (r.getType() == Type.DNSKEY && !rName.equals(zonename)) {
DSRecord ds = calculateDSRecord((DNSKEYRecord) r, digestAlg, r.getTTL());
DSRecord ds = dsAlgorithm.calculateDSRecord((DNSKEYRecord) r, digestAlg, r.getTTL());
i.set(ds);
}
@@ -1376,53 +1414,6 @@ public class SignUtils {
}
}
/**
* Given a DNSKEY record, generate the DS record from it.
*
* @param keyrec the KEY record in question.
* @param digestAlg The digest algorithm (SHA-1, SHA-256, etc.).
* @param ttl the desired TTL for the generated DS record. If zero, or
* negative, the original KEY RR's TTL will be used.
* @return the corresponding {@link org.xbill.DNS.DSRecord}
*/
public static DSRecord calculateDSRecord(DNSKEYRecord keyrec, int digestAlg, long ttl) {
if (keyrec == null)
return null;
if (ttl <= 0)
ttl = keyrec.getTTL();
DNSOutput os = new DNSOutput();
os.writeByteArray(keyrec.getName().toWireCanonical());
os.writeByteArray(keyrec.rdataToWireCanonical());
try {
byte[] digest;
MessageDigest md;
switch (digestAlg) {
case DNSSEC.Digest.SHA1:
md = MessageDigest.getInstance("SHA");
digest = md.digest(os.toByteArray());
break;
case DNSSEC.Digest.SHA256:
md = MessageDigest.getInstance("SHA-256");
digest = md.digest(os.toByteArray());
break;
default:
throw new IllegalArgumentException("Unknown digest id: " + digestAlg);
}
return new DSRecord(keyrec.getName(), keyrec.getDClass(), ttl,
keyrec.getFootprint(), keyrec.getAlgorithm(), digestAlg,
digest);
} catch (NoSuchAlgorithmException e) {
log.severe(e.toString());
return null;
}
}
/**
* Calculate an NSEC3 hash based on a DNS name and NSEC3 hash parameters.

View File

@@ -0,0 +1,397 @@
package com.verisignlabs.dnssec.security;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.logging.Logger;
import org.xbill.DNS.Master;
import org.xbill.DNS.NSEC3PARAMRecord;
import org.xbill.DNS.NSEC3Record;
import org.xbill.DNS.Name;
import org.xbill.DNS.RRSIGRecord;
import org.xbill.DNS.RRset;
import org.xbill.DNS.Record;
import org.xbill.DNS.SOARecord;
import org.xbill.DNS.Type;
public class ZoneData {
private SortedMap<Name, Set<Integer>> mNodeMap;
private HashMap<String, RRset> mRRsetMap;
private SortedMap<Name, MarkRRset> mNSECMap;
private SortedMap<Name, MarkRRset> mNSEC3Map;
private Name mZoneName;
private DNSSECType mDNSSECType;
private NSEC3PARAMRecord mNSEC3params;
// Configuration parameters
private boolean mIgnoreDuplicateRRs;
private boolean mStripDNSSEC;
private Logger log = Logger.getLogger(ZoneData.class.toString());
// The various types of signed zones.
enum DNSSECType {
UNSIGNED, NSEC, NSEC3, NSEC3_OPTOUT;
}
// The types of nodes (a node consists of all RRs with the same name).
enum NodeType {
NORMAL, DELEGATION, GLUE, DNAME, INVALID;
}
// if mStripDNSSEC is true, then RRsets of the following types will be skipped
// on load, or stripped.
private static final Set<Integer> GENTYPES_SET = Set.of(Type.RRSIG, Type.NSEC, Type.NSEC3, Type.NSEC3PARAM);
/**
* This is a subclass of {@link org.xbill.DNS.RRset} that adds a "mark".
*/
public class MarkRRset extends RRset {
private static final long serialVersionUID = 1L;
private boolean mIsMarked = false;
@Override
public boolean equals(Object o) {
return super.equals(o);
}
@Override
public int hashCode() {
return super.hashCode();
}
boolean getMark() {
return mIsMarked;
}
void setMark(boolean value) {
mIsMarked = value;
}
}
public ZoneData() {
mNodeMap = new TreeMap<>();
mRRsetMap = new HashMap<>();
mNSECMap = new TreeMap<>();
mNSEC3Map = new TreeMap<>();
}
public ZoneData(boolean stripDNSSEC, boolean ignoreDuplicates) {
super();
setStripDNSSEC(stripDNSSEC);
setIgnoreDuplicateRRs(ignoreDuplicates);
}
public void setStripDNSSEC(boolean value) {
this.mStripDNSSEC = value;
}
public void setIgnoreDuplicateRRs(boolean value) {
this.mIgnoreDuplicateRRs = value;
}
/**
* @return The detected DNSSEC "type" (what form of negative proof it uses), or
* UNSIGNED if the zone is unsigned or nothing has been processed yet.
*/
public DNSSECType getDNSSECType() {
return mDNSSECType;
}
/**
* @return Any detected NSEC3PARAM record in the processed zone, or null if
* there wasn't one or no zone has been processed yet.
*/
public NSEC3PARAMRecord getNSEC3Params() {
return mNSEC3params;
}
public Name zoneName() {
return mZoneName;
}
public SOARecord getSOA() {
assert mRRsetMap != null;
RRset r = mRRsetMap.get(key(mZoneName, Type.SOA));
assert r != null;
return (SOARecord) r.first();
}
/**
* @return The computed "node map" -- this is a mapping of
* {@link org.xbill.DNS.Name} objects to simple sets of DNS typecodes.
* This will assert if no zone has been processed yet.
*/
public SortedMap<Name, Set<Integer>> nodeMap() {
assert mNodeMap != null;
return mNodeMap;
}
/**
* @return The computed NSEC map.
*/
public SortedMap<Name, MarkRRset> getNSECMap() {
assert mNSECMap != null;
return mNSECMap;
}
/**
* @return the computed NSEC3 map
*/
public SortedMap<Name, MarkRRset> getNSEC3Map() {
assert mNSEC3Map != null;
return mNSEC3Map;
}
/**
* @return an RRset from our internal map with the given name and type, or null if not found.
*/
public RRset getRRset(Name n, int type) {
assert mRRsetMap != null;
return mRRsetMap.get(key(n, type));
}
/**
* Formulate a unique key for an RRset in the RRsetMap. Since an RRset is
* defined to be the set of Records that all the same ownername and type, we can
* formulate a valid key by combining them.
*
* @param n the ownername of the RRset
* @param type The (integer) type of the RRset
* @return the key, as a string.
*/
public static String key(Name n, int type) {
return n.toString() + ':' + type;
}
/**
* Add an Record (RRSIG or otherwise) to a given RRset
*
* @param rrset The rrset to add to
* @param rr The record to add
* @return true if the record was actually added, false if not.
*/
private boolean addRRtoRRset(RRset rrset, Record rr) {
if (mIgnoreDuplicateRRs) {
rrset.addRR(rr);
return true;
}
if (rr instanceof RRSIGRecord) {
for (RRSIGRecord sigrec : rrset.sigs()) {
if (rr.equals(sigrec)) {
return false;
}
}
} else {
for (Record rec : rrset.rrs()) {
if (rr.equals(rec)) {
return false;
}
}
}
rrset.addRR(rr);
return true;
}
/**
* Add a record to the various maps.
*
* @param r the record to add
* @return true if the RR was added, false if it wasn't (because it was a
* duplicate)
*/
private boolean addRR(Record r) {
assert mNSEC3Map != null;
assert mRRsetMap != null;
Name n = r.getName();
int t = r.getType();
if (t == Type.RRSIG)
t = ((RRSIGRecord) r).getTypeCovered();
// Add NSEC and NSEC3 RRs to their respective maps
if (t == Type.NSEC) {
MarkRRset rrset = mNSECMap.computeIfAbsent(n, k -> new MarkRRset());
return addRRtoRRset(rrset, r);
}
if (t == Type.NSEC3) {
MarkRRset rrset = mNSEC3Map.computeIfAbsent(n, k -> new MarkRRset());
return addRRtoRRset(rrset, r);
}
// Add the name and type to the node map
Set<Integer> typeset = mNodeMap.computeIfAbsent(n, k -> new HashSet<>());
typeset.add(r.getType()); // add the original type
// Add the record to the RRset map
String k = key(n, t);
RRset rrset = mRRsetMap.computeIfAbsent(k, k2 -> new RRset());
return addRRtoRRset(rrset, r);
}
/**
* Given a record, determine the DNSSEC signing type. If the record doesn't
* determine that, DNSSECType.UNSIGNED is returned
*
* @param r the record to examine
* @return a DNSSECType of UNKNOWN, unless r is an NSEC or NSEC3 record, in
* which case, the type corresponding to the
*/
private DNSSECType determineDNSSECType(Record r) {
if (r.getType() == Type.NSEC)
return DNSSECType.NSEC;
if (r.getType() == Type.NSEC3) {
NSEC3Record nsec3 = (NSEC3Record) r;
if ((nsec3.getFlags() & NSEC3Record.Flags.OPT_OUT) == NSEC3Record.Flags.OPT_OUT) {
return DNSSECType.NSEC3_OPTOUT;
}
return DNSSECType.NSEC3;
}
return DNSSECType.UNSIGNED;
}
/**
* Add a resource record to the node map and rrsets map.
* @param r The record
* @return the number of errors encountered.
*/
private int addRecord(Record r) {
int errors = 0;
Name n = r.getName();
int t = r.getType();
// skip any generated DNSSEC records if we are skipping
if (mStripDNSSEC && GENTYPES_SET.contains(t)) {
return errors;
}
// Add the record to the various maps.
boolean res = addRR(r);
if (!res) {
log.warning("Record '" + r + "' detected as a duplicate");
errors++;
}
// Learn some things about the zone as we do this pass.
if (t == Type.SOA)
mZoneName = n;
if (t == Type.NSEC3PARAM)
mNSEC3params = (NSEC3PARAMRecord) r;
if (mDNSSECType == DNSSECType.UNSIGNED)
mDNSSECType = determineDNSSECType(r);
return errors;
}
/**
* Given a zone file, load the node and rrset maps, as well as
* determine the NSEC3 parameters and signing type.
*
* @param master A {@link org.xbill.DNS.Master} object.
* @return the number of errors encountered.
* @throws IOException
*/
public int calculateNodes(String zoneFileName, Name origin) {
// The zone is unsigned until we get a clue otherwise.
mDNSSECType = DNSSECType.UNSIGNED;
int errors = 0;
try (Master m = zoneFileName.equals("-") ? new Master(System.in) : new Master(zoneFileName, origin)) {
Record r = null;
while ((r = m.nextRecord()) != null) {
errors += addRecord(r);
}
} catch (IOException e) {
e.printStackTrace();
errors++;
}
return errors;
}
/**
* Given an unsorted list of records, load the node and rrset maps, as well as
* determine the NSEC3 parameters and signing type.
*
* @param records A list of {@link org.xbill.DNS.Record} objects, unsorted.
* @return the number of errors encountered.
*/
public int calculateNodes(List<Record> records) {
mNodeMap = new TreeMap<>();
mRRsetMap = new HashMap<>();
// The zone is unsigned until we get a clue otherwise.
mDNSSECType = DNSSECType.UNSIGNED;
int errors = 0;
for (Record r : records) {
errors += addRecord(r);
}
return errors;
}
/**
* Given a name, typeset, and name of the last zone cut, determine the node
* type.
*
* @param n The owner name for the node (a collection of RRsets with all
* the same owner)
* @param typeset The (simple) set of types present at that node
* @param lastCut The name of the last "zone cut". This is the last name (if
* encountering the name in DNSSEC canonical name order) that was
* either a delegation or a DNAME.
*/
public NodeType determineNodeType(Name n, Set<Integer> typeset, Name lastCut, Name lastDname) {
// Names not at or below the zone name are "invalid" -- they shouldn't
// be in the zone at all, normally. In practice, they are the same as
// glue.
if (!n.subdomain(mZoneName)) {
return NodeType.INVALID;
}
// All RRs at the zone apex are normal
if (n.equals(mZoneName))
return NodeType.NORMAL;
// If the node is not below the zone itself, we will treat it as glue (it is
// really junk).
if (!n.subdomain(mZoneName)) {
return NodeType.GLUE;
}
// If the node is below a zone cut (either a delegation or DNAME), it is
// glue.
if (lastCut != null && n.subdomain(lastCut) && !n.equals(lastCut)) {
return NodeType.GLUE;
}
if (lastDname != null && n.subdomain(lastDname) && !n.equals(lastDname)) {
return NodeType.GLUE;
}
// If the node has a NS record it is a delegation.
if (typeset.contains(Integer.valueOf(Type.NS)))
return NodeType.DELEGATION;
// If the node has a DNAME record, is similar to a delegation
if (typeset.contains(Integer.valueOf(Type.DNAME))) {
return NodeType.DNAME;
}
// everything else is normal
return NodeType.NORMAL;
}
}