Added support for gradle and restructured the source code directory from src to src/main/java directory
This commit is contained in:
325
src/main/java/com/verisignlabs/dnssec/cl/CLBase.java
Normal file
325
src/main/java/com/verisignlabs/dnssec/cl/CLBase.java
Normal file
@@ -0,0 +1,325 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
136
src/main/java/com/verisignlabs/dnssec/cl/DSTool.java
Normal file
136
src/main/java/com/verisignlabs/dnssec/cl/DSTool.java
Normal 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);
|
||||
}
|
||||
}
|
||||
226
src/main/java/com/verisignlabs/dnssec/cl/KeyGen.java
Normal file
226
src/main/java/com/verisignlabs/dnssec/cl/KeyGen.java
Normal 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);
|
||||
}
|
||||
}
|
||||
121
src/main/java/com/verisignlabs/dnssec/cl/KeyInfoTool.java
Normal file
121
src/main/java/com/verisignlabs/dnssec/cl/KeyInfoTool.java
Normal 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);
|
||||
}
|
||||
}
|
||||
393
src/main/java/com/verisignlabs/dnssec/cl/SignKeyset.java
Normal file
393
src/main/java/com/verisignlabs/dnssec/cl/SignKeyset.java
Normal 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);
|
||||
}
|
||||
}
|
||||
361
src/main/java/com/verisignlabs/dnssec/cl/SignRRset.java
Normal file
361
src/main/java/com/verisignlabs/dnssec/cl/SignRRset.java
Normal file
@@ -0,0 +1,361 @@
|
||||
// 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 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>");
|
||||
|
||||
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 ((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();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
755
src/main/java/com/verisignlabs/dnssec/cl/SignZone.java
Normal file
755
src/main/java/com/verisignlabs/dnssec/cl/SignZone.java
Normal 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);
|
||||
}
|
||||
}
|
||||
166
src/main/java/com/verisignlabs/dnssec/cl/VerifyZone.java
Normal file
166
src/main/java/com/verisignlabs/dnssec/cl/VerifyZone.java
Normal 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);
|
||||
}
|
||||
}
|
||||
214
src/main/java/com/verisignlabs/dnssec/cl/ZoneFormat.java
Normal file
214
src/main/java/com/verisignlabs/dnssec/cl/ZoneFormat.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
33
src/main/java/com/verisignlabs/dnssec/cl/package.html
Normal file
33
src/main/java/com/verisignlabs/dnssec/cl/package.html
Normal 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 © 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>
|
||||
427
src/main/java/com/verisignlabs/dnssec/security/BINDKeyUtils.java
Normal file
427
src/main/java/com/verisignlabs/dnssec/security/BINDKeyUtils.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,647 @@
|
||||
/*
|
||||
* $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);
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,633 @@
|
||||
// $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;
|
||||
|
||||
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 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());
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return pKeyRecord.getPublicKey();
|
||||
}
|
||||
catch (DNSSECException e)
|
||||
{
|
||||
throw new NoSuchAlgorithmException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
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 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);
|
||||
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 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);
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
356
src/main/java/com/verisignlabs/dnssec/security/DnsKeyPair.java
Normal file
356
src/main/java/com/verisignlabs/dnssec/security/DnsKeyPair.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
259
src/main/java/com/verisignlabs/dnssec/security/ProtoNSEC3.java
Normal file
259
src/main/java/com/verisignlabs/dnssec/security/ProtoNSEC3.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
1598
src/main/java/com/verisignlabs/dnssec/security/SignUtils.java
Normal file
1598
src/main/java/com/verisignlabs/dnssec/security/SignUtils.java
Normal file
File diff suppressed because it is too large
Load Diff
210
src/main/java/com/verisignlabs/dnssec/security/TypeMap.java
Normal file
210
src/main/java/com/verisignlabs/dnssec/security/TypeMap.java
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
169
src/main/java/com/verisignlabs/dnssec/security/ZoneUtils.java
Normal file
169
src/main/java/com/verisignlabs/dnssec/security/ZoneUtils.java
Normal 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();
|
||||
}
|
||||
}
|
||||
778
src/main/java/com/verisignlabs/dnssec/security/ZoneVerifier.java
Normal file
778
src/main/java/com/verisignlabs/dnssec/security/ZoneVerifier.java
Normal 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;
|
||||
}
|
||||
}
|
||||
35
src/main/java/com/verisignlabs/dnssec/security/package.html
Normal file
35
src/main/java/com/verisignlabs/dnssec/security/package.html
Normal 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 © 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>
|
||||
Reference in New Issue
Block a user