4 * Copyright (c) 2017 VeriSign. All rights reserved.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
9 * 1. Redistributions of source code must retain the above copyright notice,
10 * this list of conditions and the following disclaimer. 2. Redistributions in
11 * binary form must reproduce the above copyright notice, this list of
12 * conditions and the following disclaimer in the documentation and/or other
13 * materials provided with the distribution. 3. The name of the author may not
14 * be used to endorse or promote products derived from this software without
15 * specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
20 * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
22 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
24 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
25 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 package com.verisign.tat.dnssec;
32 import java.math.BigInteger;
33 import java.security.*;
34 import java.security.spec.*;
35 import java.util.Arrays;
36 import java.util.HashMap;
38 import java.util.logging.Logger;
40 import org.xbill.DNS.DNSSEC;
43 * This class handles translating DNS signing algorithm identifiers
44 * into various usable java implementations.
46 * Besides centralizing the logic surrounding matching a DNSKEY
47 * algorithm identifier with various crypto implementations, it also
48 * handles algorithm aliasing -- that is, defining a new algorithm
49 * identifier to be equivalent to an existing identifier.
51 * @author David Blacka (orig)
52 * @author $Author: davidb $ (latest)
53 * @version $Revision: 2098 $
55 public class DnsKeyAlgorithm
58 // Our base algorithm numbers. This is a normalization of the DNSSEC
59 // algorithms (which are really signature algorithms). Thus RSASHA1,
60 // RSASHA256, etc. all boil down to 'RSA' here.
61 public static final int UNKNOWN = -1;
62 public static final int RSA = 1;
63 public static final int DH = 2;
64 public static final int DSA = 3;
65 public static final int ECC_GOST = 4;
66 public static final int ECDSA = 5;
68 private static class AlgEntry
70 public int dnssecAlgorithm;
71 public String sigName;
74 public AlgEntry(int algorithm, String sigName, int baseType) {
75 this.dnssecAlgorithm = algorithm;
76 this.sigName = sigName;
77 this.baseType = baseType;
81 private static class ECAlgEntry extends AlgEntry
83 public ECParameterSpec ec_spec;
85 public ECAlgEntry(int algorithm, String sigName, int baseType, ECParameterSpec spec) {
86 super(algorithm, sigName, baseType);
92 * This is a mapping of algorithm identifier to Entry. The Entry contains the
93 * data needed to map the algorithm to the various crypto implementations.
95 private HashMap<Integer, AlgEntry> mAlgorithmMap;
97 * This is a mapping of algorithm mnemonics to algorithm identifiers.
99 private HashMap<String, Integer> mMnemonicToIdMap;
101 * This is a mapping of identifiers to preferred mnemonic -- the preferred one
102 * is the first defined one
104 private HashMap<Integer, String> mIdToMnemonicMap;
106 /** This is a cached key pair generator for RSA keys. */
107 private KeyPairGenerator mRSAKeyGenerator;
108 /** This is a cached key pair generator for DSA keys. */
109 private KeyPairGenerator mDSAKeyGenerator;
110 /** This is a cached key pair generator for ECC GOST keys. */
111 private KeyPairGenerator mECGOSTKeyGenerator;
112 /** This is a cached key pair generator for ECDSA_P256 keys. */
113 private KeyPairGenerator mECKeyGenerator;
115 private Logger log = Logger.getLogger(this.getClass().toString());
117 /** This is the global instance for this class. */
118 private static DnsKeyAlgorithm mInstance = null;
120 public DnsKeyAlgorithm() {
121 // Attempt to add the bouncycastle provider.
122 // This is so we can use this provider if it is available, but not require
123 // the user to add it as one of the java.security providers.
125 Class<?> bc_provider_class =
126 Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider");
127 Provider bc_provider = (Provider) bc_provider_class.newInstance();
128 Security.addProvider(bc_provider);
129 } catch (ReflectiveOperationException e) { }
134 private void initialize() {
135 mAlgorithmMap = new HashMap<Integer, AlgEntry>();
136 mMnemonicToIdMap = new HashMap<String, Integer>();
137 mIdToMnemonicMap = new HashMap<Integer, String>();
139 // Load the standard DNSSEC algorithms.
140 addAlgorithm(DNSSEC.Algorithm.RSAMD5, "MD5withRSA", RSA);
141 addMnemonic("RSAMD5", DNSSEC.Algorithm.RSAMD5);
143 addAlgorithm(DNSSEC.Algorithm.DH, "", DH);
144 addMnemonic("DH", DNSSEC.Algorithm.DH);
146 addAlgorithm(DNSSEC.Algorithm.DSA, "SHA1withDSA", DSA);
147 addMnemonic("DSA", DNSSEC.Algorithm.DSA);
149 addAlgorithm(DNSSEC.Algorithm.RSASHA1, "SHA1withRSA", RSA);
150 addMnemonic("RSASHA1", DNSSEC.Algorithm.RSASHA1);
151 addMnemonic("RSA", DNSSEC.Algorithm.RSASHA1);
153 // Load the (now) standard aliases
154 addAlias(DNSSEC.Algorithm.DSA_NSEC3_SHA1, "DSA-NSEC3-SHA1", DNSSEC.Algorithm.DSA);
155 addAlias(DNSSEC.Algorithm.RSA_NSEC3_SHA1, "RSA-NSEC3-SHA1", DNSSEC.Algorithm.RSASHA1);
156 // Also recognize the BIND 9.6 mnemonics
157 addMnemonic("NSEC3DSA", DNSSEC.Algorithm.DSA_NSEC3_SHA1);
158 addMnemonic("NSEC3RSASHA1", DNSSEC.Algorithm.RSA_NSEC3_SHA1);
160 // Algorithms added by RFC 5702.
161 addAlgorithm(DNSSEC.Algorithm.RSASHA256, "SHA256withRSA", RSA);
162 addMnemonic("RSASHA256", DNSSEC.Algorithm.RSASHA256);
164 addAlgorithm(DNSSEC.Algorithm.RSASHA512, "SHA512withRSA", RSA);
165 addMnemonic("RSASHA512", DNSSEC.Algorithm.RSASHA512);
167 // ECC-GOST is not supported by Java 1.8's Sun crypto provider. The
168 // bouncycastle.org provider, however, does.
169 // GostR3410-2001-CryptoPro-A is the named curve in the BC provider, but we
170 // will get the parameters directly.
171 addAlgorithm(DNSSEC.Algorithm.ECC_GOST, "GOST3411withECGOST3410", ECC_GOST, null);
172 addMnemonic("ECCGOST", DNSSEC.Algorithm.ECC_GOST);
173 addMnemonic("ECC-GOST", DNSSEC.Algorithm.ECC_GOST);
175 addAlgorithm(DNSSEC.Algorithm.ECDSAP256SHA256, "SHA256withECDSA", ECDSA, "secp256r1");
176 addMnemonic("ECDSAP256SHA256", DNSSEC.Algorithm.ECDSAP256SHA256);
177 addMnemonic("ECDSA-P256", DNSSEC.Algorithm.ECDSAP256SHA256);
179 addAlgorithm(DNSSEC.Algorithm.ECDSAP384SHA384, "SHA384withECDSA", ECDSA, "secp384r1");
180 addMnemonic("ECDSAP384SHA384", DNSSEC.Algorithm.ECDSAP384SHA384);
181 addMnemonic("ECDSA-P384", DNSSEC.Algorithm.ECDSAP384SHA384);
184 private void addAlgorithm(int algorithm, String sigName, int baseType) {
185 mAlgorithmMap.put(algorithm, new AlgEntry(algorithm, sigName, baseType));
188 private void addAlgorithm(int algorithm, String sigName, int baseType, String curveName) {
189 ECParameterSpec ec_spec = ECSpecFromAlgorithm(algorithm);
190 if (ec_spec == null) ec_spec = ECSpecFromName(curveName);
191 if (ec_spec == null) return;
192 // Check to see if we can get a Signature object for this algorithm.
194 Signature.getInstance(sigName);
195 } catch (NoSuchAlgorithmException e) {
196 // If not, do not add the algorithm.
200 ECAlgEntry entry = new ECAlgEntry(algorithm, sigName, baseType, ec_spec);
201 mAlgorithmMap.put(algorithm, entry);
204 private void addMnemonic(String m, int alg) {
205 // Do not add mnemonics for algorithms that ended up not actually being supported.
206 if (! mAlgorithmMap.containsKey(alg)) return;
208 mMnemonicToIdMap.put(m.toUpperCase(), alg);
209 if (!mIdToMnemonicMap.containsKey(alg)) {
210 mIdToMnemonicMap.put(alg, m);
214 public void addAlias(int alias, String mnemonic, int original_algorithm) {
215 if (mAlgorithmMap.containsKey(alias)) {
216 log.warning("Unable to alias algorithm " + alias + " because it already exists.");
220 if (!mAlgorithmMap.containsKey(original_algorithm)) {
221 log.warning("Unable to alias algorith " + alias +
222 " to unknown algorithm identifier " + original_algorithm);
226 mAlgorithmMap.put(alias, mAlgorithmMap.get(original_algorithm));
228 if (mnemonic != null) {
229 addMnemonic(mnemonic, alias);
233 private AlgEntry getEntry(int alg) {
234 return mAlgorithmMap.get(alg);
237 // For curves where we don't (or can't) get the parameters from a
238 // standard name, we can construct the parameters here. For now,
239 // we only do this for the ECC-GOST curve.
240 private ECParameterSpec ECSpecFromAlgorithm(int algorithm)
244 case DNSSEC.Algorithm.ECC_GOST: {
245 // From RFC 4357 Section 11.4
246 BigInteger p = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD97", 16);
247 BigInteger a = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD94", 16);
248 BigInteger b = new BigInteger("A6", 16);
249 BigInteger gx = new BigInteger("1", 16);
250 BigInteger gy = new BigInteger("8D91E471E0989CDA27DF505A453F2B7635294F2DDF23E3B122ACC99C9E9F1E14", 16);
251 BigInteger n = new BigInteger( "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6C611070995AD10045841B09B761B893", 16);
253 EllipticCurve curve = new EllipticCurve(new ECFieldFp(p), a, b);
254 return new ECParameterSpec(curve, new ECPoint(gx, gy), n, 1);
261 // Fetch the curve parameters from a named curve.
262 private ECParameterSpec ECSpecFromName(String stdName) {
264 AlgorithmParameters ap = AlgorithmParameters.getInstance("EC");
265 ECGenParameterSpec ecg_spec = new ECGenParameterSpec(stdName);
267 return ap.getParameterSpec(ECParameterSpec.class);
269 catch (NoSuchAlgorithmException e) {
270 log.info("Elliptic Curve not supported by any crypto provider: " + e.getMessage());
272 catch (InvalidParameterSpecException e) {
273 log.info("Elliptic Curve " + stdName + " not supported");
278 public String[] supportedAlgMnemonics() {
279 Set<Integer> keyset = mAlgorithmMap.keySet();
280 Integer[] algs = keyset.toArray(new Integer[keyset.size()]);
283 String[] result = new String[algs.length];
284 for (int i = 0; i < algs.length; i++) {
285 result[i] = mIdToMnemonicMap.get(algs[i]);
292 * Return a Signature object for the specified DNSSEC algorithm.
293 * @param algorithm The DNSSEC algorithm (by number).
294 * @return a Signature object.
296 public Signature getSignature(int algorithm) {
297 AlgEntry entry = getEntry(algorithm);
298 if (entry == null) return null;
303 s = Signature.getInstance(entry.sigName);
304 } catch (NoSuchAlgorithmException e) {
305 log.severe("Unable to get signature implementation for algorithm " +
306 algorithm + ": " + e);
313 * Given one of the ECDSA algorithms (ECDSAP256SHA256, etc.) return
314 * the elliptic curve parameters.
317 * The DNSSEC algorithm number.
318 * @return The calculated JCA ECParameterSpec for that DNSSEC algorithm, or
319 * null if not a recognized/supported EC algorithm.
321 public ECParameterSpec getEllipticCurveParams(int algorithm) {
322 AlgEntry entry = getEntry(algorithm);
323 if (entry == null) return null;
324 if (!(entry instanceof ECAlgEntry)) return null;
325 ECAlgEntry ec_entry = (ECAlgEntry) entry;
327 return ec_entry.ec_spec;
331 * Translate a possible algorithm alias back to the original DNSSEC algorithm
335 * a DNSSEC algorithm that may be an alias.
336 * @return -1 if the algorithm isn't recognised, the orignal algorithm number
339 public int originalAlgorithm(int algorithm) {
340 AlgEntry entry = getEntry(algorithm);
341 if (entry == null) return -1;
342 return entry.dnssecAlgorithm;
346 * Test if a given algorithm is supported.
348 * @param algorithm The DNSSEC algorithm number.
349 * @return true if the algorithm is a recognized and supported algorithm or alias.
351 public boolean supportedAlgorithm(int algorithm) {
352 if (mAlgorithmMap.containsKey(algorithm)) return true;
357 * Given an algorithm mnemonic, convert the mnemonic to a DNSSEC algorithm
361 * The mnemonic string. This is case-insensitive.
362 * @return -1 if the mnemonic isn't recognized or supported, the algorithm
365 public int stringToAlgorithm(String s) {
366 Integer alg = mMnemonicToIdMap.get(s.toUpperCase());
367 if (alg != null) return alg.intValue();
372 * Given a DNSSEC algorithm number, return the "preferred" mnemonic.
375 * A DNSSEC algorithm number.
376 * @return The preferred mnemonic string, or null if not supported or
379 public String algToString(int algorithm) {
380 return mIdToMnemonicMap.get(algorithm);
385 * Given a DNSSEC algorithm, return the "base type", i.e., RSA, DSA, ECDSA.
387 public int baseType(int algorithm) {
388 AlgEntry entry = getEntry(algorithm);
389 if (entry != null) return entry.baseType;
394 * Specifically test if an algorithm's base type is DSA. For reasons.
396 public boolean isDSA(int algorithm) {
397 return (baseType(algorithm) == DSA);
400 public KeyPair generateKeyPair(int algorithm, int keysize, boolean useLargeExp)
401 throws NoSuchAlgorithmException {
403 switch (baseType(algorithm))
406 if (mRSAKeyGenerator == null) {
407 mRSAKeyGenerator = KeyPairGenerator.getInstance("RSA");
410 RSAKeyGenParameterSpec rsa_spec;
412 rsa_spec = new RSAKeyGenParameterSpec(keysize, RSAKeyGenParameterSpec.F4);
414 rsa_spec = new RSAKeyGenParameterSpec(keysize, RSAKeyGenParameterSpec.F0);
418 mRSAKeyGenerator.initialize(rsa_spec);
419 } catch (InvalidAlgorithmParameterException e) {
420 // Fold the InvalidAlgorithmParameterException into our existing
421 // thrown exception. Ugly, but requires less code change.
422 throw new NoSuchAlgorithmException("invalid key parameter spec");
425 pair = mRSAKeyGenerator.generateKeyPair();
429 if (mDSAKeyGenerator == null) {
430 mDSAKeyGenerator = KeyPairGenerator.getInstance("DSA");
432 mDSAKeyGenerator.initialize(keysize);
433 pair = mDSAKeyGenerator.generateKeyPair();
437 if (mECGOSTKeyGenerator == null) {
438 mECGOSTKeyGenerator = KeyPairGenerator.getInstance("ECGOST3410");
441 ECParameterSpec ec_spec = getEllipticCurveParams(algorithm);
443 mECGOSTKeyGenerator.initialize(ec_spec);
444 } catch (InvalidAlgorithmParameterException e) {
445 // Fold the InvalidAlgorithmParameterException into our existing
446 // thrown exception. Ugly, but requires less code change.
447 throw new NoSuchAlgorithmException("invalid key parameter spec");
449 pair = mECGOSTKeyGenerator.generateKeyPair();
453 if (mECKeyGenerator == null) {
454 mECKeyGenerator = KeyPairGenerator.getInstance("EC");
457 ECParameterSpec ec_spec = getEllipticCurveParams(algorithm);
459 mECKeyGenerator.initialize(ec_spec);
460 } catch (InvalidAlgorithmParameterException e) {
461 // Fold the InvalidAlgorithmParameterException into our existing
462 // thrown exception. Ugly, but requires less code change.
463 throw new NoSuchAlgorithmException("invalid key parameter spec");
465 pair = mECKeyGenerator.generateKeyPair();
469 throw new NoSuchAlgorithmException("Alg " + algorithm);
475 public KeyPair generateKeyPair(int algorithm, int keysize)
476 throws NoSuchAlgorithmException {
477 return generateKeyPair(algorithm, keysize, false);
480 public static DnsKeyAlgorithm getInstance() {
481 if (mInstance == null) mInstance = new DnsKeyAlgorithm();