Merge branch 'alg-15-support' of https://github.com/pallaviaras/jdnssec-tools into pallaviaras-alg-15-support

This commit is contained in:
David Blacka
2018-07-15 16:57:59 +00:00
31 changed files with 291 additions and 1 deletions

View File

@@ -0,0 +1,343 @@
package com.verisignlabs.dnssec.cl;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import org.apache.commons.cli.AlreadySelectedException;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
import org.apache.commons.cli.UnrecognizedOptionException;
import com.verisignlabs.dnssec.security.DnsKeyAlgorithm;
/**
* This is a base class for jdnssec command line tools. Each command line tool
* should inherit from this class, create a subclass of CLIStateBase (overriding
* setupOptions and processOptions), and implement the execute() method.
* Subclasses also have their own main() methods, which should just create the
* subclass variant of the CLIState and call run().
*/
public abstract class CLBase
{
protected static Logger log;
/**
* This is a very simple log formatter that simply outputs the log level and
* log string.
*/
public static class BareLogFormatter extends Formatter
{
@Override
public String format(LogRecord arg0)
{
StringBuilder out = new StringBuilder();
String lvl = arg0.getLevel().getName();
out.append(lvl);
out.append(": ");
out.append(arg0.getMessage());
out.append("\n");
return out.toString();
}
}
/**
* This is a base class for command line parsing state. Subclasses should
* override setupOptions and processOptions.
*/
public static class CLIStateBase
{
protected Options opts;
protected String usageStr;
/**
* The base constructor. This will setup the command line options.
*
* @param usage
* The command line usage string (e.g.,
* "jdnssec-foo [..options..] zonefile")
*/
public CLIStateBase(String usage)
{
usageStr = usage;
setup();
}
/** This is the base set of command line options provided to all subclasses. */
private void setup()
{
// Set up the standard set of options that all jdnssec command line tools will implement.
opts = new Options();
// boolean options
opts.addOption("h", "help", false, "Print this message.");
opts.addOption("m", "multiline", false,
"Output DNS records using 'multiline' format");
OptionBuilder.hasOptionalArg();
OptionBuilder.withLongOpt("verbose");
OptionBuilder.withArgName("level");
OptionBuilder.withDescription("verbosity level -- 0 is silence, 3 is info, "
+ "5 is debug information, 6 is trace information. default is level 2 (warning)");
opts.addOption(OptionBuilder.create('v'));
OptionBuilder.hasArg();
OptionBuilder.withArgName("alias:original:mnemonic");
OptionBuilder.withLongOpt("alg-alias");
OptionBuilder.withDescription("Define an alias for an algorithm");
opts.addOption(OptionBuilder.create('A'));
setupOptions(opts);
}
/**
* This is an overridable method for subclasses to add their own command
* line options.
*
* @param opts
* the options object to add (via OptionBuilder, typically) new
* options to.
*/
protected void setupOptions(Options opts)
{
// Subclasses generally override this.
}
/**
* This is the main method for parsing the command line arguments.
* Subclasses generally override processOptions() rather than this method.
* This method create the parsing objects and processes the standard
* options.
*
* @param args
* The command line arguments.
* @throws ParseException
*/
public void parseCommandLine(String args[]) throws ParseException
{
CommandLineParser cli_parser = new PosixParser();
CommandLine cli = cli_parser.parse(opts, args);
if (cli.hasOption('h')) usage();
Logger rootLogger = Logger.getLogger("");
int value = parseInt(cli.getOptionValue('v'), -1);
switch (value)
{
case 0:
rootLogger.setLevel(Level.OFF);
break;
case 1:
rootLogger.setLevel(Level.SEVERE);
break;
case 2:
default:
rootLogger.setLevel(Level.WARNING);
break;
case 3:
rootLogger.setLevel(Level.INFO);
break;
case 4:
rootLogger.setLevel(Level.CONFIG);
case 5:
rootLogger.setLevel(Level.FINE);
break;
case 6:
rootLogger.setLevel(Level.ALL);
break;
}
// I hate java.util.logging, btw.
for (Handler h : rootLogger.getHandlers())
{
h.setLevel(rootLogger.getLevel());
h.setFormatter(new BareLogFormatter());
}
if (cli.hasOption('m'))
{
org.xbill.DNS.Options.set("multiline");
}
String[] optstrs = null;
if ((optstrs = cli.getOptionValues('A')) != null)
{
for (int i = 0; i < optstrs.length; i++)
{
addArgAlias(optstrs[i]);
}
}
processOptions(cli);
}
/**
* Process additional tool-specific options. Subclasses generally override
* this.
*
* @param cli
* The {@link CommandLine} object containing the parsed command
* line state.
*/
protected void processOptions(CommandLine cli) throws ParseException
{
// Subclasses generally override this.
}
/** Print out the usage and help statements, then quit. */
public void usage()
{
HelpFormatter f = new HelpFormatter();
PrintWriter out = new PrintWriter(System.err);
// print our own usage statement:
f.printHelp(out, 75, usageStr, null, opts, HelpFormatter.DEFAULT_LEFT_PAD,
HelpFormatter.DEFAULT_DESC_PAD, null);
out.flush();
System.exit(64);
}
protected void addArgAlias(String s)
{
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);
}
}
public static int parseInt(String s, int def)
{
try
{
int v = Integer.parseInt(s);
return v;
}
catch (NumberFormatException e)
{
return def;
}
}
public static long parseLong(String s, long def)
{
try
{
long v = Long.parseLong(s);
return v;
}
catch (NumberFormatException e)
{
return def;
}
}
/**
* Calculate a date/time from a command line time/offset duration string.
*
* @param start
* the start time to calculate offsets from.
* @param duration
* the time/offset string to parse.
* @return the calculated time.
*/
public static Date convertDuration(Date start, String duration) throws ParseException
{
if (start == null) start = new Date();
if (duration.startsWith("now"))
{
start = new Date();
if (duration.indexOf("+") < 0) return start;
duration = duration.substring(3);
}
if (duration.startsWith("+"))
{
long offset = (long) parseInt(duration.substring(1), 0) * 1000;
return new Date(start.getTime() + offset);
}
if (duration.length() <= 10)
{
long epoch = parseLong(duration, 0) * 1000;
return new Date(epoch);
}
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyyMMddHHmmss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT"));
try
{
return dateFormatter.parse(duration);
}
catch (java.text.ParseException e)
{
throw new ParseException(e.getMessage());
}
}
public abstract void execute() throws Exception;
public void run(CLIStateBase state, String[] args)
{
try
{
state.parseCommandLine(args);
}
catch (UnrecognizedOptionException e)
{
System.err.println("error: unknown option encountered: " + e.getMessage());
state.usage();
}
catch (AlreadySelectedException e)
{
System.err.println("error: mutually exclusive options have "
+ "been selected:\n " + e.getMessage());
state.usage();
}
catch (Exception e)
{
System.err.println("error: unknown command line parsing exception:");
e.printStackTrace();
state.usage();
}
log = Logger.getLogger(this.getClass().toString());
try
{
execute();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,136 @@
// Copyright (C) 2001-2003, 2011 VeriSign, Inc.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
// USA
package com.verisignlabs.dnssec.cl;
import java.io.FileWriter;
import java.io.PrintWriter;
import org.apache.commons.cli.*;
import org.xbill.DNS.DLVRecord;
import org.xbill.DNS.DNSKEYRecord;
import org.xbill.DNS.DSRecord;
import org.xbill.DNS.Record;
import com.verisignlabs.dnssec.security.*;
/**
* This class forms the command line implementation of a DNSSEC DS/DLV generator
*
* @author David Blacka
*/
public class DSTool extends CLBase
{
private CLIState state;
/**
* This is a small inner class used to hold all of the command line option
* state.
*/
protected static class CLIState extends CLIStateBase
{
public boolean createDLV = false;
public String outputfile = null;
public String keyname = null;
public int digest_id = DSRecord.SHA1_DIGEST_ID;
public CLIState()
{
super("jdnssec-dstool [..options..] keyfile");
}
/**
* Set up the command line options.
*
* @return a set of command line options.
*/
protected void setupOptions(Options opts)
{
OptionBuilder.withLongOpt("dlv");
OptionBuilder.withDescription("Generate a DLV record instead.");
opts.addOption(OptionBuilder.create());
OptionBuilder.hasArg();
OptionBuilder.withLongOpt("digest");
OptionBuilder.withArgName("id");
OptionBuilder.withDescription("The Digest ID to use (numerically): either 1 for SHA1 or 2 for SHA256");
opts.addOption(OptionBuilder.create('d'));
}
protected void processOptions(CommandLine cli)
throws org.apache.commons.cli.ParseException
{
outputfile = cli.getOptionValue('f');
createDLV = cli.hasOption("dlv");
String optstr = cli.getOptionValue('d');
if (optstr != null) digest_id = parseInt(optstr, digest_id);
String[] cl_args = cli.getArgs();
if (cl_args.length < 1)
{
System.err.println("error: missing key file ");
usage();
}
keyname = cl_args[0];
}
}
public void execute() throws Exception
{
DnsKeyPair key = BINDKeyUtils.loadKey(state.keyname, null);
DNSKEYRecord dnskey = key.getDNSKEYRecord();
if ((dnskey.getFlags() & DNSKEYRecord.Flags.SEP_KEY) == 0)
{
log.warning("DNSKEY is not an SEP-flagged key.");
}
DSRecord ds = SignUtils.calculateDSRecord(dnskey, state.digest_id, dnskey.getTTL());
Record res = ds;
if (state.createDLV)
{
log.fine("creating DLV.");
DLVRecord dlv = new DLVRecord(ds.getName(), ds.getDClass(), ds.getTTL(),
ds.getFootprint(), ds.getAlgorithm(),
ds.getDigestID(), ds.getDigest());
res = dlv;
}
if (state.outputfile != null)
{
PrintWriter out = new PrintWriter(new FileWriter(state.outputfile));
out.println(res);
out.close();
}
else
{
System.out.println(res);
}
}
public static void main(String[] args)
{
DSTool tool = new DSTool();
tool.state = new CLIState();
tool.run(tool.state, args);
}
}

View File

@@ -0,0 +1,226 @@
// Copyright (C) 2001-2003, 2011 VeriSign, Inc.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
// USA
package com.verisignlabs.dnssec.cl;
import java.io.File;
import org.apache.commons.cli.*;
import org.xbill.DNS.DClass;
import org.xbill.DNS.DNSKEYRecord;
import org.xbill.DNS.Name;
import com.verisignlabs.dnssec.security.*;
/**
* This class forms the command line implementation of a DNSSEC key generator
*
* @author David Blacka
*/
public class KeyGen extends CLBase
{
private CLIState state;
/**
* This is a small inner class used to hold all of the command line option
* state.
*/
protected static class CLIState extends CLIStateBase
{
public int algorithm = 8;
public int keylength = 1024;
public boolean useLargeE = true;
public String outputfile = null;
public File keydir = null;
public boolean zoneKey = true;
public boolean kskFlag = false;
public String owner = null;
public long ttl = 86400;
public CLIState()
{
super("jdnssec-keygen [..options..] name");
}
/**
* Set up the command line options.
*/
protected void setupOptions(Options opts)
{
// boolean options
opts.addOption("k", "kskflag", false,
"Key is a key-signing-key (sets the SEP flag).");
opts.addOption("e", "large-exponent", false, "Use large RSA exponent (default)");
opts.addOption("E", "small-exponent", false, "Use small RSA exponent");
// Argument options
OptionBuilder.hasArg();
OptionBuilder.withLongOpt("nametype");
OptionBuilder.withArgName("type");
OptionBuilder.withDescription("ZONE | OTHER (default ZONE)");
opts.addOption(OptionBuilder.create('n'));
String[] algStrings = DnsKeyAlgorithm.getInstance().supportedAlgMnemonics();
OptionBuilder.hasArg();
OptionBuilder.withArgName("algorithm");
OptionBuilder.withDescription(String.join(" | ", algStrings) +
" | alias, RSASHA256 is default.");
opts.addOption(OptionBuilder.create('a'));
OptionBuilder.hasArg();
OptionBuilder.withArgName("size");
OptionBuilder.withDescription("key size, in bits. default is 1024. "
+ "RSA: [512..4096], DSA: [512..1024], DH: [128..4096], "
+ "ECDSA: ignored");
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'));
}
protected void processOptions(CommandLine cli)
throws org.apache.commons.cli.ParseException
{
String optstr = null;
String[] optstrs = null;
if (cli.hasOption('k')) kskFlag = true;
if (cli.hasOption('e')) useLargeE = true;
outputfile = cli.getOptionValue('f');
if ((optstr = cli.getOptionValue('d')) != null)
{
keydir = new File(optstr);
}
if ((optstr = cli.getOptionValue('n')) != null)
{
if (!optstr.equalsIgnoreCase("ZONE"))
{
zoneKey = false;
}
}
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);
if (algorithm < 0)
{
System.err.println("DNSSEC algorithm " + optstr + " is not supported");
usage();
}
}
if ((optstr = cli.getOptionValue('b')) != null)
{
keylength = parseInt(optstr, 1024);
}
if ((optstr = cli.getOptionValue("ttl")) != null)
{
ttl = parseInt(optstr, 86400);
}
String[] cl_args = cli.getArgs();
if (cl_args.length < 1)
{
System.err.println("error: missing key owner name");
usage();
}
owner = cl_args[0];
}
}
private static int parseAlg(String s)
{
DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance();
int alg = parseInt(s, -1);
if (alg > 0)
{
if (algs.supportedAlgorithm(alg)) return alg;
return -1;
}
return algs.stringToAlgorithm(s);
}
public void execute() throws Exception
{
JCEDnsSecSigner signer = new JCEDnsSecSigner();
// Minor hack to make the owner name absolute.
if (!state.owner.endsWith("."))
{
state.owner = state.owner + ".";
}
Name owner_name = Name.fromString(state.owner);
// Calculate our flags
int flags = 0;
if (state.zoneKey) flags |= DNSKEYRecord.Flags.ZONE_KEY;
if (state.kskFlag) flags |= DNSKEYRecord.Flags.SEP_KEY;
log.fine("create key pair with (name = " + owner_name + ", ttl = " + state.ttl
+ ", alg = " + state.algorithm + ", flags = " + flags + ", length = "
+ state.keylength + ")");
DnsKeyPair pair = signer.generateKey(owner_name, state.ttl, DClass.IN,
state.algorithm, flags, state.keylength,
state.useLargeE);
if (state.outputfile != null)
{
BINDKeyUtils.writeKeyFiles(state.outputfile, pair, state.keydir);
}
else
{
BINDKeyUtils.writeKeyFiles(pair, state.keydir);
System.out.println(BINDKeyUtils.keyFileBase(pair));
}
}
public static void main(String[] args)
{
KeyGen tool = new KeyGen();
tool.state = new CLIState();
tool.run(tool.state, args);
}
}

View File

@@ -0,0 +1,121 @@
// Copyright (C) 2001-2003, 2011 VeriSign, Inc.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
// USA
package com.verisignlabs.dnssec.cl;
import java.security.interfaces.DSAPublicKey;
import java.security.interfaces.RSAPublicKey;
import org.apache.commons.cli.*;
import org.xbill.DNS.DNSKEYRecord;
import com.verisignlabs.dnssec.security.*;
/**
* This class forms the command line implementation of a key introspection tool.
*
* @author David Blacka
*/
public class KeyInfoTool extends CLBase
{
private CLIState state;
/**
* This is a small inner class used to hold all of the command line option
* state.
*/
protected static class CLIState extends CLIStateBase
{
public String[] keynames = null;
public CLIState()
{
super("jdnssec-keyinfo [..options..] keyfile");
}
/**
* Set up the command line options.
*/
protected void setupOptions(Options opts)
{
// no special options at the moment.
}
protected void processOptions(CommandLine cli) throws ParseException
{
keynames = cli.getArgs();
if (keynames.length < 1)
{
System.err.println("error: missing key file ");
usage();
}
}
}
public void execute() throws Exception
{
for (int i = 0; i < state.keynames.length; ++i)
{
String keyname = state.keynames[i];
DnsKeyPair key = BINDKeyUtils.loadKey(keyname, null);
DNSKEYRecord dnskey = key.getDNSKEYRecord();
DnsKeyAlgorithm dnskeyalg = DnsKeyAlgorithm.getInstance();
boolean isSEP = (dnskey.getFlags() & DNSKEYRecord.Flags.SEP_KEY) != 0;
System.out.println(keyname + ":");
System.out.println("Name: " + dnskey.getName());
System.out.println("SEP: " + isSEP);
System.out.println("Algorithm: " + dnskeyalg.algToString(dnskey.getAlgorithm())
+ " (" + dnskey.getAlgorithm() + ")");
System.out.println("ID: " + dnskey.getFootprint());
System.out.println("KeyFileBase: " + BINDKeyUtils.keyFileBase(key));
int basetype = dnskeyalg.baseType(dnskey.getAlgorithm());
switch (basetype)
{
case DnsKeyAlgorithm.RSA: {
RSAPublicKey pub = (RSAPublicKey) key.getPublic();
System.out.println("RSA Public Exponent: " + pub.getPublicExponent());
System.out.println("RSA Modulus: " + pub.getModulus());
break;
}
case DnsKeyAlgorithm.DSA: {
DSAPublicKey pub = (DSAPublicKey) key.getPublic();
System.out.println("DSA base (G): " + pub.getParams().getG());
System.out.println("DSA prime (P): " + pub.getParams().getP());
System.out.println("DSA subprime (Q): " + pub.getParams().getQ());
System.out.println("DSA public (Y): " + pub.getY());
break;
}
}
if (state.keynames.length - i > 1)
{
System.out.println();
}
}
}
public static void main(String[] args)
{
KeyInfoTool tool = new KeyInfoTool();
tool.state = new CLIState();
tool.run(tool.state, args);
}
}

View File

@@ -0,0 +1,393 @@
// Copyright (C) 2001-2003, 2011 VeriSign, Inc.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
// USA
package com.verisignlabs.dnssec.cl;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.xbill.DNS.Name;
import org.xbill.DNS.RRSIGRecord;
import org.xbill.DNS.RRset;
import org.xbill.DNS.Record;
import org.xbill.DNS.Type;
import com.verisignlabs.dnssec.security.*;
/**
* This class forms the command line implementation of a DNSSEC keyset signer.
* Instead of being able to sign an entire zone, it will just sign a given
* DNSKEY RRset.
*
* @author David Blacka
*/
public class SignKeyset extends CLBase
{
private CLIState state;
/**
* This is an inner class used to hold all of the command line option state.
*/
protected static class CLIState extends CLIStateBase
{
public File keyDirectory = null;
public String[] keyFiles = null;
public Date start = null;
public Date expire = null;
public String inputfile = null;
public String outputfile = null;
public boolean verifySigs = false;
public CLIState()
{
super("jdnssec-signkeyset [..options..] dnskeyset_file [key_file ...]");
}
/**
* Set up the command line options.
*/
protected void setupOptions(Options opts)
{
// boolean options
opts.addOption("a", "verify", false, "verify generated signatures>");
// Argument options
OptionBuilder.hasArg();
OptionBuilder.withArgName("dir");
OptionBuilder.withLongOpt("key-directory");
OptionBuilder.withDescription("directory to find key files (default '.').");
opts.addOption(OptionBuilder.create('D'));
OptionBuilder.hasArg();
OptionBuilder.withArgName("time/offset");
OptionBuilder.withLongOpt("start-time");
OptionBuilder.withDescription("signature starting time (default is now - 1 hour)");
opts.addOption(OptionBuilder.create('s'));
OptionBuilder.hasArg();
OptionBuilder.withArgName("time/offset");
OptionBuilder.withLongOpt("expire-time");
OptionBuilder.withDescription("signature expiration time (default is start-time + 30 days).");
opts.addOption(OptionBuilder.create('e'));
OptionBuilder.hasArg();
OptionBuilder.withArgName("outfile");
OptionBuilder.withDescription("file the signed keyset is written to.");
opts.addOption(OptionBuilder.create('f'));
}
protected void processOptions(CommandLine cli)
throws org.apache.commons.cli.ParseException
{
String optstr = null;
if (cli.hasOption('a')) verifySigs = true;
if ((optstr = cli.getOptionValue('D')) != null)
{
keyDirectory = new File(optstr);
if (!keyDirectory.isDirectory())
{
System.err.println("error: " + optstr + " is not a directory");
usage();
}
}
if ((optstr = cli.getOptionValue('s')) != null)
{
start = convertDuration(null, optstr);
}
else
{
// default is now - 1 hour.
start = new Date(System.currentTimeMillis() - (3600 * 1000));
}
if ((optstr = cli.getOptionValue('e')) != null)
{
expire = convertDuration(start, optstr);
}
else
{
expire = convertDuration(start, "+2592000"); // 30 days
}
outputfile = cli.getOptionValue('f');
String[] files = cli.getArgs();
if (files.length < 1)
{
System.err.println("error: missing zone file and/or key files");
usage();
}
inputfile = files[0];
if (files.length > 1)
{
keyFiles = new String[files.length - 1];
System.arraycopy(files, 1, keyFiles, 0, files.length - 1);
}
}
}
/**
* Verify the generated signatures.
*
* @param zonename
* the origin name of the zone.
* @param records
* a list of {@link org.xbill.DNS.Record}s.
* @param keypairs
* a list of keypairs used the sign the zone.
* @return true if all of the signatures validated.
*/
private static boolean verifySigs(Name zonename, List<Record> records,
List<DnsKeyPair> keypairs)
{
boolean secure = true;
DnsSecVerifier verifier = new DnsSecVerifier();
for (DnsKeyPair pair : keypairs)
{
verifier.addTrustedKey(pair);
}
verifier.setVerifyAllSigs(true);
List<RRset> rrsets = SignUtils.assembleIntoRRsets(records);
for (RRset rrset : rrsets)
{
// skip unsigned rrsets.
if (!rrset.sigs().hasNext()) continue;
boolean result = verifier.verify(rrset);
if (!result)
{
log.fine("Signatures did not verify for RRset: " + rrset);
secure = false;
}
}
return secure;
}
/**
* Load the key pairs from the key files.
*
* @param keyfiles
* a string array containing the base names or paths of the keys
* to be loaded.
* @param start_index
* the starting index of keyfiles string array to use. This
* allows us to use the straight command line argument array.
* @param inDirectory
* the directory to look in (may be null).
* @return a list of keypair objects.
*/
private static List<DnsKeyPair> getKeys(String[] keyfiles, int start_index,
File inDirectory) throws IOException
{
if (keyfiles == null) return null;
int len = keyfiles.length - start_index;
if (len <= 0) return null;
ArrayList<DnsKeyPair> keys = new ArrayList<DnsKeyPair>(len);
for (int i = start_index; i < keyfiles.length; i++)
{
DnsKeyPair k = BINDKeyUtils.loadKeyPair(keyfiles[i], inDirectory);
if (k != null) keys.add(k);
}
return keys;
}
private static class KeyFileFilter implements FileFilter
{
private String prefix;
public KeyFileFilter(Name origin)
{
prefix = "K" + origin.toString();
}
public boolean accept(File pathname)
{
if (!pathname.isFile()) return false;
String name = pathname.getName();
if (name.startsWith(prefix) && name.endsWith(".private")) return true;
return false;
}
}
private static List<DnsKeyPair> findZoneKeys(File inDirectory, Name zonename)
throws IOException
{
if (inDirectory == null)
{
inDirectory = new File(".");
}
// get the list of "K<zone>.*.private files.
FileFilter filter = new KeyFileFilter(zonename);
File[] files = inDirectory.listFiles(filter);
// read in all of the records
ArrayList<DnsKeyPair> keys = new ArrayList<DnsKeyPair>();
for (int i = 0; i < files.length; i++)
{
DnsKeyPair p = BINDKeyUtils.loadKeyPair(files[i].getName(), inDirectory);
keys.add(p);
}
if (keys.size() > 0) return keys;
return null;
}
@SuppressWarnings("unchecked")
public void execute() throws Exception
{
// Read in the zone
List<Record> records = ZoneUtils.readZoneFile(state.inputfile, null);
if (records == null || records.size() == 0)
{
System.err.println("error: empty keyset file");
state.usage();
}
// Make sure that all records are DNSKEYs with the same name.
Name keysetName = null;
RRset keyset = new RRset();
for (Record r : records)
{
if (r.getType() != Type.DNSKEY)
{
System.err.println("error: Non DNSKEY RR found in keyset: " + r);
continue;
}
if (keysetName == null)
{
keysetName = r.getName();
}
if (!r.getName().equals(keysetName))
{
System.err.println("error: DNSKEY with a different name found!");
state.usage();
}
keyset.addRR(r);
}
if (keyset.size() == 0)
{
System.err.println("error: No DNSKEYs found in keyset file");
state.usage();
}
// Load the key pairs.
List<DnsKeyPair> keypairs = getKeys(state.keyFiles, 0, state.keyDirectory);
// If we *still* don't have any key pairs, look for keys the key
// directory
// that match
if (keypairs == null)
{
keypairs = findZoneKeys(state.keyDirectory, keysetName);
}
// If there *still* aren't any ZSKs defined, bail.
if (keypairs == null || keypairs.size() == 0)
{
System.err.println("error: No signing keys could be determined.");
state.usage();
}
// default the output file, if not set.
if (state.outputfile == null)
{
if (keysetName.isAbsolute())
{
state.outputfile = keysetName + "signed_keyset";
}
else
{
state.outputfile = keysetName + ".signed_keyset";
}
}
JCEDnsSecSigner signer = new JCEDnsSecSigner();
List<RRSIGRecord> sigs = signer.signRRset(keyset, keypairs, state.start, state.expire);
for (RRSIGRecord s : sigs)
{
keyset.addRR(s);
}
// write out the signed RRset
List<Record> signed_records = new ArrayList<Record>();
for (Iterator<Record> i = keyset.rrs(); i.hasNext();)
{
signed_records.add(i.next());
}
for (Iterator<Record> i = keyset.sigs(); i.hasNext();)
{
signed_records.add(i.next());
}
// write out the signed zone
ZoneUtils.writeZoneFile(signed_records, state.outputfile);
if (state.verifySigs)
{
log.fine("verifying generated signatures");
boolean res = verifySigs(keysetName, signed_records, keypairs);
if (res)
{
System.out.println("Generated signatures verified");
// log.info("Generated signatures verified");
}
else
{
System.out.println("Generated signatures did not verify.");
// log.warn("Generated signatures did not verify.");
}
}
}
public static void main(String[] args)
{
SignKeyset tool = new SignKeyset();
tool.state = new CLIState();
tool.run(tool.state, args);
}
}

