X-Git-Url: https://blacka.com/cgi-bin/gitweb.cgi?p=captive-validator.git;a=blobdiff_plain;f=src%2Fcom%2Fverisign%2Ftat%2Fdnssec%2FDnsKeyConverter.java;fp=src%2Fcom%2Fverisign%2Ftat%2Fdnssec%2FDnsKeyConverter.java;h=4245f5ccbbe71087552f7b92640c1a00043c59d7;hp=0000000000000000000000000000000000000000;hb=fcd4daa7fe43354ddb8c1bc7f92977fb2b3e3cb2;hpb=bc35e72aed44b6cf79766c7f4f27ea81390931df diff --git a/src/com/verisign/tat/dnssec/DnsKeyConverter.java b/src/com/verisign/tat/dnssec/DnsKeyConverter.java new file mode 100644 index 0000000..4245f5c --- /dev/null +++ b/src/com/verisign/tat/dnssec/DnsKeyConverter.java @@ -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(); + } + +}