Make it build again; print responses in debug mode
authorDavid Blacka <davidb@verisign.com>
Wed, 17 Jun 2020 13:05:11 +0000 (13:05 +0000)
committerDavid Blacka <davidb@verisign.com>
Wed, 17 Jun 2020 13:05:11 +0000 (13:05 +0000)
Also make sure we were using the "latest" dnsjava.

TODO
build.xml
lib/dnsjava-2.1.9-vrsn-1.jar [moved from lib/dnsjava-2.1.7-vrsn-1.jar with 69% similarity]
src/com/verisign/cl/DNSSECValTool.java
src/com/verisign/tat/dnssec/DnsKeyAlgorithm.java [new file with mode: 0644]
src/com/verisign/tat/dnssec/DnsKeyConverter.java [new file with mode: 0644]
src/com/verisign/tat/dnssec/DnsSecVerifier.java

diff --git a/TODO b/TODO
index a0124a8..6caf032 100644 (file)
--- a/TODO
+++ b/TODO
@@ -1,22 +1,23 @@
 TODO
 
-* Complete CNAME response validation code.
+* Remove references to TAT
 
-  This differs from the original Unbound code in that it can only
-  validate CNAME/DNAME chains as long as we have the exact keys for
-  each element of the chain.  The Unbound (java) version solved this
-  by requerying for each element of the CNAME chain and validating
-  each element independently (that is, it could construct a chain of
-  trust to each link separately).
+  TAT was an internal, java-based test framework that is no longer in
+  use.  Instead this code is generally just invoked directly from the
+  command line.  Results are generally determined from the log output.
 
-* Add way to report errors and validation failure conditions.
-
-  For the TAT handler, what we want is a way to fetch all of the
-  various reason why a validation failed, so it can be spit out in the
-  test results.  A globally available vector of error messages?  Pass
-  around a vector of error messages?
-
-* Create the TAT handler that uses this bit of code.
+* Implement some form of parallelism.
 
+  A common use case for this tool is to give it a (possibly long) list
+  of queries to do.  Right now, it will just process them serially.
+  However, with some parallelism, we should be able to go faster.
 
+* Complete CNAME response validation code (in progress).
 
+  In the unbound-prototype, we split the CNAME chain and then
+  requeried for each element of the chain.  This would allow us to
+  re-determine the chain of trust for each element.  In this code,
+  however, since we don't have a facility (nor want one) to establish
+  chains of trust, we are going to try and validate the response in
+  one pass.  Note that we have to account for wildcard CNAME
+  expressions, as well as validate the end-of-chain.
index 5fe3b8b..9a3ca9e 100644 (file)
--- a/build.xml
+++ b/build.xml
@@ -60,7 +60,7 @@
     <jar destfile="${build.lib.dest}/dnssecvaltool.jar">
       <zipfileset dir="${build.dest}" includes="**/*.class" />
 
-      <zipfileset src="${lib.dir}/dnsjava-2.1.7-vrsn-1.jar" />
+      <zipfileset src="${lib.dir}/dnsjava-2.1.9-vrsn-1.jar" />
       <zipfileset src="${lib.dir}/log4j-1.2.15.jar" />
       <manifest>
         <attribute name="Main-Class"
similarity index 69%
rename from lib/dnsjava-2.1.7-vrsn-1.jar
rename to lib/dnsjava-2.1.9-vrsn-1.jar
index 819ab30..3cbf6fb 100644 (file)
Binary files a/lib/dnsjava-2.1.7-vrsn-1.jar and b/lib/dnsjava-2.1.9-vrsn-1.jar differ
index 9fa36ec..02d22cf 100644 (file)
@@ -243,6 +243,10 @@ public class DNSSECValTool {
             }
             byte result = validator.validateMessage(response, zone.toString());
 