View File

@@ -0,0 +1,364 @@
// Copyright (C) 2001-2003, 2011 VeriSign, Inc.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
// USA
package com.verisignlabs.dnssec.cl;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.xbill.DNS.Name;
import org.xbill.DNS.RRSIGRecord;
import org.xbill.DNS.RRset;
import org.xbill.DNS.Record;
import org.xbill.DNS.Type;
import com.verisignlabs.dnssec.security.*;
/**
* This class forms the command line implementation of a DNSSEC RRset signer.
* Instead of being able to sign an entire zone, it will just sign a given
* RRset. Note that it will sign any RRset with any private key without
* consideration of whether or not the RRset *should* be signed in the context
* of a zone.
*
* @author David Blacka
*/
public class SignRRset extends CLBase
{
private CLIState state;
/**
* This is an inner class used to hold all of the command line option state.
*/
protected static class CLIState extends CLIStateBase
{
private File keyDirectory = null;
public String[] keyFiles = null;
public Date start = null;
public Date expire = null;
public String inputfile = null;
public String outputfile = null;
public boolean verifySigs = false;
public boolean verboseSigning = false;
public CLIState()
{
super("jdnssec-signrrset [..options..] rrset_file key_file [key_file ...]");
}
/**
* Set up the command line options.
*/
protected void setupOptions(Options opts)
{
// boolean options
opts.addOption("a", "verify", false, "verify generated signatures>");
opts.addOption("V", "verbose-signing", false, "Display verbose signing activity.");
OptionBuilder.hasArg();
OptionBuilder.withArgName("dir");
OptionBuilder.withLongOpt("key-directory");
OptionBuilder.withDescription("directory to find key files (default '.').");
opts.addOption(OptionBuilder.create('D'));
OptionBuilder.hasArg();
OptionBuilder.withArgName("time/offset");
OptionBuilder.withLongOpt("start-time");
OptionBuilder.withDescription("signature starting time (default is now - 1 hour)");
opts.addOption(OptionBuilder.create('s'));
OptionBuilder.hasArg();
OptionBuilder.withArgName("time/offset");
OptionBuilder.withLongOpt("expire-time");
OptionBuilder.withDescription("signature expiration time (default is start-time + 30 days).");
opts.addOption(OptionBuilder.create('e'));
OptionBuilder.hasArg();
OptionBuilder.withArgName("outfile");
OptionBuilder.withDescription("file the signed rrset is written to.");
opts.addOption(OptionBuilder.create('f'));
}
protected void processOptions(CommandLine cli) throws org.apache.commons.cli.ParseException
{
String optstr = null;
if (cli.hasOption('a')) verifySigs = true;
if (cli.hasOption('V')) verboseSigning = true;
if ((optstr = cli.getOptionValue('D')) != null)
{
keyDirectory = new File(optstr);
if (!keyDirectory.isDirectory())
{
System.err.println("error: " + optstr + " is not a directory");
usage();
}
}
if ((optstr = cli.getOptionValue('s')) != null)
{
start = convertDuration(null, optstr);
}
else
{
// default is now - 1 hour.
start = new Date(System.currentTimeMillis() - (3600 * 1000));
}
if ((optstr = cli.getOptionValue('e')) != null)
{
expire = convertDuration(start, optstr);
}
else
{
expire = convertDuration(start, "+2592000"); // 30 days
}
outputfile = cli.getOptionValue('f');
String[] files = cli.getArgs();
if (files.length < 1)
{
System.err.println("error: missing zone file and/or key files");
usage();
}
inputfile = files[0];
if (files.length > 1)
{
keyFiles = new String[files.length - 1];
System.arraycopy(files, 1, keyFiles, 0, files.length - 1);
}
}
}
/**
* Verify the generated signatures.
*
* @param zonename
* the origin name of the zone.
* @param records
* a list of {@link org.xbill.DNS.Record}s.
* @param keypairs
* a list of keypairs used the sign the zone.
* @return true if all of the signatures validated.
*/
private static boolean verifySigs(Name zonename, List<Record> records, List<DnsKeyPair> keypairs)
{
boolean secure = true;
DnsSecVerifier verifier = new DnsSecVerifier();
for (DnsKeyPair pair : keypairs)
{
verifier.addTrustedKey(pair);
}
verifier.setVerifyAllSigs(true);
List<RRset> rrsets = SignUtils.assembleIntoRRsets(records);
for (RRset rrset : rrsets)
{
// skip unsigned rrsets.
if (!rrset.sigs().hasNext()) continue;
boolean result = verifier.verify(rrset);
if (!result)
{
log.fine("Signatures did not verify for RRset: " + rrset);
secure = false;
}
}
return secure;
}
/**
* Load the key pairs from the key files.
*
* @param keyfiles
* a string array containing the base names or paths of the keys
* to be loaded.
* @param start_index
* the starting index of keyfiles string array to use. This
* allows us to use the straight command line argument array.
* @param inDirectory
* the directory to look in (may be null).
* @return a list of keypair objects.
*/
private static List<DnsKeyPair> getKeys(String[] keyfiles, int start_index,
File inDirectory) throws IOException
{
if (keyfiles == null) return null;
int len = keyfiles.length - start_index;
if (len <= 0) return null;
ArrayList<DnsKeyPair> keys = new ArrayList<DnsKeyPair>(len);
for (int i = start_index; i < keyfiles.length; i++)
{
DnsKeyPair k = BINDKeyUtils.loadKeyPair(keyfiles[i], inDirectory);
if (k != null) keys.add(k);
}
return keys;
}
@SuppressWarnings("unchecked")
public void execute() throws Exception
{
// Read in the zone
List<Record> records = ZoneUtils.readZoneFile(state.inputfile, null);
if (records == null || records.size() == 0)
{
System.err.println("error: empty RRset file");
state.usage();
}
// Construct the RRset. Complain if the records in the input file
// consist of more than one RRset.
RRset rrset = null;
for (Record r : records)
{
// skip RRSIGs
if (r.getType() == Type.RRSIG || r.getType() == Type.SIG)
{
continue;
}
// Handle the first record.
if (rrset == null)
{
rrset = new RRset();
rrset.addRR(r);
continue;
}
// Ensure that the remaining records all belong to the same rrset.
if (rrset.getName().equals(r.getName()) && rrset.getType() == r.getType()
&& rrset.getDClass() == r.getDClass())
{
rrset.addRR(r);
}
else
{
System.err.println("Records do not all belong to the same RRset.");
state.usage();
}
}
if (rrset.size() == 0)
{
System.err.println("No records found in inputfile.");
state.usage();
}
// Load the key pairs.
if (state.keyFiles.length == 0)
{
System.err.println("error: at least one keyfile must be specified");
state.usage();
}
List<DnsKeyPair> keypairs = getKeys(state.keyFiles, 0, state.keyDirectory);
// Make sure that all the keypairs have the same name.
// This will be used as the zone name, too.
Name keysetName = null;
for (DnsKeyPair pair : keypairs)
{
if (keysetName == null)
{
keysetName = pair.getDNSKEYName();
continue;
}
if (!pair.getDNSKEYName().equals(keysetName))
{
System.err.println("Keys do not all have the same name.");
state.usage();
}
}
// default the output file, if not set.
if (state.outputfile == null && !state.inputfile.equals("-"))
{
state.outputfile = state.inputfile + ".signed";
}
JCEDnsSecSigner signer = new JCEDnsSecSigner(state.verboseSigning);
List<RRSIGRecord> sigs = signer.signRRset(rrset, keypairs, state.start, state.expire);
for (RRSIGRecord s : sigs)
{
rrset.addRR(s);
}
// write out the signed RRset
List<Record> signed_records = new ArrayList<Record>();
for (Iterator<Record> i = rrset.rrs(); i.hasNext();)
{
signed_records.add(i.next());
}
for (Iterator<Record> i = rrset.sigs(); i.hasNext();)
{
signed_records.add(i.next());
}
// write out the signed zone
ZoneUtils.writeZoneFile(signed_records, state.outputfile);
if (state.verifySigs)
{
log.fine("verifying generated signatures");
boolean res = verifySigs(keysetName, signed_records, keypairs);
if (res)
{
System.out.println("Generated signatures verified");
// log.info("Generated signatures verified");
}
else
{
System.out.println("Generated signatures did not verify.");
// log.warn("Generated signatures did not verify.");
}
}
}
public static void main(String[] args)
{
SignRRset tool = new SignRRset();
tool.state = new CLIState();
tool.run(tool.state, args);
}
}

View File

@@ -0,0 +1,755 @@
// Copyright (C) 2001-2003, 2011 VeriSign, Inc.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
// USA
package com.verisignlabs.dnssec.cl;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.xbill.DNS.DNSKEYRecord;
import org.xbill.DNS.DSRecord;
import org.xbill.DNS.Name;
import org.xbill.DNS.RRset;
import org.xbill.DNS.Record;
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.SignUtils;
import com.verisignlabs.dnssec.security.ZoneUtils;
/**
* This class forms the command line implementation of a DNSSEC zone signer.
*
* @author David Blacka
*/
public class SignZone extends CLBase
{
private CLIState state;
/**
* This is an inner class used to hold all of the command line option state.
*/
private static class CLIState extends CLIStateBase
{
public File keyDirectory = null;
public File keysetDirectory = null;
public String[] kskFiles = null;
public String[] keyFiles = null;
public String zonefile = null;
public Date start = null;
public Date expire = null;
public String outputfile = null;
public boolean verifySigs = false;
public boolean useOptOut = false;
public boolean fullySignKeyset = false;
public List<Name> includeNames = null;
public boolean useNsec3 = false;
public byte[] salt = null;
public int iterations = 0;
public int digest_id = DSRecord.SHA1_DIGEST_ID;
public long nsec3paramttl = -1;
public boolean verboseSigning = false;
public CLIState()
{
super("jdnssec-signzone [..options..] zone_file [key_file ...]");
}
protected void setupOptions(Options opts)
{
// boolean options
opts.addOption("a", "verify", false, "verify generated signatures>");
opts.addOption("F", "fully-sign-keyset", false,
"sign the zone apex keyset with all available keys.");
opts.addOption("V", "verbose-signing", false, "Display verbose signing activity.");
// Argument options
OptionBuilder.hasArg();
OptionBuilder.withArgName("dir");
OptionBuilder.withLongOpt("keyset-directory");
OptionBuilder.withDescription("directory to find keyset files (default '.').");
opts.addOption(OptionBuilder.create('d'));
OptionBuilder.hasArg();
OptionBuilder.withArgName("dir");
OptionBuilder.withLongOpt("key-directory");
OptionBuilder.withDescription("directory to find key files (default '.').");
opts.addOption(OptionBuilder.create('D'));
OptionBuilder.hasArg();
OptionBuilder.withArgName("time/offset");
OptionBuilder.withLongOpt("start-time");
OptionBuilder.withDescription("signature starting time (default is now - 1 hour)");
opts.addOption(OptionBuilder.create('s'));
OptionBuilder.hasArg();
OptionBuilder.withArgName("time/offset");
OptionBuilder.withLongOpt("expire-time");
OptionBuilder.withDescription("signature expiration time (default is start-time + 30 days).");
opts.addOption(OptionBuilder.create('e'));
OptionBuilder.hasArg();
OptionBuilder.withArgName("outfile");
OptionBuilder.withDescription("file the signed zone is written to (default is <origin>.signed).");
opts.addOption(OptionBuilder.create('f'));
OptionBuilder.hasArgs();
OptionBuilder.withArgName("KSK file");
OptionBuilder.withLongOpt("ksk-file");
OptionBuilder.withDescription("this key is a key signing key (may repeat).");
opts.addOption(OptionBuilder.create('k'));
OptionBuilder.hasArg();
OptionBuilder.withArgName("file");
OptionBuilder.withLongOpt("include-file");
OptionBuilder.withDescription("include names in this file in the NSEC/NSEC3 chain.");
opts.addOption(OptionBuilder.create('I'));
// NSEC3 options
opts.addOption("3", "use-nsec3", false, "use NSEC3 instead of NSEC");
opts.addOption("O", "use-opt-out", false,
"generate a fully Opt-Out zone (only valid with NSEC3).");
OptionBuilder.hasArg();
OptionBuilder.withLongOpt("salt");
OptionBuilder.withArgName("hex value");
OptionBuilder.withDescription("supply a salt value.");
opts.addOption(OptionBuilder.create('S'));
OptionBuilder.hasArg();
OptionBuilder.withLongOpt("random-salt");
OptionBuilder.withArgName("length");
OptionBuilder.withDescription("generate a random salt.");
opts.addOption(OptionBuilder.create('R'));
OptionBuilder.hasArg();
OptionBuilder.withLongOpt("iterations");
OptionBuilder.withArgName("value");
OptionBuilder.withDescription("use this value for the iterations in NSEC3.");
opts.addOption(OptionBuilder.create());
OptionBuilder.hasArg();
OptionBuilder.withLongOpt("nsec3paramttl");
OptionBuilder.withArgName("ttl");
OptionBuilder.withDescription("use this value for the NSEC3PARAM RR ttl");
opts.addOption(OptionBuilder.create());
OptionBuilder.hasArg();
OptionBuilder.withArgName("id");
OptionBuilder.withLongOpt("ds-digest");
OptionBuilder.withDescription("Digest algorithm to use for generated DSs");
opts.addOption(OptionBuilder.create());
}
protected void processOptions(CommandLine cli) throws ParseException
{
String optstr = null;
if (cli.hasOption('a')) verifySigs = true;
if (cli.hasOption('3')) useNsec3 = true;
if (cli.hasOption('O')) useOptOut = true;
if (cli.hasOption('V')) verboseSigning = true;
if (useOptOut && !useNsec3)
{
System.err.println("Opt-Out not supported without NSEC3 -- ignored.");
useOptOut = false;
}
if (cli.hasOption('F')) fullySignKeyset = true;
if ((optstr = cli.getOptionValue('d')) != null)
{
keysetDirectory = new File(optstr);
if (!keysetDirectory.isDirectory())
{
System.err.println("error: " + optstr + " is not a directory");
usage();
}
}
if ((optstr = cli.getOptionValue('D')) != null)
{
keyDirectory = new File(optstr);
if (!keyDirectory.isDirectory())
{
System.err.println("error: " + optstr + " is not a directory");
usage();
}
}
if ((optstr = cli.getOptionValue('s')) != null)
{
start = CLBase.convertDuration(null, optstr);
}
else
{
// default is now - 1 hour.
start = new Date(System.currentTimeMillis() - (3600 * 1000));
}
if ((optstr = cli.getOptionValue('e')) != null)
{
expire = CLBase.convertDuration(start, optstr);
}
else
{
expire = CLBase.convertDuration(start, "+2592000"); // 30 days
}
outputfile = cli.getOptionValue('f');
kskFiles = cli.getOptionValues('k');
if ((optstr = cli.getOptionValue('I')) != null)
{
File includeNamesFile = new File(optstr);
try
{
includeNames = getNameList(includeNamesFile);
}
catch (IOException e)
{
throw new ParseException(e.getMessage());
}
}
if ((optstr = cli.getOptionValue('S')) != null)
{
salt = base16.fromString(optstr);
if (salt == null && !optstr.equals("-"))
{
System.err.println("error: salt is not valid hexidecimal.");
usage();
}
}
if ((optstr = cli.getOptionValue('R')) != null)
{
int length = parseInt(optstr, 0);
if (length > 0 && length <= 255)
{
Random random = new Random();
salt = new byte[length];
random.nextBytes(salt);
}
}
if ((optstr = cli.getOptionValue("iterations")) != null)
{
iterations = parseInt(optstr, iterations);
if (iterations < 0 || iterations > 8388607)
{
System.err.println("error: iterations value is invalid");
usage();
}
}
if ((optstr = cli.getOptionValue("ds-digest")) != null)
{
digest_id = parseInt(optstr, -1);
if (digest_id < 0)
{
System.err.println("error: DS digest ID is not a valid identifier");
usage();
}
}
if ((optstr = cli.getOptionValue("nsec3paramttl")) != null)
{
nsec3paramttl = parseInt(optstr, -1);
}
String[] files = cli.getArgs();
if (files.length < 1)
{
System.err.println("error: missing zone file and/or key files");
usage();
}
zonefile = files[0];
if (files.length > 1)
{
keyFiles = new String[files.length - 1];
System.arraycopy(files, 1, keyFiles, 0, files.length - 1);
}
}
}
/**
* Verify the generated signatures.
*
* @param zonename
* the origin name of the zone.
* @param records
* a list of {@link org.xbill.DNS.Record}s.
* @param keypairs
* a list of keypairs used the sign the zone.
* @return true if all of the signatures validated.
*/
private static boolean verifyZoneSigs(Name zonename, List<Record> records,
List<DnsKeyPair> keypairs)
{
boolean secure = true;
DnsSecVerifier verifier = new DnsSecVerifier();
for (DnsKeyPair pair : keypairs)
{
verifier.addTrustedKey(pair);
}
verifier.setVerifyAllSigs(true);
List<RRset> rrsets = SignUtils.assembleIntoRRsets(records);
for (RRset rrset : rrsets)
{
// skip unsigned rrsets.
if (!rrset.sigs().hasNext()) continue;
boolean result = verifier.verify(rrset);
if (!result)
{
log.fine("Signatures did not verify for RRset: " + rrset);
secure = false;
}
}
return secure;
}
/**
* Load the key pairs from the key files.
*
* @param keyfiles
* a string array containing the base names or paths of the keys to
* be loaded.
* @param start_index
* the starting index of keyfiles string array to use. This allows
* us
* to use the straight command line argument array.
* @param inDirectory
* the directory to look in (may be null).
* @return a list of keypair objects.
*/
private static List<DnsKeyPair> getKeys(String[] keyfiles, int start_index,
File inDirectory) throws IOException
{
if (keyfiles == null) return null;
int len = keyfiles.length - start_index;
if (len <= 0) return null;
ArrayList<DnsKeyPair> keys = new ArrayList<DnsKeyPair>(len);
for (int i = start_index; i < keyfiles.length; i++)
{
DnsKeyPair k = BINDKeyUtils.loadKeyPair(keyfiles[i], inDirectory);
if (k != null) keys.add(k);
}
return keys;
}
private static List<DnsKeyPair> getKeys(List<Record> dnskeyrrs, File inDirectory)
throws IOException
{
List<DnsKeyPair> res = new ArrayList<DnsKeyPair>();
for (Record r : dnskeyrrs)
{
if (r.getType() != Type.DNSKEY) continue;
// Construct a public-key-only DnsKeyPair just so we can calculate the
// base name.
DnsKeyPair pub = new DnsKeyPair((DNSKEYRecord) r);
DnsKeyPair pair = BINDKeyUtils.loadKeyPair(BINDKeyUtils.keyFileBase(pub),
inDirectory);
if (pair != null)
{
res.add(pair);
}
}
if (res.size() > 0) return res;
return null;
}
private static class KeyFileFilter implements FileFilter
{
private String prefix;
public KeyFileFilter(Name origin)
{
prefix = "K" + origin.toString();
}
public boolean accept(File pathname)
{
if (!pathname.isFile()) return false;
String name = pathname.getName();
if (name.startsWith(prefix) && name.endsWith(".private")) return true;
return false;
}
}
private static List<DnsKeyPair> findZoneKeys(File inDirectory, Name zonename)
throws IOException
{
if (inDirectory == null)
{
inDirectory = new File(".");
}
// get the list of "K<zone>.*.private files.
FileFilter filter = new KeyFileFilter(zonename);
File[] files = inDirectory.listFiles(filter);
// read in all of the records
ArrayList<DnsKeyPair> keys = new ArrayList<DnsKeyPair>();
for (int i = 0; i < files.length; i++)
{
DnsKeyPair p = BINDKeyUtils.loadKeyPair(files[i].getName(), inDirectory);
keys.add(p);
}
if (keys.size() > 0) return keys;
return null;
}
/**
* This is an implementation of a file filter used for finding BIND 9-style
* keyset-* files.
*/
private static class KeysetFileFilter implements FileFilter
{
public boolean accept(File pathname)
{
if (!pathname.isFile()) return false;
String name = pathname.getName();
if (name.startsWith("keyset-")) return true;
return false;
}
}
/**
* Load keysets (which contain delegation point security info).
*
* @param inDirectory
* the directory to look for the keyset files (may be null, in
* which
* case it defaults to looking in the current working directory).
* @param zonename
* the name of the zone we are signing, so we can ignore keysets
* that
* do not belong in the zone.
* @return a list of {@link org.xbill.DNS.Record}s found in the keyset
* files.
*/
private static List<Record> getKeysets(File inDirectory, Name zonename)
throws IOException
{
if (inDirectory == null)
{
inDirectory = new File(".");
}
// get the list of "keyset-" files.
FileFilter filter = new KeysetFileFilter();
File[] files = inDirectory.listFiles(filter);
// read in all of the records
ArrayList<Record> keysetRecords = new ArrayList<Record>();
for (int i = 0; i < files.length; i++)
{
List<Record> l = ZoneUtils.readZoneFile(files[i].getAbsolutePath(), zonename);
keysetRecords.addAll(l);
}
// discard records that do not belong to the zone in question.
for (Iterator<Record> i = keysetRecords.iterator(); i.hasNext();)
{
Record r = i.next();
if (!r.getName().subdomain(zonename))
{
i.remove();
}
}
return keysetRecords;
}
/**
* Load a list of DNS names from a file.
*
* @param nameListFile
* the path of a file containing a bare list of DNS names.
* @return a list of {@link org.xbill.DNS.Name} objects.
*/
private static List<Name> getNameList(File nameListFile) throws IOException
{
BufferedReader br = new BufferedReader(new FileReader(nameListFile));
List<Name> res = new ArrayList<Name>();
String line = null;
while ((line = br.readLine()) != null)
{
try
{
Name n = Name.fromString(line);
// force the name to be absolute.
// FIXME: we should probably get some fancy logic here to
// detect if the name needs the origin appended, or just the
// root.
if (!n.isAbsolute()) n = Name.concatenate(n, Name.root);
res.add(n);
}
catch (TextParseException e)
{
log.severe("DNS Name parsing error:" + e);
}
}
br.close();
if (res.size() == 0) return null;
return res;
}
/**
* Determine if the given keypairs can be used to sign the zone.
*
* @param zonename
* the zone origin.
* @param keypairs
* a list of {@link DnsKeyPair} objects that will be used to sign
* the
* zone.
* @return true if the keypairs valid.
*/
private static boolean keyPairsValidForZone(Name zonename, List<DnsKeyPair> keypairs)
{
if (keypairs == null) return true; // technically true, I guess.
for (DnsKeyPair kp : keypairs)
{
Name keyname = kp.getDNSKEYRecord().getName();
if (!keyname.equals(zonename))
{
return false;
}
}
return true;
}
public void execute() throws Exception
{
// Read in the zone
List<Record> records = ZoneUtils.readZoneFile(state.zonefile, null);
if (records == null || records.size() == 0)
{
System.err.println("error: empty zone file");
state.usage();
}
// calculate the zone name.
Name zonename = ZoneUtils.findZoneName(records);
if (zonename == null)
{
System.err.println("error: invalid zone file - no SOA");
state.usage();
}
// Load the key pairs.
List<DnsKeyPair> keypairs = getKeys(state.keyFiles, 0, state.keyDirectory);
List<DnsKeyPair> kskpairs = getKeys(state.kskFiles, 0, state.keyDirectory);
// If we didn't get any keys on the command line, look at the zone apex for
// any public keys.
if (keypairs == null && kskpairs == null)
{
List<Record> dnskeys = ZoneUtils.findRRs(records, zonename, Type.DNSKEY);
keypairs = getKeys(dnskeys, state.keyDirectory);
}
// If we *still* don't have any key pairs, look for keys the key directory
// that match
if (keypairs == null && kskpairs == null)
{
keypairs = findZoneKeys(state.keyDirectory, zonename);
}
// If we don't have any KSKs, but we do have more than one zone
// signing key (presumably), presume that the zone signing keys
// are just not differentiated and try to figure out which keys
// are actually ksks by looking at the SEP flag.
if ((kskpairs == null || kskpairs.size() == 0) && keypairs != null
&& keypairs.size() > 1)
{
for (Iterator<DnsKeyPair> i = keypairs.iterator(); i.hasNext();)
{
DnsKeyPair pair = i.next();
DNSKEYRecord kr = pair.getDNSKEYRecord();
if ((kr.getFlags() & DNSKEYRecord.Flags.SEP_KEY) != 0)
{
if (kskpairs == null) kskpairs = new ArrayList<DnsKeyPair>();
kskpairs.add(pair);
i.remove();
}
}
}
// If there are no ZSKs defined at this point (yet there are KSKs
// provided), all KSKs will be treated as ZSKs, as well.
if (keypairs == null || keypairs.size() == 0)
{
keypairs = kskpairs;
}
// If there *still* aren't any ZSKs defined, bail.
if (keypairs == null || keypairs.size() == 0)
{
System.err.println("No zone signing keys could be determined.");
state.usage();
}
// default the output file, if not set.
if (state.outputfile == null && !state.zonefile.equals("-"))
{
if (zonename.isAbsolute())
{
state.outputfile = zonename + "signed";
}
else
{
state.outputfile = zonename + ".signed";
}
}
// Verify that the keys can be in the zone.
if (!keyPairsValidForZone(zonename, keypairs)
|| !keyPairsValidForZone(zonename, kskpairs))
{
System.err.println("error: specified keypairs are not valid for the zone.");
state.usage();
}
// We force the signing keys to be in the zone by just appending
// them to the zone here. Currently JCEDnsSecSigner.signZone
// removes duplicate records.
if (kskpairs != null)
{
for (DnsKeyPair pair : kskpairs)
{
records.add(pair.getDNSKEYRecord());
}
}
if (keypairs != null)
{
for (DnsKeyPair pair : keypairs)
{
records.add(pair.getDNSKEYRecord());
}
}
// read in the keysets, if any.
List<Record> keysetrecs = getKeysets(state.keysetDirectory, zonename);
if (keysetrecs != null)
{
records.addAll(keysetrecs);
}
JCEDnsSecSigner signer = new JCEDnsSecSigner(state.verboseSigning);
// Sign the zone.
List<Record> signed_records;
if (state.useNsec3)
{
signed_records = signer.signZoneNSEC3(zonename, records, kskpairs, keypairs,
state.start, state.expire,
state.fullySignKeyset, state.useOptOut,
state.includeNames, state.salt,
state.iterations, state.digest_id,
state.nsec3paramttl);
}
else
{
signed_records = signer.signZone(zonename, records, kskpairs, keypairs,
state.start, state.expire, state.fullySignKeyset,
state.digest_id);
}
// write out the signed zone
ZoneUtils.writeZoneFile(signed_records, state.outputfile);
if (state.verifySigs)
{
// FIXME: ugh.
if (kskpairs != null)
{
keypairs.addAll(kskpairs);
}
log.fine("verifying generated signatures");
boolean res = verifyZoneSigs(zonename, signed_records, keypairs);
if (res)
{
System.out.println("Generated signatures verified");
// log.info("Generated signatures verified");
}
else
{
System.out.println("Generated signatures did not verify.");
// log.warn("Generated signatures did not verify.");
}
}
}
public static void main(String[] args)
{
SignZone tool = new SignZone();
tool.state = new CLIState();
tool.run(tool.state, args);
}
}

