Elliptic curve support.

Improve usage, unknown algorithm error handling in jdnssec-keygen
Use the bouncycastle crypto provider for ECCGOST if available
This commit is contained in:
David Blacka 2016-12-02 17:49:09 -05:00
parent 6bbcf38fe1
commit f170bd170a
9 changed files with 773 additions and 321 deletions

View File

@ -1,3 +1,21 @@
2016-12-05 David Blacka <davidb@verisign.com>
* Add key generation, signing, verification support for elliptic
curve algorithms: ECC-GOST (12), ECDSA P-256 (13) and ECDSA P-384 (15).
2016-08-22 David Blacka <davidb@verisign.com>
* Update internal dnsjava to 2.1.7-vrsn-1.
2014-04-22 David Blacka <davidb@verisign.com>
* ZoneFormat: Make -N also compute original ownernames for empty
non-terminal NSEC3 records.
* ZoneVerifier: Improve the zone verifiers handling of "junk" in a
zone (i.e., ignore resource records that aren't actually in the
zone itself.)
2012-07-16 David Blacka <davidb@verisign.com> 2012-07-16 David Blacka <davidb@verisign.com>
* Released version 0.12. * Released version 0.12.

View File

@ -44,8 +44,6 @@
<javac srcdir="${build.src}" <javac srcdir="${build.src}"
destdir="${build.dest}" destdir="${build.dest}"
classpathref="project.classpath" classpathref="project.classpath"
source="1.5"
target="1.5"
deprecation="true" deprecation="true"
includeantruntime="false" includeantruntime="false"
includes="com/verisignlabs/dnssec/" /> includes="com/verisignlabs/dnssec/" />
@ -70,7 +68,7 @@
verbose="true" author="true" verbose="true" author="true"
windowtitle="jdnssec-tools-${version}" windowtitle="jdnssec-tools-${version}"
use="true"> use="true">
<link href="http://java.sun.com/j2se/1.4.2/docs/api/" /> <link href="https://docs.oracle.com/javase/8/docs/api/" />
<link href="http://www.xbill.org/dnsjava/doc/" /> <link href="http://www.xbill.org/dnsjava/doc/" />
</javadoc> </javadoc>
</target> </target>
@ -172,4 +170,3 @@
</target> </target>
</project> </project>

View File

@ -74,17 +74,18 @@ public class KeyGen extends CLBase
OptionBuilder.withDescription("ZONE | OTHER (default ZONE)"); OptionBuilder.withDescription("ZONE | OTHER (default ZONE)");
opts.addOption(OptionBuilder.create('n')); opts.addOption(OptionBuilder.create('n'));
String[] algStrings = DnsKeyAlgorithm.getInstance().supportedAlgMnemonics();
OptionBuilder.hasArg(); OptionBuilder.hasArg();
OptionBuilder.withArgName("algorithm"); OptionBuilder.withArgName("algorithm");
OptionBuilder.withDescription("RSA | RSASHA1 | RSAMD5 | DH | DSA " OptionBuilder.withDescription(String.join(" | ", algStrings) +
+ "| RSA-NSEC3-SHA1 | DSA-NSEC3-SHA1 " " | alias, RSASHA256 is default.");
+ "| RSASHA256 | RSASHA512 | alias, RSASHA1 is default.");
opts.addOption(OptionBuilder.create('a')); opts.addOption(OptionBuilder.create('a'));
OptionBuilder.hasArg(); OptionBuilder.hasArg();
OptionBuilder.withArgName("size"); OptionBuilder.withArgName("size");
OptionBuilder.withDescription("key size, in bits. (default = 1024)\n" OptionBuilder.withDescription("key size, in bits. default is 1024. "
+ "RSA: [512..4096]\n" + "DSA: [512..1024]\n" + "DH: [128..4096]"); + "RSA: [512..4096], DSA: [512..1024], DH: [128..4096], "
+ "ECDSA: ignored");
opts.addOption(OptionBuilder.create('b')); opts.addOption(OptionBuilder.create('b'));
OptionBuilder.hasArg(); OptionBuilder.hasArg();
@ -98,7 +99,6 @@ public class KeyGen extends CLBase
OptionBuilder.withArgName("dir"); OptionBuilder.withArgName("dir");
OptionBuilder.withDescription("place generated key files in this " + "directory"); OptionBuilder.withDescription("place generated key files in this " + "directory");
opts.addOption(OptionBuilder.create('d')); opts.addOption(OptionBuilder.create('d'));
opts.addOption(OptionBuilder.create('A'));
} }
protected void processOptions(CommandLine cli) protected void processOptions(CommandLine cli)
@ -136,6 +136,11 @@ public class KeyGen extends CLBase
if ((optstr = cli.getOptionValue('a')) != null) if ((optstr = cli.getOptionValue('a')) != null)
{ {
algorithm = parseAlg(optstr); algorithm = parseAlg(optstr);
if (algorithm < 0)
{
System.err.println("DNSSEC algorithm " + optstr + " is not supported");
usage();
}
} }
if ((optstr = cli.getOptionValue('b')) != null) if ((optstr = cli.getOptionValue('b')) != null)
@ -166,7 +171,11 @@ public class KeyGen extends CLBase
DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance(); DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance();
int alg = parseInt(s, -1); int alg = parseInt(s, -1);
if (alg > 0) return alg; if (alg > 0)
{
if (algs.supportedAlgorithm(alg)) return alg;
return -1;
}
return algs.stringToAlgorithm(s); return algs.stringToAlgorithm(s);
} }

