Make it build again; print responses in debug mode
[captive-validator.git] / src / com / verisign / tat / dnssec / DnsKeyConverter.java
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();
+    }
+
+}