View File

@@ -0,0 +1,166 @@
// Copyright (C) 2011 VeriSign, Inc.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
// USA
package com.verisignlabs.dnssec.cl;
import java.util.List;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.xbill.DNS.Record;
import com.verisignlabs.dnssec.security.ZoneUtils;
import com.verisignlabs.dnssec.security.ZoneVerifier;
/**
* This class forms the command line implementation of a DNSSEC zone validator.
*
* @author David Blacka
*/
public class VerifyZone extends CLBase
{
private CLIState state;
/**
* This is a small inner class used to hold all of the command line option
* state.
*/
protected static class CLIState extends CLIStateBase
{
public String zonefile = null;
public String[] keyfiles = null;
public int startfudge = 0;
public int expirefudge = 0;
public boolean ignoreTime = false;
public boolean ignoreDups = false;
public CLIState()
{
super("jdnssec-verifyzone [..options..] zonefile");
}
protected void setupOptions(Options opts)
{
OptionBuilder.hasOptionalArg();
OptionBuilder.withLongOpt("sig-start-fudge");
OptionBuilder.withArgName("seconds");
OptionBuilder.withDescription("'fudge' RRSIG inception times by 'seconds' seconds.");
opts.addOption(OptionBuilder.create('S'));
OptionBuilder.hasOptionalArg();
OptionBuilder.withLongOpt("sig-expire-fudge");
OptionBuilder.withArgName("seconds");
OptionBuilder.withDescription("'fudge' RRSIG expiration times by 'seconds' seconds.");
opts.addOption(OptionBuilder.create('E'));
OptionBuilder.withLongOpt("ignore-time");
OptionBuilder.withDescription("Ignore RRSIG inception and expiration time errors.");
opts.addOption(OptionBuilder.create());
OptionBuilder.withLongOpt("ignore-duplicate-rrs");
OptionBuilder.withDescription("Ignore duplicate record errors.");
opts.addOption(OptionBuilder.create());
}
protected void processOptions(CommandLine cli)
{
if (cli.hasOption("ignore-time"))
{
ignoreTime = true;
}
if (cli.hasOption("ignore-duplicate-rrs"))
{
ignoreDups = true;
}
String optstr = null;
if ((optstr = cli.getOptionValue('S')) != null)
{
startfudge = parseInt(optstr, 0);
}
if ((optstr = cli.getOptionValue('E')) != null)
{
expirefudge = parseInt(optstr, 0);
}
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)
{
System.err.println("error: missing zone file");
usage();
}
zonefile = cl_args[0];
if (cl_args.length >= 2)
{
keyfiles = new String[cl_args.length - 1];
System.arraycopy(cl_args, 1, keyfiles, 0, keyfiles.length);
}
}
}
public void execute() throws Exception
{
ZoneVerifier zoneverifier = new ZoneVerifier();
zoneverifier.getVerifier().setStartFudge(state.startfudge);
zoneverifier.getVerifier().setExpireFudge(state.expirefudge);
zoneverifier.getVerifier().setIgnoreTime(state.ignoreTime);
zoneverifier.setIgnoreDuplicateRRs(state.ignoreDups);
List<Record> records = ZoneUtils.readZoneFile(state.zonefile, null);
log.fine("verifying zone...");
int errors = zoneverifier.verifyZone(records);
log.fine("completed verification process.");
if (errors > 0)
{
System.out.println("zone did not verify.");
System.exit(1);
}
else
{
System.out.println("zone verified.");
System.exit(0);
}
}
public static void main(String[] args)
{
VerifyZone tool = new VerifyZone();
tool.state = new CLIState();
tool.run(tool.state, args);
}
}

View File

@@ -0,0 +1,214 @@
// Copyright (C) 2011 VeriSign, Inc.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
// USA
package com.verisignlabs.dnssec.cl;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.xbill.DNS.Master;
import org.xbill.DNS.NSEC3PARAMRecord;
import org.xbill.DNS.NSEC3Record;
import org.xbill.DNS.Name;
import org.xbill.DNS.Record;
import org.xbill.DNS.Section;
import org.xbill.DNS.Type;
import org.xbill.DNS.utils.base32;
import com.verisignlabs.dnssec.security.RecordComparator;
/**
* This class forms the command line implementation of a zone file normalizer.
* That is, a tool to rewrite zones in a consistent, comparable format.
*
* @author David Blacka (original)
* @author $Author: davidb $
* @version $Revision: 2218 $
*/
public class ZoneFormat extends CLBase
{
private CLIState state;
/**
* This is a small inner class used to hold all of the command line option
* state.
*/
protected static class CLIState extends CLIStateBase
{
public String file;
public boolean assignNSEC3;
public CLIState()
{
super("jdnssec-zoneformat [..options..] zonefile");
}
protected void setupOptions(Options opts)
{
opts.addOption("N", "nsec3", false,
"attempt to determine the original ownernames for NSEC3 RRs.");
}
protected void processOptions(CommandLine cli) throws ParseException
{
if (cli.hasOption('N')) assignNSEC3 = true;
String[] cl_args = cli.getArgs();
if (cl_args.length < 1)
{
System.err.println("error: must specify a zone file");
usage();
}
file = cl_args[0];
}
}
private static List<Record> readZoneFile(String filename) throws IOException
{
Master master = new Master(filename);
List<Record> res = new ArrayList<Record>();
Record r = null;
while ((r = master.nextRecord()) != null)
{
// Normalize each record by round-tripping it through canonical wire line
// format. Mostly this just lowercases names that are subject to it.
byte[] wire = r.toWireCanonical();
Record canon_record = Record.fromWire(wire, Section.ANSWER);
res.add(canon_record);
}
return res;
}
private static void formatZone(List<Record> zone)
{
// Put the zone into a consistent (name and RR type) order.
RecordComparator cmp = new RecordComparator();
Collections.sort(zone, cmp);
for (Record r : zone)
{
System.out.println(r.toString());
}
}
private static void determineNSEC3Owners(List<Record> zone)
throws NoSuchAlgorithmException
{
// Put the zone into a consistent (name and RR type) order.
Collections.sort(zone, new RecordComparator());
// first, find the NSEC3PARAM record -- this is an inefficient linear
// search, although it should be near the head of the list.
NSEC3PARAMRecord nsec3param = null;
HashMap<String, String> map = new HashMap<String, String>();
base32 b32 = new base32(base32.Alphabet.BASE32HEX, false, true);
Name zonename = null;
for (Record r : zone)
{
if (r.getType() == Type.SOA)
{
zonename = r.getName();
continue;
}
if (r.getType() == Type.NSEC3PARAM)
{
nsec3param = (NSEC3PARAMRecord) r;
break;
}
}
// If we couldn't determine a zone name, we have an issue.
if (zonename == null) return;
// If there wasn't one, we have nothing to do.
if (nsec3param == null) return;
// Next pass, calculate a mapping between ownernames and hashnames
Name last_name = null;
for (Record r : zone)
{
if (r.getName().equals(last_name)) continue;
if (r.getType() == Type.NSEC3) continue;
Name n = r.getName();
byte[] hash = nsec3param.hashName(n);
String hashname = b32.toString(hash);
map.put(hashname, n.toString().toLowerCase());
last_name = n;
// inefficiently create hashes for the possible ancestor ENTs
for (int i = zonename.labels() + 1; i < n.labels(); ++i)
{
Name parent = new Name(n, n.labels() - i);
byte[] parent_hash = nsec3param.hashName(parent);
String parent_hashname = b32.toString(parent_hash);
if (!map.containsKey(parent_hashname))
{
map.put(parent_hashname, parent.toString().toLowerCase());
}
}
}
// Final pass, assign the names if we can
for (ListIterator<Record> i = zone.listIterator(); i.hasNext();)
{
Record r = i.next();
if (r.getType() != Type.NSEC3) continue;
NSEC3Record nsec3 = (NSEC3Record) r;
String hashname = nsec3.getName().getLabelString(0).toLowerCase();
String ownername = (String) map.get(hashname);
NSEC3Record new_nsec3 = new NSEC3Record(nsec3.getName(), nsec3.getDClass(),
nsec3.getTTL(), nsec3.getHashAlgorithm(),
nsec3.getFlags(), nsec3.getIterations(),
nsec3.getSalt(), nsec3.getNext(),
nsec3.getTypes(), ownername);
i.set(new_nsec3);
}
}
public void execute() throws IOException, NoSuchAlgorithmException
{
List<Record> z = readZoneFile(state.file);
if (state.assignNSEC3) determineNSEC3Owners(z);
formatZone(z);
}
public static void main(String[] args)
{
ZoneFormat tool = new ZoneFormat();
tool.state = new CLIState();
tool.run(tool.state, args);
}
}

View File

@@ -0,0 +1,33 @@
<body bgcolor="#FFFFFF">
java-dnssec-tools is a collection of Java-based command line tools for
managing DNSSEC zones and keys.
<p>
<center>
<table width="90%" border=1 cellpadding=10 cellspacing=0>
<tr><td class="TableRowColor">
Copyright &copy; 2003-2005 Verisign, Inc. by
<a href="mailto:davidb@verisignlabs.com">David Blacka</a><P>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.<P>
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.<P>
You should have received a copy of the
<a href="http://www.gnu.org/copyleft/lgpl.html">GNU Library General Public License</a>
along with this library; if not, write to the
Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.
</td></tr>
</table><P>
</center>
</body>

View File