+            if (debug) {
+                System.out.println(response);
+            }
+
             switch (result) {
             case SecurityStatus.BOGUS:
             case SecurityStatus.INVALID:
diff --git a/src/com/verisign/tat/dnssec/DnsKeyAlgorithm.java b/src/com/verisign/tat/dnssec/DnsKeyAlgorithm.java
new file mode 100644 (file)
index 0000000..7745729
--- /dev/null
@@ -0,0 +1,484 @@
+/*
+ * $Id$
+ *
+ * Copyright (c) 2017 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.verisign.tat.dnssec;
+
+import java.math.BigInteger;
+import java.security.*;
+import java.security.spec.*;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Set;
+import java.util.logging.Logger;
+
+import org.xbill.DNS.DNSSEC;
+
+/**
+ * This class handles translating DNS signing algorithm identifiers
+ * into various usable java implementations.
+ *
+ * Besides centralizing the logic surrounding matching a DNSKEY
+ * algorithm identifier with various crypto implementations, it also
+ * handles algorithm aliasing -- that is, defining a new algorithm
+ * identifier to be equivalent to an existing identifier.
+ *
+ * @author David Blacka (orig)
+ * @author $Author: davidb $ (latest)
+ * @version $Revision: 2098 $
+ */
+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 RSA      = 1;
+    public static final int DH       = 2;
+    public static final int DSA      = 3;
+    public static final int ECC_GOST = 4;
+    public static final int ECDSA    = 5;
+
+    private static class AlgEntry
+    {
+        public int    dnssecAlgorithm;
+        public String sigName;
+        public int    baseType;
+
+        public AlgEntry(int algorithm, String sigName, int baseType) {
+            this.dnssecAlgorithm = algorithm;
+            this.sigName         = sigName;
+            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
+     * data needed to map the algorithm to the various crypto implementations.
+     */
+    private HashMap<Integer, AlgEntry>  mAlgorithmMap;
+    /**
+     * This is a mapping of algorithm mnemonics to algorithm identifiers.
+     */
+    private HashMap<String, Integer> mMnemonicToIdMap;
+    /**
+     * This is a mapping of identifiers to preferred mnemonic -- the preferred one
+     * is the first defined one
+     */
+    private HashMap<Integer, String> mIdToMnemonicMap;
+
+    /** This is a cached key pair generator for RSA keys. */
+    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;
+
+    private Logger                   log = Logger.getLogger(this.getClass().toString());
+
+    /** This is the global instance for this class. */
+    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<?> 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>();
+        mIdToMnemonicMap = new HashMap<Integer, String>();
+
+        // Load the standard DNSSEC algorithms.
+        addAlgorithm(DNSSEC.Algorithm.RSAMD5, "MD5withRSA", RSA);
+        addMnemonic("RSAMD5", DNSSEC.Algorithm.RSAMD5);
+
+        addAlgorithm(DNSSEC.Algorithm.DH, "", DH);
+        addMnemonic("DH", DNSSEC.Algorithm.DH);
+
+        addAlgorithm(DNSSEC.Algorithm.DSA, "SHA1withDSA", DSA);
+        addMnemonic("DSA", DNSSEC.Algorithm.DSA);
+
+        addAlgorithm(DNSSEC.Algorithm.RSASHA1, "SHA1withRSA", RSA);
+        addMnemonic("RSASHA1", DNSSEC.Algorithm.RSASHA1);
+        addMnemonic("RSA", DNSSEC.Algorithm.RSASHA1);
+
+        // Load the (now) standard aliases
+        addAlias(DNSSEC.Algorithm.DSA_NSEC3_SHA1, "DSA-NSEC3-SHA1", DNSSEC.Algorithm.DSA);
+        addAlias(DNSSEC.Algorithm.RSA_NSEC3_SHA1, "RSA-NSEC3-SHA1", DNSSEC.Algorithm.RSASHA1);
+        // Also recognize the BIND 9.6 mnemonics
+        addMnemonic("NSEC3DSA", DNSSEC.Algorithm.DSA_NSEC3_SHA1);
+        addMnemonic("NSEC3RSASHA1", DNSSEC.Algorithm.RSA_NSEC3_SHA1);
+
+        // Algorithms added by RFC 5702.
+        addAlgorithm(DNSSEC.Algorithm.RSASHA256, "SHA256withRSA", RSA);
+        addMnemonic("RSASHA256", DNSSEC.Algorithm.RSASHA256);
+
+        addAlgorithm(DNSSEC.Algorithm.RSASHA512, "SHA512withRSA", 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.
+        // 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, 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);
+    }
+
+    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);
+        if (!mIdToMnemonicMap.containsKey(alg)) {
+            mIdToMnemonicMap.put(alg, m);
+        }
+    }
+
+    public void addAlias(int alias, String mnemonic, int original_algorithm) {
+        if (mAlgorithmMap.containsKey(alias)) {
+            log.warning("Unable to alias algorithm " + alias + " because it already exists.");
+            return;
+        }
+
+        if (!mAlgorithmMap.containsKey(original_algorithm)) {
+            log.warning("Unable to alias algorith " + alias +
+                        " to unknown algorithm identifier " + original_algorithm);
+            return;
+        }
+
+        mAlgorithmMap.put(alias, mAlgorithmMap.get(original_algorithm));
+
+        if (mnemonic != null) {
+            addMnemonic(mnemonic, alias);
+        }
+    }
+
+    private AlgEntry getEntry(int 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) {
+        AlgEntry entry = getEntry(algorithm);
+        if (entry == null) return null;
+
+        Signature s = null;
+
+        try {
+            s = Signature.getInstance(entry.sigName);
+        } catch (NoSuchAlgorithmException e) {
+            log.severe("Unable to get signature implementation for algorithm " +
+                       algorithm + ": " + e);
+        }
+
+        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) {
+        Integer alg = mMnemonicToIdMap.get(s.toUpperCase());
+        if (alg != null) return alg.intValue();
+        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) {
+        return mIdToMnemonicMap.get(algorithm);
+    }
+
+
+    /**
+     * Given a DNSSEC algorithm, return the "base type", i.e., RSA, DSA, ECDSA.
+     */
+    public int baseType(int algorithm) {
+        AlgEntry entry = getEntry(algorithm);
+        if (entry != null) return entry.baseType;
+        return UNKNOWN;
+    }
+
+    /**
+     *  Specifically test if an algorithm's base type is DSA.  For reasons.
+     */
+    public boolean isDSA(int algorithm) {
+        return (baseType(algorithm) == DSA);
+    }
+
+    public KeyPair generateKeyPair(int algorithm, int keysize, boolean useLargeExp)
+        throws NoSuchAlgorithmException {
+        KeyPair pair = null;
+        switch (baseType(algorithm))
+        {
+        case RSA: {
+            if (mRSAKeyGenerator == null) {
+                mRSAKeyGenerator = KeyPairGenerator.getInstance("RSA");
+            }
+
+            RSAKeyGenParameterSpec rsa_spec;
+            if (useLargeExp) {
+                rsa_spec = new RSAKeyGenParameterSpec(keysize, RSAKeyGenParameterSpec.F4);
+            } else {
+                rsa_spec = new RSAKeyGenParameterSpec(keysize, RSAKeyGenParameterSpec.F0);
+            }
+
+            try {
+                mRSAKeyGenerator.initialize(rsa_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 = mRSAKeyGenerator.generateKeyPair();
+            break;
+        }
+        case DSA: {
+            if (mDSAKeyGenerator == null) {
+                mDSAKeyGenerator = KeyPairGenerator.getInstance("DSA");
+            }
+            mDSAKeyGenerator.initialize(keysize);
+            pair = mDSAKeyGenerator.generateKeyPair();
+            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:
+            throw new NoSuchAlgorithmException("Alg " + algorithm);
+        }
+
+        return pair;
+    }
+
+    public KeyPair generateKeyPair(int algorithm, int keysize)
+        throws NoSuchAlgorithmException {
+        return generateKeyPair(algorithm, keysize, false);
+    }
+
+  public static DnsKeyAlgorithm getInstance() {
+      if (mInstance == null) mInstance = new DnsKeyAlgorithm();
+      return mInstance;
+  }
+}
diff --git a/src/com/verisign/tat/dnssec/DnsKeyConverter.java b/src/com/verisign/tat/dnssec/DnsKeyConverter.java
new file mode 100644 (file)
index 0000000..4245f5c
--- /dev/null
@@ -0,0 +1,555 @@
+// Copyright (C) 2017 verisign, Inc.
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+package com.verisign.tat.dnssec;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.interfaces.*;
+import java.security.spec.*;
+import java.util.StringTokenizer;
+
+import javax.crypto.interfaces.DHPrivateKey;
+import javax.crypto.interfaces.DHPublicKey;
+import javax.crypto.spec.DHParameterSpec;
+import javax.crypto.spec.DHPrivateKeySpec;
+
+import org.xbill.DNS.DNSKEYRecord;
+import org.xbill.DNS.DNSSEC.DNSSECException;
+import org.xbill.DNS.Name;
+import org.xbill.DNS.utils.base64;
+
+/**
+ * This class handles conversions between JCA key formats and DNSSEC and BIND9
+ * key formats.
+ *
+ * @author David Blacka (original)
+ * @author $Author$ (latest)
+ * @version $Revision$
+ */
+public class DnsKeyConverter
+{
+    private KeyFactory      mRSAKeyFactory;
+    private KeyFactory      mDSAKeyFactory;
+    private KeyFactory      mDHKeyFactory;
+    private KeyFactory      mECKeyFactory;
+    private DnsKeyAlgorithm mAlgorithms;
+
+    public DnsKeyConverter() {
+        mAlgorithms = DnsKeyAlgorithm.getInstance();
+    }
+
+    /**
+     * Given a DNS KEY record, return the JCA public key
+     *
+     * @throws NoSuchAlgorithmException
+     */
+    public PublicKey parseDNSKEYRecord(DNSKEYRecord pKeyRecord)
+        throws NoSuchAlgorithmException {
+
+        if (pKeyRecord.getKey() == null) return null;
+
+        // Because we have arbitrarily aliased algorithms, we need to
+        // possibly translate the aliased algorithm back to the actual
+        // algorithm.
+
+        int originalAlgorithm = mAlgorithms.originalAlgorithm(pKeyRecord.getAlgorithm());
+
+        if (originalAlgorithm <= 0) {
+            throw new NoSuchAlgorithmException("DNSKEY algorithm " +
+                                               pKeyRecord.getAlgorithm() + " is unrecognized");
+        }
+
+        if (pKeyRecord.getAlgorithm() != originalAlgorithm) {
+            pKeyRecord = new DNSKEYRecord(pKeyRecord.getName(), pKeyRecord.getDClass(),
+                                          pKeyRecord.getTTL(), pKeyRecord.getFlags(),
+                                          pKeyRecord.getProtocol(), originalAlgorithm,
+                                          pKeyRecord.getKey());
+        }
+
+        try {
+            return pKeyRecord.getPublicKey();
+        } catch (DNSSECException e) {
+            throw new NoSuchAlgorithmException(e);
+        }
+    }
+
+    /**
+     * Given a JCA public key and the ancillary data, generate a DNSKEY record.
+     */
+    public DNSKEYRecord generateDNSKEYRecord(Name      name,
+                                             int       dclass,
+                                             long      ttl,
+                                             int       flags,
+                                             int       alg,
+                                             PublicKey key) {
+        try {
+            return new DNSKEYRecord(name, dclass, ttl, flags, DNSKEYRecord.Protocol.DNSSEC, alg, key);
+        } catch (DNSSECException e) {
+            // FIXME: this mimics the behavior of
+            // KEYConverter.buildRecord(), which would return null if
+            // the algorithm was unknown.
+            return null;
+        }
+    }
+
+    // Private Key Specific Parsing routines
+
+    /**
+     * Convert a PKCS#8 encoded private key into a PrivateKey object.
+     */
+    public PrivateKey convertEncodedPrivateKey(byte[] key, int algorithm) {
+
+        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(key);
+
+        try {
+            switch (mAlgorithms.baseType(algorithm))
+            {
+            case DnsKeyAlgorithm.RSA:
+                return mRSAKeyFactory.generatePrivate(spec);
+            case DnsKeyAlgorithm.DSA:
+                return mDSAKeyFactory.generatePrivate(spec);
+            }
+        } catch (GeneralSecurityException e) {
+            e.printStackTrace();
+        }
+
+        return null;
+    }
+
+    /**
+     * A simple wrapper for parsing integers; parse failures result in
+     * the supplied default.
+     */
+    private static int parseInt(String s, int def) {
+        try {
+            return Integer.parseInt(s);
+        } catch (NumberFormatException e) {
+            return def;
+        }
+    }
+
+    /**
+     * @return a JCA private key, given a BIND9-style textual encoding
+     */
+    public PrivateKey parsePrivateKeyString(String key)
+        throws IOException, NoSuchAlgorithmException {
+
+        StringTokenizer lines = new StringTokenizer(key, "\n");
+
+        while (lines.hasMoreTokens()) {
+            String line = lines.nextToken();
+            if (line == null) continue;
+            if (line.startsWith("#")) continue;
+
+            String val = value(line);
+            if (val == null) continue;
+
+            if (line.startsWith("Private-key-format: ")) {
+                if (!val.equals("v1.2") && !val.equals("v1.3")) {
+                    throw new IOException("unsupported private key format: " + val);
+                }
+            } else if (line.startsWith("Algorithm: ")) {
+                // here we assume that the value looks like # (MNEM)
+                // or just the number.
+                String[] toks = val.split("\\s", 2);
+                val = toks[0];
+                int alg = parseInt(val, -1);
+
+                switch (mAlgorithms.baseType(alg))
+                {
+                case DnsKeyAlgorithm.RSA:
+                    return parsePrivateRSA(lines);
+                case DnsKeyAlgorithm.DSA:
+                    return parsePrivateDSA(lines);
+                case DnsKeyAlgorithm.DH:
+                    return parsePrivateDH(lines);
+                case DnsKeyAlgorithm.ECC_GOST:
+                    return parsePrivateECDSA(lines, alg);
+                case DnsKeyAlgorithm.ECDSA:
+                    return parsePrivateECDSA(lines, alg);
+                default:
+                    throw new IOException("unsupported private key algorithm: " + val);
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * @return the value part of an "attribute:value" pair. The value
+     * is trimmed.
+     */
+    private static String value(String av) {
+        if (av == null) return null;
+
+        int pos = av.indexOf(':');
+        if (pos < 0) return av;
+
+        if (pos >= av.length()) return null;
+
+        return av.substring(pos + 1).trim();
+    }
+
+    /**
+     * Given the rest of the RSA BIND9 string format private key,
+     * parse and translate into a JCA private key
+     *
+     * @throws NoSuchAlgorithmException
+     *           if the RSA algorithm is not available.
+     */
+    private PrivateKey parsePrivateRSA(StringTokenizer lines)
+        throws NoSuchAlgorithmException {
+
+        BigInteger modulus          = null;
+        BigInteger public_exponent  = null;
+        BigInteger private_exponent = null;
+        BigInteger prime_p          = null;
+        BigInteger prime_q          = null;
+        BigInteger prime_p_exponent = null;
+        BigInteger prime_q_exponent = null;
+        BigInteger coefficient      = 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("Modulus: ")) {
+                modulus = new BigInteger(1, data);
+                // printBigIntCompare(data, modulus);
+            } else if (line.startsWith("PublicExponent: ")) {
+                public_exponent = new BigInteger(1, data);
+                // printBigIntCompare(data, public_exponent);
+            } else if (line.startsWith("PrivateExponent: ")) {
+                private_exponent = new BigInteger(1, data);
+                // printBigIntCompare(data, private_exponent);
+            } else if (line.startsWith("Prime1: ")) {
+                prime_p = new BigInteger(1, data);
+                // printBigIntCompare(data, prime_p);
+            } else if (line.startsWith("Prime2: ")) {
+                prime_q = new BigInteger(1, data);
+                // printBigIntCompare(data, prime_q);
+            } else if (line.startsWith("Exponent1: ")) {
+                prime_p_exponent = new BigInteger(1, data);
+            } else if (line.startsWith("Exponent2: ")) {
+                prime_q_exponent = new BigInteger(1, data);
+            } else if (line.startsWith("Coefficient: ")) {
+                coefficient = new BigInteger(1, data);
+            }
+        }
+
+        try {
+            KeySpec spec = new RSAPrivateCrtKeySpec(modulus, public_exponent,
+                                                    private_exponent, prime_p,
+                                                    prime_q, prime_p_exponent,
+                                                    prime_q_exponent, coefficient);
+            if (mRSAKeyFactory == null) {
+                mRSAKeyFactory = KeyFactory.getInstance("RSA");
+            }
+            return mRSAKeyFactory.generatePrivate(spec);
+        } catch (InvalidKeySpecException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * Given the remaining lines in a BIND9 style DH private key,
+     * parse the key info and translate it into a JCA private key.
+     *
+     * @throws NoSuchAlgorithmException if the DH algorithm is not
+     *           available.
+     */
+    private PrivateKey parsePrivateDH(StringTokenizer lines)
+        throws NoSuchAlgorithmException
+    {
+        BigInteger p = null;
+        BigInteger x = null;
+        BigInteger g = 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("Prime(p): ")) {
+                p = new BigInteger(1, data);
+            } else if (line.startsWith("Generator(g): ")) {
+                g = new BigInteger(1, data);
+            } else if (line.startsWith("Private_value(x): ")) {
+                x = new BigInteger(1, data);
+            }
+        }
+
+        try {
+            KeySpec spec = new DHPrivateKeySpec(x, p, g);
+            if (mDHKeyFactory == null) {
+                mDHKeyFactory = KeyFactory.getInstance("DH");
+            }
+            return mDHKeyFactory.generatePrivate(spec);
+        } catch (InvalidKeySpecException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * Given the remaining lines in a BIND9 style DSA private key, parse the key
+     * info and translate it into a JCA private key.
+     *
+     * @throws NoSuchAlgorithmException
+     *           if the DSA algorithm is not available.
+     */
+    private PrivateKey parsePrivateDSA(StringTokenizer lines)
+        throws NoSuchAlgorithmException
+    {
+        BigInteger p = null;
+        BigInteger q = null;
+        BigInteger g = null;
+        BigInteger x = 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("Prime(p): ")) {
+                p = new BigInteger(1, data);
+            } else if (line.startsWith("Subprime(q): ")) {
+                q = new BigInteger(1, data);
+            } else if (line.startsWith("Base(g): ")) {
+                g = new BigInteger(1, data);
+            } else if (line.startsWith("Private_value(x): ")) {
+                x = new BigInteger(1, data);
+            }
+        }
+
+        try {
+            KeySpec spec = new DSAPrivateKeySpec(x, p, q, g);
+            if (mDSAKeyFactory == null) {
+                mDSAKeyFactory = KeyFactory.getInstance("DSA");
+            }
+            return mDSAKeyFactory.generatePrivate(spec);
+        } catch (InvalidKeySpecException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * 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 format.
+     */
+    public String generatePrivateKeyString(PrivateKey priv, PublicKey pub, int alg) {
+        if (priv instanceof RSAPrivateCrtKey) {
+            return generatePrivateRSA((RSAPrivateCrtKey) priv, alg);
+        } else if (priv instanceof DSAPrivateKey && pub instanceof DSAPublicKey) {
+            return generatePrivateDSA((DSAPrivateKey) priv, (DSAPublicKey) pub, alg);
+        } else if (priv instanceof DHPrivateKey && pub instanceof DHPublicKey) {
+            return generatePrivateDH((DHPrivateKey) priv, (DHPublicKey) pub, alg);
+        } else if (priv instanceof ECPrivateKey && pub instanceof ECPublicKey) {
+            return generatePrivateEC((ECPrivateKey) priv, (ECPublicKey) pub, alg);
+        }
+        return null;
+    }
+
+    /**
+     * Convert from 'unsigned' big integer to original 'signed format' in Base64
+     */
+    private static String b64BigInt(BigInteger i) {
+        byte[] orig_bytes = i.toByteArray();
+
+        if (orig_bytes[0] != 0 || orig_bytes.length == 1) {
+            return base64.toString(orig_bytes);
+        }
+
+        byte[] signed_bytes = new byte[orig_bytes.length - 1];
+        System.arraycopy(orig_bytes, 1, signed_bytes, 0, signed_bytes.length);
+
+        return base64.toString(signed_bytes);
+    }
+
+    /**
+     * Given a RSA private key (in Crt format), return the BIND9-style text
+     * encoding.
+     */
+    private String generatePrivateRSA(RSAPrivateCrtKey key, int algorithm) {
+        StringWriter sw = new StringWriter();
+        PrintWriter out = new PrintWriter(sw);
+
+        out.println("Private-key-format: v1.2");
+        out.println("Algorithm: " + algorithm + " (" + mAlgorithms.algToString(algorithm)
+                    + ")");
+        out.print("Modulus: ");
+        out.println(b64BigInt(key.getModulus()));
+        out.print("PublicExponent: ");
+        out.println(b64BigInt(key.getPublicExponent()));
+        out.print("PrivateExponent: ");
+        out.println(b64BigInt(key.getPrivateExponent()));
+        out.print("Prime1: ");
+        out.println(b64BigInt(key.getPrimeP()));
+        out.print("Prime2: ");
+        out.println(b64BigInt(key.getPrimeQ()));
+        out.print("Exponent1: ");
+        out.println(b64BigInt(key.getPrimeExponentP()));
+        out.print("Exponent2: ");
+        out.println(b64BigInt(key.getPrimeExponentQ()));
+        out.print("Coefficient: ");
+        out.println(b64BigInt(key.getCrtCoefficient()));
+
+        return sw.toString();
+    }
+
+    /** Given a DH key pair, return the BIND9-style text encoding */
+    private String generatePrivateDH(DHPrivateKey key,
+                                     DHPublicKey  pub,
+                                     int          algorithm) {
+        StringWriter sw = new StringWriter();
+        PrintWriter out = new PrintWriter(sw);
+
+        DHParameterSpec p = key.getParams();
+
+        out.println("Private-key-format: v1.2");
+        out.println("Algorithm: " + algorithm + " (" + mAlgorithms.algToString(algorithm)
+                    + ")");
+        out.print("Prime(p): ");
+        out.println(b64BigInt(p.getP()));
+        out.print("Generator(g): ");
+        out.println(b64BigInt(p.getG()));
+        out.print("Private_value(x): ");
+        out.println(b64BigInt(key.getX()));
+        out.print("Public_value(y): ");
+        out.println(b64BigInt(pub.getY()));
+
+        return sw.toString();
+    }
+
+    /** Given a DSA key pair, return the BIND9-style text encoding */
+    private String generatePrivateDSA(DSAPrivateKey key,
+                                      DSAPublicKey  pub,
+                                      int           algorithm) {
+        StringWriter sw = new StringWriter();
+        PrintWriter out = new PrintWriter(sw);
+
+        DSAParams p = key.getParams();
+
+        out.println("Private-key-format: v1.2");
+        out.println("Algorithm: " + algorithm + " (" + mAlgorithms.algToString(algorithm)
+                    + ")");
+        out.print("Prime(p): ");
+        out.println(b64BigInt(p.getP()));
+        out.print("Subprime(q): ");
+        out.println(b64BigInt(p.getQ()));
+        out.print("Base(g): ");
+        out.println(b64BigInt(p.getG()));
+        out.print("Private_value(x): ");
+        out.println(b64BigInt(key.getX()));
+        out.print("Public_value(y): ");
+        out.println(b64BigInt(pub.getY()));
+
+        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();
+    }
+
+}
index 581d12e..18441d8 100644 (file)
@@ -26,7 +26,7 @@ package com.verisign.tat.dnssec;
 import org.apache.log4j.Logger;
 
 import org.xbill.DNS.*;
-import org.xbill.DNS.security.*;
+import org.xbill.DNS.DNSSEC.DNSSECException;
 
 import java.io.*;
 
@@ -237,7 +237,7 @@ public class DnsSecVerifier {
         return SecurityStatus.SECURE;
     }
 
-    public PublicKey parseDNSKEY(DNSKEYRecord key) {
+    public PublicKey parseDNSKEY(DNSKEYRecord key) throws DNSSECException {
         AlgEntry ae = (AlgEntry) mAlgorithmMap.get(Integer.valueOf(key.getAlgorithm()));
 
         if (key.getAlgorithm() != ae.dnssecAlg) {
@@ -250,7 +250,7 @@ public class DnsSecVerifier {
                                    ae.dnssecAlg, key.getKey());
         }
 
-        return KEYConverter.parseRecord(key);
+        return key.getPublicKey();
     }
 
     /**
@@ -304,6 +304,8 @@ public class DnsSecVerifier {
             log.trace("Signature verified: " + sigrec);
 
             return SecurityStatus.SECURE;
+        } catch (DNSSECException e) {
+            log.error("DNSSEC key parsing error", e);
         } catch (IOException e) {
             log.error("I/O error", e);
         } catch (GeneralSecurityException e) {