// 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(); } }