Compare commits
2 Commits
v0.20
...
davidb/ref
| Author | SHA1 | Date | |
|---|---|---|---|
| 4478d7e3af | |||
| 1406cd2e68 |
@@ -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
|
||||
|
||||
@@ -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
BIN
lib/bcprov-jdk18on-1.78.jar
Normal file
Binary file not shown.
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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);
|
||||
|
||||
167
src/main/java/com/verisignlabs/dnssec/security/DSAlgorithm.java
Normal file
167
src/main/java/com/verisignlabs/dnssec/security/DSAlgorithm.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
switch (baseType) {
|
||||
case ECDSA:
|
||||
addECDSAAlgorithm(algorithm, sigName, curveName);
|
||||
} else if (baseType == BaseAlgorithm.EDDSA) {
|
||||
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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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.
|
||||
|
||||
397
src/main/java/com/verisignlabs/dnssec/security/ZoneData.java
Normal file
397
src/main/java/com/verisignlabs/dnssec/security/ZoneData.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user