/* * Copyright (c) 2006, 2022 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.verisignlabs.dnssec.security; import java.math.BigInteger; import java.security.AlgorithmParameters; import java.security.InvalidAlgorithmParameterException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.Provider; import java.security.SecureRandom; import java.security.Security; import java.security.Signature; import java.security.spec.ECFieldFp; import java.security.spec.ECGenParameterSpec; import java.security.spec.ECParameterSpec; import java.security.spec.ECPoint; import java.security.spec.EllipticCurve; import java.security.spec.InvalidParameterSpecException; import java.security.spec.RSAKeyGenParameterSpec; import java.util.Arrays; import java.util.HashMap; import java.util.Set; import java.util.logging.Logger; import org.xbill.DNS.DNSSEC; import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable; // for now, we need to import the EdDSA parameter spec classes // because they have no generic form in java.security.spec.* // sadly, this will currently fail if you don't have the lib. import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec; /** * 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 */ 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; public static final int EDDSA = 6; 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 ecSpec; public ECAlgEntry(int algorithm, String sigName, int baseType, ECParameterSpec spec) { super(algorithm, sigName, baseType); this.ecSpec = spec; } } private static class EdAlgEntry extends AlgEntry { public EdDSAParameterSpec edSpec; public EdAlgEntry(int algorithm, String sigName, int baseType, EdDSAParameterSpec spec) { super(algorithm, sigName, baseType); this.edSpec = 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; /** This is a cached key pair generator for EdDSA keys. */ private KeyPairGenerator mEdKeyGenerator; 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(); Provider bc_provider = (Provider) bc_provider_class.getDeclaredConstructor().newInstance(); Security.addProvider(bc_provider); } catch (ReflectiveOperationException e) { } // Attempt to add the EdDSA-Java provider. try { Class eddsa_provider_class = Class.forName("net.i2p.crypto.eddsa.EdDSASecurityProvider"); // Provider eddsa_provider = (Provider) eddsa_provider_class.newInstance(); Provider eddsa_provider = (Provider) eddsa_provider_class.getDeclaredConstructor().newInstance(); Security.addProvider(eddsa_provider); } catch (ReflectiveOperationException e) { log.warning("Unable to load EdDSA provider"); } 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 support it. // 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); // EdDSA is not supported by either the Java 1.8 Sun crypto // provider or bouncycastle. It is added by the Ed25519-Java // library. We don't have a corresponding constant in // org.xbill.DNS.DNSSEC yet, though. addAlgorithm(15, "NONEwithEdDSA", EDDSA, "Ed25519"); addMnemonic("ED25519", 15); } 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) { if (baseType == ECDSA) { ECParameterSpec ecSpec = ECSpecFromAlgorithm(algorithm); if (ecSpec == null) ecSpec = ECSpecFromName(curveName); if (ecSpec == null) return; // Check to see if we can get a Signature object for this algorithm. try { Signature.getInstance(sigName); } catch (NoSuchAlgorithmException e) { // for now, let's find out log.severe("could not get signature for " + sigName + ": " + e.getMessage()); // If not, do not add the algorithm. return; } ECAlgEntry entry = new ECAlgEntry(algorithm, sigName, baseType, ecSpec); mAlgorithmMap.put(algorithm, entry); } else if (baseType == EDDSA) { EdDSAParameterSpec edSpec = EdDSASpecFromName(curveName); if (edSpec == null) return; // Check to see if we can get a Signature object for this algorithm. try { Signature.getInstance(sigName); } catch (NoSuchAlgorithmException e) { // for now, let's find out log.severe("could not get signature for " + sigName + ": " + e.getMessage()); // If not, do not add the algorithm. return; } EdAlgEntry entry = new EdAlgEntry(algorithm, sigName, baseType, edSpec); 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); mIdToMnemonicMap.computeIfAbsent(alg, k -> 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 algorithm " + 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 ECDSA 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; } // Fetch the curve parameters from a named EdDSA curve. private EdDSAParameterSpec EdDSASpecFromName(String stdName) { try { EdDSAParameterSpec spec = EdDSANamedCurveTable.getByName(stdName); if (spec != null) return spec; throw new InvalidParameterSpecException("Edwards Curve " + stdName + " not found."); } // catch (NoSuchAlgorithmException e) { // log.info("Edwards Curve not supported by any crypto provider: " + // e.getMessage()); // } catch (InvalidParameterSpecException e) { log.info("Edwards 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.ecSpec; } /** * Given one of the EdDSA algorithms (Ed25519, Ed448) return the * elliptic curve parameters. * * @param algorithm * The DNSSEC algorithm number. * @return The stored EdDSAParameterSpec for that algorithm, or * null if not a recognized/supported EdDSA algorithm. */ public EdDSAParameterSpec getEdwardsCurveParams(int algorithm) { AlgEntry entry = getEntry(algorithm); if (entry == null) return null; if (!(entry instanceof EdAlgEntry)) return null; EdAlgEntry ed_entry = (EdAlgEntry) entry; return ed_entry.edSpec; } /** * 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); } public int baseType(int algorithm) { AlgEntry entry = getEntry(algorithm); if (entry != null) return entry.baseType; return UNKNOWN; } 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 ecSpec = getEllipticCurveParams(algorithm); try { mECGOSTKeyGenerator.initialize(ecSpec); } 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 ecSpec = getEllipticCurveParams(algorithm); try { mECKeyGenerator.initialize(ecSpec); } 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; } case EDDSA: { if (mEdKeyGenerator == null) { mEdKeyGenerator = KeyPairGenerator.getInstance("EdDSA"); } EdDSAParameterSpec edSpec = getEdwardsCurveParams(algorithm); try { mEdKeyGenerator.initialize(edSpec, new SecureRandom()); } 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 = mEdKeyGenerator.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; } }