Make it build again; print responses in debug mode
[captive-validator.git] / src / com / verisign / tat / dnssec / DnsKeyConverter.java
1 // Copyright (C) 2017 verisign, Inc.
2 //
3 // This library is free software; you can redistribute it and/or
4 // modify it under the terms of the GNU Lesser General Public
5 // License as published by the Free Software Foundation; either
6 // version 2.1 of the License, or (at your option) any later version.
7 //
8 // This library is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 // Lesser General Public License for more details.
12 //
13 // You should have received a copy of the GNU Lesser General Public
14 // License along with this library; if not, write to the Free Software
15 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
17 package com.verisign.tat.dnssec;
18
19 import java.io.IOException;
20 import java.io.PrintWriter;
21 import java.io.StringWriter;
22 import java.math.BigInteger;
23 import java.security.GeneralSecurityException;
24 import java.security.KeyFactory;
25 import java.security.NoSuchAlgorithmException;
26 import java.security.PrivateKey;
27 import java.security.PublicKey;
28 import java.security.interfaces.*;
29 import java.security.spec.*;
30 import java.util.StringTokenizer;
31
32 import javax.crypto.interfaces.DHPrivateKey;
33 import javax.crypto.interfaces.DHPublicKey;
34 import javax.crypto.spec.DHParameterSpec;
35 import javax.crypto.spec.DHPrivateKeySpec;
36
37 import org.xbill.DNS.DNSKEYRecord;
38 import org.xbill.DNS.DNSSEC.DNSSECException;
39 import org.xbill.DNS.Name;
40 import org.xbill.DNS.utils.base64;
41
42 /**
43  * This class handles conversions between JCA key formats and DNSSEC and BIND9
44  * key formats.
45  *
46  * @author David Blacka (original)
47  * @author $Author$ (latest)
48  * @version $Revision$
49  */
50 public class DnsKeyConverter
51 {
52     private KeyFactory      mRSAKeyFactory;
53     private KeyFactory      mDSAKeyFactory;
54     private KeyFactory      mDHKeyFactory;
55     private KeyFactory      mECKeyFactory;
56     private DnsKeyAlgorithm mAlgorithms;
57
58     public DnsKeyConverter() {
59         mAlgorithms = DnsKeyAlgorithm.getInstance();
60     }
61
62     /**
63      * Given a DNS KEY record, return the JCA public key
64      *
65      * @throws NoSuchAlgorithmException
66      */
67     public PublicKey parseDNSKEYRecord(DNSKEYRecord pKeyRecord)
68         throws NoSuchAlgorithmException {
69
70         if (pKeyRecord.getKey() == null) return null;
71
72         // Because we have arbitrarily aliased algorithms, we need to
73         // possibly translate the aliased algorithm back to the actual
74         // algorithm.
75
76         int originalAlgorithm = mAlgorithms.originalAlgorithm(pKeyRecord.getAlgorithm());
77
78         if (originalAlgorithm <= 0) {
79             throw new NoSuchAlgorithmException("DNSKEY algorithm " +
80                                                pKeyRecord.getAlgorithm() + " is unrecognized");
81         }
82
83         if (pKeyRecord.getAlgorithm() != originalAlgorithm) {
84             pKeyRecord = new DNSKEYRecord(pKeyRecord.getName(), pKeyRecord.getDClass(),
85                                           pKeyRecord.getTTL(), pKeyRecord.getFlags(),
86                                           pKeyRecord.getProtocol(), originalAlgorithm,
87                                           pKeyRecord.getKey());
88         }
89
90         try {
91             return pKeyRecord.getPublicKey();
92         } catch (DNSSECException e) {
93             throw new NoSuchAlgorithmException(e);
94         }
95     }
96
97     /**
98      * Given a JCA public key and the ancillary data, generate a DNSKEY record.
99      */
100     public DNSKEYRecord generateDNSKEYRecord(Name      name,
101                                              int       dclass,
102                                              long      ttl,
103                                              int       flags,
104                                              int       alg,
105                                              PublicKey key) {
106         try {
107             return new DNSKEYRecord(name, dclass, ttl, flags, DNSKEYRecord.Protocol.DNSSEC, alg, key);
108         } catch (DNSSECException e) {
109             // FIXME: this mimics the behavior of
110             // KEYConverter.buildRecord(), which would return null if
111             // the algorithm was unknown.
112             return null;
113         }
114     }
115
116     // Private Key Specific Parsing routines
117
118     /**
119      * Convert a PKCS#8 encoded private key into a PrivateKey object.
120      */
121     public PrivateKey convertEncodedPrivateKey(byte[] key, int algorithm) {
122
123         PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(key);
124
125         try {
126             switch (mAlgorithms.baseType(algorithm))
127             {
128             case DnsKeyAlgorithm.RSA:
129                 return mRSAKeyFactory.generatePrivate(spec);
130             case DnsKeyAlgorithm.DSA:
131                 return mDSAKeyFactory.generatePrivate(spec);
132             }
133         } catch (GeneralSecurityException e) {
134             e.printStackTrace();
135         }
136
137         return null;
138     }
139
140     /**
141      * A simple wrapper for parsing integers; parse failures result in
142      * the supplied default.
143      */
144     private static int parseInt(String s, int def) {
145         try {
146             return Integer.parseInt(s);
147         } catch (NumberFormatException e) {
148             return def;
149         }
150     }
151
152     /**
153      * @return a JCA private key, given a BIND9-style textual encoding
154      */
155     public PrivateKey parsePrivateKeyString(String key)
156         throws IOException, NoSuchAlgorithmException {
157
158         StringTokenizer lines = new StringTokenizer(key, "\n");
159
160         while (lines.hasMoreTokens()) {
161             String line = lines.nextToken();
162             if (line == null) continue;
163             if (line.startsWith("#")) continue;
164
165             String val = value(line);
166             if (val == null) continue;
167
168             if (line.startsWith("Private-key-format: ")) {
169                 if (!val.equals("v1.2") && !val.equals("v1.3")) {
170                     throw new IOException("unsupported private key format: " + val);
171                 }
172             } else if (line.startsWith("Algorithm: ")) {
173                 // here we assume that the value looks like # (MNEM)
174                 // or just the number.
175                 String[] toks = val.split("\\s", 2);
176                 val = toks[0];
177                 int alg = parseInt(val, -1);
178
179                 switch (mAlgorithms.baseType(alg))
180                 {
181                 case DnsKeyAlgorithm.RSA:
182                     return parsePrivateRSA(lines);
183                 case DnsKeyAlgorithm.DSA:
184                     return parsePrivateDSA(lines);
185                 case DnsKeyAlgorithm.DH:
186                     return parsePrivateDH(lines);
187                 case DnsKeyAlgorithm.ECC_GOST:
188                     return parsePrivateECDSA(lines, alg);
189                 case DnsKeyAlgorithm.ECDSA:
190                     return parsePrivateECDSA(lines, alg);
191                 default:
192                     throw new IOException("unsupported private key algorithm: " + val);
193                 }
194             }
195         }
196         return null;
197     }
198
199     /**
200      * @return the value part of an "attribute:value" pair. The value
201      * is trimmed.
202      */
203     private static String value(String av) {
204         if (av == null) return null;
205
206         int pos = av.indexOf(':');
207         if (pos < 0) return av;
208
209         if (pos >= av.length()) return null;
210
211         return av.substring(pos + 1).trim();
212     }
213
214     /**
215      * Given the rest of the RSA BIND9 string format private key,
216      * parse and translate into a JCA private key
217      *
218      * @throws NoSuchAlgorithmException
219      *           if the RSA algorithm is not available.
220      */
221     private PrivateKey parsePrivateRSA(StringTokenizer lines)
222         throws NoSuchAlgorithmException {
223
224         BigInteger modulus          = null;
225         BigInteger public_exponent  = null;
226         BigInteger private_exponent = null;
227         BigInteger prime_p          = null;
228         BigInteger prime_q          = null;
229         BigInteger prime_p_exponent = null;
230         BigInteger prime_q_exponent = null;
231         BigInteger coefficient      = null;
232
233         while (lines.hasMoreTokens()) {
234             String line = lines.nextToken();
235             if (line == null) continue;
236             if (line.startsWith("#")) continue;
237
238             String val = value(line);
239             if (val == null) continue;
240
241             byte[] data = base64.fromString(val);
242
243             if (line.startsWith("Modulus: ")) {
244                 modulus = new BigInteger(1, data);
245                 // printBigIntCompare(data, modulus);
246             } else if (line.startsWith("PublicExponent: ")) {
247                 public_exponent = new BigInteger(1, data);
248                 // printBigIntCompare(data, public_exponent);
249             } else if (line.startsWith("PrivateExponent: ")) {
250                 private_exponent = new BigInteger(1, data);
251                 // printBigIntCompare(data, private_exponent);
252             } else if (line.startsWith("Prime1: ")) {
253                 prime_p = new BigInteger(1, data);
254                 // printBigIntCompare(data, prime_p);
255             } else if (line.startsWith("Prime2: ")) {
256                 prime_q = new BigInteger(1, data);
257                 // printBigIntCompare(data, prime_q);
258             } else if (line.startsWith("Exponent1: ")) {
259                 prime_p_exponent = new BigInteger(1, data);
260             } else if (line.startsWith("Exponent2: ")) {
261                 prime_q_exponent = new BigInteger(1, data);
262             } else if (line.startsWith("Coefficient: ")) {
263                 coefficient = new BigInteger(1, data);
264             }
265         }
266
267         try {
268             KeySpec spec = new RSAPrivateCrtKeySpec(modulus, public_exponent,
269                                                     private_exponent, prime_p,
270                                                     prime_q, prime_p_exponent,
271                                                     prime_q_exponent, coefficient);
272             if (mRSAKeyFactory == null) {
273                 mRSAKeyFactory = KeyFactory.getInstance("RSA");
274             }
275             return mRSAKeyFactory.generatePrivate(spec);
276         } catch (InvalidKeySpecException e) {
277             e.printStackTrace();
278             return null;
279         }
280     }
281
282     /**
283      * Given the remaining lines in a BIND9 style DH private key,
284      * parse the key info and translate it into a JCA private key.
285      *
286      * @throws NoSuchAlgorithmException if the DH algorithm is not
287      *           available.
288      */
289     private PrivateKey parsePrivateDH(StringTokenizer lines)
290         throws NoSuchAlgorithmException
291     {
292         BigInteger p = null;
293         BigInteger x = null;
294         BigInteger g = null;
295
296         while (lines.hasMoreTokens()) {
297             String line = lines.nextToken();
298             if (line == null)         continue;
299             if (line.startsWith("#")) continue;
300
301             String val = value(line);
302             if (val == null) continue;
303
304             byte[] data = base64.fromString(val);
305
306             if (line.startsWith("Prime(p): ")) {
307                 p = new BigInteger(1, data);
308             } else if (line.startsWith("Generator(g): ")) {
309                 g = new BigInteger(1, data);
310             } else if (line.startsWith("Private_value(x): ")) {
311                 x = new BigInteger(1, data);
312             }
313         }
314
315         try {
316             KeySpec spec = new DHPrivateKeySpec(x, p, g);
317             if (mDHKeyFactory == null) {
318                 mDHKeyFactory = KeyFactory.getInstance("DH");
319             }
320             return mDHKeyFactory.generatePrivate(spec);
321         } catch (InvalidKeySpecException e) {
322             e.printStackTrace();
323             return null;
324         }
325     }
326
327     /**
328      * Given the remaining lines in a BIND9 style DSA private key, parse the key
329      * info and translate it into a JCA private key.
330      *
331      * @throws NoSuchAlgorithmException
332      *           if the DSA algorithm is not available.
333      */
334     private PrivateKey parsePrivateDSA(StringTokenizer lines)
335         throws NoSuchAlgorithmException
336     {
337         BigInteger p = null;
338         BigInteger q = null;
339         BigInteger g = null;
340         BigInteger x = null;
341
342         while (lines.hasMoreTokens()) {
343             String line = lines.nextToken();
344             if (line == null) continue;
345             if (line.startsWith("#")) continue;
346
347             String val = value(line);
348             if (val == null) continue;
349
350             byte[] data = base64.fromString(val);
351
352             if (line.startsWith("Prime(p): ")) {
353                 p = new BigInteger(1, data);
354             } else if (line.startsWith("Subprime(q): ")) {
355                 q = new BigInteger(1, data);
356             } else if (line.startsWith("Base(g): ")) {
357                 g = new BigInteger(1, data);
358             } else if (line.startsWith("Private_value(x): ")) {
359                 x = new BigInteger(1, data);
360             }
361         }
362
363         try {
364             KeySpec spec = new DSAPrivateKeySpec(x, p, q, g);
365             if (mDSAKeyFactory == null) {
366                 mDSAKeyFactory = KeyFactory.getInstance("DSA");
367             }
368             return mDSAKeyFactory.generatePrivate(spec);
369         } catch (InvalidKeySpecException e) {
370             e.printStackTrace();
371             return null;
372         }
373     }
374
375     /**
376      * Given the remaining lines in a BIND9-style ECDSA private key,
377      * parse the key info and translate it into a JCA private key
378      * object.
379      *
380      * @param lines The remaining lines in a private key file (after
381      * @throws NoSuchAlgorithmException
382      *           If elliptic curve is not available.
383      */
384     private PrivateKey parsePrivateECDSA(StringTokenizer lines, int algorithm)
385         throws NoSuchAlgorithmException {
386
387         BigInteger s = null;
388
389         while (lines.hasMoreTokens()) {
390             String line = lines.nextToken();
391             if (line == null) continue;
392             if (line.startsWith("#")) continue;
393
394             String val = value(line);
395             if (val == null) continue;
396
397             byte[] data = base64.fromString(val);
398
399             if (line.startsWith("PrivateKey: ")) {
400                 s = new BigInteger(1, data);
401             }
402         }
403
404         if (mECKeyFactory == null) {
405             mECKeyFactory = KeyFactory.getInstance("EC");
406         }
407         ECParameterSpec ec_spec = mAlgorithms.getEllipticCurveParams(algorithm);
408         if (ec_spec == null) {
409             throw new NoSuchAlgorithmException("DNSSEC algorithm " + algorithm +
410                                                " is not a recognized Elliptic Curve algorithm");
411         }
412
413         KeySpec spec = new ECPrivateKeySpec(s, ec_spec);
414
415         try {
416             return mECKeyFactory.generatePrivate(spec);
417         } catch (InvalidKeySpecException e) {
418             e.printStackTrace();
419             return null;
420         }
421     }
422
423     /**
424      * Given a private key and public key, generate the BIND9 style
425      * private key format.
426      */
427     public String generatePrivateKeyString(PrivateKey priv, PublicKey pub, int alg) {
428         if (priv instanceof RSAPrivateCrtKey) {
429             return generatePrivateRSA((RSAPrivateCrtKey) priv, alg);
430         } else if (priv instanceof DSAPrivateKey && pub instanceof DSAPublicKey) {
431             return generatePrivateDSA((DSAPrivateKey) priv, (DSAPublicKey) pub, alg);
432         } else if (priv instanceof DHPrivateKey && pub instanceof DHPublicKey) {
433             return generatePrivateDH((DHPrivateKey) priv, (DHPublicKey) pub, alg);
434         } else if (priv instanceof ECPrivateKey && pub instanceof ECPublicKey) {
435             return generatePrivateEC((ECPrivateKey) priv, (ECPublicKey) pub, alg);
436         }
437         return null;
438     }
439
440     /**
441      * Convert from 'unsigned' big integer to original 'signed format' in Base64
442      */
443     private static String b64BigInt(BigInteger i) {
444         byte[] orig_bytes = i.toByteArray();
445
446         if (orig_bytes[0] != 0 || orig_bytes.length == 1) {
447             return base64.toString(orig_bytes);
448         }
449
450         byte[] signed_bytes = new byte[orig_bytes.length - 1];
451         System.arraycopy(orig_bytes, 1, signed_bytes, 0, signed_bytes.length);
452
453         return base64.toString(signed_bytes);
454     }
455
456     /**
457      * Given a RSA private key (in Crt format), return the BIND9-style text
458      * encoding.
459      */
460     private String generatePrivateRSA(RSAPrivateCrtKey key, int algorithm) {
461         StringWriter sw = new StringWriter();
462         PrintWriter out = new PrintWriter(sw);
463
464         out.println("Private-key-format: v1.2");
465         out.println("Algorithm: " + algorithm + " (" + mAlgorithms.algToString(algorithm)
466                     + ")");
467         out.print("Modulus: ");
468         out.println(b64BigInt(key.getModulus()));
469         out.print("PublicExponent: ");
470         out.println(b64BigInt(key.getPublicExponent()));
471         out.print("PrivateExponent: ");
472         out.println(b64BigInt(key.getPrivateExponent()));
473         out.print("Prime1: ");
474         out.println(b64BigInt(key.getPrimeP()));
475         out.print("Prime2: ");
476         out.println(b64BigInt(key.getPrimeQ()));
477         out.print("Exponent1: ");
478         out.println(b64BigInt(key.getPrimeExponentP()));
479         out.print("Exponent2: ");
480         out.println(b64BigInt(key.getPrimeExponentQ()));
481         out.print("Coefficient: ");
482         out.println(b64BigInt(key.getCrtCoefficient()));
483
484         return sw.toString();
485     }
486
487     /** Given a DH key pair, return the BIND9-style text encoding */
488     private String generatePrivateDH(DHPrivateKey key,
489                                      DHPublicKey  pub,
490                                      int          algorithm) {
491         StringWriter sw = new StringWriter();
492         PrintWriter out = new PrintWriter(sw);
493
494         DHParameterSpec p = key.getParams();
495
496         out.println("Private-key-format: v1.2");
497         out.println("Algorithm: " + algorithm + " (" + mAlgorithms.algToString(algorithm)
498                     + ")");
499         out.print("Prime(p): ");
500         out.println(b64BigInt(p.getP()));
501         out.print("Generator(g): ");
502         out.println(b64BigInt(p.getG()));
503         out.print("Private_value(x): ");
504         out.println(b64BigInt(key.getX()));
505         out.print("Public_value(y): ");
506         out.println(b64BigInt(pub.getY()));
507
508         return sw.toString();
509     }
510
511     /** Given a DSA key pair, return the BIND9-style text encoding */
512     private String generatePrivateDSA(DSAPrivateKey key,
513                                       DSAPublicKey  pub,
514                                       int           algorithm) {
515         StringWriter sw = new StringWriter();
516         PrintWriter out = new PrintWriter(sw);
517
518         DSAParams p = key.getParams();
519
520         out.println("Private-key-format: v1.2");
521         out.println("Algorithm: " + algorithm + " (" + mAlgorithms.algToString(algorithm)
522                     + ")");
523         out.print("Prime(p): ");
524         out.println(b64BigInt(p.getP()));
525         out.print("Subprime(q): ");
526         out.println(b64BigInt(p.getQ()));
527         out.print("Base(g): ");
528         out.println(b64BigInt(p.getG()));
529         out.print("Private_value(x): ");
530         out.println(b64BigInt(key.getX()));
531         out.print("Public_value(y): ");
532         out.println(b64BigInt(pub.getY()));
533
534         return sw.toString();
535     }
536
537     /**
538      * Given an elliptic curve key pair, and the actual algorithm
539      * (which will describe the curve used), return the BIND9-style
540      * text encoding.
541      */
542     private String generatePrivateEC(ECPrivateKey priv, ECPublicKey pub, int alg) {
543         StringWriter sw = new StringWriter();
544         PrintWriter out = new PrintWriter(sw);
545
546         out.println("Private-key-format: v1.2");
547         out.println("Algorithm: " + alg + " (" + mAlgorithms.algToString(alg)
548                     + ")");
549         out.print("PrivateKey: ");
550         out.println(b64BigInt(priv.getS()));
551
552         return sw.toString();
553     }
554
555 }