1 // Copyright (C) 2017 verisign, Inc.
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.
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.
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
17 package com.verisign.tat.dnssec;
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;
32 import javax.crypto.interfaces.DHPrivateKey;
33 import javax.crypto.interfaces.DHPublicKey;
34 import javax.crypto.spec.DHParameterSpec;
35 import javax.crypto.spec.DHPrivateKeySpec;
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;
43 * This class handles conversions between JCA key formats and DNSSEC and BIND9
46 * @author David Blacka (original)
47 * @author $Author$ (latest)
50 public class DnsKeyConverter
52 private KeyFactory mRSAKeyFactory;
53 private KeyFactory mDSAKeyFactory;
54 private KeyFactory mDHKeyFactory;
55 private KeyFactory mECKeyFactory;
56 private DnsKeyAlgorithm mAlgorithms;
58 public DnsKeyConverter() {
59 mAlgorithms = DnsKeyAlgorithm.getInstance();
63 * Given a DNS KEY record, return the JCA public key
65 * @throws NoSuchAlgorithmException
67 public PublicKey parseDNSKEYRecord(DNSKEYRecord pKeyRecord)
68 throws NoSuchAlgorithmException {
70 if (pKeyRecord.getKey() == null) return null;
72 // Because we have arbitrarily aliased algorithms, we need to
73 // possibly translate the aliased algorithm back to the actual
76 int originalAlgorithm = mAlgorithms.originalAlgorithm(pKeyRecord.getAlgorithm());
78 if (originalAlgorithm <= 0) {
79 throw new NoSuchAlgorithmException("DNSKEY algorithm " +
80 pKeyRecord.getAlgorithm() + " is unrecognized");
83 if (pKeyRecord.getAlgorithm() != originalAlgorithm) {
84 pKeyRecord = new DNSKEYRecord(pKeyRecord.getName(), pKeyRecord.getDClass(),
85 pKeyRecord.getTTL(), pKeyRecord.getFlags(),
86 pKeyRecord.getProtocol(), originalAlgorithm,
91 return pKeyRecord.getPublicKey();
92 } catch (DNSSECException e) {
93 throw new NoSuchAlgorithmException(e);
98 * Given a JCA public key and the ancillary data, generate a DNSKEY record.
100 public DNSKEYRecord generateDNSKEYRecord(Name name,
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.
116 // Private Key Specific Parsing routines
119 * Convert a PKCS#8 encoded private key into a PrivateKey object.
121 public PrivateKey convertEncodedPrivateKey(byte[] key, int algorithm) {
123 PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(key);
126 switch (mAlgorithms.baseType(algorithm))
128 case DnsKeyAlgorithm.RSA:
129 return mRSAKeyFactory.generatePrivate(spec);
130 case DnsKeyAlgorithm.DSA:
131 return mDSAKeyFactory.generatePrivate(spec);
133 } catch (GeneralSecurityException e) {
141 * A simple wrapper for parsing integers; parse failures result in
142 * the supplied default.
144 private static int parseInt(String s, int def) {
146 return Integer.parseInt(s);
147 } catch (NumberFormatException e) {
153 * @return a JCA private key, given a BIND9-style textual encoding
155 public PrivateKey parsePrivateKeyString(String key)
156 throws IOException, NoSuchAlgorithmException {
158 StringTokenizer lines = new StringTokenizer(key, "\n");
160 while (lines.hasMoreTokens()) {
161 String line = lines.nextToken();
162 if (line == null) continue;
163 if (line.startsWith("#")) continue;
165 String val = value(line);
166 if (val == null) continue;
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);
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);
177 int alg = parseInt(val, -1);
179 switch (mAlgorithms.baseType(alg))
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);
192 throw new IOException("unsupported private key algorithm: " + val);
200 * @return the value part of an "attribute:value" pair. The value
203 private static String value(String av) {
204 if (av == null) return null;
206 int pos = av.indexOf(':');
207 if (pos < 0) return av;
209 if (pos >= av.length()) return null;
211 return av.substring(pos + 1).trim();
215 * Given the rest of the RSA BIND9 string format private key,
216 * parse and translate into a JCA private key
218 * @throws NoSuchAlgorithmException
219 * if the RSA algorithm is not available.
221 private PrivateKey parsePrivateRSA(StringTokenizer lines)
222 throws NoSuchAlgorithmException {
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;
233 while (lines.hasMoreTokens()) {
234 String line = lines.nextToken();
235 if (line == null) continue;
236 if (line.startsWith("#")) continue;
238 String val = value(line);
239 if (val == null) continue;
241 byte[] data = base64.fromString(val);
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);
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");
275 return mRSAKeyFactory.generatePrivate(spec);
276 } catch (InvalidKeySpecException e) {
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.
286 * @throws NoSuchAlgorithmException if the DH algorithm is not
289 private PrivateKey parsePrivateDH(StringTokenizer lines)
290 throws NoSuchAlgorithmException
296 while (lines.hasMoreTokens()) {
297 String line = lines.nextToken();
298 if (line == null) continue;
299 if (line.startsWith("#")) continue;
301 String val = value(line);
302 if (val == null) continue;
304 byte[] data = base64.fromString(val);
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);
316 KeySpec spec = new DHPrivateKeySpec(x, p, g);
317 if (mDHKeyFactory == null) {
318 mDHKeyFactory = KeyFactory.getInstance("DH");
320 return mDHKeyFactory.generatePrivate(spec);
321 } catch (InvalidKeySpecException e) {
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.
331 * @throws NoSuchAlgorithmException
332 * if the DSA algorithm is not available.
334 private PrivateKey parsePrivateDSA(StringTokenizer lines)
335 throws NoSuchAlgorithmException
342 while (lines.hasMoreTokens()) {
343 String line = lines.nextToken();
344 if (line == null) continue;
345 if (line.startsWith("#")) continue;
347 String val = value(line);
348 if (val == null) continue;
350 byte[] data = base64.fromString(val);
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);
364 KeySpec spec = new DSAPrivateKeySpec(x, p, q, g);
365 if (mDSAKeyFactory == null) {
366 mDSAKeyFactory = KeyFactory.getInstance("DSA");
368 return mDSAKeyFactory.generatePrivate(spec);
369 } catch (InvalidKeySpecException e) {
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
380 * @param lines The remaining lines in a private key file (after
381 * @throws NoSuchAlgorithmException
382 * If elliptic curve is not available.
384 private PrivateKey parsePrivateECDSA(StringTokenizer lines, int algorithm)
385 throws NoSuchAlgorithmException {
389 while (lines.hasMoreTokens()) {
390 String line = lines.nextToken();
391 if (line == null) continue;
392 if (line.startsWith("#")) continue;
394 String val = value(line);
395 if (val == null) continue;
397 byte[] data = base64.fromString(val);
399 if (line.startsWith("PrivateKey: ")) {
400 s = new BigInteger(1, data);
404 if (mECKeyFactory == null) {
405 mECKeyFactory = KeyFactory.getInstance("EC");
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");
413 KeySpec spec = new ECPrivateKeySpec(s, ec_spec);
416 return mECKeyFactory.generatePrivate(spec);
417 } catch (InvalidKeySpecException e) {
424 * Given a private key and public key, generate the BIND9 style
425 * private key format.
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);
441 * Convert from 'unsigned' big integer to original 'signed format' in Base64
443 private static String b64BigInt(BigInteger i) {
444 byte[] orig_bytes = i.toByteArray();
446 if (orig_bytes[0] != 0 || orig_bytes.length == 1) {
447 return base64.toString(orig_bytes);
450 byte[] signed_bytes = new byte[orig_bytes.length - 1];
451 System.arraycopy(orig_bytes, 1, signed_bytes, 0, signed_bytes.length);
453 return base64.toString(signed_bytes);
457 * Given a RSA private key (in Crt format), return the BIND9-style text
460 private String generatePrivateRSA(RSAPrivateCrtKey key, int algorithm) {
461 StringWriter sw = new StringWriter();
462 PrintWriter out = new PrintWriter(sw);
464 out.println("Private-key-format: v1.2");
465 out.println("Algorithm: " + algorithm + " (" + mAlgorithms.algToString(algorithm)
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()));
484 return sw.toString();
487 /** Given a DH key pair, return the BIND9-style text encoding */
488 private String generatePrivateDH(DHPrivateKey key,
491 StringWriter sw = new StringWriter();
492 PrintWriter out = new PrintWriter(sw);
494 DHParameterSpec p = key.getParams();
496 out.println("Private-key-format: v1.2");
497 out.println("Algorithm: " + algorithm + " (" + mAlgorithms.algToString(algorithm)
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()));
508 return sw.toString();
511 /** Given a DSA key pair, return the BIND9-style text encoding */
512 private String generatePrivateDSA(DSAPrivateKey key,
515 StringWriter sw = new StringWriter();
516 PrintWriter out = new PrintWriter(sw);
518 DSAParams p = key.getParams();
520 out.println("Private-key-format: v1.2");
521 out.println("Algorithm: " + algorithm + " (" + mAlgorithms.algToString(algorithm)
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()));
534 return sw.toString();
538 * Given an elliptic curve key pair, and the actual algorithm
539 * (which will describe the curve used), return the BIND9-style
542 private String generatePrivateEC(ECPrivateKey priv, ECPublicKey pub, int alg) {
543 StringWriter sw = new StringWriter();
544 PrintWriter out = new PrintWriter(sw);
546 out.println("Private-key-format: v1.2");
547 out.println("Algorithm: " + alg + " (" + mAlgorithms.algToString(alg)
549 out.print("PrivateKey: ");
550 out.println(b64BigInt(priv.getS()));
552 return sw.toString();