7745729ffdf6baf9abdf731a833efb8fb96d2ec5
[captive-validator.git] / src / com / verisign / tat / dnssec / DnsKeyAlgorithm.java
1 /*
2  * $Id$
3  *
4  * Copyright (c) 2017 VeriSign. All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are met:
8  *
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.
16  *
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.
27  *
28  */
29
30 package com.verisign.tat.dnssec;
31
32 import java.math.BigInteger;
33 import java.security.*;
34 import java.security.spec.*;
35 import java.util.Arrays;
36 import java.util.HashMap;
37 import java.util.Set;
38 import java.util.logging.Logger;
39
40 import org.xbill.DNS.DNSSEC;
41
42 /**
43  * This class handles translating DNS signing algorithm identifiers
44  * into various usable java implementations.
45  *
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.
50  *
51  * @author David Blacka (orig)
52  * @author $Author: davidb $ (latest)
53  * @version $Revision: 2098 $
54  */
55 public class DnsKeyAlgorithm
56 {
57
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;
67
68     private static class AlgEntry
69     {
70         public int    dnssecAlgorithm;
71         public String sigName;
72         public int    baseType;
73
74         public AlgEntry(int algorithm, String sigName, int baseType) {
75             this.dnssecAlgorithm = algorithm;
76             this.sigName         = sigName;
77             this.baseType        = baseType;
78         }
79     }
80
81     private static class ECAlgEntry extends AlgEntry
82     {
83         public ECParameterSpec ec_spec;
84
85         public ECAlgEntry(int algorithm, String sigName, int baseType, ECParameterSpec spec) {
86             super(algorithm, sigName, baseType);
87             this.ec_spec = spec;
88         }
89     }
90
91     /**
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.
94      */
95     private HashMap<Integer, AlgEntry>  mAlgorithmMap;
96     /**
97      * This is a mapping of algorithm mnemonics to algorithm identifiers.
98      */
99     private HashMap<String, Integer> mMnemonicToIdMap;
100     /**
101      * This is a mapping of identifiers to preferred mnemonic -- the preferred one
102      * is the first defined one
103      */
104     private HashMap<Integer, String> mIdToMnemonicMap;
105
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;
114
115     private Logger                   log = Logger.getLogger(this.getClass().toString());
116
117     /** This is the global instance for this class. */
118     private static DnsKeyAlgorithm   mInstance = null;
119
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.
124         try {
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) { }
130
131         initialize();
132     }
133
134     private void initialize() {
135         mAlgorithmMap    = new HashMap<Integer, AlgEntry>();
136         mMnemonicToIdMap = new HashMap<String, Integer>();
137         mIdToMnemonicMap = new HashMap<Integer, String>();
138
139         // Load the standard DNSSEC algorithms.
140         addAlgorithm(DNSSEC.Algorithm.RSAMD5, "MD5withRSA", RSA);
141         addMnemonic("RSAMD5", DNSSEC.Algorithm.RSAMD5);
142
143         addAlgorithm(DNSSEC.Algorithm.DH, "", DH);
144         addMnemonic("DH", DNSSEC.Algorithm.DH);
145
146         addAlgorithm(DNSSEC.Algorithm.DSA, "SHA1withDSA", DSA);
147         addMnemonic("DSA", DNSSEC.Algorithm.DSA);
148
149         addAlgorithm(DNSSEC.Algorithm.RSASHA1, "SHA1withRSA", RSA);
150         addMnemonic("RSASHA1", DNSSEC.Algorithm.RSASHA1);
151         addMnemonic("RSA", DNSSEC.Algorithm.RSASHA1);
152
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);
159
160         // Algorithms added by RFC 5702.
161         addAlgorithm(DNSSEC.Algorithm.RSASHA256, "SHA256withRSA", RSA);
162         addMnemonic("RSASHA256", DNSSEC.Algorithm.RSASHA256);
163
164         addAlgorithm(DNSSEC.Algorithm.RSASHA512, "SHA512withRSA", RSA);
165         addMnemonic("RSASHA512", DNSSEC.Algorithm.RSASHA512);
166
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);
174
175         addAlgorithm(DNSSEC.Algorithm.ECDSAP256SHA256, "SHA256withECDSA", ECDSA, "secp256r1");
176         addMnemonic("ECDSAP256SHA256", DNSSEC.Algorithm.ECDSAP256SHA256);
177         addMnemonic("ECDSA-P256", DNSSEC.Algorithm.ECDSAP256SHA256);
178
179         addAlgorithm(DNSSEC.Algorithm.ECDSAP384SHA384, "SHA384withECDSA", ECDSA, "secp384r1");
180         addMnemonic("ECDSAP384SHA384", DNSSEC.Algorithm.ECDSAP384SHA384);
181         addMnemonic("ECDSA-P384", DNSSEC.Algorithm.ECDSAP384SHA384);
182     }
183
184     private void addAlgorithm(int algorithm, String sigName, int baseType) {
185         mAlgorithmMap.put(algorithm, new AlgEntry(algorithm, sigName, baseType));
186     }
187
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.
193         try {
194             Signature.getInstance(sigName);
195         } catch (NoSuchAlgorithmException e) {
196             // If not, do not add the algorithm.
197             return;
198         }
199
200         ECAlgEntry entry = new ECAlgEntry(algorithm, sigName, baseType, ec_spec);
201         mAlgorithmMap.put(algorithm, entry);
202     }
203
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;
207
208         mMnemonicToIdMap.put(m.toUpperCase(), alg);
209         if (!mIdToMnemonicMap.containsKey(alg)) {
210             mIdToMnemonicMap.put(alg, m);
211         }
212     }
213
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.");
217             return;
218         }
219
220         if (!mAlgorithmMap.containsKey(original_algorithm)) {
221             log.warning("Unable to alias algorith " + alias +
222                         " to unknown algorithm identifier " + original_algorithm);
223             return;
224         }
225
226         mAlgorithmMap.put(alias, mAlgorithmMap.get(original_algorithm));
227
228         if (mnemonic != null) {
229             addMnemonic(mnemonic, alias);
230         }
231     }
232
233     private AlgEntry getEntry(int alg) {
234         return mAlgorithmMap.get(alg);
235     }
236
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)
241     {
242         switch (algorithm)
243         {
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);
252
253             EllipticCurve curve = new EllipticCurve(new ECFieldFp(p), a, b);
254             return new ECParameterSpec(curve, new ECPoint(gx, gy), n, 1);
255         }
256         default:
257             return null;
258         }
259     }
260
261     // Fetch the curve parameters from a named curve.
262     private ECParameterSpec ECSpecFromName(String stdName) {
263         try {
264             AlgorithmParameters ap = AlgorithmParameters.getInstance("EC");
265             ECGenParameterSpec ecg_spec = new ECGenParameterSpec(stdName);
266             ap.init(ecg_spec);
267             return ap.getParameterSpec(ECParameterSpec.class);
268         }
269         catch (NoSuchAlgorithmException e) {
270             log.info("Elliptic Curve not supported by any crypto provider: " + e.getMessage());
271         }
272         catch (InvalidParameterSpecException e) {
273             log.info("Elliptic Curve " + stdName + " not supported");
274         }
275         return null;
276     }
277
278     public String[] supportedAlgMnemonics() {
279         Set<Integer> keyset = mAlgorithmMap.keySet();
280         Integer[] algs = keyset.toArray(new Integer[keyset.size()]);
281         Arrays.sort(algs);
282
283         String[] result = new String[algs.length];
284         for (int i = 0; i < algs.length; i++) {
285             result[i] = mIdToMnemonicMap.get(algs[i]);
286         }
287
288         return result;
289     }
290
291     /**
292      * Return a Signature object for the specified DNSSEC algorithm.
293      * @param algorithm The DNSSEC algorithm (by number).
294      * @return a Signature object.
295      */
296     public Signature getSignature(int algorithm) {
297         AlgEntry entry = getEntry(algorithm);
298         if (entry == null) return null;
299
300         Signature s = null;
301
302         try {
303             s = Signature.getInstance(entry.sigName);
304         } catch (NoSuchAlgorithmException e) {
305             log.severe("Unable to get signature implementation for algorithm " +
306                        algorithm + ": " + e);
307         }
308
309         return s;
310     }
311
312     /**
313      * Given one of the ECDSA algorithms (ECDSAP256SHA256, etc.) return
314      * the elliptic curve parameters.
315      *
316      * @param algorithm
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.
320      */
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;
326
327         return ec_entry.ec_spec;
328     }
329
330     /**
331      * Translate a possible algorithm alias back to the original DNSSEC algorithm
332      * number
333      *
334      * @param algorithm
335      *          a DNSSEC algorithm that may be an alias.
336      * @return -1 if the algorithm isn't recognised, the orignal algorithm number
337      *         if it is.
338      */
339     public int originalAlgorithm(int algorithm) {
340         AlgEntry entry = getEntry(algorithm);
341         if (entry == null) return -1;
342         return entry.dnssecAlgorithm;
343     }
344
345     /**
346      * Test if a given algorithm is supported.
347      *
348      * @param algorithm The DNSSEC algorithm number.
349      * @return true if the algorithm is a recognized and supported algorithm or alias.
350      */
351     public boolean supportedAlgorithm(int algorithm) {
352         if (mAlgorithmMap.containsKey(algorithm)) return true;
353         return false;
354     }
355
356     /**
357      * Given an algorithm mnemonic, convert the mnemonic to a DNSSEC algorithm
358      * number.
359      *
360      * @param s
361      *          The mnemonic string. This is case-insensitive.
362      * @return -1 if the mnemonic isn't recognized or supported, the algorithm
363      *         number if it is.
364      */
365     public int stringToAlgorithm(String s) {
366         Integer alg = mMnemonicToIdMap.get(s.toUpperCase());
367         if (alg != null) return alg.intValue();
368         return -1;
369     }
370
371     /**
372      * Given a DNSSEC algorithm number, return the "preferred" mnemonic.
373      *
374      * @param algorithm
375      *          A DNSSEC algorithm number.
376      * @return The preferred mnemonic string, or null if not supported or
377      *         recognized.
378      */
379     public String algToString(int algorithm) {
380         return mIdToMnemonicMap.get(algorithm);
381     }
382
383
384     /**
385      * Given a DNSSEC algorithm, return the "base type", i.e., RSA, DSA, ECDSA.
386      */
387     public int baseType(int algorithm) {
388         AlgEntry entry = getEntry(algorithm);
389         if (entry != null) return entry.baseType;
390         return UNKNOWN;
391     }
392
393     /**
394      *  Specifically test if an algorithm's base type is DSA.  For reasons.
395      */
396     public boolean isDSA(int algorithm) {
397         return (baseType(algorithm) == DSA);
398     }
399
400     public KeyPair generateKeyPair(int algorithm, int keysize, boolean useLargeExp)
401         throws NoSuchAlgorithmException {
402         KeyPair pair = null;
403         switch (baseType(algorithm))
404         {
405         case RSA: {
406             if (mRSAKeyGenerator == null) {
407                 mRSAKeyGenerator = KeyPairGenerator.getInstance("RSA");
408             }
409
410             RSAKeyGenParameterSpec rsa_spec;
411             if (useLargeExp) {
412                 rsa_spec = new RSAKeyGenParameterSpec(keysize, RSAKeyGenParameterSpec.F4);
413             } else {
414                 rsa_spec = new RSAKeyGenParameterSpec(keysize, RSAKeyGenParameterSpec.F0);
415             }
416
417             try {
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");
423             }
424
425             pair = mRSAKeyGenerator.generateKeyPair();
426             break;
427         }
428         case DSA: {
429             if (mDSAKeyGenerator == null) {
430                 mDSAKeyGenerator = KeyPairGenerator.getInstance("DSA");
431             }
432             mDSAKeyGenerator.initialize(keysize);
433             pair = mDSAKeyGenerator.generateKeyPair();
434             break;
435         }
436         case ECC_GOST: {
437             if (mECGOSTKeyGenerator == null) {
438                 mECGOSTKeyGenerator = KeyPairGenerator.getInstance("ECGOST3410");
439             }
440
441             ECParameterSpec ec_spec = getEllipticCurveParams(algorithm);
442             try {
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");
448             }
449             pair = mECGOSTKeyGenerator.generateKeyPair();
450             break;
451         }
452         case ECDSA: {
453             if (mECKeyGenerator == null) {
454                 mECKeyGenerator = KeyPairGenerator.getInstance("EC");
455             }
456
457             ECParameterSpec ec_spec = getEllipticCurveParams(algorithm);
458             try {
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");
464             }
465             pair = mECKeyGenerator.generateKeyPair();
466             break;
467         }
468         default:
469             throw new NoSuchAlgorithmException("Alg " + algorithm);
470         }
471
472         return pair;
473     }
474
475     public KeyPair generateKeyPair(int algorithm, int keysize)
476         throws NoSuchAlgorithmException {
477         return generateKeyPair(algorithm, keysize, false);
478     }
479
480   public static DnsKeyAlgorithm getInstance() {
481       if (mInstance == null) mInstance = new DnsKeyAlgorithm();
482       return mInstance;
483   }
484 }