@@ -0,0 +1,427 @@
// $Id$
//
// Copyright (C) 2001-2003 VeriSign, Inc.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
// USA
package com.verisignlabs.dnssec.security;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.text.NumberFormat;
import org.xbill.DNS.DNSKEYRecord;
import org.xbill.DNS.Master;
import org.xbill.DNS.Name;
import org.xbill.DNS.RRset;
import org.xbill.DNS.Record;
import org.xbill.DNS.Type;
import org.xbill.DNS.utils.base64;
/**
* This class contains a series of static methods used for manipulating BIND
* 9.x.x-style DNSSEC key files.
*
* In this class, the "base" key path or name is the file name without the
* trailing ".key" or ".private".
*
* @author David Blacka (original)
* @author $Author$
* @version $Revision$
*/
public class BINDKeyUtils
{
// formatters used to generated the BIND key file names
private static NumberFormat mAlgNumberFormatter;
private static NumberFormat mKeyIdNumberFormatter;
/**
* Calculate the BIND9 key file base name (i.e., without the ".key" or
* ".private" extensions)
*/
private static String getKeyFileBase(Name signer, int algorithm, int keyid)
{
if (mAlgNumberFormatter == null)
{
mAlgNumberFormatter = NumberFormat.getNumberInstance();
mAlgNumberFormatter.setMaximumIntegerDigits(3);
mAlgNumberFormatter.setMinimumIntegerDigits(3);
}
if (mKeyIdNumberFormatter == null)
{
mKeyIdNumberFormatter = NumberFormat.getNumberInstance();
mKeyIdNumberFormatter.setMaximumIntegerDigits(5);
mKeyIdNumberFormatter.setMinimumIntegerDigits(5);
mKeyIdNumberFormatter.setGroupingUsed(false);
}
keyid &= 0xFFFF;
String fn = "K" + signer + "+" + mAlgNumberFormatter.format(algorithm)
+ "+" + mKeyIdNumberFormatter.format(keyid);
return fn;
}
/** Reads in the DNSKEYRecord from the public key file */
private static DNSKEYRecord loadPublicKeyFile(File publicKeyFile)
throws IOException
{
Master m = new Master(publicKeyFile.getAbsolutePath(), null, 600);
Record r;
DNSKEYRecord result = null;
while ((r = m.nextRecord()) != null)
{
if (r.getType() == Type.DNSKEY)
{
result = (DNSKEYRecord) r;
}
}
return result;
}
/** Reads in the private key verbatim from the private key file */
private static String loadPrivateKeyFile(File privateKeyFile)
throws IOException
{
BufferedReader in = new BufferedReader(new FileReader(privateKeyFile));
StringBuffer key_buf = new StringBuffer();
String line;
while ((line = in.readLine()) != null)
{
key_buf.append(line);
key_buf.append('\n');
}
in.close();
return key_buf.toString().trim();
}
/**
* Given an actual path for one of the key files, return the base name
*/
private static String fixKeyFileBasePath(String basePath)
{
if (basePath == null) throw new IllegalArgumentException();
if (basePath.endsWith(".key") || basePath.endsWith(".private"))
{
basePath = basePath.substring(0, basePath.lastIndexOf("."));
}
return basePath;
}
/**
* Given the information necessary to construct the path to a BIND9 generated
* key pair, load the key pair.
*
* @param signer
* the DNS name of the key.
* @param algorithm
* the DNSSEC algorithm of the key.
* @param keyid
* the DNSSEC key footprint.
* @param inDirectory
* the directory to look for the files (may be null).
* @return the loaded key pair.
* @throws IOException
* if there was a problem reading the BIND9 files.
*/
public static DnsKeyPair loadKeyPair(Name signer, int algorithm, int keyid,
File inDirectory) throws IOException
{
String keyFileBase = getKeyFileBase(signer, algorithm, keyid);
return loadKeyPair(keyFileBase, inDirectory);
}
/**
* Given a base path to a BIND9 key pair, load the key pair.
*
* @param keyFileBasePath
* the base filename (or real filename for either the public or
* private key) of the key.
* @param inDirectory
* the directory to look in, if the keyFileBasePath is relative.
* @return the loaded key pair.
* @throws IOException
* if there was a problem reading the files
*/
public static DnsKeyPair loadKeyPair(String keyFileBasePath, File inDirectory)
throws IOException
{
keyFileBasePath = fixKeyFileBasePath(keyFileBasePath);
// FIXME: should we throw the IOException when one of the files
// cannot be found, or just when both cannot be found?
File publicKeyFile = new File(inDirectory, keyFileBasePath + ".key");
File privateKeyFile = new File(inDirectory, keyFileBasePath + ".private");
DnsKeyPair kp = new DnsKeyPair();
DNSKEYRecord kr = loadPublicKeyFile(publicKeyFile);
kp.setDNSKEYRecord(kr);
String pk = loadPrivateKeyFile(privateKeyFile);
kp.setPrivateKeyString(pk);
return kp;
}
/**
* Given a base path to a BIND9 key pair, load the public part (only) of the
* key pair
*
* @param keyFileBasePath
* the base or real path to the public part of a key pair.
* @param inDirectory
* the directory to look in if the path is relative (may be null).
* @return a {@link DnsKeyPair} containing just the public key information.
* @throws IOException
* if there was a problem reading the public key file.
*/
public static DnsKeyPair loadKey(String keyFileBasePath, File inDirectory)
throws IOException
{
keyFileBasePath = fixKeyFileBasePath(keyFileBasePath);
File publicKeyFile = new File(inDirectory, keyFileBasePath + ".key");
DnsKeyPair kp = new DnsKeyPair();
DNSKEYRecord kr = loadPublicKeyFile(publicKeyFile);
kp.setDNSKEYRecord(kr);
return kp;
}
/**
* Load a BIND keyset file. The BIND 9 dnssec tools typically call these files
* "keyset-[signer]." where [signer] is the DNS owner name of the key. The
* keyset may be signed, but doesn't have to be.
*
* @param keysetFileName
* the name of the keyset file.
* @param inDirectory
* the directory to look in if the path is relative (may be null,
* defaults to the current working directory).
* @return a RRset contain the KEY records and any associated SIG records.
* @throws IOException
* if there was a problem reading the keyset file.
*/
public static RRset loadKeySet(String keysetFileName, File inDirectory)
throws IOException
{
File keysetFile = new File(inDirectory, keysetFileName);
Master m = new Master(keysetFile.getAbsolutePath());
Record r;
RRset keyset = new RRset();
while ((r = m.nextRecord()) != null)
{
keyset.addRR(r);
}
return keyset;
}
/**
* Calculate the key file base for this key pair.
*
* @param pair
* the {@link DnsKeyPair} to work from. It only needs a public key.
* @return the base name of the key files.
*/
public static String keyFileBase(DnsKeyPair pair)
{
DNSKEYRecord keyrec = pair.getDNSKEYRecord();
if (keyrec == null) return null;
return getKeyFileBase(keyrec.getName(), keyrec.getAlgorithm(),
keyrec.getFootprint());
}
/**
* @return a {@link java.io.File} object representing the BIND9 public key
* file.
*/
public static File getPublicKeyFile(DnsKeyPair pair, File inDirectory)
{
String keyfilebase = keyFileBase(pair);
if (keyfilebase == null) return null;
return new File(inDirectory, keyfilebase + ".key");
}
/**
* @return a {@link java.io.File} object representing the BIND9 private key
* file
*/
public static File getPrivateKeyFile(DnsKeyPair pair, File inDirectory)
{
String keyfilebase = keyFileBase(pair);
if (keyfilebase == null) return null;
return new File(inDirectory, keyfilebase + ".private");
}
/**
* Given a the contents of a BIND9 private key file, convert it into a native
* {@link java.security.PrivateKey} object.
*
* @param privateKeyString
* the contents of a BIND9 key file in string form.
* @return a {@link java.security.PrivateKey}
*/
public static PrivateKey convertPrivateKeyString(String privateKeyString)
{
if (privateKeyString == null) return null;
// FIXME: should this swallow exceptions or actually propagate
// them?
try
{
DnsKeyConverter conv = new DnsKeyConverter();
return conv.parsePrivateKeyString(privateKeyString);
}
catch (IOException e)
{
e.printStackTrace();
}
catch (NoSuchAlgorithmException e)
{
e.printStackTrace();
}
return null;
}
/**
* Given a native private key, convert it into a BIND9 private key file
* format.
*
* @param priv
* the private key to convert.
* @param pub
* the private key's corresponding public key. Some algorithms
* require information from both.
* @return a string containing the contents of a BIND9 private key file.
*/
public static String convertPrivateKey(PrivateKey priv, PublicKey pub, int alg)
{
if (priv != null)
{
DnsKeyConverter keyconv = new DnsKeyConverter();
String priv_string = keyconv.generatePrivateKeyString(priv, pub, alg);
return priv_string;
}
return null;
}
/**
* Convert the KEY record to the exact string format that the dnssec-*
* routines need. Currently, the DNSJAVA package uses a multiline mode for its
* record formatting. The BIND9 tools require everything on a single line.
*/
private static String DNSKEYtoString(DNSKEYRecord rec)
{
StringBuffer buf = new StringBuffer();
buf.append(rec.getName());
buf.append(" IN DNSKEY ");
buf.append(rec.getFlags() & 0xFFFF);
buf.append(" ");
buf.append(rec.getProtocol());
buf.append(" ");
buf.append(rec.getAlgorithm());
buf.append(" ");
buf.append(base64.toString(rec.getKey()));
return buf.toString();
}
/**
* This routine will write out the BIND9 dnssec-* tool compatible files.
*
* @param baseFileName
* use this base file name. If null, the standard BIND9 base file
* name will be computed.
* @param pair
* the keypair in question.
* @param inDirectory
* the directory to write to (may be null).
* @throws IOException
* if there is a problem writing the files.
*/
public static void writeKeyFiles(String baseFileName, DnsKeyPair pair,
File inDirectory) throws IOException
{
DNSKEYRecord pub = pair.getDNSKEYRecord();
String priv = pair.getPrivateKeyString();
if (priv == null)
{
priv = convertPrivateKey(pair.getPrivate(), pair.getPublic(),
pair.getDNSKEYAlgorithm());
}
if (pub == null || priv == null) return;
// Write the public key file
File pubkeyfile = new File(inDirectory, baseFileName + ".key");
PrintWriter out = new PrintWriter(new FileWriter(pubkeyfile));
out.println(DNSKEYtoString(pub));
out.close();
// Write the private key file
File privkeyfile = new File(inDirectory, baseFileName + ".private");
out = new PrintWriter(new FileWriter(privkeyfile));
out.print(priv);
out.close();
}
/**
* This routine will write out the BIND9 dnssec-* tool compatible files to the
* standard file names.
*
* @param pair
* the key pair in question.
* @param inDirectory
* the directory to write to (may be null).
*/
public static void writeKeyFiles(DnsKeyPair pair, File inDirectory)
throws IOException
{
String base = keyFileBase(pair);
writeKeyFiles(base, pair, inDirectory);
}
}

View File

@@ -0,0 +1,66 @@
// $Id$
//
// Copyright (C) 2001-2003 VeriSign, Inc.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.verisignlabs.dnssec.security;
import java.util.Comparator;
import java.util.logging.Logger;
/**
* This class implements a basic comparator for byte arrays. It is primarily
* useful for comparing RDATA portions of DNS records in doing DNSSEC canonical
* ordering.
*
* @author David Blacka (original)
* @author $Author$
* @version $Revision$
*/
public class ByteArrayComparator implements Comparator<byte[]>
{
private int mOffset = 0;
private boolean mDebug = false;
private Logger log;
public ByteArrayComparator()
{
}
public ByteArrayComparator(int offset, boolean debug)
{
mOffset = offset;
mDebug = debug;
}
public int compare(byte[] b1, byte[] b2)
{
for (int i = mOffset; i < b1.length && i < b2.length; i++)
{
if (b1[i] != b2[i])
{
if (mDebug)
{
log.info("offset " + i + " differs (this is "
+ (i - mOffset) + " bytes in from our offset.)");
}
return (b1[i] & 0xFF) - (b2[i] & 0xFF);
}
}
return b1.length - b2.length;
}
}

View File

@@ -0,0 +1,646 @@
/*
* $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.math.BigInteger;
import java.security.*;
import java.security.spec.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Set;
import java.util.logging.Logger;
import org.xbill.DNS.DNSSEC;
// for now, we need to import the EdDSA parameter spec classes
// because they have no generic form in java.security.spec.*
// sadly, this will currently fail if you don't have the lib.
import net.i2p.crypto.eddsa.spec.*;
/**
* This class handles translating 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
{
// Our base algorithm numbers. This is a normalization of the DNSSEC
// algorithms (which are really signature algorithms). Thus RSASHA1,
// RSASHA256, etc. all boil down to 'RSA' here.
public static final int UNKNOWN = -1;
public static final int RSA = 1;
public static final int DH = 2;
public static final int DSA = 3;
public static final int ECC_GOST = 4;
public static final int ECDSA = 5;
public static final int EDDSA = 6;
private static class AlgEntry
{
public int dnssecAlgorithm;
public String sigName;
public int baseType;
public AlgEntry(int algorithm, String sigName, int baseType)
{
this.dnssecAlgorithm = algorithm;
this.sigName = sigName;
this.baseType = baseType;
}
}
private static class ECAlgEntry extends AlgEntry
{
public ECParameterSpec ec_spec;
public ECAlgEntry(int algorithm, String sigName, int baseType, ECParameterSpec spec)
{
super(algorithm, sigName, baseType);
this.ec_spec = spec;
}
}
private static class EdAlgEntry extends AlgEntry
{
public EdDSAParameterSpec ed_spec;
public EdAlgEntry(int algorithm, String sigName, int baseType, EdDSAParameterSpec spec)
{
super(algorithm, sigName, baseType);
this.ed_spec = spec;
}
}
/**
* 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<Integer, AlgEntry> mAlgorithmMap;
/**
* This is a mapping of algorithm mnemonics to algorithm identifiers.
*/
private HashMap<String, Integer> mMnemonicToIdMap;
/**
* This is a mapping of identifiers to preferred mnemonic -- the preferred one
* is the first defined one
*/
private HashMap<Integer, String> mIdToMnemonicMap;
/** This is a cached key pair generator for RSA keys. */
private KeyPairGenerator mRSAKeyGenerator;
/** This is a cached key pair generator for DSA keys. */
private KeyPairGenerator mDSAKeyGenerator;
/** This is a cached key pair generator for ECC GOST keys. */
private KeyPairGenerator mECGOSTKeyGenerator;
/** This is a cached key pair generator for ECDSA_P256 keys. */
private KeyPairGenerator mECKeyGenerator;
/** This is a cached key pair generator for EdDSA keys. */
private KeyPairGenerator mEdKeyGenerator;
private Logger log = Logger.getLogger(this.getClass().toString());
/** This is the global instance for this class. */
private static DnsKeyAlgorithm mInstance = null;
public DnsKeyAlgorithm()
{
// Attempt to add the bouncycastle provider.
// This is so we can use this provider if it is available, but not require
// the user to add it as one of the java.security providers.
try
{
Class<?> bc_provider_class = Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider");
Provider bc_provider = (Provider) bc_provider_class.newInstance();
Security.addProvider(bc_provider);
}
catch (ReflectiveOperationException e) { }
// Attempt to add the EdDSA-Java provider.
try
{
Class<?> eddsa_provider_class = Class.forName("net.i2p.crypto.eddsa.EdDSASecurityProvider");
Provider eddsa_provider = (Provider) eddsa_provider_class.newInstance();
Security.addProvider(eddsa_provider);
}
catch (ReflectiveOperationException e) {
log.warning("Unable to load EdDSA provider");
}
initialize();
}
private void initialize()
{
mAlgorithmMap = new HashMap<Integer, AlgEntry>();
mMnemonicToIdMap = new HashMap<String, Integer>();
mIdToMnemonicMap = new HashMap<Integer, String>();
// Load the standard DNSSEC algorithms.
addAlgorithm(DNSSEC.Algorithm.RSAMD5, "MD5withRSA", RSA);
addMnemonic("RSAMD5", DNSSEC.Algorithm.RSAMD5);
addAlgorithm(DNSSEC.Algorithm.DH, "", DH);
addMnemonic("DH", DNSSEC.Algorithm.DH);
addAlgorithm(DNSSEC.Algorithm.DSA, "SHA1withDSA", DSA);
addMnemonic("DSA", DNSSEC.Algorithm.DSA);
addAlgorithm(DNSSEC.Algorithm.RSASHA1, "SHA1withRSA", RSA);
addMnemonic("RSASHA1", DNSSEC.Algorithm.RSASHA1);
addMnemonic("RSA", DNSSEC.Algorithm.RSASHA1);
// Load the (now) standard aliases
addAlias(DNSSEC.Algorithm.DSA_NSEC3_SHA1, "DSA-NSEC3-SHA1", DNSSEC.Algorithm.DSA);
addAlias(DNSSEC.Algorithm.RSA_NSEC3_SHA1, "RSA-NSEC3-SHA1", DNSSEC.Algorithm.RSASHA1);
// Also recognize the BIND 9.6 mnemonics
addMnemonic("NSEC3DSA", DNSSEC.Algorithm.DSA_NSEC3_SHA1);
addMnemonic("NSEC3RSASHA1", DNSSEC.Algorithm.RSA_NSEC3_SHA1);
// Algorithms added by RFC 5702.
addAlgorithm(DNSSEC.Algorithm.RSASHA256, "SHA256withRSA", RSA);
addMnemonic("RSASHA256", DNSSEC.Algorithm.RSASHA256);
addAlgorithm(DNSSEC.Algorithm.RSASHA512, "SHA512withRSA", RSA);
addMnemonic("RSASHA512", DNSSEC.Algorithm.RSASHA512);
// ECC-GOST is not supported by Java 1.8's Sun crypto provider. The
// bouncycastle.org provider, however, does.
// GostR3410-2001-CryptoPro-A is the named curve in the BC provider, but we
// will get the parameters directly.
addAlgorithm(DNSSEC.Algorithm.ECC_GOST, "GOST3411withECGOST3410", ECC_GOST, null);
addMnemonic("ECCGOST", DNSSEC.Algorithm.ECC_GOST);
addMnemonic("ECC-GOST", DNSSEC.Algorithm.ECC_GOST);
addAlgorithm(DNSSEC.Algorithm.ECDSAP256SHA256, "SHA256withECDSA", ECDSA, "secp256r1");
addMnemonic("ECDSAP256SHA256", DNSSEC.Algorithm.ECDSAP256SHA256);
addMnemonic("ECDSA-P256", DNSSEC.Algorithm.ECDSAP256SHA256);
addAlgorithm(DNSSEC.Algorithm.ECDSAP384SHA384, "SHA384withECDSA", ECDSA, "secp384r1");
addMnemonic("ECDSAP384SHA384", DNSSEC.Algorithm.ECDSAP384SHA384);
addMnemonic("ECDSA-P384", DNSSEC.Algorithm.ECDSAP384SHA384);
// EdDSA is not supported by either the Java 1.8 Sun crypto
// provider or bouncycastle. It is added by the Ed25519-Java
// library.
// FIXME: add constant for the EdDSA algs to DNSJava.
addAlgorithm(15, "NONEwithEdDSA", EDDSA, "Ed25519");
addMnemonic("ED25519", 15);
}
private void addAlgorithm(int algorithm, String sigName, int baseType)
{
mAlgorithmMap.put(algorithm, new AlgEntry(algorithm, sigName, baseType));
}
private void addAlgorithm(int algorithm, String sigName, int baseType, String curveName)
{
if (baseType == ECDSA)
{
ECParameterSpec ec_spec = ECSpecFromAlgorithm(algorithm);
if (ec_spec == null) ec_spec = ECSpecFromName(curveName);
if (ec_spec == null) return;
// Check to see if we can get a Signature object for this algorithm.
try {
Signature.getInstance(sigName);
} catch (NoSuchAlgorithmException e) {
// for now, let's find out
log.severe("could not get signature for " + sigName + ": " + e.getMessage());
// If not, do not add the algorithm.
return;
}
ECAlgEntry entry = new ECAlgEntry(algorithm, sigName, baseType, ec_spec);
mAlgorithmMap.put(algorithm, entry);
}
else if (baseType == EDDSA)
{
EdDSAParameterSpec ed_spec = EdDSASpecFromAlgorithm(algorithm);
if (ed_spec == null) ed_spec = EdDSASpecFromName(curveName);
if (ed_spec == null) return;
// Check to see if we can get a Signature object for this algorithm.
try {
Signature.getInstance(sigName);
} catch (NoSuchAlgorithmException e) {
// for now, let's find out
log.severe("could not get signature for " + sigName + ": " + e.getMessage());
// If not, do not add the algorithm.
return;
}
EdAlgEntry entry = new EdAlgEntry(algorithm, sigName, baseType, ed_spec);
mAlgorithmMap.put(algorithm, entry);
}
}
private void addMnemonic(String m, int alg)
{
// Do not add mnemonics for algorithms that ended up not actually being supported.
if (!mAlgorithmMap.containsKey(alg)) return;
mMnemonicToIdMap.put(m.toUpperCase(), alg);
if (!mIdToMnemonicMap.containsKey(alg))
{
mIdToMnemonicMap.put(alg, m);
}
}
public void addAlias(int alias, String mnemonic, int original_algorithm)
{
if (mAlgorithmMap.containsKey(alias))
{
log.warning("Unable to alias algorithm " + alias + " because it already exists.");
return;
}
if (!mAlgorithmMap.containsKey(original_algorithm))
{
log.warning("Unable to alias algorithm " + alias
+ " to unknown algorithm identifier " + original_algorithm);
return;
}
mAlgorithmMap.put(alias, mAlgorithmMap.get(original_algorithm));
if (mnemonic != null)
{
addMnemonic(mnemonic, alias);
}
}
private AlgEntry getEntry(int alg)
{
return mAlgorithmMap.get(alg);
}
// For curves where we don't (or can't) get the parameters from a standard
// name, we can construct the parameters here. For now, we only do this for
// the ECC-GOST curve.
private ECParameterSpec ECSpecFromAlgorithm(int algorithm)
{
switch (algorithm)
{
case DNSSEC.Algorithm.ECC_GOST:
{
// From RFC 4357 Section 11.4
BigInteger p = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD97", 16);
BigInteger a = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD94", 16);
BigInteger b = new BigInteger("A6", 16);
BigInteger gx = new BigInteger("1", 16);
BigInteger gy = new BigInteger("8D91E471E0989CDA27DF505A453F2B7635294F2DDF23E3B122ACC99C9E9F1E14", 16);
BigInteger n = new BigInteger( "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6C611070995AD10045841B09B761B893", 16);
EllipticCurve curve = new EllipticCurve(new ECFieldFp(p), a, b);
return new ECParameterSpec(curve, new ECPoint(gx, gy), n, 1);
}
default:
return null;
}
}
// Fetch the curve parameters from a named curve.
private ECParameterSpec ECSpecFromName(String stdName)
{
try
{
AlgorithmParameters ap = AlgorithmParameters.getInstance("EC");
ECGenParameterSpec ecg_spec = new ECGenParameterSpec(stdName);
ap.init(ecg_spec);
return ap.getParameterSpec(ECParameterSpec.class);
}
catch (NoSuchAlgorithmException e) {
log.info("Elliptic Curve not supported by any crypto provider: " + e.getMessage());
}
catch (InvalidParameterSpecException e) {
log.info("Elliptic Curve " + stdName + " not supported");
}
return null;
}
// For curves where we don't (or can't) get the parameters from a standard
// name, we can construct the parameters here.
private EdDSAParameterSpec EdDSASpecFromAlgorithm(int algorithm)
{
return null;
}
private EdDSAParameterSpec EdDSASpecFromName(String stdName)
{
try
{
EdDSAParameterSpec spec = EdDSANamedCurveTable.getByName(stdName);
if (spec != null) return spec;
throw new InvalidParameterSpecException("Edwards Curve " + stdName + " not found.");
}
// catch (NoSuchAlgorithmException e) {
// log.info("Edwards Curve not supported by any crypto provider: " + e.getMessage());
// }
catch (InvalidParameterSpecException e) {
log.info("Edwards Curve " + stdName + " not supported");
}
return null;
}
public String[] supportedAlgMnemonics()
{
Set<Integer> keyset = mAlgorithmMap.keySet();
Integer[] algs = keyset.toArray(new Integer[keyset.size()]);
Arrays.sort(algs);
String[] result = new String[algs.length];
for (int i = 0; i < algs.length; i++)
{
result[i] = mIdToMnemonicMap.get(algs[i]);
}
return result;
}
/**
* Return a Signature object for the specified DNSSEC algorithm.
* @param algorithm The DNSSEC algorithm (by number).
* @return a Signature object.
*/
public Signature getSignature(int algorithm)
{
AlgEntry 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;
}
/**
* Given one of the ECDSA algorithms (ECDSAP256SHA256, etc.) return
* the elliptic curve parameters.
*
* @param algorithm
* The DNSSEC algorithm number.
* @return The calculated JCA ECParameterSpec for that DNSSEC algorithm, or
* null if not a recognized/supported EC algorithm.
*/
public ECParameterSpec getEllipticCurveParams(int algorithm)
{
AlgEntry entry = getEntry(algorithm);
if (entry == null) return null;
if (!(entry instanceof ECAlgEntry)) return null;
ECAlgEntry ec_entry = (ECAlgEntry) entry;
return ec_entry.ec_spec;
}
public EdDSAParameterSpec getEdwardsCurveParams(int algorithm)
{
AlgEntry entry = getEntry(algorithm);
if (entry == null) return null;
if (!(entry instanceof EdAlgEntry)) return null;
EdAlgEntry ed_entry = (EdAlgEntry) entry;
return ed_entry.ed_spec;
}
/**
* Translate a possible algorithm alias back to the original DNSSEC algorithm
* number
*
* @param algorithm
* a DNSSEC algorithm that may be an alias.
* @return -1 if the algorithm isn't recognised, the orignal algorithm number
* if it is.
*/
public int originalAlgorithm(int algorithm)
{
AlgEntry entry = getEntry(algorithm);
if (entry == null) return -1;
return entry.dnssecAlgorithm;
}
/**
* Test if a given algorithm is supported.
*
* @param algorithm The DNSSEC algorithm number.
* @return true if the algorithm is a recognized and supported algorithm or alias.
*/
public boolean supportedAlgorithm(int algorithm)
{
if (mAlgorithmMap.containsKey(algorithm)) return true;
return false;
}
/**
* Given an algorithm mnemonic, convert the mnemonic to a DNSSEC algorithm
* number.
*
* @param s
* The mnemonic string. This is case-insensitive.
* @return -1 if the mnemonic isn't recognized or supported, the algorithm
* number if it is.
*/
public int stringToAlgorithm(String s)
{
Integer alg = mMnemonicToIdMap.get(s.toUpperCase());
if (alg != null) return alg.intValue();
return -1;
}
/**
* Given a DNSSEC algorithm number, return the "preferred" mnemonic.
*
* @param algorithm
* A DNSSEC algorithm number.
* @return The preferred mnemonic string, or null if not supported or
* recognized.
*/
public String algToString(int algorithm)
{
return mIdToMnemonicMap.get(algorithm);
}
public int baseType(int algorithm)
{
AlgEntry entry = getEntry(algorithm);
if (entry != null) return entry.baseType;
return UNKNOWN;
}
public boolean isDSA(int algorithm)
{
return (baseType(algorithm) == DSA);
}
public KeyPair generateKeyPair(int algorithm, int keysize, boolean useLargeExp)
throws NoSuchAlgorithmException
{
KeyPair pair = null;
switch (baseType(algorithm))
{
case RSA:
{
if (mRSAKeyGenerator == null)
{
mRSAKeyGenerator = KeyPairGenerator.getInstance("RSA");
}
RSAKeyGenParameterSpec rsa_spec;
if (useLargeExp)
{
rsa_spec = new RSAKeyGenParameterSpec(keysize, RSAKeyGenParameterSpec.F4);
}
else
{
rsa_spec = new RSAKeyGenParameterSpec(keysize, RSAKeyGenParameterSpec.F0);
}
try
{
mRSAKeyGenerator.initialize(rsa_spec);
}
catch (InvalidAlgorithmParameterException e)
{
// Fold the InvalidAlgorithmParameterException into our existing
// thrown exception. Ugly, but requires less code change.
throw new NoSuchAlgorithmException("invalid key parameter spec");
}
pair = mRSAKeyGenerator.generateKeyPair();
break;
}
case DSA:
{
if (mDSAKeyGenerator == null)
{
mDSAKeyGenerator = KeyPairGenerator.getInstance("DSA");
}
mDSAKeyGenerator.initialize(keysize);
pair = mDSAKeyGenerator.generateKeyPair();
break;
}
case ECC_GOST:
{
if (mECGOSTKeyGenerator == null)
{
mECGOSTKeyGenerator = KeyPairGenerator.getInstance("ECGOST3410");
}
ECParameterSpec ec_spec = getEllipticCurveParams(algorithm);
try
{
mECGOSTKeyGenerator.initialize(ec_spec);
}
catch (InvalidAlgorithmParameterException e)
{
// Fold the InvalidAlgorithmParameterException into our existing
// thrown exception. Ugly, but requires less code change.
throw new NoSuchAlgorithmException("invalid key parameter spec");
}
pair = mECGOSTKeyGenerator.generateKeyPair();
break;
}
case ECDSA:
{
if (mECKeyGenerator == null)
{
mECKeyGenerator = KeyPairGenerator.getInstance("EC");
}
ECParameterSpec ec_spec = getEllipticCurveParams(algorithm);
try
{
mECKeyGenerator.initialize(ec_spec);
}
catch (InvalidAlgorithmParameterException e)
{
// Fold the InvalidAlgorithmParameterException into our existing
// thrown exception. Ugly, but requires less code change.
throw new NoSuchAlgorithmException("invalid key parameter spec");
}
pair = mECKeyGenerator.generateKeyPair();
break;
}
case EDDSA:
{
if (mEdKeyGenerator == null)
{
mEdKeyGenerator = KeyPairGenerator.getInstance("EdDSA");
}
EdDSAParameterSpec ed_spec = getEdwardsCurveParams(algorithm);
try
{
mEdKeyGenerator.initialize(ed_spec, new SecureRandom());
}
catch (InvalidAlgorithmParameterException e)
{
// Fold the InvalidAlgorithmParameterException into our existing
// thrown exception. Ugly, but requires less code change.
throw new NoSuchAlgorithmException("invalid key parameter spec");
}
pair = mEdKeyGenerator.generateKeyPair();
break;
}
default:
throw new NoSuchAlgorithmException("Alg " + algorithm);
}
return pair;
}
public KeyPair generateKeyPair(int algorithm, int keysize)
throws NoSuchAlgorithmException
{
return generateKeyPair(algorithm, keysize, false);
}
public static DnsKeyAlgorithm getInstance()
{
if (mInstance == null) mInstance = new DnsKeyAlgorithm();
return mInstance;
}
}