View File

@ -546,6 +546,7 @@ public class SignZone extends CLBase
} }
} }
br.close();
if (res.size() == 0) return null; if (res.size() == 0) return null;
return res; return res;
} }

View File

@ -29,13 +29,12 @@
package com.verisignlabs.dnssec.security; package com.verisignlabs.dnssec.security;
import java.security.InvalidAlgorithmParameterException; import java.math.BigInteger;
import java.security.KeyPair; import java.security.*;
import java.security.KeyPairGenerator; import java.security.spec.*;
import java.security.NoSuchAlgorithmException; import java.util.Arrays;
import java.security.Signature;
import java.security.spec.RSAKeyGenParameterSpec;
import java.util.HashMap; import java.util.HashMap;
import java.util.Set;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.xbill.DNS.DNSSEC; import org.xbill.DNS.DNSSEC;
@ -56,28 +55,46 @@ import org.xbill.DNS.DNSSEC;
public class DnsKeyAlgorithm public class DnsKeyAlgorithm
{ {
// Our base algorithm numbers. This is a normalization of the DNSSEC
// algorithms (which are really signature algorithms). Thus RSASHA1,
// RSASHA256, etc. all boil down to 'RSA' here.
public static final int UNKNOWN = -1; public static final int UNKNOWN = -1;
public static final int RSA = 1; public static final int RSA = 1;
public static final int DH = 2; public static final int DH = 2;
public static final int DSA = 3; public static final int DSA = 3;
public static final int ECC_GOST = 4;
public static final int ECDSA = 5;
private static class Entry private static class AlgEntry
{ {
public int dnssecAlgorithm;
public String sigName; public String sigName;
public int baseType; public int baseType;
public Entry(String sigName, int baseType) public AlgEntry(int algorithm, String sigName, int baseType)
{ {
this.dnssecAlgorithm = algorithm;
this.sigName = sigName; this.sigName = sigName;
this.baseType = baseType; this.baseType = baseType;
} }
} }
private static class ECAlgEntry extends AlgEntry
{
public ECParameterSpec ec_spec;
public ECAlgEntry(int algorithm, String sigName, int baseType, ECParameterSpec spec)
{
super(algorithm, sigName, baseType);
this.ec_spec = spec;
}
}
/** /**
* This is a mapping of algorithm identifier to Entry. The Entry contains the * This is a mapping of algorithm identifier to Entry. The Entry contains the
* data needed to map the algorithm to the various crypto implementations. * data needed to map the algorithm to the various crypto implementations.
*/ */
private HashMap<Integer, Entry> mAlgorithmMap; private HashMap<Integer, AlgEntry> mAlgorithmMap;
/** /**
* This is a mapping of algorithm mnemonics to algorithm identifiers. * This is a mapping of algorithm mnemonics to algorithm identifiers.
*/ */
@ -90,8 +107,12 @@ public class DnsKeyAlgorithm
/** This is a cached key pair generator for RSA keys. */ /** This is a cached key pair generator for RSA keys. */
private KeyPairGenerator mRSAKeyGenerator; private KeyPairGenerator mRSAKeyGenerator;
/** This is a cache key pair generator for DSA keys. */ /** This is a cached key pair generator for DSA keys. */
private KeyPairGenerator mDSAKeyGenerator; 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;
private Logger log = Logger.getLogger(this.getClass().toString()); private Logger log = Logger.getLogger(this.getClass().toString());
@ -100,21 +121,37 @@ public class DnsKeyAlgorithm
public DnsKeyAlgorithm() public DnsKeyAlgorithm()
{ {
mAlgorithmMap = new HashMap<Integer, Entry>(); // 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<?> bc_provider_class = Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider");
Provider bc_provider = (Provider) bc_provider_class.newInstance();
Security.addProvider(bc_provider);
}
catch (ReflectiveOperationException e) { }
initialize();
}
private void initialize()
{
mAlgorithmMap = new HashMap<Integer, AlgEntry>();
mMnemonicToIdMap = new HashMap<String, Integer>(); mMnemonicToIdMap = new HashMap<String, Integer>();
mIdToMnemonicMap = new HashMap<Integer, String>(); mIdToMnemonicMap = new HashMap<Integer, String>();
// Load the standard DNSSEC algorithms. // Load the standard DNSSEC algorithms.
addAlgorithm(DNSSEC.Algorithm.RSAMD5, new Entry("MD5withRSA", RSA)); addAlgorithm(DNSSEC.Algorithm.RSAMD5, "MD5withRSA", RSA);
addMnemonic("RSAMD5", DNSSEC.Algorithm.RSAMD5); addMnemonic("RSAMD5", DNSSEC.Algorithm.RSAMD5);
addAlgorithm(DNSSEC.Algorithm.DH, new Entry("", DH)); addAlgorithm(DNSSEC.Algorithm.DH, "", DH);
addMnemonic("DH", DNSSEC.Algorithm.DH); addMnemonic("DH", DNSSEC.Algorithm.DH);
addAlgorithm(DNSSEC.Algorithm.DSA, new Entry("SHA1withDSA", DSA)); addAlgorithm(DNSSEC.Algorithm.DSA, "SHA1withDSA", DSA);
addMnemonic("DSA", DNSSEC.Algorithm.DSA); addMnemonic("DSA", DNSSEC.Algorithm.DSA);
addAlgorithm(DNSSEC.Algorithm.RSASHA1, new Entry("SHA1withRSA", RSA)); addAlgorithm(DNSSEC.Algorithm.RSASHA1, "SHA1withRSA", RSA);
addMnemonic("RSASHA1", DNSSEC.Algorithm.RSASHA1); addMnemonic("RSASHA1", DNSSEC.Algorithm.RSASHA1);
addMnemonic("RSA", DNSSEC.Algorithm.RSASHA1); addMnemonic("RSA", DNSSEC.Algorithm.RSASHA1);
@ -126,22 +163,56 @@ public class DnsKeyAlgorithm
addMnemonic("NSEC3RSASHA1", DNSSEC.Algorithm.RSA_NSEC3_SHA1); addMnemonic("NSEC3RSASHA1", DNSSEC.Algorithm.RSA_NSEC3_SHA1);
// Algorithms added by RFC 5702. // Algorithms added by RFC 5702.
// NOTE: these algorithms aren't available in Java 1.4's sunprovider addAlgorithm(DNSSEC.Algorithm.RSASHA256, "SHA256withRSA", RSA);
// implementation (but are in java 1.5's and later). addMnemonic("RSASHA256", DNSSEC.Algorithm.RSASHA256);
addAlgorithm(8, new Entry("SHA256withRSA", RSA));
addMnemonic("RSASHA256", 8);
addAlgorithm(10, new Entry("SHA512withRSA", RSA)); addAlgorithm(DNSSEC.Algorithm.RSASHA512, "SHA512withRSA", RSA);
addMnemonic("RSASHA512", 10); addMnemonic("RSASHA512", DNSSEC.Algorithm.RSASHA512);
// ECC-GOST is not supported by Java 1.8's Sun crypto provider. The
// bouncycastle.org provider, however, does.
// 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", ECC_GOST, null);
addMnemonic("ECCGOST", DNSSEC.Algorithm.ECC_GOST);
addMnemonic("ECC-GOST", DNSSEC.Algorithm.ECC_GOST);
addAlgorithm(DNSSEC.Algorithm.ECDSAP256SHA256, "SHA256withECDSA", ECDSA, "secp256r1");
addMnemonic("ECDSAP256SHA256", DNSSEC.Algorithm.ECDSAP256SHA256);
addMnemonic("ECDSA-P256", DNSSEC.Algorithm.ECDSAP256SHA256);
addAlgorithm(DNSSEC.Algorithm.ECDSAP384SHA384, "SHA384withECDSA", ECDSA, "secp384r1");
addMnemonic("ECDSAP384SHA384", DNSSEC.Algorithm.ECDSAP384SHA384);
addMnemonic("ECDSA-P384", DNSSEC.Algorithm.ECDSAP384SHA384);
} }
private void addAlgorithm(int algorithm, Entry entry) private void addAlgorithm(int algorithm, String sigName, int baseType)
{ {
mAlgorithmMap.put(algorithm, new AlgEntry(algorithm, sigName, baseType));
}
private void addAlgorithm(int algorithm, String sigName, int baseType, String curveName)
{
ECParameterSpec ec_spec = ECSpecFromAlgorithm(algorithm);
if (ec_spec == null) ec_spec = ECSpecFromName(curveName);
if (ec_spec == null) return;
// Check to see if we can get a Signature object for this algorithm.
try {
Signature.getInstance(sigName);
} catch (NoSuchAlgorithmException e) {
// If not, do not add the algorithm.
return;
}
ECAlgEntry entry = new ECAlgEntry(algorithm, sigName, baseType, ec_spec);
mAlgorithmMap.put(algorithm, entry); mAlgorithmMap.put(algorithm, entry);
} }
private void addMnemonic(String m, int alg) private void addMnemonic(String m, int alg)
{ {
// Do not add mnemonics for algorithms that ended up not actually being supported.
if (!mAlgorithmMap.containsKey(alg)) return;
mMnemonicToIdMap.put(m.toUpperCase(), alg); mMnemonicToIdMap.put(m.toUpperCase(), alg);
if (!mIdToMnemonicMap.containsKey(alg)) if (!mIdToMnemonicMap.containsKey(alg))
{ {
@ -172,14 +243,77 @@ public class DnsKeyAlgorithm
} }
} }
private Entry getEntry(int alg) private AlgEntry getEntry(int alg)
{ {
return mAlgorithmMap.get(alg); 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)
{
switch (algorithm)
{
case 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);
}
default:
return null;
}
}
// Fetch the curve parameters from a named curve.
private ECParameterSpec ECSpecFromName(String stdName)
{
try
{
AlgorithmParameters ap = AlgorithmParameters.getInstance("EC");
ECGenParameterSpec ecg_spec = new ECGenParameterSpec(stdName);
ap.init(ecg_spec);
return ap.getParameterSpec(ECParameterSpec.class);
}
catch (NoSuchAlgorithmException e) {
log.info("Elliptic Curve not supported by any crypto provider: " + e.getMessage());
}
catch (InvalidParameterSpecException e) {
log.info("Elliptic Curve " + stdName + " not supported");
}
return null;
}
public String[] supportedAlgMnemonics()
{
Set<Integer> keyset = mAlgorithmMap.keySet();
Integer[] algs = keyset.toArray(new Integer[keyset.size()]);
Arrays.sort(algs);
String[] result = new String[algs.length];
for (int i = 0; i < algs.length; i++)
{
result[i] = mIdToMnemonicMap.get(algs[i]);
}
return result;
}
/**
* Return a Signature object for the specified DNSSEC algorithm.
* @param algorithm The DNSSEC algorithm (by number).
* @return a Signature object.
*/
public Signature getSignature(int algorithm) public Signature getSignature(int algorithm)
{ {
Entry entry = getEntry(algorithm); AlgEntry entry = getEntry(algorithm);
if (entry == null) return null; if (entry == null) return null;
Signature s = null; Signature s = null;
@ -197,6 +331,62 @@ public class DnsKeyAlgorithm
return s; return s;
} }
/**
* Given one of the ECDSA algorithms (ECDSAP256SHA256, etc.) return
* the elliptic curve parameters.
*
* @param algorithm
* The DNSSEC algorithm number.
* @return The calculated JCA ECParameterSpec for that DNSSEC algorithm, or
* null if not a recognized/supported EC algorithm.
*/
public ECParameterSpec getEllipticCurveParams(int algorithm)
{
AlgEntry entry = getEntry(algorithm);
if (entry == null) return null;
if (!(entry instanceof ECAlgEntry)) return null;
ECAlgEntry ec_entry = (ECAlgEntry) entry;
return ec_entry.ec_spec;
}
/**
* Translate a possible algorithm alias back to the original DNSSEC algorithm
* number
*
* @param algorithm
* a DNSSEC algorithm that may be an alias.
* @return -1 if the algorithm isn't recognised, the orignal algorithm number
* if it is.
*/
public int originalAlgorithm(int algorithm)
{
AlgEntry entry = getEntry(algorithm);
if (entry == null) return -1;
return entry.dnssecAlgorithm;
}
/**
* Test if a given algorithm is supported.
*
* @param algorithm The DNSSEC algorithm number.
* @return true if the algorithm is a recognized and supported algorithm or alias.
*/
public boolean supportedAlgorithm(int algorithm)
{
if (mAlgorithmMap.containsKey(algorithm)) return true;
return false;
}
/**
* Given an algorithm mnemonic, convert the mnemonic to a DNSSEC algorithm
* number.
*
* @param s
* The mnemonic string. This is case-insensitive.
* @return -1 if the mnemonic isn't recognized or supported, the algorithm
* number if it is.
*/
public int stringToAlgorithm(String s) public int stringToAlgorithm(String s)
{ {
Integer alg = mMnemonicToIdMap.get(s.toUpperCase()); Integer alg = mMnemonicToIdMap.get(s.toUpperCase());
@ -204,6 +394,14 @@ public class DnsKeyAlgorithm
return -1; return -1;
} }
/**
* Given a DNSSEC algorithm number, return the "preferred" mnemonic.
*
* @param algorithm
* A DNSSEC algorithm number.
* @return The preferred mnemonic string, or null if not supported or
* recognized.
*/
public String algToString(int algorithm) public String algToString(int algorithm)
{ {
return mIdToMnemonicMap.get(algorithm); return mIdToMnemonicMap.get(algorithm);
@ -211,26 +409,11 @@ public class DnsKeyAlgorithm
public int baseType(int algorithm) public int baseType(int algorithm)
{ {
Entry entry = getEntry(algorithm); AlgEntry entry = getEntry(algorithm);
if (entry != null) return entry.baseType; if (entry != null) return entry.baseType;
return UNKNOWN; return UNKNOWN;
} }
public int standardAlgorithm(int algorithm)
{
switch (baseType(algorithm))
{
case RSA:
return DNSSEC.Algorithm.RSASHA1;
case DSA:
return DNSSEC.Algorithm.DSA;
case DH:
return DNSSEC.Algorithm.DH;
default:
return UNKNOWN;
}
}
public boolean isDSA(int algorithm) public boolean isDSA(int algorithm)
{ {
return (baseType(algorithm) == DSA); return (baseType(algorithm) == DSA);
@ -243,6 +426,7 @@ public class DnsKeyAlgorithm
switch (baseType(algorithm)) switch (baseType(algorithm))
{ {
case RSA: case RSA:
{
if (mRSAKeyGenerator == null) if (mRSAKeyGenerator == null)
{ {
mRSAKeyGenerator = KeyPairGenerator.getInstance("RSA"); mRSAKeyGenerator = KeyPairGenerator.getInstance("RSA");
@ -270,7 +454,9 @@ public class DnsKeyAlgorithm
pair = mRSAKeyGenerator.generateKeyPair(); pair = mRSAKeyGenerator.generateKeyPair();
break; break;
}
case DSA: case DSA:
{
if (mDSAKeyGenerator == null) if (mDSAKeyGenerator == null)
{ {
mDSAKeyGenerator = KeyPairGenerator.getInstance("DSA"); mDSAKeyGenerator = KeyPairGenerator.getInstance("DSA");
@ -278,6 +464,49 @@ public class DnsKeyAlgorithm
mDSAKeyGenerator.initialize(keysize); mDSAKeyGenerator.initialize(keysize);
pair = mDSAKeyGenerator.generateKeyPair(); pair = mDSAKeyGenerator.generateKeyPair();
break; break;
}
case ECC_GOST:
{
if (mECGOSTKeyGenerator == null)
{
mECGOSTKeyGenerator = KeyPairGenerator.getInstance("ECGOST3410");
}
ECParameterSpec ec_spec = getEllipticCurveParams(algorithm);
try
{
mECGOSTKeyGenerator.initialize(ec_spec);
}
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");
}
ECParameterSpec ec_spec = getEllipticCurveParams(algorithm);
try
{
mECKeyGenerator.initialize(ec_spec);
}
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 = mECKeyGenerator.generateKeyPair();
break;
}
default: default:
throw new NoSuchAlgorithmException("Alg " + algorithm); throw new NoSuchAlgorithmException("Alg " + algorithm);
} }

View File

@ -27,15 +27,8 @@ import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.interfaces.DSAParams; import java.security.interfaces.*;
import java.security.interfaces.DSAPrivateKey; import java.security.spec.*;
import java.security.interfaces.DSAPublicKey;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.spec.DSAPrivateKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import javax.crypto.interfaces.DHPrivateKey; import javax.crypto.interfaces.DHPrivateKey;
@ -44,6 +37,7 @@ import javax.crypto.spec.DHParameterSpec;
import javax.crypto.spec.DHPrivateKeySpec; import javax.crypto.spec.DHPrivateKeySpec;
import org.xbill.DNS.DNSKEYRecord; import org.xbill.DNS.DNSKEYRecord;
import org.xbill.DNS.DNSSEC;
import org.xbill.DNS.DNSSEC.DNSSECException; import org.xbill.DNS.DNSSEC.DNSSECException;
import org.xbill.DNS.Name; import org.xbill.DNS.Name;
import org.xbill.DNS.utils.base64; import org.xbill.DNS.utils.base64;
@ -61,9 +55,12 @@ public class DnsKeyConverter
private KeyFactory mRSAKeyFactory; private KeyFactory mRSAKeyFactory;
private KeyFactory mDSAKeyFactory; private KeyFactory mDSAKeyFactory;
private KeyFactory mDHKeyFactory; private KeyFactory mDHKeyFactory;
private KeyFactory mECKeyFactory;
private DnsKeyAlgorithm mAlgorithms;
public DnsKeyConverter() public DnsKeyConverter()
{ {
mAlgorithms = DnsKeyAlgorithm.getInstance();
} }
/** /**
@ -76,23 +73,19 @@ public class DnsKeyConverter
{ {
if (pKeyRecord.getKey() == null) return null; if (pKeyRecord.getKey() == null) return null;
// For now, instead of re-implementing parseRecord (or adding this stuff // Because we have arbitrarily aliased algorithms, we need to possibly
// to DNSjava), we will just translate the algorithm back to a standard // translate the aliased algorithm back to the actual algorithm.
// algorithm. Note that this will unnecessarily convert RSAMD5 to RSASHA1.
DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance(); int originalAlgorithm = mAlgorithms.originalAlgorithm(pKeyRecord.getAlgorithm());
int standard_alg = algs.standardAlgorithm(pKeyRecord.getAlgorithm());
if (standard_alg <= 0) if (originalAlgorithm <= 0) throw new NoSuchAlgorithmException("DNSKEY algorithm "
throw new NoSuchAlgorithmException("DNSKEY algorithm "
+ pKeyRecord.getAlgorithm() + " is unrecognized"); + pKeyRecord.getAlgorithm() + " is unrecognized");
if (pKeyRecord.getAlgorithm() != standard_alg) if (pKeyRecord.getAlgorithm() != originalAlgorithm)
{ {
pKeyRecord = new DNSKEYRecord(pKeyRecord.getName(), pKeyRecord = new DNSKEYRecord(pKeyRecord.getName(), pKeyRecord.getDClass(),
pKeyRecord.getDClass(),
pKeyRecord.getTTL(), pKeyRecord.getFlags(), pKeyRecord.getTTL(), pKeyRecord.getFlags(),
pKeyRecord.getProtocol(), standard_alg, pKeyRecord.getProtocol(), originalAlgorithm,
pKeyRecord.getKey()); pKeyRecord.getKey());
} }
@ -132,11 +125,9 @@ public class DnsKeyConverter
public PrivateKey convertEncodedPrivateKey(byte[] key, int algorithm) public PrivateKey convertEncodedPrivateKey(byte[] key, int algorithm)
{ {
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(key); PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(key);
DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance();
try try
{ {
switch (algs.baseType(algorithm)) switch (mAlgorithms.baseType(algorithm))
{ {
case DnsKeyAlgorithm.RSA: case DnsKeyAlgorithm.RSA:
return mRSAKeyFactory.generatePrivate(spec); return mRSAKeyFactory.generatePrivate(spec);
@ -146,12 +137,17 @@ public class DnsKeyConverter
} }
catch (GeneralSecurityException e) catch (GeneralSecurityException e)
{ {
e.printStackTrace();
} }
return null; return null;
} }
private int parseInt(String s, int def) /**
* A simple wrapper for parsing integers; parse failures result in the
* supplied default.
*/
private static int parseInt(String s, int def)
{ {
try try
{ {
@ -195,9 +191,8 @@ public class DnsKeyConverter
String[] toks = val.split("\\s", 2); String[] toks = val.split("\\s", 2);
val = toks[0]; val = toks[0];
int alg = parseInt(val, -1); int alg = parseInt(val, -1);
DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance();
switch (algs.baseType(alg)) switch (mAlgorithms.baseType(alg))
{ {
case DnsKeyAlgorithm.RSA: case DnsKeyAlgorithm.RSA:
return parsePrivateRSA(lines); return parsePrivateRSA(lines);
@ -205,6 +200,10 @@ public class DnsKeyConverter
return parsePrivateDSA(lines); return parsePrivateDSA(lines);
case DnsKeyAlgorithm.DH: case DnsKeyAlgorithm.DH:
return parsePrivateDH(lines); return parsePrivateDH(lines);
case DnsKeyAlgorithm.ECC_GOST:
return parsePrivateECDSA(lines, alg);
case DnsKeyAlgorithm.ECDSA:
return parsePrivateECDSA(lines, alg);
default: default:
throw new IOException("unsupported private key algorithm: " + val); throw new IOException("unsupported private key algorithm: " + val);
} }
@ -216,7 +215,7 @@ public class DnsKeyConverter
/** /**
* @return the value part of an "attribute:value" pair. The value is trimmed. * @return the value part of an "attribute:value" pair. The value is trimmed.
*/ */
private String value(String av) private static String value(String av)
{ {
if (av == null) return null; if (av == null) return null;
@ -434,6 +433,60 @@ public class DnsKeyConverter
} }
} }
/**
* Given the remaining lines in a BIND9-style ECDSA private key, parse the key
* info and translate it into a JCA private key object.
* @param lines The remaining lines in a private key file (after
* @throws NoSuchAlgorithmException
* If elliptic curve is not available.
*/
private PrivateKey parsePrivateECDSA(StringTokenizer lines, int algorithm)
throws NoSuchAlgorithmException
{
BigInteger s = null;
while (lines.hasMoreTokens())
{
String line = lines.nextToken();
if (line == null) continue;
if (line.startsWith("#")) continue;
String val = value(line);
if (val == null) continue;
byte[] data = base64.fromString(val);
if (line.startsWith("PrivateKey: "))
{
s = new BigInteger(1, data);
}
}
if (mECKeyFactory == null)
{
mECKeyFactory = KeyFactory.getInstance("EC");
}
ECParameterSpec ec_spec = mAlgorithms.getEllipticCurveParams(algorithm);
if (ec_spec == null)
{
throw new NoSuchAlgorithmException("DNSSEC algorithm " + algorithm +
" is not a recognized Elliptic Curve algorithm");
}
KeySpec spec = new ECPrivateKeySpec(s, ec_spec);
try
{
return mECKeyFactory.generatePrivate(spec);
}
catch (InvalidKeySpecException e)
{
e.printStackTrace();
return null;
}
}
/** /**
* Given a private key and public key, generate the BIND9 style private key * Given a private key and public key, generate the BIND9 style private key
* format. * format.
@ -452,14 +505,17 @@ public class DnsKeyConverter
{ {
return generatePrivateDH((DHPrivateKey) priv, (DHPublicKey) pub, alg); return generatePrivateDH((DHPrivateKey) priv, (DHPublicKey) pub, alg);
} }
else if (priv instanceof ECPrivateKey && pub instanceof ECPublicKey)
{
return generatePrivateEC((ECPrivateKey) priv, (ECPublicKey) pub, alg);
}
return null; return null;
} }
/** /**
* Convert from 'unsigned' big integer to original 'signed format' in Base64 * Convert from 'unsigned' big integer to original 'signed format' in Base64
*/ */
private String b64BigInt(BigInteger i) private static String b64BigInt(BigInteger i)
{ {
byte[] orig_bytes = i.toByteArray(); byte[] orig_bytes = i.toByteArray();
@ -482,10 +538,9 @@ public class DnsKeyConverter
{ {
StringWriter sw = new StringWriter(); StringWriter sw = new StringWriter();
PrintWriter out = new PrintWriter(sw); PrintWriter out = new PrintWriter(sw);
DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance();
out.println("Private-key-format: v1.2"); out.println("Private-key-format: v1.2");
out.println("Algorithm: " + algorithm + " (" + algs.algToString(algorithm) out.println("Algorithm: " + algorithm + " (" + mAlgorithms.algToString(algorithm)
+ ")"); + ")");
out.print("Modulus: "); out.print("Modulus: ");
out.println(b64BigInt(key.getModulus())); out.println(b64BigInt(key.getModulus()));
@ -513,12 +568,11 @@ public class DnsKeyConverter
{ {
StringWriter sw = new StringWriter(); StringWriter sw = new StringWriter();
PrintWriter out = new PrintWriter(sw); PrintWriter out = new PrintWriter(sw);
DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance();
DHParameterSpec p = key.getParams(); DHParameterSpec p = key.getParams();
out.println("Private-key-format: v1.2"); out.println("Private-key-format: v1.2");
out.println("Algorithm: " + algorithm + " (" + algs.algToString(algorithm) out.println("Algorithm: " + algorithm + " (" + mAlgorithms.algToString(algorithm)
+ ")"); + ")");
out.print("Prime(p): "); out.print("Prime(p): ");
out.println(b64BigInt(p.getP())); out.println(b64BigInt(p.getP()));
@ -538,12 +592,11 @@ public class DnsKeyConverter
{ {
StringWriter sw = new StringWriter(); StringWriter sw = new StringWriter();
PrintWriter out = new PrintWriter(sw); PrintWriter out = new PrintWriter(sw);
DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance();
DSAParams p = key.getParams(); DSAParams p = key.getParams();
out.println("Private-key-format: v1.2"); out.println("Private-key-format: v1.2");
out.println("Algorithm: " + algorithm + " (" + algs.algToString(algorithm) out.println("Algorithm: " + algorithm + " (" + mAlgorithms.algToString(algorithm)
+ ")"); + ")");
out.print("Prime(p): "); out.print("Prime(p): ");
out.println(b64BigInt(p.getP())); out.println(b64BigInt(p.getP()));
@ -558,4 +611,23 @@ public class DnsKeyConverter
return sw.toString(); return sw.toString();
} }
/**
* Given an elliptic curve key pair, and the actual algorithm (which will
* describe the curve used), return the BIND9-style text encoding.
*/
private String generatePrivateEC(ECPrivateKey priv, ECPublicKey pub, int alg)
{
StringWriter sw = new StringWriter();
PrintWriter out = new PrintWriter(sw);
out.println("Private-key-format: v1.2");
out.println("Algorithm: " + alg + " (" + mAlgorithms.algToString(alg)
+ ")");
out.print("PrivateKey: ");
out.println(b64BigInt(priv.getS()));
return sw.toString();
}
} }

View File

@ -256,6 +256,12 @@ public class DnsSecVerifier
sig = SignUtils.convertDSASignature(sig); sig = SignUtils.convertDSASignature(sig);
} }
if (sigrec.getAlgorithm() == DNSSEC.Algorithm.ECDSAP256SHA256 ||
sigrec.getAlgorithm() == DNSSEC.Algorithm.ECDSAP384SHA384)
{
sig = SignUtils.convertECDSASignature(sig);
}
if (!signer.verify(sig)) if (!signer.verify(sig))
{ {
if (reasons != null) reasons.add("Signature failed to verify cryptographically"); if (reasons != null) reasons.add("Signature failed to verify cryptographically");
@ -283,7 +289,6 @@ public class DnsSecVerifier
* *
* @return true if the set verified, false if it did not. * @return true if the set verified, false if it did not.
*/ */
@SuppressWarnings("unchecked")
public boolean verify(RRset rrset) public boolean verify(RRset rrset)
{ {
boolean result = mVerifyAllSigs ? true : false; boolean result = mVerifyAllSigs ? true : false;

View File

@ -33,12 +33,7 @@ import java.util.List;
import java.util.ListIterator; import java.util.ListIterator;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.xbill.DNS.DNSKEYRecord; import org.xbill.DNS.*;
import org.xbill.DNS.Name;
import org.xbill.DNS.RRSIGRecord;
import org.xbill.DNS.RRset;
import org.xbill.DNS.Record;
import org.xbill.DNS.Type;
import org.xbill.DNS.utils.hexdump; import org.xbill.DNS.utils.hexdump;
/** /**
@ -58,7 +53,7 @@ public class JCEDnsSecSigner
private DnsKeyConverter mKeyConverter; private DnsKeyConverter mKeyConverter;
private boolean mVerboseSigning = false; private boolean mVerboseSigning = false;
private Logger log; private Logger log = Logger.getLogger(this.getClass().toString());
public JCEDnsSecSigner() public JCEDnsSecSigner()
{ {
@ -197,6 +192,12 @@ public class JCEDnsSecSigner
DSAPublicKey pk = (DSAPublicKey) pair.getPublic(); DSAPublicKey pk = (DSAPublicKey) pair.getPublic();
sig = SignUtils.convertDSASignature(pk.getParams(), sig); sig = SignUtils.convertDSASignature(pk.getParams(), sig);
} }
// Convert to RFC 6605, etc format
if (pair.getDNSKEYAlgorithm() == DNSSEC.Algorithm.ECDSAP256SHA256 ||
pair.getDNSKEYAlgorithm() == DNSSEC.Algorithm.ECDSAP384SHA384)
{
sig = SignUtils.convertECDSASignature(pair.getDNSKEYAlgorithm(), sig);
}
RRSIGRecord sigrec = SignUtils.generateRRSIG(sig, presig); RRSIGRecord sigrec = SignUtils.generateRRSIG(sig, presig);
if (mVerboseSigning) if (mVerboseSigning)
{ {

View File

@ -429,6 +429,126 @@ public class SignUtils
return sig; return sig;
} }
// Given one of the ECDSA algorithms determine the "length", which is the
// length, in bytes, of both 'r' and 's' in the ECDSA signature.
private static int ecdsaLength(int algorithm) throws SignatureException
{
switch (algorithm)
{
case DNSSEC.Algorithm.ECDSAP256SHA256: return 32;
case DNSSEC.Algorithm.ECDSAP384SHA384: return 48;
default:
throw new SignatureException("Algorithm " + algorithm +
" is not a supported ECDSA signature algorithm.");
}
}
/**
* Convert a JCE standard ECDSA signature (which is a ASN.1 encoding) into a
* standard DNS signature.
*
* The format of the ASN.1 signature is
*
* ASN1_SEQ . seq_length . ASN1_INT . r_length . R . ANS1_INT . s_length . S
*
* where R and S may have a leading zero byte if without it the values would
* be negative.
*
* The format of the DNSSEC signature is just R . S where R and S are both
* exactly "length" bytes.
*
* @param signature
* The output of a ECDSA signature object.
* @return signature data formatted for use in DNSSEC.
* @throws SignatureException if the ASN.1 encoding appears to be corrupt.
*/
public static byte[] convertECDSASignature(int algorithm, byte[] signature)
throws SignatureException
{
int exp_length = ecdsaLength(algorithm);
byte[] sig = new byte[exp_length * 2];
if (signature[0] != ASN1_SEQ || signature[2] != ASN1_INT)
{
throw new SignatureException("Invalid ASN.1 signature format: expected SEQ, INT");
}
int r_len = signature[3];
int r_pos = 4;
if (signature[r_pos + r_len] != ASN1_INT)
{
throw new SignatureException("Invalid ASN.1 signature format: expected SEQ, INT, INT");
}
int s_pos = r_pos + r_len + 2;
int s_len = signature[r_pos + r_len + 1];
// Adjust for leading zeros on both R and S
if (signature[r_pos] == 0) {
r_pos++;
}
if (signature[s_pos] == 0) {
s_pos++;
}
System.arraycopy(signature, r_pos, sig, 0, exp_length);
System.arraycopy(signature, s_pos, sig, exp_length, exp_length);
return sig;
}
/**
* Convert a DNS standard ECDSA signature (defined in RFC 6605) into a
* JCE standard ECDSA signature, which is encoded in ASN.1.
*
* The format of the ASN.1 signature is
*
* ASN1_SEQ . seq_length . ASN1_INT . r_length . R . ANS1_INT . s_length . S
*
* where R and S may have a leading zero byte if without it the values would
* be negative.
*
* The format of the DNSSEC signature is just R . S where R and S are both
* exactly "length" bytes.
*
* @param signature
* The binary signature data from an RRSIG record.
* @return signature data that may be used in a JCE Signature object for
* verification purposes.
*/
public static byte[] convertECDSASignature(byte[] signature)
{
byte r_src_pos, r_src_len, r_pad, s_src_pos, s_src_len, s_pad, len;
r_src_len = s_src_len = (byte) (signature.length / 2);
r_src_pos = 0; r_pad = 0;
s_src_pos = (byte) (r_src_pos + r_src_len); s_pad = 0;
len = (byte) (6 + r_src_len + s_src_len);
if (signature[r_src_pos] < 0) {
r_pad = 1; len++;
}
if (signature[s_src_pos] < 0) {
s_pad = 1; len++;
}
byte[] sig = new byte[len];
byte pos = 0;
sig[pos++] = ASN1_SEQ;
sig[pos++] = (byte) (len - 2);
sig[pos++] = ASN1_INT;
sig[pos++] = (byte) (r_src_len + r_pad);
pos += r_pad;
System.arraycopy(signature, r_src_pos, sig, pos, r_src_len);
pos += r_src_len;
sig[pos++] = ASN1_INT;
sig[pos++] = (byte) (s_src_len + s_pad);
pos += s_pad;
System.arraycopy(signature, s_src_pos, sig, pos, s_src_len);
return sig;
}
/** /**
* This is a convenience routine to help us classify records/RRsets. * This is a convenience routine to help us classify records/RRsets.
* *