From 435acff6d050a75f148309b41fda9bc539f55bbd Mon Sep 17 00:00:00 2001 From: David Blacka Date: Tue, 23 May 2006 21:24:00 +0000 Subject: [PATCH] add support for algorithm aliases, fix SignZone so you can specify more than one KSK git-svn-id: https://svn.verisignlabs.com/jdnssec/tools/trunk@64 4cbd57fe-54e5-0310-bd9a-f30fe5ea5e6e --- src/com/verisignlabs/dnssec/cl/KeyGen.java | 180 ++++++------ src/com/verisignlabs/dnssec/cl/SignZone.java | 172 ++++++------ .../verisignlabs/dnssec/cl/VerifyZone.java | 111 +++++--- .../dnssec/security/DnsKeyAlgorithm.java | 263 ++++++++++++++++++ .../dnssec/security/DnsKeyConverter.java | 103 +++++-- .../dnssec/security/DnsKeyPair.java | 47 +--- .../dnssec/security/DnsSecVerifier.java | 6 +- .../dnssec/security/JCEDnsSecSigner.java | 50 +--- .../dnssec/security/SignUtils.java | 7 - 9 files changed, 631 insertions(+), 308 deletions(-) create mode 100644 src/com/verisignlabs/dnssec/security/DnsKeyAlgorithm.java diff --git a/src/com/verisignlabs/dnssec/cl/KeyGen.java b/src/com/verisignlabs/dnssec/cl/KeyGen.java index e4e0138..08914ea 100644 --- a/src/com/verisignlabs/dnssec/cl/KeyGen.java +++ b/src/com/verisignlabs/dnssec/cl/KeyGen.java @@ -27,10 +27,10 @@ import java.util.logging.Logger; import org.apache.commons.cli.*; import org.xbill.DNS.DClass; import org.xbill.DNS.DNSKEYRecord; -import org.xbill.DNS.DNSSEC; import org.xbill.DNS.Name; import com.verisignlabs.dnssec.security.BINDKeyUtils; +import com.verisignlabs.dnssec.security.DnsKeyAlgorithm; import com.verisignlabs.dnssec.security.DnsKeyPair; import com.verisignlabs.dnssec.security.JCEDnsSecSigner; @@ -66,6 +66,73 @@ public class KeyGen setupCLI(); } + /** + * Set up the command line options. + * + * @return a set of command line options. + */ + private void setupCLI() + { + opts = new Options(); + + // boolean options + opts.addOption("h", "help", false, "Print this message."); + opts.addOption("k", + "kskflag", + false, + "Key is a key-signing-key (sets the SEP flag)."); + + // Argument options + OptionBuilder.hasArg(); + OptionBuilder.withLongOpt("nametype"); + OptionBuilder.withArgName("type"); + OptionBuilder.withDescription("ZONE | OTHER (default ZONE)"); + opts.addOption(OptionBuilder.create('n')); + + OptionBuilder.hasOptionalArg(); + OptionBuilder.withLongOpt("verbose"); + OptionBuilder.withArgName("level"); + OptionBuilder.withDescription("verbosity level -- 0 is silence, " + + "5 is debug information, " + "6 is trace information.\n" + + "default is level 5."); + opts.addOption(OptionBuilder.create('v')); + + OptionBuilder.hasArg(); + OptionBuilder.withArgName("algorithm"); + OptionBuilder + .withDescription("RSA | RSASHA1 | RSAMD5 | DH | DSA | alias, " + + "RSASHA1 is default."); + opts.addOption(OptionBuilder.create('a')); + + OptionBuilder.hasArg(); + OptionBuilder.withArgName("size"); + OptionBuilder.withDescription("key size, in bits. (default = 1024)\n" + + "RSA|RSASHA1|RSAMD5: [512..4096]\n" + + "DSA: [512..1024]\n" + + "DH: [128..4096]"); + opts.addOption(OptionBuilder.create('b')); + + OptionBuilder.hasArg(); + OptionBuilder.withArgName("file"); + OptionBuilder.withLongOpt("output-file"); + OptionBuilder + .withDescription("base filename for the public/private key files"); + opts.addOption(OptionBuilder.create('f')); + + OptionBuilder.hasArg(); + OptionBuilder.withLongOpt("keydir"); + OptionBuilder.withArgName("dir"); + OptionBuilder.withDescription("place generated key files in this " + + "directory"); + opts.addOption(OptionBuilder.create('d')); + + OptionBuilder.hasArg(); + OptionBuilder.withLongOpt("alg-alias"); + OptionBuilder.withArgName("alias:original:mnemonic"); + OptionBuilder.withDescription("define an alias for an algorithm"); + opts.addOption(OptionBuilder.create('A')); + } + public void parseCommandLine(String[] args) throws org.apache.commons.cli.ParseException { @@ -111,6 +178,16 @@ public class KeyGen zoneKey = false; } } + + String[] optstrs; + if ((optstrs = cli.getOptionValues('A')) != null) + { + for (int i = 0; i < optstrs.length; i++) + { + addArgAlias(optstrs[i]); + } + } + if ((optstr = cli.getOptionValue('a')) != null) { algorithm = parseAlg(optstr); @@ -137,64 +214,25 @@ public class KeyGen owner = cl_args[0]; } - /** - * Set up the command line options. - * - * @return a set of command line options. - */ - private void setupCLI() + private void addArgAlias(String s) { - opts = new Options(); - - // boolean options - opts.addOption("h", "help", false, "Print this message."); - opts.addOption("k", - "kskflag", - false, - "Key is a key-signing-key (sets the SEP flag)."); - - // Argument options - OptionBuilder.hasArg(); - OptionBuilder.withLongOpt("nametype"); - OptionBuilder.withArgName("type"); - OptionBuilder.withDescription("ZONE | OTHER (default ZONE)"); - opts.addOption(OptionBuilder.create('n')); - - OptionBuilder.hasOptionalArg(); - OptionBuilder.withLongOpt("verbose"); - OptionBuilder.withArgName("level"); - OptionBuilder.withDescription("verbosity level -- 0 is silence, " - + "5 is debug information, " + "6 is trace information.\n" - + "default is level 5."); - opts.addOption(OptionBuilder.create('v')); - - OptionBuilder.hasArg(); - OptionBuilder.withArgName("algorithm"); - OptionBuilder.withDescription("RSA | RSASHA1 | RSAMD5 | DH | DSA, " - + "RSASHA1 is default."); - opts.addOption(OptionBuilder.create('a')); - - OptionBuilder.hasArg(); - OptionBuilder.withArgName("size"); - OptionBuilder.withDescription("key size, in bits. (default = 1024)\n" - + "RSA|RSASHA1|RSAMD5: [512..4096]\n" - + "DSA: [512..1024]\n" - + "DH: [128..4096]"); - opts.addOption(OptionBuilder.create('b')); - - OptionBuilder.hasArg(); - OptionBuilder.withArgName("file"); - OptionBuilder.withLongOpt("output-file"); - OptionBuilder.withDescription("base filename for the public/private key files"); - opts.addOption(OptionBuilder.create('f')); - - OptionBuilder.hasArg(); - OptionBuilder.withLongOpt("keydir"); - OptionBuilder.withArgName("dir"); - OptionBuilder.withDescription("place generated key files in this directory"); - opts.addOption(OptionBuilder.create('d')); + if (s == null) return; + + DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance(); + + String[] v = s.split(":"); + if (v.length < 2) return; + + int alias = parseInt(v[0], -1); + if (alias <= 0) return; + int orig = parseInt(v[1], -1); + if (orig <= 0) return; + String mn = null; + if (v.length > 2) mn = v[2]; + + algs.addAlias(alias, mn, orig); } - + /** Print out the usage and help statements, then quit. */ private void usage() { @@ -239,34 +277,12 @@ public class KeyGen private static int parseAlg(String s) { + DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance(); + int alg = parseInt(s, -1); if (alg > 0) return alg; - s = s.toUpperCase(); - - if (s.equals("RSA")) - { - return DNSSEC.RSASHA1; - } - else if (s.equals("RSAMD5")) - { - return DNSSEC.RSA; - } - else if (s.equals("DH")) - { - return DNSSEC.DH; - } - else if (s.equals("DSA")) - { - return DNSSEC.DSA; - } - else if (s.equals("RSASHA1")) - { - return DNSSEC.RSASHA1; - } - - // default - return DNSSEC.RSASHA1; + return algs.stringToAlgorithm(s); } public static void execute(CLIState state) throws Exception diff --git a/src/com/verisignlabs/dnssec/cl/SignZone.java b/src/com/verisignlabs/dnssec/cl/SignZone.java index 1f3d41c..8d0c674 100644 --- a/src/com/verisignlabs/dnssec/cl/SignZone.java +++ b/src/com/verisignlabs/dnssec/cl/SignZone.java @@ -44,13 +44,7 @@ import org.xbill.DNS.TextParseException; import org.xbill.DNS.Type; import org.xbill.DNS.utils.base16; -import com.verisignlabs.dnssec.security.BINDKeyUtils; -import com.verisignlabs.dnssec.security.DnsKeyPair; -import com.verisignlabs.dnssec.security.DnsSecVerifier; -import com.verisignlabs.dnssec.security.JCEDnsSecSigner; -import com.verisignlabs.dnssec.security.RecordComparator; -import com.verisignlabs.dnssec.security.SignUtils; -import com.verisignlabs.dnssec.security.ZoneUtils; +import com.verisignlabs.dnssec.security.*; /** * This class forms the command line implementation of a DNSSEC zone signer. @@ -91,6 +85,76 @@ public class SignZone setupCLI(); } + /** + * Set up the command line options. + * + * @return a set of command line options. + */ + private void setupCLI() + { + opts = new Options(); + + // boolean options + opts.addOption("h", "help", false, "Print this message."); + opts.addOption("a", "verify", false, "verify generated signatures>"); + opts.addOption("F", + "fully-sign-keyset", + false, + "sign the zone apex keyset with all available keys."); + + // Argument options + opts.addOption(OptionBuilder.hasOptionalArg().withLongOpt("verbose") + .withArgName("level").withDescription("verbosity level") + .create('v')); + opts.addOption(OptionBuilder.hasArg().withArgName("dir") + .withLongOpt("keyset-directory") + .withDescription("directory to find keyset files (default '.').") + .create('d')); + opts.addOption(OptionBuilder.hasArg().withArgName("dir") + .withLongOpt("key-directory") + .withDescription("directory to find key files (default '.').") + .create('D')); + opts.addOption(OptionBuilder.hasArg().withArgName("time/offset") + .withLongOpt("start-time") + .withDescription("signature starting time " + + "(default is now - 1 hour)").create('s')); + opts.addOption(OptionBuilder.hasArg().withArgName("time/offset") + .withLongOpt("expire-time") + .withDescription("signature expiration time " + + "(default is start-time + 30 days).").create('e')); + opts.addOption(OptionBuilder.hasArg().withArgName("outfile") + .withDescription("file the signed zone is written to " + + "(default is .signed).").create('f')); + opts.addOption(OptionBuilder.hasArg().withArgName("KSK file") + .withLongOpt("ksk-file") + .withDescription("this key is the key signing key.").create('k')); + opts.addOption(OptionBuilder.hasArg().withArgName("file") + .withLongOpt("include-file") + .withDescription("include names in this " + + "file in the NSEC/NSEC3 chain.").create('I')); + + opts.addOption(OptionBuilder.hasArg() + .withArgName("alias:original:mnemonic").withLongOpt("alg-alias") + .withDescription("Define an alias for an algorithm").create('A')); + // NSEC3 options + opts.addOption("3", "use-nsec3", false, "use NSEC3 instead of NSEC"); + opts.addOption("O", + "use-opt-in", + false, + "generate a fully Opt-In zone."); + + opts.addOption(OptionBuilder.hasArg().withLongOpt("salt") + .withArgName("hex value").withDescription("supply a salt value.") + .create('S')); + opts.addOption(OptionBuilder.hasArg().withLongOpt("random-salt") + .withArgName("length").withDescription("generate a random salt.") + .create('R')); + opts.addOption(OptionBuilder.hasArg().withLongOpt("iterations") + .withArgName("value") + .withDescription("use this value for the iterations in NSEC3.") + .create()); + } + public void parseCommandLine(String[] args) throws org.apache.commons.cli.ParseException, ParseException, IOException @@ -99,6 +163,7 @@ public class SignZone CommandLine cli = cli_parser.parse(opts, args); String optstr = null; + String[] optstrs = null; if (cli.hasOption('h')) usage(); @@ -138,6 +203,14 @@ public class SignZone useOptIn = false; } + if ((optstrs = cli.getOptionValues('A')) != null) + { + for (int i = 0; i < optstrs.length; i++) + { + addArgAlias(optstrs[i]); + } + } + if (cli.hasOption('F')) fullySignKeyset = true; if ((optstr = cli.getOptionValue('d')) != null) @@ -182,15 +255,7 @@ public class SignZone outputfile = cli.getOptionValue('f'); - // FIXME: this is a bit awkward, because we really want -k to repeat, - // but the CLI classes don't do it quite right. Instead we just convert - // our single argument to an array. - String kskFile = cli.getOptionValue('k'); - if (kskFile != null) - { - kskFiles = new String[1]; - kskFiles[0] = kskFile; - } + kskFiles = cli.getOptionValues('k'); if ((optstr = cli.getOptionValue('I')) != null) { @@ -235,74 +300,25 @@ public class SignZone } } - /** - * Set up the command line options. - * - * @return a set of command line options. - */ - private void setupCLI() + private void addArgAlias(String s) { - opts = new Options(); + if (s == null) return; - // boolean options - opts.addOption("h", "help", false, "Print this message."); - opts.addOption("a", "verify", false, "verify generated signatures>"); - opts.addOption("F", - "fully-sign-keyset", - false, - "sign the zone apex keyset with all available keys."); + DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance(); - // Argument options - opts.addOption(OptionBuilder.hasOptionalArg().withLongOpt("verbose") - .withArgName("level").withDescription("verbosity level") - .create('v')); - opts.addOption(OptionBuilder.hasArg().withArgName("dir") - .withLongOpt("keyset-directory") - .withDescription("directory to find keyset files (default '.').") - .create('d')); - opts.addOption(OptionBuilder.hasArg().withArgName("dir") - .withLongOpt("key-directory") - .withDescription("directory to find key files (default '.').") - .create('D')); - opts.addOption(OptionBuilder.hasArg().withArgName("time/offset") - .withLongOpt("start-time") - .withDescription("signature starting time " - + "(default is now - 1 hour)").create('s')); - opts.addOption(OptionBuilder.hasArg().withArgName("time/offset") - .withLongOpt("expire-time") - .withDescription("signature expiration time " - + "(default is start-time + 30 days).").create('e')); - opts.addOption(OptionBuilder.hasArg().withArgName("outfile") - .withDescription("file the signed zone is written to " - + "(default is .signed).").create('f')); - opts.addOption(OptionBuilder.hasArg() - .withArgName("KSK file").withLongOpt("ksk-file") - .withDescription("this key is the key signing key.") - .create('k')); - opts.addOption(OptionBuilder.hasArg().withArgName("file") - .withLongOpt("include-file") - .withDescription("include names in this " - + "file in the NSEC/NSEC3 chain.").create('I')); + String[] v = s.split(":"); + if (v.length < 2) return; - // NSEC3 options - opts.addOption("3", "use-nsec3", false, "use NSEC3 instead of NSEC"); - opts.addOption("O", - "use-opt-in", - false, - "generate a fully Opt-In zone."); + int alias = parseInt(v[0], -1); + if (alias <= 0) return; + int orig = parseInt(v[1], -1); + if (orig <= 0) return; + String mn = null; + if (v.length > 2) mn = v[2]; - opts.addOption(OptionBuilder.hasArg().withLongOpt("salt") - .withArgName("hex value").withDescription("supply a salt value.") - .create('S')); - opts.addOption(OptionBuilder.hasArg().withLongOpt("random-salt") - .withArgName("length").withDescription("generate a random salt.") - .create('R')); - opts.addOption(OptionBuilder.hasArg().withLongOpt("iterations") - .withArgName("value") - .withDescription("use this value for the iterations in NSEC3.") - .create()); + algs.addAlias(alias, mn, orig); } - + /** Print out the usage and help statements, then quit. */ private void usage() { diff --git a/src/com/verisignlabs/dnssec/cl/VerifyZone.java b/src/com/verisignlabs/dnssec/cl/VerifyZone.java index 9957ba3..ac4810a 100644 --- a/src/com/verisignlabs/dnssec/cl/VerifyZone.java +++ b/src/com/verisignlabs/dnssec/cl/VerifyZone.java @@ -33,12 +33,7 @@ import org.apache.commons.cli.*; import org.apache.commons.cli.Options; import org.xbill.DNS.*; -import com.verisignlabs.dnssec.security.BINDKeyUtils; -import com.verisignlabs.dnssec.security.DnsKeyPair; -import com.verisignlabs.dnssec.security.DnsSecVerifier; -import com.verisignlabs.dnssec.security.RecordComparator; -import com.verisignlabs.dnssec.security.SignUtils; -import com.verisignlabs.dnssec.security.ZoneUtils; +import com.verisignlabs.dnssec.security.*; /** * This class forms the command line implementation of a DNSSEC zone @@ -69,6 +64,40 @@ public class VerifyZone setupCLI(); } + /** + * Set up the command line options. + * + * @return a set of command line options. + */ + private void setupCLI() + { + opts = new Options(); + + // boolean options + opts.addOption("h", "help", false, "Print this message."); + opts.addOption("s", + "strict", + false, + "Zone will only be considered valid if all " + + "signatures could be cryptographically verified"); + + // Argument options + opts.addOption(OptionBuilder.hasArg().withLongOpt("keydir") + .withArgName("dir").withDescription("directory to find " + + "trusted key files").create('d')); + + opts.addOption(OptionBuilder.hasOptionalArg().withLongOpt("verbose") + .withArgName("level") + .withDescription("verbosity level -- 0 is silence, " + + "5 is debug information, 6 is trace information.\n" + + "default is level 5.").create('v')); + + opts.addOption(OptionBuilder.hasArg() + .withArgName("alias:original:mnemonic").withLongOpt("alg-alias") + .withDescription("Define an alias for an algorithm").create('A')); + + } + public void parseCommandLine(String[] args) throws org.apache.commons.cli.ParseException { @@ -105,6 +134,15 @@ public class VerifyZone keydir = new File(optstr); } + String[] optstrs = null; + if ((optstrs = cli.getOptionValues('A')) != null) + { + for (int i = 0; i < optstrs.length; i++) + { + addArgAlias(optstrs[i]); + } + } + String[] cl_args = cli.getArgs(); if (cl_args.length < 1) @@ -122,40 +160,25 @@ public class VerifyZone } } - /** - * Set up the command line options. - * - * @return a set of command line options. - */ - private void setupCLI() + private void addArgAlias(String s) { - opts = new Options(); - - // boolean options - opts.addOption("h", "help", false, "Print this message."); - opts.addOption("s", - "strict", - false, - "Zone will only be considered valid if all " - + "signatures could be cryptographically verified"); - - // Argument options - OptionBuilder.hasArg(); - OptionBuilder.withLongOpt("keydir"); - OptionBuilder.withArgName("dir"); - OptionBuilder.withDescription("directory to find trusted key files"); - opts.addOption(OptionBuilder.create('d')); - - OptionBuilder.hasOptionalArg(); - OptionBuilder.withLongOpt("verbose"); - OptionBuilder.withArgName("level"); - OptionBuilder.withDescription("verbosity level -- 0 is silence, " - + "5 is debug information, 6 is trace information.\n" - + "default is level 5."); - opts.addOption(OptionBuilder.create('v')); - + if (s == null) return; + + DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance(); + + String[] v = s.split(":"); + if (v.length < 2) return; + + int alias = parseInt(v[0], -1); + if (alias <= 0) return; + int orig = parseInt(v[1], -1); + if (orig <= 0) return; + String mn = null; + if (v.length > 2) mn = v[2]; + + algs.addAlias(alias, mn, orig); } - + /** Print out the usage and help statements, then quit. */ public void usage() { @@ -209,7 +232,9 @@ public class VerifyZone for (Iterator i = keypairs.iterator(); i.hasNext();) { - verifier.addTrustedKey((DnsKeyPair) i.next()); + DnsKeyPair pair = (DnsKeyPair) i.next(); + if (pair.getPublic() == null) continue; + verifier.addTrustedKey(pair); } List rrsets = SignUtils.assembleIntoRRsets(records); @@ -254,16 +279,17 @@ public class VerifyZone { zonename = r.getName(); } - - if (r.getName().equals(zonename) && r.getType() == Type.DNSKEY) + + if (r.getName().equals(zonename) && r.getType() == Type.DNSKEY) { DnsKeyPair pair = new DnsKeyPair((DNSKEYRecord) r); res.add(pair); } } - + return res; } + private static List getTrustedKeys(String[] keyfiles, File inDirectory) throws IOException { @@ -283,7 +309,6 @@ public class VerifyZone public static void execute(CLIState state) throws Exception { - List records = ZoneUtils.readZoneFile(state.zonefile, null); List keypairs = null; if (state.keyfiles != null) diff --git a/src/com/verisignlabs/dnssec/security/DnsKeyAlgorithm.java b/src/com/verisignlabs/dnssec/security/DnsKeyAlgorithm.java new file mode 100644 index 0000000..8a9d5aa --- /dev/null +++ b/src/com/verisignlabs/dnssec/security/DnsKeyAlgorithm.java @@ -0,0 +1,263 @@ +/* + * $Id$ + * + * Copyright (c) 2006 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.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.Signature; +import java.util.HashMap; +import java.util.logging.Logger; + +import org.xbill.DNS.DNSSEC; + +/** + * This class handles translated 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 (orig) + * @author $Author: davidb $ (latest) + * @version $Revision: 2098 $ + */ +public class DnsKeyAlgorithm +{ + + public static final int UNKNOWN = -1; + public static final int RSA = 1; + public static final int DH = 2; + public static final int DSA = 3; + + private static class Entry + { + public String sigName; + public int baseType; + + public Entry(String sigName, int baseType) + { + this.sigName = sigName; + this.baseType = baseType; + } + } + + /** + * 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 cache key pair generator for DSA keys. */ + private KeyPairGenerator mDSAKeyGenerator; + + private Logger log = Logger.getLogger(this.getClass() + .toString()); + + /** This is the global instance for this class. */ + private static DnsKeyAlgorithm mInstance = null; + + public DnsKeyAlgorithm() + { + mAlgorithmMap = new HashMap(); + mMnemonicToIdMap = new HashMap(); + mIdToMnemonicMap = new HashMap(); + + // Load the standard DNSSEC algorithms. + addAlgorithm(DNSSEC.RSAMD5, new Entry("MD5withRSA", RSA)); + addMnemonic("RSAMD5", DNSSEC.RSAMD5); + + addAlgorithm(DNSSEC.DH, new Entry("", DH)); + addMnemonic("DH", DNSSEC.DH); + + addAlgorithm(DNSSEC.DSA, new Entry("SHA1withDSA", DSA)); + addMnemonic("DSA", DNSSEC.DSA); + + addAlgorithm(DNSSEC.RSASHA1, new Entry("SHA1withRSA", RSA)); + addMnemonic("RSASHA1", DNSSEC.RSASHA1); + addMnemonic("RSA", DNSSEC.RSASHA1); + } + + private void addAlgorithm(int algorithm, Entry entry) + { + Integer a = new Integer(algorithm); + mAlgorithmMap.put(a, entry); + } + + private void addMnemonic(String m, int alg) + { + Integer a = new Integer(alg); + mMnemonicToIdMap.put(m.toUpperCase(), a); + if (! mIdToMnemonicMap.containsKey(a)) + { + mIdToMnemonicMap.put(a, m); + } + } + + public void addAlias(int alias, String mnemonic, int original_algorithm) + { + Integer a = new Integer(alias); + Integer o = new Integer(original_algorithm); + + if (mAlgorithmMap.containsKey(a)) + { + log.warning("Unable to alias algorithm " + alias + + " because it already exists."); + return; + } + + if (!mAlgorithmMap.containsKey(o)) + { + log.warning("Unable to alias algorith " + alias + + " to unknown algorithm identifier " + original_algorithm); + return; + } + + mAlgorithmMap.put(a, mAlgorithmMap.get(o)); + + if (mnemonic != null) + { + addMnemonic(mnemonic, alias); + } + } + + private Entry getEntry(int alg) + { + return (Entry) mAlgorithmMap.get(new Integer(alg)); + } + + public Signature getSignature(int algorithm) + { + Entry 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; + } + + public int stringToAlgorithm(String s) + { + Integer alg = (Integer) mMnemonicToIdMap.get(s.toUpperCase()); + if (alg != null) return alg.intValue(); + return -1; + } + + public String algToString(int algorithm) + { + return (String) mIdToMnemonicMap.get(new Integer(algorithm)); + } + + public int baseType(int algorithm) + { + Entry entry = getEntry(algorithm); + if (entry != null) return entry.baseType; + return UNKNOWN; + } + + public int standardAlgorithm(int algorithm) + { + switch (baseType(algorithm)) + { + case RSA : + return DNSSEC.RSASHA1; + case DSA : + return DNSSEC.DSA; + case DH : + return DNSSEC.DH; + default : + return UNKNOWN; + } + } + + public boolean isDSA(int algorithm) + { + return (baseType(algorithm) == DSA); + } + + public KeyPair generateKeyPair(int algorithm, int keysize) + throws NoSuchAlgorithmException + { + KeyPair pair = null; + switch (baseType(algorithm)) + { + case RSA : + if (mRSAKeyGenerator == null) + { + mRSAKeyGenerator = KeyPairGenerator.getInstance("RSA"); + } + mRSAKeyGenerator.initialize(keysize); + pair = mRSAKeyGenerator.generateKeyPair(); + break; + case DSA : + if (mDSAKeyGenerator == null) + { + mDSAKeyGenerator = KeyPairGenerator.getInstance("DSA"); + } + mDSAKeyGenerator.initialize(keysize); + pair = mDSAKeyGenerator.generateKeyPair(); + break; + default : + throw new NoSuchAlgorithmException("Alg " + algorithm); + } + + return pair; + } + + public static DnsKeyAlgorithm getInstance() + { + if (mInstance == null) mInstance = new DnsKeyAlgorithm(); + return mInstance; + } +} diff --git a/src/com/verisignlabs/dnssec/security/DnsKeyConverter.java b/src/com/verisignlabs/dnssec/security/DnsKeyConverter.java index b2ea724..99d9a7e 100644 --- a/src/com/verisignlabs/dnssec/security/DnsKeyConverter.java +++ b/src/com/verisignlabs/dnssec/security/DnsKeyConverter.java @@ -44,7 +44,6 @@ import javax.crypto.spec.DHParameterSpec; import javax.crypto.spec.DHPrivateKeySpec; import org.xbill.DNS.DNSKEYRecord; -import org.xbill.DNS.DNSSEC; import org.xbill.DNS.KEYRecord; import org.xbill.DNS.Name; import org.xbill.DNS.security.KEYConverter; @@ -68,11 +67,35 @@ public class DnsKeyConverter { } - /** Given a DNS KEY record, return the JCA public key */ + /** + * Given a DNS KEY record, return the JCA public key + * + * @throws NoSuchAlgorithmException + */ public PublicKey parseDNSKEYRecord(DNSKEYRecord pKeyRecord) + throws NoSuchAlgorithmException { if (pKeyRecord.getKey() == null) return null; + // FIXME: this won't work at all with alg aliases. + // For now, instead of re-implementing parseRecord (or adding this stuff + // to DNSjava), we will just translate the algorithm back to a standard + // algorithm. Note that this will unnecessarily convert RSAMD5 to RSASHA1. + + DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance(); + int standard_alg = algs.standardAlgorithm(pKeyRecord.getAlgorithm()); + + if (standard_alg <= 0) + throw new NoSuchAlgorithmException("DNSKEY algorithm " + + pKeyRecord.getAlgorithm() + " is unrecognized"); + + if (pKeyRecord.getAlgorithm() != standard_alg) + { + pKeyRecord = new DNSKEYRecord(pKeyRecord.getName(), pKeyRecord + .getDClass(), pKeyRecord.getTTL(), pKeyRecord.getFlags(), + pKeyRecord.getProtocol(), standard_alg, pKeyRecord.getKey()); + } + return KEYConverter.parseRecord(pKeyRecord); } @@ -104,15 +127,15 @@ public class DnsKeyConverter public PrivateKey convertEncodedPrivateKey(byte[] key, int algorithm) { PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(key); + DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance(); try { - switch (algorithm) + switch (algs.baseType(algorithm)) { - case DNSSEC.RSAMD5 : - case DNSSEC.RSASHA1 : + case DnsKeyAlgorithm.RSA : return mRSAKeyFactory.generatePrivate(spec); - case DNSSEC.DSA : + case DnsKeyAlgorithm.DSA : return mDSAKeyFactory.generatePrivate(spec); } } @@ -122,11 +145,23 @@ public class DnsKeyConverter return null; } + private int parseInt(String s, int def) + { + try + { + return Integer.parseInt(s); + } + catch (NumberFormatException e) + { + return def; + } + } + /** * @return a JCA private key, given a BIND9-style textual encoding */ - public PrivateKey parsePrivateKeyString(String key) throws IOException, - NoSuchAlgorithmException + public PrivateKey parsePrivateKeyString(String key) + throws IOException, NoSuchAlgorithmException { StringTokenizer lines = new StringTokenizer(key, "\n"); @@ -149,11 +184,24 @@ public class DnsKeyConverter } else if (line.startsWith("Algorithm: ")) { - if (val.startsWith("1 ")) return parsePrivateRSA(lines); - if (val.startsWith("5 ")) return parsePrivateRSA(lines); - if (val.startsWith("2 ")) return parsePrivateDH(lines); - if (val.startsWith("3 ")) return parsePrivateDSA(lines); - throw new IOException("unsupported private key algorithm: " + val); + // here we assume that the value looks like # (MNEM) or just the + // number. + String[] toks = val.split("\\s", 2); + val = toks[0]; + int alg = parseInt(val, -1); + DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance(); + + switch (algs.baseType(alg)) + { + case DnsKeyAlgorithm.RSA : + return parsePrivateRSA(lines); + case DnsKeyAlgorithm.DSA : + return parsePrivateDSA(lines); + case DnsKeyAlgorithm.DH : + return parsePrivateDH(lines); + default : + throw new IOException("unsupported private key algorithm: " + val); + } } } return null; @@ -390,11 +438,11 @@ public class DnsKeyConverter } else if (priv instanceof DSAPrivateKey && pub instanceof DSAPublicKey) { - return generatePrivateDSA((DSAPrivateKey) priv, (DSAPublicKey) pub); + return generatePrivateDSA((DSAPrivateKey) priv, (DSAPublicKey) pub, alg); } else if (priv instanceof DHPrivateKey && pub instanceof DHPublicKey) { - return generatePrivateDH((DHPrivateKey) priv, (DHPublicKey) pub); + return generatePrivateDH((DHPrivateKey) priv, (DHPublicKey) pub, alg); } return null; @@ -426,16 +474,11 @@ public class DnsKeyConverter { StringWriter sw = new StringWriter(); PrintWriter out = new PrintWriter(sw); + DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance(); out.println("Private-key-format: v1.2"); - if (algorithm == DNSSEC.RSAMD5) - { - out.println("Algorithm: 1 (RSAMD5)"); - } - else - { - out.println("Algorithm: 5 (RSASHA1)"); - } + out.println("Algorithm: " + algorithm + " (" + + algs.algToString(algorithm) + ")"); out.print("Modulus: "); out.println(b64BigInt(key.getModulus())); out.print("PublicExponent: "); @@ -457,15 +500,18 @@ public class DnsKeyConverter } /** Given a DH key pair, return the BIND9-style text encoding */ - private String generatePrivateDH(DHPrivateKey key, DHPublicKey pub) + private String generatePrivateDH(DHPrivateKey key, DHPublicKey pub, + int algorithm) { StringWriter sw = new StringWriter(); PrintWriter out = new PrintWriter(sw); + DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance(); DHParameterSpec p = key.getParams(); out.println("Private-key-format: v1.2"); - out.println("Algorithm: 2 (DH)"); + out.println("Algorithm: " + algorithm + " (" + + algs.algToString(algorithm) + ")"); out.print("Prime(p): "); out.println(b64BigInt(p.getP())); out.print("Generator(g): "); @@ -479,15 +525,18 @@ public class DnsKeyConverter } /** Given a DSA key pair, return the BIND9-style text encoding */ - private String generatePrivateDSA(DSAPrivateKey key, DSAPublicKey pub) + private String generatePrivateDSA(DSAPrivateKey key, DSAPublicKey pub, + int algorithm) { StringWriter sw = new StringWriter(); PrintWriter out = new PrintWriter(sw); + DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance(); DSAParams p = key.getParams(); out.println("Private-key-format: v1.2"); - out.println("Algorithm: 3 (DSA)"); + out.println("Algorithm: " + algorithm + " (" + + algs.algToString(algorithm) + ")"); out.print("Prime(p): "); out.println(b64BigInt(p.getP())); out.print("Subprime(q): "); diff --git a/src/com/verisignlabs/dnssec/security/DnsKeyPair.java b/src/com/verisignlabs/dnssec/security/DnsKeyPair.java index 4808db0..3fcd00b 100644 --- a/src/com/verisignlabs/dnssec/security/DnsKeyPair.java +++ b/src/com/verisignlabs/dnssec/security/DnsKeyPair.java @@ -106,7 +106,7 @@ public class DnsKeyPair setDNSKEYRecord(keyRecord); setPrivateKeyString(null); } - + public DnsKeyPair(Name keyName, int algorithm, PublicKey publicKey, PrivateKey privateKey) { @@ -146,35 +146,8 @@ public class DnsKeyPair /** @return the appropriate Signature object for this keypair. */ protected Signature getSignature() { - Signature s = null; - - // First try and deduce the algorithm from the KEYRecord (which - // will be specific), then try and deduce it from the private key. - // We should have one or the other. - try - { - switch (getDNSKEYAlgorithm()) - { - case DNSSEC.RSAMD5 : - s = Signature.getInstance("MD5withRSA"); - break; - case DNSSEC.DSA : - s = Signature.getInstance("SHA1withDSA"); - break; - case DNSSEC.RSASHA1 : - s = Signature.getInstance("SHA1withRSA"); - break; - case -1 : - s = null; - break; - } - } - catch (NoSuchAlgorithmException e) - { - log.severe("error getting Signature object: " + e); - } - - return s; + DnsKeyAlgorithm algorithms = DnsKeyAlgorithm.getInstance(); + return algorithms.getSignature(getDNSKEYAlgorithm()); } /** @@ -184,8 +157,16 @@ public class DnsKeyPair { if (mPublicKey == null && getDNSKEYRecord() != null) { - DnsKeyConverter conv = getKeyConverter(); - setPublic(conv.parseDNSKEYRecord(getDNSKEYRecord())); + try + { + DnsKeyConverter conv = getKeyConverter(); + setPublic(conv.parseDNSKEYRecord(getDNSKEYRecord())); + } + catch (NoSuchAlgorithmException e) + { + log.severe(e.toString()); + return null; + } } return mPublicKey; @@ -220,6 +201,7 @@ public class DnsKeyPair /** * @return the opaque private key string, null if one doesn't exist. + * @throws NoSuchAlgorithmException */ public String getPrivateKeyString() { @@ -304,6 +286,7 @@ public class DnsKeyPair /** * @return a Signature object initialized for verifying, or null if this key * pair does not have a valid public key. + * @throws NoSuchAlgorithmException */ public Signature getVerifier() { diff --git a/src/com/verisignlabs/dnssec/security/DnsSecVerifier.java b/src/com/verisignlabs/dnssec/security/DnsSecVerifier.java index 43384b6..8a59c10 100644 --- a/src/com/verisignlabs/dnssec/security/DnsSecVerifier.java +++ b/src/com/verisignlabs/dnssec/security/DnsSecVerifier.java @@ -263,14 +263,18 @@ public class DnsSecVerifier implements Verifier { byte[] data = SignUtils.generateSigData(rrset, sigrec); + DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance(); + Signature signer = keypair.getVerifier(); signer.update(data); byte[] sig = sigrec.getSignature(); - if (sigrec.getAlgorithm() == DNSSEC.DSA) + + if (algs.baseType(sigrec.getAlgorithm()) == DnsKeyAlgorithm.DSA) { sig = SignUtils.convertDSASignature(sig); } + if (!signer.verify(sig)) { log.info("Signature failed to verify cryptographically"); diff --git a/src/com/verisignlabs/dnssec/security/JCEDnsSecSigner.java b/src/com/verisignlabs/dnssec/security/JCEDnsSecSigner.java index c1e320b..a96ef4d 100644 --- a/src/com/verisignlabs/dnssec/security/JCEDnsSecSigner.java +++ b/src/com/verisignlabs/dnssec/security/JCEDnsSecSigner.java @@ -22,7 +22,6 @@ package com.verisignlabs.dnssec.security; import java.io.IOException; import java.security.GeneralSecurityException; import java.security.KeyPair; -import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.Signature; import java.security.interfaces.DSAPublicKey; @@ -34,7 +33,6 @@ import java.util.List; import java.util.ListIterator; import org.xbill.DNS.DNSKEYRecord; -import org.xbill.DNS.DNSSEC; import org.xbill.DNS.Name; import org.xbill.DNS.RRSIGRecord; import org.xbill.DNS.RRset; @@ -54,10 +52,7 @@ import org.xbill.DNS.Type; */ public class JCEDnsSecSigner { - private DnsKeyConverter mKeyConverter; - - private KeyPairGenerator mRSAKeyGenerator; - private KeyPairGenerator mDSAKeyGenerator; + private DnsKeyConverter mKeyConverter; /** * Cryptographically generate a new DNSSEC key. @@ -73,32 +68,11 @@ public class JCEDnsSecSigner public DnsKeyPair generateKey(Name owner, long ttl, int dclass, int algorithm, int flags, int keysize) throws NoSuchAlgorithmException { - KeyPair pair; + DnsKeyAlgorithm algorithms = DnsKeyAlgorithm.getInstance(); if (ttl < 0) ttl = 86400; // set to a reasonable default. - switch (algorithm) - { - case DNSSEC.RSAMD5 : - case DNSSEC.RSASHA1 : - if (mRSAKeyGenerator == null) - { - mRSAKeyGenerator = KeyPairGenerator.getInstance("RSA"); - } - mRSAKeyGenerator.initialize(keysize); - pair = mRSAKeyGenerator.generateKeyPair(); - break; - case DNSSEC.DSA : - if (mDSAKeyGenerator == null) - { - mDSAKeyGenerator = KeyPairGenerator.getInstance("DSA"); - } - mDSAKeyGenerator.initialize(keysize); - pair = mDSAKeyGenerator.generateKeyPair(); - break; - default : - throw new NoSuchAlgorithmException("Alg " + algorithm); - } + KeyPair pair = algorithms.generateKeyPair(algorithm, keysize); if (mKeyConverter == null) { @@ -111,6 +85,7 @@ public class JCEDnsSecSigner flags, algorithm, pair.getPublic()); + DnsKeyPair dnspair = new DnsKeyPair(); dnspair.setDNSKEYRecord(keyrec); dnspair.setPublic(pair.getPublic()); // keep from conv. the keyrec back. @@ -173,11 +148,12 @@ public class JCEDnsSecSigner signer.update(sign_data); byte[] sig = signer.sign(); + DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance(); // Convert to RFC 2536 format, if necessary. - if (pair.getDNSKEYAlgorithm() == DNSSEC.DSA) + if (algs.baseType(pair.getDNSKEYAlgorithm()) == DnsKeyAlgorithm.DSA) { - sig = SignUtils.convertDSASignature(((DSAPublicKey) pair.getPublic()).getParams(), - sig); + sig = SignUtils.convertDSASignature(((DSAPublicKey) pair.getPublic()) + .getParams(), sig); } RRSIGRecord sigrec = SignUtils.generateRRSIG(sig, presig); sigs.add(sigrec); @@ -234,8 +210,8 @@ public class JCEDnsSecSigner */ private Name addRRset(List toList, Name zonename, RRset rrset, List keysigningkeypairs, List zonekeypairs, Date start, Date expire, - boolean fullySignKeyset, Name last_cut) throws IOException, - GeneralSecurityException + boolean fullySignKeyset, Name last_cut) + throws IOException, GeneralSecurityException { // add the records themselves for (Iterator i = rrset.rrs(); i.hasNext();) @@ -243,10 +219,8 @@ public class JCEDnsSecSigner toList.add(i.next()); } - int type = SignUtils.recordSecType(zonename, - rrset.getName(), - rrset.getType(), - last_cut); + int type = SignUtils.recordSecType(zonename, rrset.getName(), rrset + .getType(), last_cut); // we don't sign non-normal sets (delegations, glue, invalid). // we also don't sign the zone key set unless we've been asked. diff --git a/src/com/verisignlabs/dnssec/security/SignUtils.java b/src/com/verisignlabs/dnssec/security/SignUtils.java index 256083c..3ad020d 100644 --- a/src/com/verisignlabs/dnssec/security/SignUtils.java +++ b/src/com/verisignlabs/dnssec/security/SignUtils.java @@ -157,13 +157,6 @@ public class SignUtils // Caculate the offset where the RDATA begins (we have to skip // past the length byte) - // FIXME: currently, draft-ietf-dnsext-dnssec-records-06 has us - // sorting by length first, then bytes. This can be accomplished - // by not skipping past the RDLENGTH field, I think. - // FIXME update: I pointed this out as an error, and subsequent - // versions should correct this, setting the standard back to - // bytes, then length. - int offset = rrset.getName().toWireCanonical().length + 10; ByteArrayComparator bac = new ByteArrayComparator(offset, false);