View File

@@ -0,0 +1,756 @@
// $Id$
//
// Copyright (C) 2001-2003 VeriSign, Inc.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.verisignlabs.dnssec.security;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.*;
import java.security.spec.*;
import java.util.StringTokenizer;
import javax.crypto.interfaces.DHPrivateKey;
import javax.crypto.interfaces.DHPublicKey;
import javax.crypto.spec.DHParameterSpec;
import javax.crypto.spec.DHPrivateKeySpec;
// For now, just import the native EdDSA classes
import net.i2p.crypto.eddsa.EdDSAPublicKey;
import net.i2p.crypto.eddsa.EdDSAPrivateKey;
import net.i2p.crypto.eddsa.spec.*;
import org.xbill.DNS.DNSKEYRecord;
import org.xbill.DNS.DNSSEC;
import org.xbill.DNS.DNSSEC.DNSSECException;
import org.xbill.DNS.Name;
import org.xbill.DNS.utils.base64;
/**
* This class handles conversions between JCA key formats and DNSSEC and BIND9
* key formats.
*
* @author David Blacka (original)
* @author $Author$ (latest)
* @version $Revision$
*/
public class DnsKeyConverter
{
private KeyFactory mRSAKeyFactory;
private KeyFactory mDSAKeyFactory;
private KeyFactory mDHKeyFactory;
private KeyFactory mECKeyFactory;
private KeyFactory mEdKeyFactory;
private DnsKeyAlgorithm mAlgorithms;
public DnsKeyConverter()
{
mAlgorithms = DnsKeyAlgorithm.getInstance();
}
/**
* 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;
// Because we have arbitrarily aliased algorithms, we need to possibly
// translate the aliased algorithm back to the actual algorithm.
int originalAlgorithm = mAlgorithms.originalAlgorithm(pKeyRecord.getAlgorithm());
if (originalAlgorithm <= 0) throw new NoSuchAlgorithmException("DNSKEY algorithm "
+ pKeyRecord.getAlgorithm() + " is unrecognized");
if (pKeyRecord.getAlgorithm() != originalAlgorithm)
{
pKeyRecord = new DNSKEYRecord(pKeyRecord.getName(), pKeyRecord.getDClass(),
pKeyRecord.getTTL(), pKeyRecord.getFlags(),
pKeyRecord.getProtocol(), originalAlgorithm,
pKeyRecord.getKey());
}
// do not rely on DNSJava's method for EdDSA for now.
if (mAlgorithms.baseType(originalAlgorithm) == DnsKeyAlgorithm.EDDSA)
{
try {
return parseEdDSADNSKEYRecord(pKeyRecord);
} catch (InvalidKeySpecException e) {
// just to be expedient, recast this as a NoSuchAlgorithmException.
throw new NoSuchAlgorithmException(e.getMessage());
}
}
try
{
// This uses DNSJava's DNSSEC.toPublicKey() method.
return pKeyRecord.getPublicKey();
}
catch (DNSSECException e)
{
throw new NoSuchAlgorithmException(e);
}
}
/** Since we don't (yet) have support in DNSJava for parsing the
newer EdDSA algorithms, here is a local version. */
private PublicKey parseEdDSADNSKEYRecord(DNSKEYRecord pKeyRecord)
throws IllegalArgumentException, NoSuchAlgorithmException, InvalidKeySpecException
{
byte[] seed = pKeyRecord.getKey();
EdDSAPublicKeySpec spec = new EdDSAPublicKeySpec
(seed, mAlgorithms.getEdwardsCurveParams(pKeyRecord.getAlgorithm()));
KeyFactory factory = KeyFactory.getInstance("EdDSA");
return factory.generatePublic(spec);
}
/**
* Given a JCA public key and the ancillary data, generate a DNSKEY record.
*/
public DNSKEYRecord generateDNSKEYRecord(Name name, int dclass, long ttl,
int flags, int alg, PublicKey key)
{
try
{
if (mAlgorithms.baseType(alg) == DnsKeyAlgorithm.EDDSA) {
return generateEdDSADNSKEYRecord(name, dclass, ttl, flags, alg, key);
}
return new DNSKEYRecord(name, dclass, ttl, flags, DNSKEYRecord.Protocol.DNSSEC, alg,
key);
}
catch (DNSSECException e)
{
// FIXME: this mimics the behavior of KEYConverter.buildRecord(), which would return null if the algorithm was unknown.
return null;
}
}
private DNSKEYRecord generateEdDSADNSKEYRecord(Name name, int dclass, long ttl,
int flags, int alg, PublicKey key)
{
EdDSAPublicKey ed_key = (EdDSAPublicKey) key;
byte[] key_data = ed_key.getAbyte();
return new DNSKEYRecord(name, dclass, ttl, flags, DNSKEYRecord.Protocol.DNSSEC, alg,
key_data);
}
// Private Key Specific Parsing routines
/**
* Convert a PKCS#8 encoded private key into a PrivateKey object.
*/
public PrivateKey convertEncodedPrivateKey(byte[] key, int algorithm)
{
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(key);
try
{
switch (mAlgorithms.baseType(algorithm))
{
case DnsKeyAlgorithm.RSA:
return mRSAKeyFactory.generatePrivate(spec);
case DnsKeyAlgorithm.DSA:
return mDSAKeyFactory.generatePrivate(spec);
}
}
catch (GeneralSecurityException e)
{
e.printStackTrace();
}
return null;
}
/**
* A simple wrapper for parsing integers; parse failures result in the
* supplied default.
*/
private static 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
{
StringTokenizer lines = new StringTokenizer(key, "\n");
while (lines.hasMoreTokens())
{
String line = lines.nextToken();
if (line == null) continue;
if (line.startsWith("#")) continue;
String val = value(line);
if (val == null) continue;
if (line.startsWith("Private-key-format: "))
{
if (!val.equals("v1.2") && !val.equals("v1.3"))
{
throw new IOException("unsupported private key format: " + val);
}
}
else if (line.startsWith("Algorithm: "))
{
// 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);
switch (mAlgorithms.baseType(alg))
{
case DnsKeyAlgorithm.RSA:
return parsePrivateRSA(lines);
case DnsKeyAlgorithm.DSA:
return parsePrivateDSA(lines);
case DnsKeyAlgorithm.DH:
return parsePrivateDH(lines);
case DnsKeyAlgorithm.ECC_GOST:
return parsePrivateECDSA(lines, alg);
case DnsKeyAlgorithm.ECDSA:
return parsePrivateECDSA(lines, alg);
case DnsKeyAlgorithm.EDDSA:
return parsePrivateEdDSA(lines, alg);
default:
throw new IOException("unsupported private key algorithm: " + val);
}
}
}
return null;
}
/**
* @return the value part of an "attribute:value" pair. The value is trimmed.
*/
private static String value(String av)
{
if (av == null) return null;
int pos = av.indexOf(':');
if (pos < 0) return av;
if (pos >= av.length()) return null;
return av.substring(pos + 1).trim();
}
/**
* Given the rest of the RSA BIND9 string format private key, parse and
* translate into a JCA private key
*
* @throws NoSuchAlgorithmException
* if the RSA algorithm is not available.
*/
private PrivateKey parsePrivateRSA(StringTokenizer lines)
throws NoSuchAlgorithmException
{
BigInteger modulus = null;
BigInteger public_exponent = null;
BigInteger private_exponent = null;
BigInteger prime_p = null;
BigInteger prime_q = null;
BigInteger prime_p_exponent = null;
BigInteger prime_q_exponent = null;
BigInteger coefficient = null;
while (lines.hasMoreTokens())
{
String line = lines.nextToken();
if (line == null) continue;
if (line.startsWith("#")) continue;
String val = value(line);
if (val == null) continue;
byte[] data = base64.fromString(val);
if (line.startsWith("Modulus: "))
{
modulus = new BigInteger(1, data);
// printBigIntCompare(data, modulus);
}
else if (line.startsWith("PublicExponent: "))
{
public_exponent = new BigInteger(1, data);
// printBigIntCompare(data, public_exponent);
}
else if (line.startsWith("PrivateExponent: "))
{
private_exponent = new BigInteger(1, data);
// printBigIntCompare(data, private_exponent);
}
else if (line.startsWith("Prime1: "))
{
prime_p = new BigInteger(1, data);
// printBigIntCompare(data, prime_p);
}
else if (line.startsWith("Prime2: "))
{
prime_q = new BigInteger(1, data);
// printBigIntCompare(data, prime_q);
}
else if (line.startsWith("Exponent1: "))
{
prime_p_exponent = new BigInteger(1, data);
}
else if (line.startsWith("Exponent2: "))
{
prime_q_exponent = new BigInteger(1, data);
}
else if (line.startsWith("Coefficient: "))
{
coefficient = new BigInteger(1, data);
}
}
try
{
KeySpec spec = new RSAPrivateCrtKeySpec(modulus, public_exponent,
private_exponent, prime_p,
prime_q, prime_p_exponent,
prime_q_exponent, coefficient);
if (mRSAKeyFactory == null)
{
mRSAKeyFactory = KeyFactory.getInstance("RSA");
}
return mRSAKeyFactory.generatePrivate(spec);
}
catch (InvalidKeySpecException e)
{
e.printStackTrace();
return null;
}
}
/**
* Given the remaining lines in a BIND9 style DH private key, parse the key
* info and translate it into a JCA private key.
*
* @throws NoSuchAlgorithmException
* if the DH algorithm is not available.
*/
private PrivateKey parsePrivateDH(StringTokenizer lines)
throws NoSuchAlgorithmException
{
BigInteger p = null;
BigInteger x = null;
BigInteger g = null;
while (lines.hasMoreTokens())
{
String line = lines.nextToken();
if (line == null) continue;
if (line.startsWith("#")) continue;
String val = value(line);
if (val == null) continue;
byte[] data = base64.fromString(val);
if (line.startsWith("Prime(p): "))
{
p = new BigInteger(1, data);
}
else if (line.startsWith("Generator(g): "))
{
g = new BigInteger(1, data);
}
else if (line.startsWith("Private_value(x): "))
{
x = new BigInteger(1, data);
}
}
try
{
KeySpec spec = new DHPrivateKeySpec(x, p, g);
if (mDHKeyFactory == null)
{
mDHKeyFactory = KeyFactory.getInstance("DH");
}
return mDHKeyFactory.generatePrivate(spec);
}
catch (InvalidKeySpecException e)
{
e.printStackTrace();
return null;
}
}
/**
* Given the remaining lines in a BIND9 style DSA private key, parse the key
* info and translate it into a JCA private key.
*
* @throws NoSuchAlgorithmException
* if the DSA algorithm is not available.
*/
private PrivateKey parsePrivateDSA(StringTokenizer lines)
throws NoSuchAlgorithmException
{
BigInteger p = null;
BigInteger q = null;
BigInteger g = null;
BigInteger x = null;
while (lines.hasMoreTokens())
{
String line = lines.nextToken();
if (line == null) continue;
if (line.startsWith("#")) continue;
String val = value(line);
if (val == null) continue;
byte[] data = base64.fromString(val);
if (line.startsWith("Prime(p): "))
{
p = new BigInteger(1, data);
}
else if (line.startsWith("Subprime(q): "))
{
q = new BigInteger(1, data);
}
else if (line.startsWith("Base(g): "))
{
g = new BigInteger(1, data);
}
else if (line.startsWith("Private_value(x): "))
{
x = new BigInteger(1, data);
}
}
try
{
KeySpec spec = new DSAPrivateKeySpec(x, p, q, g);
if (mDSAKeyFactory == null)
{
mDSAKeyFactory = KeyFactory.getInstance("DSA");
}
return mDSAKeyFactory.generatePrivate(spec);
}
catch (InvalidKeySpecException e)
{
e.printStackTrace();
return null;
}
}
/**
* Given the remaining lines in a BIND9-style ECDSA private key, parse the key
* info and translate it into a JCA private key object.
* @param lines The remaining lines in a private key file (after
* @throws NoSuchAlgorithmException
* If elliptic curve is not available.
*/
private PrivateKey parsePrivateECDSA(StringTokenizer lines, int algorithm)
throws NoSuchAlgorithmException
{
BigInteger s = null;
while (lines.hasMoreTokens())
{
String line = lines.nextToken();
if (line == null) continue;
if (line.startsWith("#")) continue;
String val = value(line);
if (val == null) continue;
byte[] data = base64.fromString(val);
if (line.startsWith("PrivateKey: "))
{
s = new BigInteger(1, data);
}
}
if (mECKeyFactory == null)
{
mECKeyFactory = KeyFactory.getInstance("EC");
}
ECParameterSpec ec_spec = mAlgorithms.getEllipticCurveParams(algorithm);
if (ec_spec == null)
{
throw new NoSuchAlgorithmException("DNSSEC algorithm " + algorithm +
" is not a recognized Elliptic Curve algorithm");
}
KeySpec spec = new ECPrivateKeySpec(s, ec_spec);
try
{
return mECKeyFactory.generatePrivate(spec);
}
catch (InvalidKeySpecException e)
{
e.printStackTrace();
return null;
}
}
/**
* Given the remaining lines in a BIND9-style ECDSA private key, parse the key
* info and translate it into a JCA private key object.
* @param lines The remaining lines in a private key file (after
* @throws NoSuchAlgorithmException
* If elliptic curve is not available.
*/
private PrivateKey parsePrivateEdDSA(StringTokenizer lines, int algorithm)
throws NoSuchAlgorithmException
{
byte[] seed = null;
while (lines.hasMoreTokens())
{
String line = lines.nextToken();
if (line == null) continue;
if (line.startsWith("#")) continue;
String val = value(line);
if (val == null) continue;
byte[] data = base64.fromString(val);
if (line.startsWith("PrivateKey: "))
{
seed = data;
}
}
if (mEdKeyFactory == null)
{
mEdKeyFactory = KeyFactory.getInstance("EdDSA");
}
EdDSAParameterSpec ed_spec = mAlgorithms.getEdwardsCurveParams(algorithm);
if (ed_spec == null)
{
throw new NoSuchAlgorithmException("DNSSEC algorithm " + algorithm +
" is not a recognized Edwards Curve algorithm");
}
KeySpec spec = new EdDSAPrivateKeySpec(seed, ed_spec);
try
{
return mEdKeyFactory.generatePrivate(spec);
}
catch (InvalidKeySpecException e)
{
e.printStackTrace();
return null;
}
}
/**
* Given a private key and public key, generate the BIND9 style private key
* format.
*/
public String generatePrivateKeyString(PrivateKey priv, PublicKey pub, int alg)
{
if (priv instanceof RSAPrivateCrtKey)
{
return generatePrivateRSA((RSAPrivateCrtKey) priv, alg);
}
else if (priv instanceof DSAPrivateKey && pub instanceof DSAPublicKey)
{
return generatePrivateDSA((DSAPrivateKey) priv, (DSAPublicKey) pub, alg);
}
else if (priv instanceof DHPrivateKey && pub instanceof DHPublicKey)
{
return generatePrivateDH((DHPrivateKey) priv, (DHPublicKey) pub, alg);
}
else if (priv instanceof ECPrivateKey && pub instanceof ECPublicKey)
{
return generatePrivateEC((ECPrivateKey) priv, (ECPublicKey) pub, alg);
}
else if (priv instanceof EdDSAPrivateKey && pub instanceof EdDSAPublicKey)
{
return generatePrivateED((EdDSAPrivateKey) priv, (EdDSAPublicKey) pub, alg);
}
return null;
}
/**
* Convert from 'unsigned' big integer to original 'signed format' in Base64
*/
private static String b64BigInt(BigInteger i)
{
byte[] orig_bytes = i.toByteArray();
if (orig_bytes[0] != 0 || orig_bytes.length == 1)
{
return base64.toString(orig_bytes);
}
byte[] signed_bytes = new byte[orig_bytes.length - 1];
System.arraycopy(orig_bytes, 1, signed_bytes, 0, signed_bytes.length);
return base64.toString(signed_bytes);
}
/**
* Given a RSA private key (in Crt format), return the BIND9-style text
* encoding.
*/
private String generatePrivateRSA(RSAPrivateCrtKey key, int algorithm)
{
StringWriter sw = new StringWriter();
PrintWriter out = new PrintWriter(sw);
out.println("Private-key-format: v1.2");
out.println("Algorithm: " + algorithm + " (" + mAlgorithms.algToString(algorithm)
+ ")");
out.print("Modulus: ");
out.println(b64BigInt(key.getModulus()));
out.print("PublicExponent: ");
out.println(b64BigInt(key.getPublicExponent()));
out.print("PrivateExponent: ");
out.println(b64BigInt(key.getPrivateExponent()));
out.print("Prime1: ");
out.println(b64BigInt(key.getPrimeP()));
out.print("Prime2: ");
out.println(b64BigInt(key.getPrimeQ()));
out.print("Exponent1: ");
out.println(b64BigInt(key.getPrimeExponentP()));
out.print("Exponent2: ");
out.println(b64BigInt(key.getPrimeExponentQ()));
out.print("Coefficient: ");
out.println(b64BigInt(key.getCrtCoefficient()));
return sw.toString();
}
/** Given a DH key pair, return the BIND9-style text encoding */
private String generatePrivateDH(DHPrivateKey key, DHPublicKey pub,
int algorithm)
{
StringWriter sw = new StringWriter();
PrintWriter out = new PrintWriter(sw);
DHParameterSpec p = key.getParams();
out.println("Private-key-format: v1.2");
out.println("Algorithm: " + algorithm + " (" + mAlgorithms.algToString(algorithm)
+ ")");
out.print("Prime(p): ");
out.println(b64BigInt(p.getP()));
out.print("Generator(g): ");
out.println(b64BigInt(p.getG()));
out.print("Private_value(x): ");
out.println(b64BigInt(key.getX()));
out.print("Public_value(y): ");
out.println(b64BigInt(pub.getY()));
return sw.toString();
}
/** Given a DSA key pair, return the BIND9-style text encoding */
private String generatePrivateDSA(DSAPrivateKey key, DSAPublicKey pub,
int algorithm)
{
StringWriter sw = new StringWriter();
PrintWriter out = new PrintWriter(sw);
DSAParams p = key.getParams();
out.println("Private-key-format: v1.2");
out.println("Algorithm: " + algorithm + " (" + mAlgorithms.algToString(algorithm)
+ ")");
out.print("Prime(p): ");
out.println(b64BigInt(p.getP()));
out.print("Subprime(q): ");
out.println(b64BigInt(p.getQ()));
out.print("Base(g): ");
out.println(b64BigInt(p.getG()));
out.print("Private_value(x): ");
out.println(b64BigInt(key.getX()));
out.print("Public_value(y): ");
out.println(b64BigInt(pub.getY()));
return sw.toString();
}
/**
* Given an elliptic curve key pair, and the actual algorithm (which will
* describe the curve used), return the BIND9-style text encoding.
*/
private String generatePrivateEC(ECPrivateKey priv, ECPublicKey pub, int alg)
{
StringWriter sw = new StringWriter();
PrintWriter out = new PrintWriter(sw);
out.println("Private-key-format: v1.2");
out.println("Algorithm: " + alg + " (" + mAlgorithms.algToString(alg)
+ ")");
out.print("PrivateKey: ");
out.println(b64BigInt(priv.getS()));
return sw.toString();
}
/**
* Given an edwards curve key pair, and the actual algorithm (which will
* describe the curve used), return the BIND9-style text encoding.
*/
private String generatePrivateED(EdDSAPrivateKey priv, EdDSAPublicKey pub, int alg)
{
StringWriter sw = new StringWriter();
PrintWriter out = new PrintWriter(sw);
out.println("Private-key-format: v1.2");
out.println("Algorithm: " + alg + " (" + mAlgorithms.algToString(alg)
+ ")");
out.print("PrivateKey: ");
out.println(base64.toString(priv.getSeed()));
return sw.toString();
}
}

View File

