remove ECC-GOST, add new DS algorithms (#18)

This commit is contained in:
David Blacka 2024-04-13 11:39:18 -04:00 committed by GitHub
parent 1727d7c7d8
commit 1406cd2e68
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 204 additions and 143 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

@ -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,14 +135,14 @@ public class DSTool extends CLBase {
}
public void execute() throws Exception {
for (String keyname : keynames){
for (String keyname : keynames) {
createDS(keyname);
}
}
public static void main(String[] args) {
DSTool tool = new DSTool("dstool", "jdnssec-dstool [..options..] keyfile [keyfile..]");
tool.run(args);
}
}

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

@ -1317,6 +1317,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 +1330,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 +1378,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.