X-Git-Url: https://blacka.com/cgi-bin/gitweb.cgi?p=captive-validator.git;a=blobdiff_plain;f=src%2Fcom%2Fverisign%2Ftat%2Fdnssec%2FDnsKeyAlgorithm.java;fp=src%2Fcom%2Fverisign%2Ftat%2Fdnssec%2FDnsKeyAlgorithm.java;h=7745729ffdf6baf9abdf731a833efb8fb96d2ec5;hp=0000000000000000000000000000000000000000;hb=fcd4daa7fe43354ddb8c1bc7f92977fb2b3e3cb2;hpb=bc35e72aed44b6cf79766c7f4f27ea81390931df diff --git a/src/com/verisign/tat/dnssec/DnsKeyAlgorithm.java b/src/com/verisign/tat/dnssec/DnsKeyAlgorithm.java new file mode 100644 index 0000000..7745729 --- /dev/null +++ b/src/com/verisign/tat/dnssec/DnsKeyAlgorithm.java @@ -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 mAlgorithmMap; + /** + * This is a mapping of algorithm mnemonics to algorithm identifiers. + */ + private HashMap mMnemonicToIdMap; + /** + * This is a mapping of identifiers to preferred mnemonic -- the preferred one + * is the first defined one + */ + private HashMap 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(); + mMnemonicToIdMap = new HashMap(); + mIdToMnemonicMap = new HashMap(); + + // 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 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; + } +}