@@ -0,0 +1,356 @@
// $Id$
//
// Copyright (C) 2001-2003 VeriSign, Inc.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.verisignlabs.dnssec.security;
import java.security.*;
import java.security.interfaces.*;
import java.util.logging.Logger;
import org.xbill.DNS.*;
/**
* This class forms the basis for representing public/private key pairs in a
* DNSSEC context. It is possible to get a JCA public and private key from this
* object, as well as a DNSKEYRecord encoding of the public key. This class is
* implemented as a UNION of all the functionality needed for handing native
* java, BIND, and possibly other underlying DNSKEY engines.
*
* JCA == Java Cryptography Architecture.
*
* @author David Blacka (orig)
* @author $Author$ (latest)
* @version $Revision$
*/
// NOTE: this class is designed to do "lazy" evaluation of it's
// various cached objects and format conversions, so methods should
// avoid direct access to the member variables.
public class DnsKeyPair
{
/** This is the real (base) encoding of the public key. */
protected DNSKEYRecord mPublicKeyRecord;
/**
* This is a pre-calculated cache of the DNSKEYRecord converted into a JCA
* public key.
*/
private PublicKey mPublicKey;
/**
* The private key in Base64 encoded format. This version is presumed to be
* opaque, so no attempts will be made to convert it to a JCA private key.
*/
protected String mPrivateKeyString;
/**
* The private key in JCA format. This is the base encoding for instances where
* JCA private keys are used.
*/
protected PrivateKey mPrivateKey;
/** The local key converter. */
protected DnsKeyConverter mKeyConverter;
/**
* a cached Signature used for signing (initialized with the private key)
*/
protected Signature mSigner;
/**
* a caches Signature used for verifying (initialized with the public key)
*/
protected Signature mVerifier;
private Logger log;
public DnsKeyPair()
{
log = Logger.getLogger(this.getClass().toString());
}
public DnsKeyPair(DNSKEYRecord keyRecord, PrivateKey privateKey)
{
this();
setDNSKEYRecord(keyRecord);
setPrivate(privateKey);
}
public DnsKeyPair(DNSKEYRecord keyRecord, String privateKeyString)
{
this();
setDNSKEYRecord(keyRecord);
setPrivateKeyString(privateKeyString);
}
public DnsKeyPair(DNSKEYRecord keyRecord)
{
this();
setDNSKEYRecord(keyRecord);
setPrivateKeyString(null);
}
public DnsKeyPair(Name keyName, int algorithm, PublicKey publicKey,
PrivateKey privateKey)
{
this();
DnsKeyConverter conv = new DnsKeyConverter();
DNSKEYRecord keyrec = conv.generateDNSKEYRecord(keyName, DClass.IN, 0, 0,
algorithm, publicKey);
setDNSKEYRecord(keyrec);
setPrivate(privateKey);
}
public DnsKeyPair(DnsKeyPair pair)
{
this();
setDNSKEYRecord(pair.getDNSKEYRecord());
setPrivate(pair.getPrivate());
setPrivateKeyString(pair.getPrivateKeyString());
}
/** @return cached DnsKeyConverter object. */
protected DnsKeyConverter getKeyConverter()
{
if (mKeyConverter == null)
{
mKeyConverter = new DnsKeyConverter();
}
return mKeyConverter;
}
/** @return the appropriate Signature object for this keypair. */
protected Signature getSignature()
{
DnsKeyAlgorithm algorithms = DnsKeyAlgorithm.getInstance();
return algorithms.getSignature(getDNSKEYAlgorithm());
}
/**
* @return the public key, translated from the KEYRecord, if necessary.
*/
public PublicKey getPublic()
{
if (mPublicKey == null && getDNSKEYRecord() != null)
{
try
{
DnsKeyConverter conv = getKeyConverter();
setPublic(conv.parseDNSKEYRecord(getDNSKEYRecord()));
}
catch (NoSuchAlgorithmException e)
{
log.severe(e.toString());
return null;
}
}
return mPublicKey;
}
/**
* sets the public key. This method is generally not used directly.
*/
protected void setPublic(PublicKey k)
{
mPublicKey = k;
}
/** @return the private key. */
public PrivateKey getPrivate()
{
// attempt to convert the private key string format into a JCA
// private key.
if (mPrivateKey == null && mPrivateKeyString != null)
{
mPrivateKey = BINDKeyUtils.convertPrivateKeyString(mPrivateKeyString);
}
return mPrivateKey;
}
/** sets the private key */
public void setPrivate(PrivateKey k)
{
mPrivateKey = k;
}
/**
* @return the opaque private key string, null if one doesn't exist.
* @throws NoSuchAlgorithmException
*/
public String getPrivateKeyString()
{
if (mPrivateKeyString == null && mPrivateKey != null)
{
PublicKey pub = getPublic();
mPrivateKeyString = BINDKeyUtils.convertPrivateKey(mPrivateKey, pub,
getDNSKEYAlgorithm());
}
return mPrivateKeyString;
}
/** sets the opaque private key string. */
public void setPrivateKeyString(String p)
{
mPrivateKeyString = p;
}
/** @return the private key in an encoded form (normally PKCS#8). */
public byte[] getEncodedPrivate()
{
PrivateKey priv = getPrivate();
if (priv != null) return priv.getEncoded();
return null;
}
/**
* Sets the private key from the encoded form (PKCS#8). This routine requires
* that the public key already be assigned. Currently it can only handle DSA
* and RSA keys.
*/
public void setEncodedPrivate(byte[] encoded)
{
int alg = getDNSKEYAlgorithm();
if (alg >= 0)
{
DnsKeyConverter conv = getKeyConverter();
setPrivate(conv.convertEncodedPrivateKey(encoded, alg));
}
}
/** @return the public DNSKEY record */
public DNSKEYRecord getDNSKEYRecord()
{
return mPublicKeyRecord;
}
/**
* @return a Signature object initialized for signing, or null if this key
* pair does not have a valid private key.
*/
public Signature getSigner()
{
if (mSigner == null)
{
mSigner = getSignature();
PrivateKey priv = getPrivate();
if (mSigner != null && priv != null)
{
try
{
mSigner.initSign(priv);
}
catch (InvalidKeyException e)
{
log.severe("Signature error: " + e);
}
}
else
{
// do not return an uninitialized signer.
return null;
}
}
return mSigner;
}
/**
* @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()
{
if (mVerifier == null)
{
mVerifier = getSignature();
PublicKey pk = getPublic();
if (mVerifier != null && pk != null)
{
try
{
mVerifier.initVerify(pk);
}
catch (InvalidKeyException e)
{
}
}
else
{
// do not return an uninitialized verifier
return null;
}
}
return mVerifier;
}
/** sets the public key record */
public void setDNSKEYRecord(DNSKEYRecord r)
{
mPublicKeyRecord = r;
// force the conversion to PublicKey:
mPublicKey = null;
}
public Name getDNSKEYName()
{
DNSKEYRecord kr = getDNSKEYRecord();
if (kr != null) return kr.getName();
return null;
}
public int getDNSKEYAlgorithm()
{
DNSKEYRecord kr = getDNSKEYRecord();
if (kr != null) return kr.getAlgorithm();
PublicKey pk = getPublic();
if (pk != null)
{
// currently, alg 5 is the default over alg 1 (RSASHA1).
if (pk instanceof RSAPublicKey) return DNSSEC.Algorithm.RSASHA1;
if (pk instanceof DSAPublicKey) return DNSSEC.Algorithm.DSA;
}
PrivateKey priv = getPrivate();
if (priv != null)
{
if (priv instanceof RSAPrivateKey) return DNSSEC.Algorithm.RSASHA1;
if (priv instanceof DSAPrivateKey) return DNSSEC.Algorithm.DSA;
}
return -1;
}
public int getDNSKEYFootprint()
{
DNSKEYRecord kr = getDNSKEYRecord();
if (kr != null) return kr.getFootprint();
return -1;
}
}

View File

@@ -0,0 +1,329 @@
// $Id$
//
// Copyright (C) 2001-2003 VeriSign, Inc.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
// USA
package com.verisignlabs.dnssec.security;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Logger;
import org.xbill.DNS.*;
/**
* A class for performing basic DNSSEC verification. The DNSJAVA package
* contains a similar class. This differs (for the moment, anyway) by allowing
* timing "fudge" factors and logging more specifically why an RRset did not
* validate.
*
* @author David Blacka (original)
* @author $Author$
* @version $Revision$
*/
public class DnsSecVerifier
{
private class TrustedKeyStore
{
// for now, this is implemented as a hash table of lists of
// DnsKeyPair objects (obviously, all of them will not have
// private keys).
private HashMap<String, List<DnsKeyPair>> mKeyMap;
public TrustedKeyStore()
{
mKeyMap = new HashMap<String, List<DnsKeyPair>>();
}
public void add(DnsKeyPair pair)
{
String n = pair.getDNSKEYName().toString().toLowerCase();
List<DnsKeyPair> l = mKeyMap.get(n);
if (l == null)
{
l = new ArrayList<DnsKeyPair>();
mKeyMap.put(n, l);
}
l.add(pair);
}
public void add(DNSKEYRecord keyrec)
{
DnsKeyPair pair = new DnsKeyPair(keyrec, (PrivateKey) null);
add(pair);
}
public void add(Name name, int algorithm, PublicKey key)
{
DnsKeyPair pair = new DnsKeyPair(name, algorithm, key, null);
add(pair);
}
public DnsKeyPair find(Name name, int algorithm, int keyid)
{
String n = name.toString().toLowerCase();
List<DnsKeyPair> l = mKeyMap.get(n);
if (l == null) return null;
// FIXME: this algorithm assumes that name+alg+footprint is
// unique, which isn't necessarily true.
for (DnsKeyPair p : l)
{
if (p.getDNSKEYAlgorithm() == algorithm && p.getDNSKEYFootprint() == keyid)
{
return p;
}
}
return null;
}
}
private TrustedKeyStore mKeyStore;
private int mStartFudge = 0;
private int mExpireFudge = 0;
private boolean mVerifyAllSigs = false;
private boolean mIgnoreTime = false;
private Logger log;
public DnsSecVerifier()
{
log = Logger.getLogger(this.getClass().toString());
mKeyStore = new TrustedKeyStore();
}
public void addTrustedKey(DNSKEYRecord keyrec)
{
mKeyStore.add(keyrec);
}
public void addTrustedKey(DnsKeyPair pair)
{
mKeyStore.add(pair);
}
public void addTrustedKey(Name name, int algorithm, PublicKey key)
{
mKeyStore.add(name, algorithm, key);
}
public void addTrustedKey(Name name, PublicKey key)
{
mKeyStore.add(name, 0, key);
}
public void setExpireFudge(int fudge)
{
mExpireFudge = fudge;
}
public void setStartFudge(int fudge)
{
mStartFudge = fudge;
}
public void setVerifyAllSigs(boolean v)
{
mVerifyAllSigs = v;
}
public void setIgnoreTime(boolean v)
{
mIgnoreTime = v;
}
private DnsKeyPair findKey(Name name, int algorithm, int footprint)
{
return mKeyStore.find(name, algorithm, footprint);
}
private boolean validateSignature(RRset rrset, RRSIGRecord sigrec, List<String> reasons)
{
if (rrset == null || sigrec == null) return false;
if (!rrset.getName().equals(sigrec.getName()))
{
log.fine("Signature name does not match RRset name");
if (reasons != null) reasons.add("Signature name does not match RRset name");
return false;
}
if (rrset.getType() != sigrec.getTypeCovered())
{
log.fine("Signature type does not match RRset type");
if (reasons != null) reasons.add("Signature type does not match RRset type");
}
if (mIgnoreTime) return true;
Date now = new Date();
Date start = sigrec.getTimeSigned();
Date expire = sigrec.getExpire();
if (mStartFudge >= 0)
{
if (mStartFudge > 0)
{
start = new Date(start.getTime() - ((long) mStartFudge * 1000));
}
if (now.before(start))
{
log.fine("Signature is not yet valid");
if (reasons != null) reasons.add("Signature not yet valid");
return false;
}
}
if (mExpireFudge >= 0)
{
if (mExpireFudge > 0)
{
expire = new Date(expire.getTime() + ((long) mExpireFudge * 1000));
}
if (now.after(expire))
{
log.fine("Signature has expired (now = " + now + ", sig expires = " + expire);
if (reasons != null) reasons.add("Signature has expired.");
return false;
}
}
if (rrset.getTTL() > sigrec.getOrigTTL())
{
log.fine("RRset's TTL is greater than the Signature's orignal TTL");
if (reasons != null) reasons.add("RRset TTL greater than RRSIG origTTL");
return false;
}
return true;
}
public boolean verifySignature(RRset rrset, RRSIGRecord sigrec)
{
return verifySignature(rrset, sigrec, null);
}
/**
* Verify an RRset against a particular signature.
*
* @return true if the signature verified, false if it did
* not verify (for any reason, including not finding the DNSKEY.)
*/
public boolean verifySignature(RRset rrset, RRSIGRecord sigrec, List<String> reasons)
{
boolean result = validateSignature(rrset, sigrec, reasons);
if (!result) return result;
DnsKeyPair keypair = findKey(sigrec.getSigner(), sigrec.getAlgorithm(),
sigrec.getFootprint());
if (keypair == null)
{
if (reasons != null) reasons.add("Could not find matching trusted key");
log.fine("could not find matching trusted key");
return false;
}
try
{
byte[] data = SignUtils.generateSigData(rrset, sigrec);
DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance();
Signature signer = keypair.getVerifier();
signer.update(data);
byte[] sig = sigrec.getSignature();
if (algs.baseType(sigrec.getAlgorithm()) == DnsKeyAlgorithm.DSA)
{
sig = SignUtils.convertDSASignature(sig);
}
if (sigrec.getAlgorithm() == DNSSEC.Algorithm.ECDSAP256SHA256 ||
sigrec.getAlgorithm() == DNSSEC.Algorithm.ECDSAP384SHA384)
{
sig = SignUtils.convertECDSASignature(sig);
}
if (!signer.verify(sig))
{
if (reasons != null) reasons.add("Signature failed to verify cryptographically");
log.fine("Signature failed to verify cryptographically");
return false;
}
return true;
}
catch (IOException e)
{
log.severe("I/O error: " + e);
}
catch (GeneralSecurityException e)
{
log.severe("Security error: " + e);
}
if (reasons != null) reasons.add("Signature failed to verify due to exception");
log.fine("Signature failed to verify due to exception");
return false;
}
/**
* Verifies an RRset. This routine does not modify the RRset.
*
* @return true if the set verified, false if it did not.
*/
public boolean verify(RRset rrset)
{
boolean result = mVerifyAllSigs ? true : false;
Iterator i = rrset.sigs();
if (!i.hasNext())
{
log.fine("RRset failed to verify due to lack of signatures");
return false;
}
while (i.hasNext())
{
RRSIGRecord sigrec = (RRSIGRecord) i.next();
boolean res = verifySignature(rrset, sigrec);
// If not requiring all signature to validate, then any successful validation is sufficient.
if (!mVerifyAllSigs && res) return res;
// Otherwise, note if a signature failed to validate.
if (mVerifyAllSigs && !res)
{
result = res;
}
}
return result;
}
}

View File

@@ -0,0 +1,601 @@
// $Id$
//
// Copyright (C) 2001-2003, 2009 VeriSign, Inc.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
// USA
package com.verisignlabs.dnssec.security;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.Signature;
import java.security.interfaces.DSAPublicKey;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.logging.Logger;
import org.xbill.DNS.*;
import org.xbill.DNS.utils.hexdump;
/**
* This class contains routines for signing DNS zones.
*
* In particular, it contains both an ability to sign an individual RRset and
* the ability to sign an entire zone. It primarily glues together the more
* basic primitives found in {@link SignUtils}.
*
* @author David Blacka (original)
* @author $Author$
* @version $Revision$
*/
public class JCEDnsSecSigner
{
private DnsKeyConverter mKeyConverter;
private boolean mVerboseSigning = false;
private Logger log = Logger.getLogger(this.getClass().toString());
public JCEDnsSecSigner()
{
this.mKeyConverter = null;
this.mVerboseSigning = false;
}
public JCEDnsSecSigner(boolean verboseSigning)
{
this.mKeyConverter = null;
this.mVerboseSigning = verboseSigning;
}
/**
* Cryptographically generate a new DNSSEC key.
*
* @param owner
* the KEY RR's owner name.
* @param ttl
* the KEY RR's TTL.
* @param dclass
* the KEY RR's DNS class.
* @param algorithm
* the DNSSEC algorithm (RSAMD5, RSASHA1, or DSA).
* @param flags
* any flags for the KEY RR.
* @param keysize
* the size of the key to generate.
* @param useLargeExponent
* if generating an RSA key, use the large exponent.
* @return a DnsKeyPair with the public and private keys populated.
*/
public DnsKeyPair generateKey(Name owner, long ttl, int dclass, int algorithm,
int flags, int keysize, boolean useLargeExponent)
throws NoSuchAlgorithmException
{
DnsKeyAlgorithm algorithms = DnsKeyAlgorithm.getInstance();
if (ttl < 0) ttl = 86400; // set to a reasonable default.
KeyPair pair = algorithms.generateKeyPair(algorithm, keysize, useLargeExponent);
if (mKeyConverter == null)
{
mKeyConverter = new DnsKeyConverter();
}
DNSKEYRecord keyrec = mKeyConverter.generateDNSKEYRecord(owner, dclass, ttl, flags,
algorithm, pair.getPublic());
DnsKeyPair dnspair = new DnsKeyPair();
dnspair.setDNSKEYRecord(keyrec);
dnspair.setPublic(pair.getPublic()); // keep from conv. the keyrec back.
dnspair.setPrivate(pair.getPrivate());
return dnspair;
}
/**
* Sign an RRset.
*
* @param rrset
* the RRset to sign -- any existing signatures are ignored.
* @param keypars
* a list of DnsKeyPair objects containing private keys.
* @param start
* the inception time for the resulting RRSIG records.
* @param expire
* the expiration time for the resulting RRSIG records.
* @return a list of RRSIGRecord objects.
*/
public List<RRSIGRecord> signRRset(RRset rrset, List<DnsKeyPair> keypairs, Date start,
Date expire) throws IOException,
GeneralSecurityException
{
if (rrset == null || keypairs == null) return null;
// default start to now, expire to start + 1 second.
if (start == null) start = new Date();
if (expire == null) expire = new Date(start.getTime() + 1000L);
if (keypairs.size() == 0) return null;
if (mVerboseSigning)
{
log.info("Signing RRset:");
log.info(ZoneUtils.rrsetToString(rrset, false));
}
// first, pre-calculate the RRset bytes.
byte[] rrset_data = SignUtils.generateCanonicalRRsetData(rrset, 0, 0);
ArrayList<RRSIGRecord> sigs = new ArrayList<RRSIGRecord>(keypairs.size());
// for each keypair, sign the RRset.
for (DnsKeyPair pair : keypairs)
{
DNSKEYRecord keyrec = pair.getDNSKEYRecord();
if (keyrec == null) continue;
RRSIGRecord presig = SignUtils.generatePreRRSIG(rrset, keyrec, start, expire,
rrset.getTTL());
byte[] sign_data = SignUtils.generateSigData(rrset_data, presig);
if (mVerboseSigning)
{
log.info("Canonical pre-signature data to sign with key "
+ keyrec.getName().toString() + "/" + keyrec.getAlgorithm() + "/"
+ keyrec.getFootprint() + ":");
log.info(hexdump.dump(null, sign_data));
}
Signature signer = pair.getSigner();
if (signer == null)
{
// debug
log.fine("missing private key that goes with:\n" + pair.getDNSKEYRecord());
throw new GeneralSecurityException("cannot sign without a valid Signer "
+ "(probably missing private key)");
}
// sign the data.
signer.update(sign_data);
byte[] sig = signer.sign();
if (mVerboseSigning)
{
log.info("Raw Signature:");
log.info(hexdump.dump(null, sig));
}
DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance();
// Convert to RFC 2536 format, if necessary.
if (algs.baseType(pair.getDNSKEYAlgorithm()) == DnsKeyAlgorithm.DSA)
{
DSAPublicKey pk = (DSAPublicKey) pair.getPublic();
sig = SignUtils.convertDSASignature(pk.getParams(), sig);
}
// Convert to RFC 6605, etc format
if (pair.getDNSKEYAlgorithm() == DNSSEC.Algorithm.ECDSAP256SHA256 ||
pair.getDNSKEYAlgorithm() == DNSSEC.Algorithm.ECDSAP384SHA384)
{
sig = SignUtils.convertECDSASignature(pair.getDNSKEYAlgorithm(), sig);
}
RRSIGRecord sigrec = SignUtils.generateRRSIG(sig, presig);
if (mVerboseSigning)
{
log.info("RRSIG:\n" + sigrec);
}
sigs.add(sigrec);
}
return sigs;
}
/**
* Create a completely self-signed DNSKEY RRset.
*
* @param keypairs
* the public & private keypairs to use in the keyset.
* @param start
* the RRSIG inception time.
* @param expire
* the RRSIG expiration time.
* @return a signed RRset.
*/
public RRset makeKeySet(List<DnsKeyPair> keypairs, Date start, Date expire)
throws IOException, GeneralSecurityException
{
// Generate a KEY RR set to sign.
RRset keyset = new RRset();
for (DnsKeyPair pair : keypairs)
{
keyset.addRR(pair.getDNSKEYRecord());
}
List<RRSIGRecord> records = signRRset(keyset, keypairs, start, expire);
for (RRSIGRecord r : records)
{
keyset.addRR(r);
}
return keyset;
}
/**
* Conditionally sign an RRset and add it to the toList.
*
* @param toList
* the list to which we are adding the processed RRsets.
* @param zonename
* the zone apex name.
* @param rrset
* the RRset under consideration.
* @param kskpairs
* the List of KSKs..
* @param zskpairs
* the List of zone keys.
* @param start
* the RRSIG inception time.
* @param expire
* the RRSIG expiration time.
* @param fullySignKeyset
* if true, sign the zone apex keyset with both KSKs and ZSKs.
* @param last_cut
* the name of the last delegation point encountered.
*
* @return the name of the new last_cut.
*/
@SuppressWarnings("unchecked")
private Name addRRset(List<Record> toList, Name zonename, RRset rrset,
List<DnsKeyPair> kskpairs, List<DnsKeyPair> zskpairs, Date start,
Date expire, boolean fullySignKeyset, Name last_cut,
Name last_dname) throws IOException, GeneralSecurityException
{
// add the records themselves
for (Iterator<Record> i = rrset.rrs(); i.hasNext();)
{
toList.add(i.next());
}
int type = SignUtils.recordSecType(zonename, rrset.getName(), rrset.getType(),
last_cut, last_dname);
// we don't sign non-normal sets (delegations, glue, invalid).
if (type == SignUtils.RR_DELEGATION)
{
return rrset.getName();
}
if (type == SignUtils.RR_GLUE || type == SignUtils.RR_INVALID)
{
return last_cut;
}
// check for the zone apex keyset.
if (rrset.getName().equals(zonename) && rrset.getType() == Type.DNSKEY)
{
// if we have ksks, sign the keyset with them, otherwise we will just sign
// them with the zsks.
if (kskpairs != null && kskpairs.size() > 0)
{
List<RRSIGRecord> sigs = signRRset(rrset, kskpairs, start, expire);
toList.addAll(sigs);
// If we aren't going to sign with all the keys, bail out now.
if (!fullySignKeyset) return last_cut;
}
}
// otherwise, we are OK to sign this set.
List<RRSIGRecord> sigs = signRRset(rrset, zskpairs, start, expire);
toList.addAll(sigs);
return last_cut;
}
// Various NSEC/NSEC3 generation modes
private static final int NSEC_MODE = 0;
private static final int NSEC3_MODE = 1;
private static final int NSEC3_OPTOUT_MODE = 2;
private static final int NSEC_EXP_OPT_IN = 3;
/**
* Master zone signing method. This method handles all of the different zone
* signing variants (NSEC with or without Opt-In, NSEC3 with or without
* Opt-Out, etc.) External users of this class are expected to use the
* appropriate public signZone* methods instead of this.
*
* @param zonename
* The name of the zone
* @param records
* The records comprising the zone. They do not have to be in any
* particular order, as this method will order them as necessary.
* @param kskpairs
* The key pairs designated as "key signing keys"
* @param zskpairs
* The key pairs designated as "zone signing keys"
* @param start
* The RRSIG inception time
* @param expire
* The RRSIG expiration time
* @param fullySignKeyset
* If true, all keys (ksk or zsk) will sign the DNSKEY RRset. If
* false, only the ksks will sign it.
* @param ds_digest_alg
* The hash algorithm to use for generating DS records
* (DSRecord.SHA1_DIGEST_ID, e.g.)
* @param mode
* The NSEC/NSEC3 generation mode: NSEC_MODE, NSEC3_MODE,
* NSEC3_OPTOUT_MODE, etc.
* @param includedNames
* When using an Opt-In/Opt-Out mode, the names listed here will be
* included in the NSEC/NSEC3 chain regardless
* @param salt
* When using an NSEC3 mode, use this salt.
* @param iterations
* When using an NSEC3 mode, use this number of iterations
* @param beConservative
* If true, then only turn on the Opt-In flag when there are insecure
* delegations in the span. Currently this only works for
* NSEC_EXP_OPT_IN mode.
* @param nsec3paramttl
* The TTL to use for the generated NSEC3PARAM record. Negative
* values will use the SOA TTL.
* @return an ordered list of {@link org.xbill.DNS.Record} objects,
* representing the signed zone.
*
* @throws IOException
* @throws GeneralSecurityException
*/
private List<Record> signZone(Name zonename, List<Record> records,
List<DnsKeyPair> kskpairs, List<DnsKeyPair> zskpairs,
Date start, Date expire, boolean fullySignKeyset,
int ds_digest_alg, int mode, List<Name> includedNames,
byte[] salt, int iterations, long nsec3paramttl,
boolean beConservative) throws IOException,
GeneralSecurityException
{
// Remove any existing generated DNSSEC records (NSEC, NSEC3, NSEC3PARAM,
// RRSIG)
SignUtils.removeGeneratedRecords(zonename, records);
RecordComparator rc = new RecordComparator();
// Sort the zone
Collections.sort(records, rc);
// Remove duplicate records
SignUtils.removeDuplicateRecords(records);
// Generate DS records. This replaces any non-zone-apex DNSKEY RRs with DS
// RRs.
SignUtils.generateDSRecords(zonename, records, ds_digest_alg);
// Generate the NSEC or NSEC3 records based on 'mode'
switch (mode)
{
case NSEC_MODE:
SignUtils.generateNSECRecords(zonename, records);
break;
case NSEC3_MODE:
SignUtils.generateNSEC3Records(zonename, records, salt, iterations, nsec3paramttl);
break;
case NSEC3_OPTOUT_MODE:
SignUtils.generateOptOutNSEC3Records(zonename, records, includedNames, salt,
iterations, nsec3paramttl);
break;
case NSEC_EXP_OPT_IN:
SignUtils.generateOptInNSECRecords(zonename, records, includedNames,
beConservative);
break;
}
// Re-sort so we can assemble into rrsets.
Collections.sort(records, rc);
// Assemble into RRsets and sign.
RRset rrset = new RRset();
ArrayList<Record> signed_records = new ArrayList<Record>();
Name last_cut = null;
Name last_dname = null;
for (ListIterator<Record> i = records.listIterator(); i.hasNext();)
{
Record r = i.next();
// First record
if (rrset.size() == 0)
{
rrset.addRR(r);
continue;
}
// Current record is part of the current RRset.
if (rrset.getName().equals(r.getName()) && rrset.getDClass() == r.getDClass()
&& rrset.getType() == r.getType())
{
rrset.addRR(r);
continue;
}
// Otherwise, we have completed the RRset
// Sign the records
// add the RRset to the list of signed_records, regardless of
// whether or not we actually end up signing the set.
last_cut = addRRset(signed_records, zonename, rrset, kskpairs, zskpairs, start,
expire, fullySignKeyset, last_cut, last_dname);
if (rrset.getType() == Type.DNAME) last_dname = rrset.getName();
rrset.clear();
rrset.addRR(r);
}
// add the last RR set
addRRset(signed_records, zonename, rrset, kskpairs, zskpairs, start, expire,
fullySignKeyset, last_cut, last_dname);
return signed_records;
}
/**
* Given a zone, sign it using standard NSEC records.
*
* @param zonename
* The name of the zone.
* @param records
* The records comprising the zone. They do not have to be in any
* particular order, as this method will order them as necessary.
* @param kskpairs
* The key pairs that are designated as "key signing keys".
* @param zskpairs
* This key pairs that are designated as "zone signing keys".
* @param start
* The RRSIG inception time.
* @param expire
* The RRSIG expiration time.
* @param fullySignKeyset
* Sign the zone apex keyset with all available keys (instead of just
* the key signing keys).
* @param ds_digest_alg
* The digest algorithm to use when generating DS records.
*
* @return an ordered list of {@link org.xbill.DNS.Record} objects,
* representing the signed zone.
*/
public List<Record> signZone(Name zonename, List<Record> records,
List<DnsKeyPair> kskpairs, List<DnsKeyPair> zskpairs,
Date start, Date expire, boolean fullySignKeyset,
int ds_digest_alg) throws IOException,
GeneralSecurityException
{
return signZone(zonename, records, kskpairs, zskpairs, start, expire,
fullySignKeyset, ds_digest_alg, NSEC_MODE, null, null, 0, 0, false);
}
/**
* Given a zone, sign it using NSEC3 records.
*
* @param signer
* A signer (utility) object used to actually sign stuff.
* @param zonename
* The name of the zone being signed.
* @param records
* The records comprising the zone. They do not have to be in any
* particular order, as this method will order them as necessary.
* @param kskpairs
* The key pairs that are designated as "key signing keys".
* @param zskpairs
* This key pairs that are designated as "zone signing keys".
* @param start
* The RRSIG inception time.
* @param expire
* The RRSIG expiration time.
* @param fullySignKeyset
* If true then the DNSKEY RRset will be signed by all available
* keys, if false, only the key signing keys.
* @param useOptOut
* If true, insecure delegations will be omitted from the NSEC3
* chain, and all NSEC3 records will have the Opt-Out flag set.
* @param includedNames
* A list of names to include in the NSEC3 chain regardless.
* @param salt
* The salt to use for the NSEC3 hashing. null means no salt.
* @param iterations
* The number of iterations to use for the NSEC3 hashing.
* @param ds_digest_alg
* The digest algorithm to use when generating DS records.
* @param nsec3paramttl
* The TTL to use for the generated NSEC3PARAM record. Negative
* values will use the SOA TTL.
* @return an ordered list of {@link org.xbill.DNS.Record} objects,
* representing the signed zone.
*
* @throws IOException
* @throws GeneralSecurityException
*/
public List<Record> signZoneNSEC3(Name zonename, List<Record> records,
List<DnsKeyPair> kskpairs, List<DnsKeyPair> zskpairs,
Date start, Date expire, boolean fullySignKeyset,
boolean useOptOut, List<Name> includedNames,
byte[] salt, int iterations, int ds_digest_alg,
long nsec3paramttl) throws IOException,
GeneralSecurityException
{
if (useOptOut)
{
return signZone(zonename, records, kskpairs, zskpairs, start, expire,
fullySignKeyset, ds_digest_alg, NSEC3_OPTOUT_MODE, includedNames,
salt, iterations, nsec3paramttl, false);
}
else
{
return signZone(zonename, records, kskpairs, zskpairs, start, expire,
fullySignKeyset, ds_digest_alg, NSEC3_MODE, null, salt, iterations,
nsec3paramttl, false);
}
}
/**
* Given a zone, sign it using experimental Opt-In NSEC records (see RFC
* 4956).
*
* @param zonename
* the name of the zone.
* @param records
* the records comprising the zone. They do not have to be in any
* particular order, as this method will order them as necessary.
* @param kskpairs
* the key pairs that are designated as "key signing keys".
* @param zskpairs
* this key pairs that are designated as "zone signing keys".
* @param start
* the RRSIG inception time.
* @param expire
* the RRSIG expiration time.
* @param useConservativeOptIn
* if true, Opt-In NSEC records will only be generated if there are
* insecure, unsigned delegations in the span.
* @param fullySignKeyset
* sign the zone apex keyset with all available keys.
* @param ds_digest_alg
* The digest algorithm to use when generating DS records.
* @param NSECIncludeNames
* names that are to be included in the NSEC chain regardless. This
* may be null.
* @return an ordered list of {@link org.xbill.DNS.Record} objects,
* representing the signed zone.
*/
public List<Record> signZoneOptIn(Name zonename, List<Record> records,
List<DnsKeyPair> kskpairs, List<DnsKeyPair> zskpairs,
Date start, Date expire,
boolean useConservativeOptIn,
boolean fullySignKeyset, List<Name> NSECIncludeNames,
int ds_digest_alg) throws IOException,
GeneralSecurityException
{
return signZone(zonename, records, kskpairs, zskpairs, start, expire,
fullySignKeyset, ds_digest_alg, NSEC_EXP_OPT_IN, NSECIncludeNames,
null, 0, 0, useConservativeOptIn);
}
}

View File

@@ -0,0 +1,259 @@
/*
* $Id$
*
* Copyright (c) 2005 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 org.xbill.DNS.*;
import org.xbill.DNS.utils.base16;
import org.xbill.DNS.utils.base32;
/**
* This is a class representing a "prototype NSEC3" resource record. These are
* used as an intermediate stage (in zone signing) between determining the list
* of NSEC3 records and forming them into a viable chain.
*
* @author David Blacka (original)
* @author $Author: davidb $
* @version $Revision: 183 $
*/
public class ProtoNSEC3
{
private Name originalOwner;
private int hashAlg;
private byte flags;
private int iterations;
private byte[] salt;
private byte[] next;
private byte[] owner; // cached numerical owner value.
private TypeMap typemap;
private Name zone;
private Name name;
private int dclass;
private long ttl;
private static final base32 b32 = new base32(base32.Alphabet.BASE32HEX, false, false);
/**
* Creates an NSEC3 Record from the given data.
*/
public ProtoNSEC3(byte[] owner, Name originalOwner, Name zone, int dclass, long ttl,
int hashAlg, byte flags, int iterations, byte[] salt, byte[] next,
TypeMap typemap)
{
this.zone = zone;
this.owner = owner;
this.dclass = dclass;
this.ttl = ttl;
this.hashAlg = hashAlg;
this.flags = flags;
this.iterations = iterations;
this.salt = salt;
this.next = next;
this.typemap = typemap;
this.originalOwner = originalOwner;
}
public ProtoNSEC3(byte[] owner, Name originalOwner, Name zone, int dclass, long ttl,
int hashAlg, byte flags, int iterations, byte[] salt, byte[] next,
int[] types)
{
this(owner, originalOwner, zone, dclass, ttl, hashAlg, flags, iterations, salt, next,
TypeMap.fromTypes(types));
}
private String hashToString(byte[] hash)
{
if (hash == null) return null;
return b32.toString(hash);
}
public Name getName()
{
if (name == null)
{
try
{
name = new Name(hashToString(owner), zone);
}
catch (TextParseException e)
{
// This isn't going to happen.
}
}
return name;
}
public byte[] getNext()
{
return next;
}
public void setNext(byte[] next)
{
this.next = next;
}
public byte getFlags()
{
return flags;
}
public boolean getOptOutFlag()
{
return (flags & NSEC3Record.Flags.OPT_OUT) != 0;
}
public void setOptOutFlag(boolean optOutFlag)
{
if (optOutFlag)
this.flags |= NSEC3Record.Flags.OPT_OUT;
else
this.flags &= ~NSEC3Record.Flags.OPT_OUT;
}
public long getTTL()
{
return ttl;
}
public void setTTL(long ttl)
{
this.ttl = ttl;
}
public TypeMap getTypemap()
{
return typemap;
}
public int[] getTypes()
{
return typemap.getTypes();
}
public void setTypemap(TypeMap typemap)
{
this.typemap = typemap;
}
public int getDClass()
{
return dclass;
}
public int getHashAlgorithm()
{
return hashAlg;
}
public int getIterations()
{
return iterations;
}
public byte[] getOwner()
{
return owner;
}
public byte[] getSalt()
{
return salt;
}
public Name getZone()
{
return zone;
}
public NSEC3Record getNSEC3Record()
{
String comment = (originalOwner == null) ? "(unknown original ownername)"
: originalOwner.toString();
return new NSEC3Record(getName(), dclass, ttl, hashAlg, flags, iterations, salt,
next, getTypes(), comment);
}
public void mergeTypes(TypeMap new_types)
{
int[] nt = new_types.getTypes();
for (int i = 0; i < nt.length; i++)
{
if (!typemap.get(nt[i])) typemap.set(nt[i]);
}
}
public int compareTo(ProtoNSEC3 o)
{
if (o == null) return 1;
byte[] o_owner = o.getOwner();
int len = owner.length < o_owner.length ? o_owner.length : owner.length;
for (int i = 0; i < len; i++)
{
int d = ((owner[i] & 0xFF) - (o_owner[i] & 0xFF));
if (d != 0) return d;
}
return owner.length - o_owner.length;
}
public String toString()
{
StringBuffer sb = new StringBuffer();
sb.append(getName());
sb.append(' ');
sb.append(ttl);
sb.append(' ');
sb.append(DClass.string(dclass));
sb.append(" NSEC3 ");
sb.append(flags);
sb.append(' ');
sb.append(hashAlg);
sb.append(' ');
sb.append(iterations);
sb.append(' ');
sb.append(salt == null ? "-" : base16.toString(salt));
sb.append(' ');
String nextstr = (next == null) ? "(null)" : b32.toString(next);
sb.append(nextstr);
sb.append(' ');
sb.append(typemap.toString());
return sb.toString();
}
public static class Comparator implements java.util.Comparator<ProtoNSEC3>
{
public int compare(ProtoNSEC3 a, ProtoNSEC3 b)
{
return a.compareTo(b);
}
}
}

View File

@@ -0,0 +1,109 @@
// $Id$
//
// Copyright (C) 2000-2003 Network Solutions, Inc.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
// USA
package com.verisignlabs.dnssec.security;
import java.util.Comparator;
import org.xbill.DNS.RRSIGRecord;
import org.xbill.DNS.Record;
import org.xbill.DNS.Type;
/**
* This class implements a comparison operator for {@link org.xbill.DNS.Record}
* objects. It imposes a canonical order consistent with DNSSEC. It does not put
* records within a RRset into canonical order: see {@link ByteArrayComparator}.
*
* @author David Blacka (original)
* @author $Author$
* @version $Revision$
*/
public class RecordComparator implements Comparator<Record>
{
public RecordComparator()
{
}
/**
* In general, types are compared numerically. However, SOA, NS, and DNAME are ordered
* before the rest.
*/
private int compareTypes(int a, int b)
{
if (a == b) return 0;
if (a == Type.SOA) return -1;
if (b == Type.SOA) return 1;
if (a == Type.NS) return -1;
if (b == Type.NS) return 1;
if (a == Type.DNAME) return -1;
if (b == Type.DNAME) return 1;
if (a < b) return -1;
return 1;
}
private int compareRDATA(Record a, Record b)
{
byte[] a_rdata = a.rdataToWireCanonical();
byte[] b_rdata = b.rdataToWireCanonical();
for (int i = 0; i < a_rdata.length && i < b_rdata.length; i++)
{
int n = (a_rdata[i] & 0xFF) - (b_rdata[i] & 0xFF);
if (n != 0) return n;
}
return (a_rdata.length - b_rdata.length);
}
public int compare(Record a, Record b)
{
if (a == null && b == null) return 0;
if (a == null) return 1;
if (b == null) return -1;
int res = a.getName().compareTo(b.getName());
if (res != 0) return res;
int a_type = a.getType();
int b_type = b.getType();
int sig_type = 0;
if (a_type == Type.RRSIG)
{
a_type = ((RRSIGRecord) a).getTypeCovered();
if (b_type != Type.RRSIG) sig_type = 1;
}
if (b_type == Type.RRSIG)
{
b_type = ((RRSIGRecord) b).getTypeCovered();
if (a.getType() != Type.RRSIG) sig_type = -1;
}
res = compareTypes(a_type, b_type);
if (res != 0) return res;
if (sig_type != 0) return sig_type;
return compareRDATA(a, b);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,210 @@
// $Id$
//
// Copyright (C) 2004 Verisign, Inc.
package com.verisignlabs.dnssec.security;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.xbill.DNS.DNSOutput;
import org.xbill.DNS.Type;
/**
* This class represents the multiple type maps of the NSEC record. Currently it
* is just used to convert the wire format type map to the int array that
* org.xbill.DNS.NSECRecord uses. Note that there is now a very similar class in
* DNSjava: {@link org.xbill.DNS.TypeBitmap}.
*/
public class TypeMap
{
private static final Integer[] integerArray = new Integer[0];
private static final byte[] emptyBitmap = new byte[0];
private Set<Integer> typeSet;
public TypeMap()
{
this.typeSet = new HashSet<Integer>();
}
/** Add the given type to the typemap. */
public void set(int type)
{
typeSet.add(type);
}
/** Remove the given type from the type map. */
public void clear(int type)
{
typeSet.remove(type);
}
/** @return true if the given type is present in the type map. */
public boolean get(int type)
{
return typeSet.contains(type);
}
/**
* Given an array of DNS type code, construct a TypeMap object.
*/
public static TypeMap fromTypes(int[] types)
{
TypeMap m = new TypeMap();
if (types == null) return m;
for (int i = 0; i < types.length; i++)
{
m.set(types[i]);
}
return m;
}
/**
* Given an array of bytes representing a wire-format type map, construct the
* TypeMap object.
*/
public static TypeMap fromBytes(byte[] map)
{
int m = 0;
TypeMap typemap = new TypeMap();
int page;
int byte_length;
while (m < map.length)
{
page = map[m++];
byte_length = map[m++];
for (int i = 0; i < byte_length; i++)
{
for (int j = 0; j < 8; j++)
{
if ((map[m + i] & (1 << (7 - j))) != 0)
{
typemap.set((page << 8) + (i * 8) + j);
}
}
}
m += byte_length;
}
return typemap;
}
/**
* Given list of type mnemonics, construct a TypeMap object.
*/
public static TypeMap fromString(String types)
{
TypeMap typemap = new TypeMap();
for (String type : types.split("\\s+"))
{
typemap.set(Type.value(type));
}
return typemap;
}
/** @return the normal string representation of the typemap. */
public String toString()
{
int[] types = getTypes();
Arrays.sort(types);
StringBuffer sb = new StringBuffer();
for (int i = 0; i < types.length; i++)
{
if (i > 0) sb.append(" ");
sb.append(Type.string(types[i]));
}
return sb.toString();
}
protected static void mapToWire(DNSOutput out, int[] types, int base, int start, int end)
{
// calculate the length of this map by looking at the largest
// typecode in this section.
int max_type = types[end - 1] & 0xFF;
int map_length = (max_type / 8) + 1;
// write the map "header" -- the base and the length of the map.
out.writeU8(base & 0xFF);
out.writeU8(map_length & 0xFF);
// allocate a temporary scratch space for caculating the actual
// bitmap.
byte[] map = new byte[map_length];
// for each type in our sub-array, set its corresponding bit in the map.
for (int i = start; i < end; i++)
{
map[(types[i] & 0xFF) / 8] |= (1 << (7 - types[i] % 8));
}
// write out the resulting binary bitmap.
for (int i = 0; i < map.length; i++)
{
out.writeU8(map[i]);
}
}
public byte[] toWire()
{
int[] types = getTypes();
if (types.length == 0) return emptyBitmap;
Arrays.sort(types);
int mapbase = -1;
int mapstart = -1;
DNSOutput out = new DNSOutput();
for (int i = 0; i < types.length; i++)
{
int base = (types[i] >> 8) & 0xFF;
if (base == mapbase) continue;
if (mapstart >= 0)
{
mapToWire(out, types, mapbase, mapstart, i);
}
mapbase = base;
mapstart = i;
}
mapToWire(out, types, mapbase, mapstart, types.length);
return out.toByteArray();
}
public int[] getTypes()
{
Integer[] a = (Integer[]) typeSet.toArray(integerArray);
int[] res = new int[a.length];
for (int i = 0; i < res.length; i++)
{
res[i] = a[i].intValue();
}
return res;
}
public static int[] fromWireToTypes(byte[] wire_fmt)
{
return TypeMap.fromBytes(wire_fmt).getTypes();
}
public static byte[] fromTypesToWire(int[] types)
{
return TypeMap.fromTypes(types).toWire();
}
}

View File

@@ -0,0 +1,169 @@
// $Id$
//
// Copyright (C) 2003 VeriSign, Inc.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
// USA
package com.verisignlabs.dnssec.security;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.xbill.DNS.Master;
import org.xbill.DNS.Name;
import org.xbill.DNS.RRset;
import org.xbill.DNS.Record;
import org.xbill.DNS.Type;
/**
* This class contains a bunch of utility methods that are generally useful in
* manipulating zones.
*
* @author David Blacka (original)
* @author $Author$
* @version $Revision$
*/
public class ZoneUtils
{
/**
* Load a zone file.
*
* @param zonefile
* the filename/path of the zonefile to read.
* @param origin
* the origin to use for the zonefile (may be null if the origin is
* specified in the zone file itself).
* @return a {@link java.util.List} of {@link org.xbill.DNS.Record} objects.
* @throws IOException
* if something goes wrong reading the zone file.
*/
public static List<Record> readZoneFile(String zonefile, Name origin) throws IOException
{
ArrayList<Record> records = new ArrayList<Record>();
Master m;
if (zonefile.equals("-"))
{
m = new Master(System.in);
}
else
{
m = new Master(zonefile, origin);
}
Record r = null;
while ((r = m.nextRecord()) != null)
{
records.add(r);
}
return records;
}
/**
* Write the records out into a zone file.
*
* @param records
* a {@link java.util.List} of {@link org.xbill.DNS.Record} objects
* forming a zone.
* @param zonefile
* the file to write to. If null or equal to "-", System.out is used.
*/
public static void writeZoneFile(List<Record> records, String zonefile) throws IOException
{
PrintWriter out = null;
if (zonefile == null || zonefile.equals("-"))
{
out = new PrintWriter(System.out);
}
else
{
out = new PrintWriter(new BufferedWriter(new FileWriter(zonefile)));
}
for (Record r : records)
{
out.println(r);
}
out.close();
}
/**
* Given just the list of records, determine the zone name (origin).
*
* @param records
* a list of {@link org.xbill.DNS.Record} objects.
* @return the zone name, if found. null if one couldn't be found.
*/
public static Name findZoneName(List<Record> records)
{
for (Record r : records)
{
int type = r.getType();
if (type == Type.SOA) return r.getName();
}
return null;
}
public static List<Record> findRRs(List<Record> records, Name name, int type)
{
List<Record> res = new ArrayList<Record>();
for (Record r : records)
{
if (r.getName().equals(name) && r.getType() == type)
{
res.add(r);
}
}
return res;
}
/** This is an alternate way to format an RRset into a string */
@SuppressWarnings("unchecked")
public static String rrsetToString(RRset rrset, boolean includeSigs)
{
StringBuilder out = new StringBuilder();
for (Iterator<Record> i = rrset.rrs(false); i.hasNext();)
{
Record r = i.next();
out.append(r.toString());
out.append("\n");
}
if (includeSigs)
{
for (Iterator<Record> i = rrset.sigs(); i.hasNext();)
{
Record r = i.next();
out.append(r.toString());
out.append("\n");
}
}
return out.toString();
}
}

View File

@@ -0,0 +1,778 @@
// $Id: DnsSecVerifier.java 172 2009-08-23 19:13:42Z davidb $
//
// Copyright (C) 2010 Verisign, Inc.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
// USA
package com.verisignlabs.dnssec.security;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.logging.Logger;
import org.xbill.DNS.DNSKEYRecord;
import org.xbill.DNS.NSEC3PARAMRecord;
import org.xbill.DNS.NSEC3Record;
import org.xbill.DNS.NSECRecord;
import org.xbill.DNS.Name;
import org.xbill.DNS.RRSIGRecord;
import org.xbill.DNS.RRset;
import org.xbill.DNS.Record;
import org.xbill.DNS.TextParseException;
import org.xbill.DNS.Type;
import org.xbill.DNS.utils.base32;
/**
* A class for whole zone DNSSEC verification. Along with cryptographically
* verifying signatures, this class will also detect invalid NSEC and NSEC3
* chains.
*
* @author David Blacka (original)
* @author $Author: davidb $
* @version $Revision: 172 $
*/
public class ZoneVerifier
{
private SortedMap<Name, Set<Integer>> mNodeMap;
private HashMap<String, RRset> mRRsetMap;
private SortedMap<Name, MarkRRset> mNSECMap;
private SortedMap<Name, MarkRRset> mNSEC3Map;
private Name mZoneName;
private DNSSECType mDNSSECType;
private NSEC3PARAMRecord mNSEC3params;
private boolean mIgnoreDuplicateRRs;
private DnsSecVerifier mVerifier;
private base32 mBase32;
private ByteArrayComparator mBAcmp;
private Logger log = Logger.getLogger("ZoneVerifier");
// The various types of signed zones.
enum DNSSECType
{
UNSIGNED, NSEC, NSEC3, NSEC3_OPTOUT;
}
// The types of nodes (a node consists of all RRs with the same name).
enum NodeType
{
NORMAL, DELEGATION, GLUE;
}
/**
* This is a subclass of {@link org.xbill.DNS.RRset} that adds a "mark".
*/
private class MarkRRset extends RRset
{
private static final long serialVersionUID = 1L;
private boolean mIsMarked = false;
boolean getMark()
{
return mIsMarked;
}
void setMark(boolean value)
{
mIsMarked = value;
}
}
public ZoneVerifier()
{
mVerifier = new DnsSecVerifier();
mBase32 = new base32(base32.Alphabet.BASE32HEX, false, true);
mBAcmp = new ByteArrayComparator();
mIgnoreDuplicateRRs = false;
}
/** @return the DnsSecVerifier object used to verify individual RRsets. */
public DnsSecVerifier getVerifier()
{
return mVerifier;
}
public void setIgnoreDuplicateRRs(boolean value)
{
mIgnoreDuplicateRRs = value;
}
private static String key(Name n, int type)
{
return n.toString() + ':' + type;
}
@SuppressWarnings("rawtypes")
private boolean addRRtoRRset(RRset rrset, Record rr)
{
if (mIgnoreDuplicateRRs)
{
rrset.addRR(rr);
return true;
}
Iterator i = (rr instanceof RRSIGRecord) ? rrset.sigs() : rrset.rrs();
for ( ; i.hasNext(); )
{
Record record = (Record) i.next();
if (rr.equals(record)) return false;
}
rrset.addRR(rr);
return true;
}
/**
* Add a record to the various maps.
* @return TODO
*/
private boolean addRR(Record r)
{
Name r_name = r.getName();
int r_type = r.getType();
if (r_type == Type.RRSIG) r_type = ((RRSIGRecord) r).getTypeCovered();
// Add NSEC and NSEC3 RRs to their respective maps
if (r_type == Type.NSEC)
{
if (mNSECMap == null) mNSECMap = new TreeMap<Name, MarkRRset>();
MarkRRset rrset = mNSECMap.get(r_name);
if (rrset == null)
{
rrset = new MarkRRset();
mNSECMap.put(r_name, rrset);
}
return addRRtoRRset(rrset, r);
}
if (r_type == Type.NSEC3)
{
if (mNSEC3Map == null) mNSEC3Map = new TreeMap<Name, MarkRRset>();
MarkRRset rrset = mNSEC3Map.get(r_name);
if (rrset == null)
{
rrset = new MarkRRset();
mNSEC3Map.put(r_name, rrset);
}
return addRRtoRRset(rrset, r);
}
// Add the name and type to the node map
Set<Integer> typeset = mNodeMap.get(r_name);
if (typeset == null)
{
typeset = new HashSet<Integer>();
mNodeMap.put(r_name, typeset);
}
typeset.add(r.getType()); // add the original type
// Add the record to the RRset map
String k = key(r_name, r_type);
RRset rrset = mRRsetMap.get(k);
if (rrset == null)
{
rrset = new RRset();
mRRsetMap.put(k, rrset);
}
return addRRtoRRset(rrset, r);
}
/**
* Given a record, determine the DNSSEC signing type. If the record doesn't
* determine that, DNSSECType.UNSIGNED is returned
*/
private DNSSECType determineDNSSECType(Record r)
{
if (r.getType() == Type.NSEC) return DNSSECType.NSEC;
if (r.getType() == Type.NSEC3)
{
NSEC3Record nsec3 = (NSEC3Record) r;
if ((nsec3.getFlags() & NSEC3Record.Flags.OPT_OUT) == NSEC3Record.Flags.OPT_OUT)
{
return DNSSECType.NSEC3_OPTOUT;
}
return DNSSECType.NSEC3;
}
return DNSSECType.UNSIGNED;
}
/**
* Given an unsorted list of records, load the node and rrset maps, as well as
* determine the NSEC3 parameters and signing type.
*
* @param records
* @return TODO
*/
private int calculateNodes(List<Record> records)
{
mNodeMap = new TreeMap<Name, Set<Integer>>();
mRRsetMap = new HashMap<String, RRset>();
// The zone is unsigned until we get a clue otherwise.
mDNSSECType = DNSSECType.UNSIGNED;
int errors = 0;
for (Record r : records)
{
Name r_name = r.getName();
int r_type = r.getType();
// Add the record to the various maps.
boolean res = addRR(r);
if (!res)
{
log.warning("Record '" + r + "' detected as a duplicate");
errors++;
}
// Learn some things about the zone as we do this pass.
if (r_type == Type.SOA) mZoneName = r_name;
if (r_type == Type.NSEC3PARAM) mNSEC3params = (NSEC3PARAMRecord) r;
if (r_type == Type.DNSKEY) {
DNSKEYRecord dnskey = (DNSKEYRecord) r;
mVerifier.addTrustedKey(dnskey);
log.info("Adding trusted key: " + dnskey + " ; keytag = "
+ dnskey.getFootprint());
}
if (mDNSSECType == DNSSECType.UNSIGNED) mDNSSECType = determineDNSSECType(r);
}
return errors;
}
/**
* Given a name, typeset, and name of the last zone cut, determine the node
* type.
*/
private NodeType determineNodeType(Name n, Set<Integer> typeset, Name last_cut)
{
// All RRs at the zone apex are normal
if (n.equals(mZoneName)) return NodeType.NORMAL;
// If the node is not below the zone itself, we will treat it as glue (it is really junk).
if (!n.subdomain(mZoneName))
{
return NodeType.GLUE;
}
// If the node is below a zone cut (either a delegation or DNAME), it is
// glue.
if (last_cut != null && n.subdomain(last_cut) && !n.equals(last_cut))
{
return NodeType.GLUE;
}
// If the node has a NS record it is a delegation.
if (typeset.contains(new Integer(Type.NS))) return NodeType.DELEGATION;
return NodeType.NORMAL;
}
private Set<Integer> cleanupDelegationTypeset(Set<Integer> typeset)
{
Set<Integer> t = new HashSet<Integer>();
if (typeset.contains(Type.NS)) t.add(Type.NS);
if (typeset.contains(Type.DS)) t.add(Type.DS);
if (typeset.contains(Type.RRSIG)) t.add(Type.RRSIG);
if (!typeset.equals(t)) return t;
return typeset;
}
/**
* For each node, determine which RRsets should be signed, verify those, and
* determine which nodes get NSEC or NSEC3 RRs and verify those.
*/
private int processNodes() throws NoSuchAlgorithmException, TextParseException
{
int errors = 0;
Name last_cut = null;
for (Map.Entry<Name, Set<Integer>> entry : mNodeMap.entrySet())
{
Name n = entry.getKey();
Set<Integer> typeset = entry.getValue();
NodeType ntype = determineNodeType(n, typeset, last_cut);
log.finest("Node " + n + " is type " + ntype);
// we can ignore glue/invalid RRs.
if (ntype == NodeType.GLUE) continue;
// record the last zone cut if this node is a zone cut.
if (ntype == NodeType.DELEGATION || typeset.contains(Type.DNAME))
{
last_cut = n;
}
// check all of the RRsets that should be signed
for (int type : typeset)
{
if (type == Type.RRSIG) continue;
// at delegation points, only DS RRs are signed (and NSEC, but those are
// checked separately)
if (ntype == NodeType.DELEGATION && type != Type.DS) continue;
// otherwise, verify the RRset.
String k = key(n, type);
RRset rrset = mRRsetMap.get(k);
errors += processRRset(rrset);
}
// cleanup the typesets of delegation nodes.
// the only types that should be there are NS, DS and RRSIG.
if (ntype == NodeType.DELEGATION)
{
typeset = cleanupDelegationTypeset(typeset);
}
switch (mDNSSECType)
{
case NSEC:
// all nodes with NSEC records have NSEC and RRSIG types
typeset.add(Type.NSEC);
typeset.add(Type.RRSIG);
errors += processNSEC(n, typeset);
break;
case NSEC3:
errors += processNSEC3(n, typeset, ntype);
break;
case NSEC3_OPTOUT:
if (ntype == NodeType.NORMAL
|| (ntype == NodeType.DELEGATION && typeset.contains(Type.DS)))
{
errors += processNSEC3(n, typeset, ntype);
}
break;
}
}
return errors;
}
private static String reasonListToString(List<String> reasons)
{
if (reasons == null) return "";
StringBuffer out = new StringBuffer();
for (Iterator<String> i = reasons.iterator(); i.hasNext();)
{
out.append("Reason: ");
out.append(i.next());
if (i.hasNext()) out.append("\n");
}
return out.toString();
}
@SuppressWarnings("unchecked")
private int processRRset(RRset rrset)
{
List<String> reasons = new ArrayList<String>();
boolean result = false;
for (Iterator<Record> i = rrset.sigs(); i.hasNext();)
{
RRSIGRecord sigrec = (RRSIGRecord) i.next();
boolean res = mVerifier.verifySignature(rrset, sigrec, reasons);
if (!res)
{
log.warning("Signature failed to verify RRset:\n rr: "
+ ZoneUtils.rrsetToString(rrset, false) + "\n sig: " + sigrec + "\n"
+ reasonListToString(reasons));
}
if (res) result = res;
}
String rrsetname = rrset.getName() + "/" + Type.string(rrset.getType());
if (result)
{
log.fine("RRset " + rrsetname + " verified.");
}
else
{
log.warning("RRset " + rrsetname + " did not verify.");
}
return result ? 0 : 1;
}
private String typesToString(int[] types)
{
StringBuilder sb = new StringBuilder();
Arrays.sort(types);
for (int i = 0; i < types.length; ++i)
{
if (i != 0) sb.append(' ');
sb.append(Type.string(types[i]));
}
return sb.toString();
}
private String typesetToString(Set<Integer> typeset)
{
if (typeset == null) return "";
int[] types = new int[typeset.size()];
int i = 0;
for (int type : typeset)
{
types[i++] = type;
}
return typesToString(types);
}
private boolean checkTypeMap(Set<Integer> typeset, int[] types)
{
// a null typeset means that we are expecting the typemap of an ENT, which
// should be empty.
if (typeset == null) return types.length == 0;
Set<Integer> compareTypeset = new HashSet<Integer>();
for (int i = 0; i < types.length; ++i)
{
compareTypeset.add(types[i]);
}
return typeset.equals(compareTypeset);
}
private int processNSEC(Name n, Set<Integer> typeset)
{
MarkRRset rrset = mNSECMap.get(n);
if (n == null)
{
log.warning("Missing NSEC for " + n);
return 1;
}
int errors = 0;
rrset.setMark(true);
NSECRecord nsec = (NSECRecord) rrset.first();
// check typemap
if (!checkTypeMap(typeset, nsec.getTypes()))
{
log.warning("Typemap for NSEC RR " + n
+ " did not match what was expected. Expected '" + typesetToString(typeset)
+ "', got '" + typesToString(nsec.getTypes()));
errors++;
}
// verify rrset
errors += processRRset(rrset);
return errors;
}
private boolean shouldCheckENTs(Name n, Set<Integer> typeset, NodeType ntype)
{
// if we are just one (or zero) labels longer than the zonename, the node
// can't create a ENT
if (n.labels() <= mZoneName.labels() + 1) return false;
// we probably won't ever get called for a GLUE node
if (ntype == NodeType.GLUE) return false;
// if we aren't doing opt-out, then all possible ENTs must be checked.
if (mDNSSECType == DNSSECType.NSEC3) return true;
// if we are opt-out, and the node is an insecure delegation, don't check
// ENTs.
if (ntype == NodeType.DELEGATION && !typeset.contains(Type.DS))
{
return false;
}
// otherwise, check ENTs.
return true;
}
private int processNSEC3(Name n, Set<Integer> typeset, NodeType ntype)
throws NoSuchAlgorithmException, TextParseException
{
// calculate the NSEC3 RR name
byte[] hash = mNSEC3params.hashName(n);
String hashstr = mBase32.toString(hash);
Name hashname = new Name(hashstr, mZoneName);
MarkRRset rrset = mNSEC3Map.get(hashname);
if (rrset == null)
{
log.warning("Missing NSEC3 for " + hashname + " corresponding to " + n);
return 1;
}
int errors = 0;
rrset.setMark(true);
NSEC3Record nsec3 = (NSEC3Record) rrset.first();
// check typemap
if (!checkTypeMap(typeset, nsec3.getTypes()))
{
log.warning("Typemap for NSEC3 RR " + hashname + " for " + n
+ " did not match what was expected. Expected '" + typesetToString(typeset)
+ "', got '" + typesToString(nsec3.getTypes()) + "'");
errors++;
}
// verify rrset
errors += processRRset(rrset);
// check NSEC3 RRs for empty non-terminals.
// this is recursive.
if (shouldCheckENTs(n, typeset, ntype))
{
Name ent = new Name(n, 1);
if (mNodeMap.get(ent) == null)
{
errors += processNSEC3(ent, null, NodeType.NORMAL);
}
}
return errors;
}
private int processNSECChain()
{
int errors = 0;
NSECRecord lastNSEC = null;
for (Iterator<Map.Entry<Name, MarkRRset>> i = mNSECMap.entrySet().iterator(); i.hasNext();)
{
// check the internal ordering of the previous NSEC record. This avoids
// looking at the last one,
// which is different.
if (lastNSEC != null)
{
if (lastNSEC.getName().compareTo(lastNSEC.getNext()) >= 0)
{
log.warning("NSEC for " + lastNSEC.getName()
+ " has next name >= owner but is not the last NSEC in the chain.");
errors++;
}
}
Map.Entry<Name, MarkRRset> entry = i.next();
Name n = entry.getKey();
MarkRRset rrset = entry.getValue();
// check to see if the NSEC is marked. If not, it was not correlated to a
// signed node.
if (!rrset.getMark())
{
log.warning("NSEC RR for " + n + " appears to be extra.");
errors++;
}
NSECRecord nsec = (NSECRecord) rrset.first();
// This is just a sanity check. If this isn't true, we are constructing
// the
// nsec map incorrectly.
if (!n.equals(nsec.getName()))
{
log.warning("The NSEC in the map for name " + n + " has name " + nsec.getName());
errors++;
}
// If this is the first row, ensure that the owner name equals the zone
// name
if (lastNSEC == null && !n.equals(mZoneName))
{
log.warning("The first NSEC in the chain does not match the zone name: name = "
+ n + " zonename = " + mZoneName);
errors++;
}
// Check that the prior NSEC's next name equals this rows owner name.
if (lastNSEC != null)
{
if (!lastNSEC.getNext().equals(nsec.getName()))
{
log.warning("NSEC for " + lastNSEC.getName()
+ " does not point to the next NSEC in the chain: " + n);
errors++;
}
}
lastNSEC = nsec;
}
// check the internal ordering of the last NSEC in the chain
// the ownername should be >= next name.
if (lastNSEC.getName().compareTo(lastNSEC.getNext()) < 0)
{
log.warning("The last NSEC RR in the chain did not have an owner >= next: owner = "
+ lastNSEC.getName() + " next = " + lastNSEC.getNext());
errors++;
}
// check to make sure it links to the first NSEC in the chain
if (!lastNSEC.getNext().equals(mZoneName))
{
log.warning("The last NSEC RR in the chain did not link to the first NSEC");
errors++;
}
return errors;
}
private int compareNSEC3Hashes(Name owner, byte[] hash)
{
// we will compare the binary images
String ownerhashstr = owner.getLabelString(0);
byte[] ownerhash = mBase32.fromString(ownerhashstr);
return mBAcmp.compare(ownerhash, hash);
}
private int processNSEC3Chain()
{
int errors = 0;
NSEC3Record lastNSEC3 = null;
NSEC3Record firstNSEC3 = null;
for (Iterator<Map.Entry<Name, MarkRRset>> i = mNSEC3Map.entrySet().iterator(); i.hasNext();)
{
// check the internal ordering of the previous NSEC3 record. This avoids
// looking at the last one,
// which is different.
if (lastNSEC3 != null)
{
if (compareNSEC3Hashes(lastNSEC3.getName(), lastNSEC3.getNext()) >= 0)
{
log.warning("NSEC3 for " + lastNSEC3.getName()
+ " has next name >= owner but is not the last NSEC3 in the chain.");
errors++;
}
}
Map.Entry<Name, MarkRRset> entry = i.next();
Name n = entry.getKey();
MarkRRset rrset = entry.getValue();
// check to see if the NSEC is marked. If not, it was not correlated to a
// signed node.
if (!rrset.getMark())
{
log.warning("NSEC3 RR for " + n + " appears to be extra.");
errors++;
}
NSEC3Record nsec3 = (NSEC3Record) rrset.first();
// This is just a sanity check. If this isn't true, we are constructing
// the
// nsec3 map incorrectly.
if (!n.equals(nsec3.getName()))
{
log.severe("The NSEC3 in the map for name " + n + " has name " + nsec3.getName());
errors++;
}
// note the first NSEC3 in the chain.
if (lastNSEC3 == null)
{
firstNSEC3 = nsec3;
}
else
// Check that the prior NSEC3's next hashed name equals this row's hashed
// owner name.
{
if (compareNSEC3Hashes(nsec3.getName(), lastNSEC3.getNext()) != 0)
{
String nextstr = mBase32.toString(lastNSEC3.getNext());
log.warning("NSEC3 for " + lastNSEC3.getName()
+ " does not point to the next NSEC3 in the chain: " + nsec3.getName()
+ ", instead points to: " + nextstr);
errors++;
}
}
lastNSEC3 = nsec3;
}
// check the internal ordering of the last NSEC in the chain
// the ownername should be >= next name.
if (compareNSEC3Hashes(lastNSEC3.getName(), lastNSEC3.getNext()) < 0)
{
String nextstr = mBase32.toString(lastNSEC3.getNext());
log.warning("The last NSEC3 RR in the chain did not have an owner >= next: owner = "
+ lastNSEC3.getName() + " next = " + nextstr);
errors++;
}
// check to make sure it links to the first NSEC in the chain
if (compareNSEC3Hashes(firstNSEC3.getName(), lastNSEC3.getNext()) != 0)
{
log.warning("The last NSEC3 RR in the chain did not link to the first NSEC3");
errors++;
}
return errors;
}
public int verifyZone(List<Record> records) throws NoSuchAlgorithmException, TextParseException
{
int errors = 0;
errors += calculateNodes(records);
errors += processNodes();
if (mDNSSECType == DNSSECType.NSEC)
{
errors += processNSECChain();
}
else if (mDNSSECType == DNSSECType.NSEC3 || mDNSSECType == DNSSECType.NSEC3_OPTOUT)
{
errors += processNSEC3Chain();
}
if (errors > 0)
{
log.info("Zone " + mZoneName + " failed verification with " + errors + " errors");
}
else
{
log.info("Zone " + mZoneName + " verified with 0 errors");
}
return errors;
}
}

View File

@@ -0,0 +1,35 @@
<body bgcolor="#FFFFFF">
This package contains a number of utility classes that can be used to
implement DNSSEC tool (key generation, zone signing, etc.)
functionality.
<p>
<center>
<table width="90%" border=1 cellpadding=10 cellspacing=0>
<tr><td class="TableRowColor">
Copyright &copy; 2003-2005 Verisign, Inc. by
<a href="mailto:davidb@verisignlabs.com">David Blacka</a><P>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.<P>
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.<P>
You should have received a copy of the
<a href="http://www.gnu.org/copyleft/lgpl.html">GNU Library General Public License</a>
along with this library; if not, write to the
Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.
</td></tr>
</table><P>
</center>
</body>