Cli improvements (#17)

* add config file processing, refactor CLIBase some

* fix algorithm aliases with key generation

* Refactor to remove CLIState et al, move CLI common statics to new Utils

* only use usage() for help, otherwise fail()

* add a universal command line client, build a one-jar to use it.

* bump the version

* update ChangeLog, README, README.TODO, minor fixes

* undo overzealous find/replace. sigh.

* fix use_large_exponent logic in KeyGen

* more fixes, minor improvements
This commit is contained in:
2024-04-07 21:12:56 -04:00
committed by GitHub
parent 2876649a4e
commit 1727d7c7d8
28 changed files with 1364 additions and 1152 deletions

View File

@@ -17,11 +17,11 @@
package com.verisignlabs.dnssec.cl;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.Date;
import java.util.TimeZone;
import java.util.Properties;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.Level;
@@ -48,8 +48,19 @@ import com.verisignlabs.dnssec.security.DnsKeyAlgorithm;
* subclass variant of the CLIState and call run().
*/
public abstract class CLBase {
protected static Logger staticLog = Logger.getLogger(CLBase.class.getName());
protected Logger log;
protected Logger log = Logger.getLogger(this.getClass().toString());
protected Options opts;
protected String name;
protected String usageStr;
protected Properties props;
protected CommandLine cli;
protected CLBase(String name, String usageStr) {
this.name = name;
this.usageStr = usageStr;
setup();
}
/**
* This is a very simple log formatter that simply outputs the log level and
@@ -70,253 +81,319 @@ public abstract class CLBase {
}
}
/** This is the base set of command line options provided to all subclasses. */
private void setupCommonOptions() {
// Set up the standard set of options that all jdnssec command line tools will
// implement.
// boolean options
opts.addOption("h", "help", false, "Print this message.");
opts.addOption("m", "multiline", false,
"Output DNS records using 'multiline' format");
opts.addOption(Option.builder("l").longOpt("log-level").argName("level").hasArg()
.desc("set the logging level with either java.util.logging levels, or 0-6").build());
opts.addOption(Option.builder("v").longOpt("verbose").desc(
"set as verbose (log-level = fine)").build());
opts.addOption(Option.builder("c").longOpt("config").argName("file").hasArg()
.desc("configuration file (format: java properties)").build());
opts.addOption(Option.builder("A").hasArg().argName("alias:original:mnemonic").longOpt("alg-alias")
.desc("Define an alias for an algorithm").build());
}
/**
* This is a base class for command line parsing state. Subclasses should
* override setupOptions and processOptions.
* This is an overridable method for subclasses to add their own command line
* options.
*/
public static class CLIStateBase {
protected Options opts;
protected String usageStr;
protected abstract void setupOptions();
/**
* 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();
/**
* Initialize the command line options
*/
public void setup() {
opts = new Options();
setupCommonOptions();
setupOptions();
}
/**
* This is the main method for parsing the command line arguments. Subclasses
* generally override processOptions() rather than this method. This method
* creates the parsing objects and processes the common options.
*
* @param args The command line arguments.
*/
public void parseCommandLine(String[] args) {
String[] logLevelOptionKeys = { "log_level", "log-level" };
String[] multilineOptionKeys = { "multiline" };
CommandLineParser parser = new DefaultParser();
try {
cli = parser.parse(opts, args);
} catch (UnrecognizedOptionException e) {
fail("unknown option encountered: " + e.getMessage());
} catch (AlreadySelectedException e) {
fail("mutually exclusive options have been selected:\n " + e.getMessage());
} catch (ParseException e) {
fail("unable to parse command line: " + e);
}
/** 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");
opts.addOption(Option.builder("v").longOpt("verbose").argName("level").hasArg().desc(
"verbosity level -- 0: silence, 1: error, 2: warning, 3: info, 4/5: fine, 6: finest; default: 2 (warning)")
.build());
opts.addOption(Option.builder("A").hasArg().argName("alias:original:mnemonic").longOpt("alg-alias")
.desc("Define an alias for an algorithm").build());
setupOptions(opts);
if (cli.hasOption('h')) {
usage();
}
/**
* 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.
String loadedConfig = loadConfig(cli.getOptionValue('c'));
Logger rootLogger = Logger.getLogger("");
// we set log level with both --log-level and -v/--verbose.
String logLevel = cliOption("log-level", logLevelOptionKeys, null);
if (logLevel == null) {
logLevel = cli.hasOption("v") ? "fine" : "warning";
}
setLogLevel(rootLogger, logLevel);
for (Handler h : rootLogger.getHandlers()) {
h.setLevel(rootLogger.getLevel());
h.setFormatter(new BareLogFormatter());
}
/**
* 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 parser = new DefaultParser();
CommandLine cli = parser.parse(opts, args);
if (loadedConfig != null) {
log.info("Loaded config file: " + loadedConfig);
}
if (cli.hasOption('h')) {
usage();
if (cliBooleanOption("m", multilineOptionKeys, false)) {
org.xbill.DNS.Options.set("multiline");
}
processAliasOptions();
processOptions();
}
/**
* Process additional tool-specific options. Subclasses generally override
* this.
*/
protected abstract void processOptions();
/**
* Load a configuration (java properties) file for jdnssec-tools. Returns
* the path of the loaded file.
*
* @param configFile a given path to a config file. This will be considered
* first.
* @return The path of the file that was actually loaded, or null if no config
* file was loaded.
*/
protected String loadConfig(String configFile) {
// Do not load config files twice
if (props != null) {
return null;
}
props = new Properties();
String[] configFiles = { configFile, "jdnssec-tools.properties", ".jdnssec-tools.properties",
System.getProperty("user.home") + "/.jdnssec-tools.properties" };
File f = null;
for (String fname : configFiles) {
if (fname == null) {
continue;
}
f = new File(fname);
if (!f.canRead()) {
continue;
}
Logger rootLogger = Logger.getLogger("");
int value = parseInt(cli.getOptionValue('v'), -1);
try (FileInputStream stream = new FileInputStream(f)) {
props.load(stream);
break; // load the first config file found in our list
} catch (IOException e) {
log.warning("Could not read config file " + f.getName() + ": " + e);
}
}
switch (value) {
if (f != null) {
return f.getPath();
}
return null;
}
protected void fail(String errorMessage) {
log.severe(errorMessage);
System.exit(64);
}
/** 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, 120, usageStr, null, opts, HelpFormatter.DEFAULT_LEFT_PAD,
HelpFormatter.DEFAULT_DESC_PAD, null);
out.flush();
System.exit(0);
}
/**
* Set the logging level based on a string value
*
* @param logger The logger to set -- usually the rootLogger
* @param levelStr A level string that is either an integer from 0 to 6, or a
* java.util.logging log level string (severe, warning, info,
* fine, finer,
* finest).
*/
private void setLogLevel(Logger logger, String levelStr) {
Level level;
int internalLogLevel = Utils.parseInt(levelStr, -1);
if (internalLogLevel != -1) {
switch (internalLogLevel) {
case 0:
rootLogger.setLevel(Level.OFF);
level = Level.OFF;
break;
case 1:
rootLogger.setLevel(Level.SEVERE);
level = Level.SEVERE;
break;
case 2:
default:
rootLogger.setLevel(Level.WARNING);
level = Level.WARNING;
break;
case 3:
rootLogger.setLevel(Level.INFO);
level = Level.INFO;
break;
case 4:
rootLogger.setLevel(Level.CONFIG);
level = Level.FINE;
break;
case 5:
rootLogger.setLevel(Level.FINE);
break;
case 6:
rootLogger.setLevel(Level.ALL);
break;
level = Level.ALL;
}
// I hate java.util.logging, btw.
for (Handler h : rootLogger.getHandlers()) {
h.setLevel(rootLogger.getLevel());
h.setFormatter(new BareLogFormatter());
} else {
try {
level = Level.parse(levelStr.toUpperCase());
} catch (IllegalArgumentException e) {
System.err.println("Verbosity level '" + levelStr + "' not recognized");
level = Level.WARNING;
}
}
logger.setLevel(level);
}
if (cli.hasOption('m')) {
org.xbill.DNS.Options.set("multiline");
}
/**
* Process both property file based alias definitions and command line alias
* definitions
*/
protected void processAliasOptions() {
DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance();
// First parse any command line options
// those look like '-A <alias-num>:<orig-num>:<mnemonic>', e.g., '-A
// 21:13:ECDSAP256-NSEC6'
String[] optstrs = null;
if ((optstrs = cli.getOptionValues('A')) != null) {
for (String value : optstrs) {
String[] valueComponents = value.split(":");
int aliasAlg = Utils.parseInt(valueComponents[0], -1);
int origAlg = Utils.parseInt(valueComponents[1], -1);
String mnemonic = valueComponents[2];
String[] optstrs = null;
if ((optstrs = cli.getOptionValues('A')) != null) {
for (int i = 0; i < optstrs.length; i++) {
addArgAlias(optstrs[i]);
if (mnemonic != null && origAlg >= 0 && aliasAlg >= 0) {
algs.addAlias(aliasAlg, mnemonic, origAlg);
}
}
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.
}
// Next see if we have any alias options in properties
// Those look like 'signzone.alias.<alias-mnemonic> =
// <orig-alg-num>:<alias-alg-num>'
for (String key : props.stringPropertyNames()) {
if (key.startsWith(name + ".alias.") || key.startsWith("alias.")) {
String[] keyComponents = key.split("\\.");
String mnemonic = keyComponents[keyComponents.length - 1];
String[] valueComponents = props.getProperty(key).split(":");
int origAlg = Utils.parseInt(valueComponents[0], -1);
int aliasAlg = Utils.parseInt(valueComponents[1], -1);
/** 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 {
return Integer.parseInt(s);
} catch (NumberFormatException e) {
return def;
}
}
public static long parseLong(String s, long def) {
try {
return Long.parseLong(s);
} catch (NumberFormatException e) {
return def;
if (mnemonic != null && origAlg >= 0 && aliasAlg >= 0) {
algs.addAlias(aliasAlg, mnemonic, origAlg);
}
}
}
}
/**
* Calculate a date/time from a command line time/offset duration string.
* Given a parsed command line, option, and list of possible config
* properties, and a default value, determine value for the option
*
* @param start
* the start time to calculate offsets from.
* @param duration
* the time/offset string to parse.
* @return the calculated time.
* @param option The option name
* @param properties A list of configuration parameters that we would like
* to use for this option, from most preferred to least.
* @param defaultValue A default value to return if either the option or
* config value cannot be parsed, or neither are present.
* @return The found value, or the default value.
*/
public static Instant convertDuration(Instant start, String duration) throws ParseException {
if (start == null) {
start = Instant.now();
protected String cliOption(String option, String[] properties, String defaultValue) {
if (cli.hasOption(option)) {
return cli.getOptionValue(option);
}
if (duration.startsWith("now")) {
start = Instant.now();
if (duration.indexOf("+") < 0)
return start;
duration = duration.substring(3);
for (String property : properties) {
// first look up the scoped version of the property
String value = props.getProperty(name + "." + property);
if (value != null) {
return value;
}
value = props.getProperty(property);
if (value != null) {
return value;
}
}
return defaultValue;
}
if (duration.startsWith("+")) {
long offset = parseLong(duration.substring(1), 0);
return start.plusSeconds(offset);
}
/**
* Given a parsed command line, option, and list of possible config
* properties, determine the value for the option, converting the value to
* long.
*/
protected long cliLongOption(String option, String[] properties, long defaultValue) {
String value = cliOption(option, properties, Long.toString(defaultValue));
return Utils.parseLong(value, defaultValue);
}
// This is a heuristic to distinguish UNIX epoch times from the zone file
// format standard (which is length == 14)
if (duration.length() <= 10) {
long epoch = parseLong(duration, 0);
return Instant.ofEpochSecond(epoch);
}
/**
* Given a parsed command line, option, and list of possible config
* properties, determine the value for the option, converting the value to
* int.
*/
protected int cliIntOption(String option, String[] properties, int defaultValue) {
String value = cliOption(option, properties, Integer.toString(defaultValue));
return Utils.parseInt(value, defaultValue);
}
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyyMMddHHmmss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT"));
try {
Date parsedDate = dateFormatter.parse(duration);
return parsedDate.toInstant();
} catch (java.text.ParseException e) {
throw new ParseException(e.getMessage());
/**
* Given a parsed command line, option, and list of possible config
* properties, determine the value for the option, converting the value to
* a boolean.
*/
protected boolean cliBooleanOption(String option, String[] properties, boolean defaultValue) {
if (cli.hasOption(option)) {
return true;
}
String value = cliOption(option, properties, Boolean.toString(defaultValue));
return Boolean.parseBoolean(value);
}
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();
}
public void run(String[] args) {
parseCommandLine(args);
log = Logger.getLogger(this.getClass().toString());
try {

View File

@@ -0,0 +1,84 @@
package com.verisignlabs.dnssec.cl;
public class CLI {
private SubCommandType subCommand = null;
private String commandSetStr = null;
enum SubCommandType {
DSTOOL, KEYGEN, KEYINFO, SIGNKEYSET, SIGNRRSET, SIGNZONE, VERIFYZONE, ZONEFORMAT;
}
public CLI(String name, String usageStr) {
StringBuilder sb = new StringBuilder();
for (SubCommandType type : SubCommandType.class.getEnumConstants()) {
sb.append(type.toString().toLowerCase());
sb.append(" ");
}
commandSetStr = sb.toString().trim();
}
private void fail(String errorMessage){
System.err.println("ERROR: " + errorMessage);
System.exit(2);
}
public void run(String[] args) {
String[] subCommandArgs = null;
if (args.length < 1) {
fail("missing command: must be one of: " + commandSetStr);
}
String command = args[0];
if (command.equals("-h")) {
System.err.println("usage: jdnssec-tools <command> <command args..>");
System.err.println(" <command> is one of: " + commandSetStr);
System.exit(0);
}
try {
subCommand = SubCommandType.valueOf(command.toUpperCase());
} catch (IllegalArgumentException e) {
fail("unrecognized command '" + command + "': must be one of: " + commandSetStr);
}
subCommandArgs = new String[args.length - 1];
System.arraycopy(args, 1, subCommandArgs, 0, args.length - 1);
CLBase cmd = null;
switch(subCommand) {
case DSTOOL:
cmd = new DSTool("dstool", "jdnssec-tools dstool [..options..] keyfile [keyfile..]");
break;
case KEYGEN:
cmd = new KeyGen("keygen", "jdnssec-tools keygen [..options..] zonename");
break;
case KEYINFO:
cmd = new KeyInfoTool("keyinfotool", "jdnssec-tools keyinfo [..options..] keyfile");
break;
case SIGNKEYSET:
cmd = new SignKeyset("signkeyset", "jdnssec-tools signkeyset [..options..] dnskeyset_file [key_file ...]");
break;
case SIGNRRSET:
cmd = new SignRRset("signrrset", "jdnssec-tools signrrset [..options..] rrset_file key_file [key_file ...]");
break;
case SIGNZONE:
cmd = new SignZone("signzone", "jdnssec-tools signzone [..options..] zone_file [key_file ...]");
break;
case VERIFYZONE:
cmd = new VerifyZone("verifyzone", "jdnssec-tools verifyzone [..options..] zonefile");
break;
case ZONEFORMAT:
cmd = new ZoneFormat("zoneformat", "jdnssec-tools zoneformat [..options..] zonefile");
break;
default:
fail("commmand " + command + " has not been implemented.");
break;
}
cmd.run(subCommandArgs);
}
public static void main(String[] args) {
CLI cli = new CLI("cli", "jdnssec-tools <command> [..args..]");
cli.run(args);
}
}

View File

@@ -18,11 +18,10 @@
package com.verisignlabs.dnssec.cl;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.xbill.DNS.CDSRecord;
import org.xbill.DNS.DLVRecord;
import org.xbill.DNS.DNSKEYRecord;
@@ -40,7 +39,15 @@ import com.verisignlabs.dnssec.security.SignUtils;
* @author David Blacka
*/
public class DSTool extends CLBase {
private CLIState state;
private dsType createType = dsType.DS;
private String outputfile = null;
private String[] keynames = null;
private int digestId = DNSSEC.Digest.SHA256;
private long dsTTL = -1;
public DSTool(String name, String usageStr) {
super(name, usageStr);
}
/** There are several records that are based on DS. */
protected enum dsType {
@@ -52,80 +59,77 @@ public class DSTool extends CLBase {
* state.
*/
protected static class CLIState extends CLIStateBase {
public dsType createType = dsType.DS;
public String outputfile = null;
public String keyname = null;
public int digestId = DNSSEC.Digest.SHA256;
public CLIState() {
super("jdnssec-dstool [..options..] keyfile");
}
/**
* Set up the command line options.
*
* @return a set of command line options.
*/
@Override
protected void setupOptions(Options opts) {
opts.addOption(Option.builder("D").longOpt("dlv").desc("Generate a DLV record instead.").build());
opts.addOption(Option.builder("C").longOpt("cds").desc("Generate a CDS record instead").build());
opts.addOption(
Option.builder("d").hasArg().argName("id").longOpt("digest").desc("The digest algorithm to use").build());
opts.addOption(Option.builder("f").hasArg().argName("file").longOpt("output").desc("output to file").build());
}
@Override
protected void processOptions(CommandLine cli)
throws org.apache.commons.cli.ParseException {
outputfile = cli.getOptionValue('f');
if (cli.hasOption("dlv")) {
createType = dsType.DLV;
} else if (cli.hasOption("cds")) {
createType = dsType.CDS;
}
String optstr = cli.getOptionValue('d');
if (optstr != null)
digestId = DNSSEC.Digest.value(optstr);
String[] args = cli.getArgs();
if (args.length < 1) {
System.err.println("error: missing key file ");
usage();
}
keyname = args[0];
}
/**
* Set up the command line options.
*
* @return a set of command line options.
*/
protected void setupOptions() {
opts.addOption(Option.builder("D").longOpt("dlv").desc("Generate a DLV record instead.").build());
opts.addOption(Option.builder("C").longOpt("cds").desc("Generate a CDS record instead").build());
opts.addOption(
Option.builder("d").hasArg().argName("id").longOpt("digest").desc("The digest algorithm to use").build());
opts.addOption(Option.builder("f").hasArg().argName("file").longOpt("output").desc("output to file").build());
opts.addOption(Option.builder("T").longOpt("ttl").hasArg().desc("TTL to use for generated DS/CDS record").build());
}
public void execute() throws Exception {
DnsKeyPair key = BINDKeyUtils.loadKey(state.keyname, null);
protected void processOptions() {
String[] digestAlgOptionKeys = { "digest_algorithm", "digest_id" };
String[] dsTTLOptionKeys = { "ds_ttl", "ttl" };
outputfile = cli.getOptionValue('f');
if (cli.hasOption("dlv")) {
createType = dsType.DLV;
} else if (cli.hasOption("cds")) {
createType = dsType.CDS;
}
String digestValue = cliOption("d", digestAlgOptionKeys, Integer.toString(digestId));
digestId = DNSSEC.Digest.value(digestValue);
dsTTL = cliLongOption("ttl", dsTTLOptionKeys, dsTTL);
String[] args = cli.getArgs();
if (args.length < 1) {
fail("missing key file");
}
keynames = args;
}
public void createDS(String keyname) throws IOException {
DnsKeyPair key = BINDKeyUtils.loadKey(keyname, null);
DNSKEYRecord dnskey = key.getDNSKEYRecord();
if ((dnskey.getFlags() & DNSKEYRecord.Flags.SEP_KEY) == 0) {
log.warning("DNSKEY is not an SEP-flagged key.");
log.warning("DNSKEY " + keyname + " is not an SEP-flagged key.");
}
DSRecord ds = SignUtils.calculateDSRecord(dnskey, state.digestId, dnskey.getTTL());
Record res = ds;
long ttl = dsTTL < 0 ? dnskey.getTTL() : dsTTL;
DSRecord ds = SignUtils.calculateDSRecord(dnskey, digestId, ttl);
Record res;
if (state.createType == dsType.DLV) {
log.fine("creating DLV.");
DLVRecord dlv = new DLVRecord(ds.getName(), ds.getDClass(), ds.getTTL(), ds.getFootprint(), ds.getAlgorithm(),
ds.getDigestID(), ds.getDigest());
res = dlv;
} else if (state.createType == dsType.CDS) {
log.fine("creating CDS.");
CDSRecord cds = new CDSRecord(ds.getName(), ds.getDClass(), ds.getTTL(), ds.getFootprint(), ds.getAlgorithm(),
ds.getDClass(), ds.getDigest());
res = cds;
switch (createType) {
case DLV:
log.fine("creating DLV.");
DLVRecord dlv = new DLVRecord(ds.getName(), ds.getDClass(), ds.getTTL(), ds.getFootprint(), ds.getAlgorithm(),
ds.getDigestID(), ds.getDigest());
res = dlv;
break;
case CDS:
log.fine("creating CDS.");
CDSRecord cds = new CDSRecord(ds.getName(), ds.getDClass(), ds.getTTL(), ds.getFootprint(), ds.getAlgorithm(),
ds.getDClass(), ds.getDigest());
res = cds;
break;
default:
res = ds;
break;
}
if (state.outputfile != null && !state.outputfile.equals("-")) {
try (PrintWriter out = new PrintWriter(new FileWriter(state.outputfile))) {
if (outputfile != null && !outputfile.equals("-")) {
try (PrintWriter out = new PrintWriter(new FileWriter(outputfile))) {
out.println(res);
}
} else {
@@ -133,10 +137,15 @@ public class DSTool extends CLBase {
}
}
public static void main(String[] args) {
DSTool tool = new DSTool();
tool.state = new CLIState();
public void execute() throws Exception {
for (String keyname : keynames){
createDS(keyname);
}
}
tool.run(tool.state, args);
public static void main(String[] args) {
DSTool tool = new DSTool("dstool", "jdnssec-dstool [..options..] keyfile [keyfile..]");
tool.run(args);
}
}

View File

@@ -19,9 +19,7 @@ package com.verisignlabs.dnssec.cl;
import java.io.File;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.xbill.DNS.DClass;
import org.xbill.DNS.DNSKEYRecord;
import org.xbill.DNS.Name;
@@ -37,177 +35,144 @@ import com.verisignlabs.dnssec.security.JCEDnsSecSigner;
* @author David Blacka
*/
public class KeyGen extends CLBase {
private CLIState state;
private int algorithm = 13;
private int keylength = 2048;
private boolean useLargeE = true;
private String outputfile = null;
private File keydir = null;
private boolean zoneKey = true;
private boolean kskFlag = false;
private String owner = null;
private long ttl = 86400;
private int givenKeyTag = -1;
public KeyGen(String name, String usageStr) {
super(name, usageStr);
}
/**
* This is a small inner class used to hold all of the command line option
* state.
* Set up the command line options.
*/
protected static class CLIState extends CLIStateBase {
public int algorithm = 13;
public int keylength = 2048;
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 int givenKeyTag = -1;
protected void setupOptions() {
// 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");
public CLIState() {
super("jdnssec-keygen [..options..] name");
// Argument options
opts.addOption(
Option.builder("n").longOpt("nametype").hasArg().argName("type").desc("ZONE | OTHER (default ZONE)").build());
String[] algStrings = DnsKeyAlgorithm.getInstance().supportedAlgMnemonics();
String algStringSet = String.join(" | ", algStrings);
opts.addOption(Option.builder("a").hasArg().argName("algorithm")
.desc(algStringSet + " | alias, ECDSAP256SHA256 is default.").build());
opts.addOption(Option.builder("b").hasArg().argName("size").desc(
"key size, in bits (default 2048). RSA: [512..4096], DSA: [512..1024], DH: [128..4096], ECDSA: ignored, EdDSA: ignored")
.build());
opts.addOption(Option.builder("f").hasArg().argName("file").longOpt("output-file")
.desc("base filename from the public/private key files").build());
opts.addOption(Option.builder("d").hasArg().argName("dir").longOpt("keydir")
.desc("generated keyfiles are written to this directory").build());
opts.addOption(Option.builder("T").hasArg().argName("ttl").longOpt("ttl")
.desc("use this TTL for the generated DNSKEY records (default: 86400").build());
opts.addOption(Option.builder().hasArg().argName("tag").longOpt("with-tag")
.desc("Generate keys until tag is the given value.").build());
}
protected void processOptions() {
String[] useLargeEOptionKeys = { "use_large_exponent", "use_large_e" };
String[] keyDirectoryOptionKeys = { "key_directory", "keydir" };
String[] algorithmOptionKeys = { "algorithm", "alg " };
String[] keyLengthOptionKeys = { "key_length", "keylen" };
String[] ttlOptionKeys = { "dnskey_ttl", "ttl" };
if (cli.hasOption('k')) {
kskFlag = true;
}
useLargeE = cli.hasOption('e'); // explicit command line option for the large exponent
useLargeE = !cli.hasOption('E'); // explicit command line option for the small exponent
String optstr = cliOption("e", useLargeEOptionKeys, Boolean.toString(useLargeE)); // get any config file properties
if (optstr != null) {
useLargeE = Boolean.parseBoolean(optstr);
}
/**
* Set up the command line options.
*/
@Override
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
opts.addOption(
Option.builder("n").longOpt("nametype").hasArg().argName("type").desc("ZONE | OTHER (default ZONE)").build());
String[] algStrings = DnsKeyAlgorithm.getInstance().supportedAlgMnemonics();
String algStringSet = String.join(" | ", algStrings);
opts.addOption(Option.builder("a").hasArg().argName("algorithm")
.desc(algStringSet + " | alias, ECDSAP256SHA256 is default.").build());
opts.addOption(Option.builder("b").hasArg().argName("size").desc(
"key size, in bits (default 2048). RSA: [512..4096], DSA: [512..1024], DH: [128..4096], ECDSA: ignored, EdDSA: ignored")
.build());
opts.addOption(Option.builder("f").hasArg().argName("file").longOpt("output-file")
.desc("base filename from the public/private key files").build());
opts.addOption(Option.builder("d").hasArg().argName("dir").longOpt("keydir")
.desc("generated keyfiles are written to this directory").build());
opts.addOption(Option.builder("T").hasArg().argName("ttl").longOpt("ttl")
.desc("use this TTL for the generated DNSKEY records (default: 86400").build());
opts.addOption(Option.builder().hasArg().argName("tag").longOpt("with-tag")
.desc("Generate keys until tag is the given value.").build());
outputfile = cli.getOptionValue('f');
String keydirName = cliOption("d", keyDirectoryOptionKeys, null);
if (keydirName != null) {
keydir = new File(keydirName);
}
@Override
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 && !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 = CLIState.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);
}
if ((optstr = cli.getOptionValue("with-tag")) != null) {
givenKeyTag = parseInt(optstr, -1);
}
String[] args = cli.getArgs();
if (args.length < 1) {
System.err.println("error: missing key owner name");
usage();
}
owner = args[0];
String algString = cliOption("a", algorithmOptionKeys, Integer.toString(algorithm));
algorithm = Utils.parseAlg(algString);
if (algorithm < 0) {
fail("DNSSEC algorithm " + algString + " is not supported");
}
private static int parseAlg(String s) {
DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance();
keylength = cliIntOption("b", keyLengthOptionKeys, keylength);
ttl = cliLongOption("ttl", ttlOptionKeys, ttl);
givenKeyTag = Utils.parseInt(cli.getOptionValue("with-tag"), -1);
int alg = parseInt(s, -1);
if (alg > 0) {
if (algs.supportedAlgorithm(alg))
return alg;
return -1;
}
String[] args = cli.getArgs();
return algs.stringToAlgorithm(s);
if (args.length < 1) {
fail("missing key owner name");
}
owner = args[0];
log.fine("keygen options => algorithm: " + algorithm + ", keylength: " + keylength +
", useLargeE: " + useLargeE + ", kskFlag: " + kskFlag + ", ttl: " + ttl + ", givenKeyTag: " + givenKeyTag);
}
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 + ".";
if (!owner.endsWith(".")) {
owner = owner + ".";
}
Name ownerName = Name.fromString(state.owner);
Name ownerName = Name.fromString(owner);
// Calculate our flags
int flags = 0;
if (state.zoneKey) {
if (zoneKey) {
flags |= DNSKEYRecord.Flags.ZONE_KEY;
}
if (state.kskFlag) {
if (kskFlag) {
flags |= DNSKEYRecord.Flags.SEP_KEY;
}
log.fine("create key pair with (name = " + ownerName + ", ttl = " + state.ttl
+ ", alg = " + state.algorithm + ", flags = " + flags + ", length = "
+ state.keylength + ")");
log.fine("create key pair with (name = " + ownerName + ", ttl = " + ttl
+ ", alg = " + algorithm + ", flags = " + flags + ", length = "
+ keylength + ")");
DnsKeyPair pair = signer.generateKey(ownerName, state.ttl, DClass.IN,
state.algorithm, flags, state.keylength,
state.useLargeE);
DnsKeyPair pair = signer.generateKey(ownerName, ttl, DClass.IN,
algorithm, flags, keylength,
useLargeE);
// If we were asked to generate a duplicate keytag, keep trying until we get one
while (state.givenKeyTag >= 0 && pair.getDNSKEYFootprint() != state.givenKeyTag) {
pair = signer.generateKey(ownerName, state.ttl, DClass.IN, state.algorithm, flags, state.keylength,
state.useLargeE);
// This can take a long time, depending on our key generation speed
while (givenKeyTag >= 0 && pair.getDNSKEYFootprint() != givenKeyTag) {
pair = signer.generateKey(ownerName, ttl, DClass.IN, algorithm, flags, keylength,
useLargeE);
}
if (state.outputfile != null) {
BINDKeyUtils.writeKeyFiles(state.outputfile, pair, state.keydir);
if (outputfile != null) {
BINDKeyUtils.writeKeyFiles(outputfile, pair, keydir);
} else {
BINDKeyUtils.writeKeyFiles(pair, state.keydir);
BINDKeyUtils.writeKeyFiles(pair, keydir);
System.out.println(BINDKeyUtils.keyFileBase(pair));
}
}
public static void main(String[] args) {
KeyGen tool = new KeyGen();
tool.state = new CLIState();
KeyGen tool = new KeyGen("keygen", "jdnssec-keygen [..options..] zonename");
tool.run(tool.state, args);
tool.run(args);
}
}

View File

@@ -20,9 +20,6 @@ package com.verisignlabs.dnssec.cl;
import java.security.interfaces.DSAPublicKey;
import java.security.interfaces.RSAPublicKey;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.xbill.DNS.DNSKEYRecord;
import com.verisignlabs.dnssec.security.BINDKeyUtils;
@@ -35,41 +32,32 @@ import com.verisignlabs.dnssec.security.DnsKeyPair;
* @author David Blacka
*/
public class KeyInfoTool extends CLBase {
private CLIState state;
private String[] keynames = null;
public KeyInfoTool(String name, String usageStr) {
super(name, usageStr);
}
/**
* This is a small inner class used to hold all of the command line option
* state.
* Set up the command line options.
*/
protected static class CLIState extends CLIStateBase {
public String[] keynames = null;
protected void setupOptions() {
// no special options at the moment.
}
public CLIState() {
super("jdnssec-keyinfo [..options..] keyfile");
}
/**
* Set up the command line options.
*/
@Override
protected void setupOptions(Options opts) {
// no special options at the moment.
}
@Override
protected void processOptions(CommandLine cli) throws ParseException {
protected void processOptions() {
keynames = cli.getArgs();
if (keynames.length < 1) {
System.err.println("error: missing key file ");
usage();
fail("missing key file");
}
}
}
public void execute() throws Exception {
for (int i = 0; i < state.keynames.length; ++i) {
String keyname = state.keynames[i];
for (int i = 0; i < keynames.length; ++i) {
String keyname = keynames[i];
DnsKeyPair key = BINDKeyUtils.loadKey(keyname, null);
DNSKEYRecord dnskey = key.getDNSKEYRecord();
DnsKeyAlgorithm dnskeyalg = DnsKeyAlgorithm.getInstance();
@@ -97,16 +85,15 @@ public class KeyInfoTool extends CLBase {
System.out.println("DSA subprime (Q): " + pub.getParams().getQ());
System.out.println("DSA public (Y): " + pub.getY());
}
if (state.keynames.length - i > 1) {
if (keynames.length - i > 1) {
System.out.println();
}
}
}
public static void main(String[] args) {
KeyInfoTool tool = new KeyInfoTool();
tool.state = new CLIState();
KeyInfoTool tool = new KeyInfoTool("keyinfotool", "jdnssec-keyinfo [..options..] keyfile");
tool.run(tool.state, args);
tool.run(args);
}
}

View File

@@ -24,9 +24,7 @@ import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.xbill.DNS.Name;
import org.xbill.DNS.RRSIGRecord;
import org.xbill.DNS.RRset;
@@ -48,70 +46,78 @@ import com.verisignlabs.dnssec.security.ZoneUtils;
* @author David Blacka
*/
public class SignKeyset extends CLBase {
private CLIState state;
private File keyDirectory = null;
private String[] keyFiles = null;
private Instant start = null;
private Instant expire = null;
private String inputfile = null;
private String outputfile = null;
private boolean verifySigs = false;
public SignKeyset(String name, String usageStr) {
super(name, usageStr);
}
/**
* This is an inner class used to hold all of the command line option state.
* Set up the command line options.
*/
protected static class CLIState extends CLIStateBase {
public File keyDirectory = null;
public String[] keyFiles = null;
public Instant start = null;
public Instant expire = null;
public String inputfile = null;
public String outputfile = null;
public boolean verifySigs = false;
public CLIState() {
super("jdnssec-signkeyset [..options..] dnskeyset_file [key_file ...]");
}
protected void setupOptions() {
// boolean options
opts.addOption("a", "verify", false, "verify generated signatures>");
/**
* Set up the command line options.
*/
@Override
protected void setupOptions(Options opts) {
// boolean options
opts.addOption("a", "verify", false, "verify generated signatures>");
// Argument options
opts.addOption(Option.builder("D").hasArg().argName("dir").longOpt("key-directory")
.desc("directory where key files are found (default '.').").build());
opts.addOption(Option.builder("s").hasArg().argName("time/offset").longOpt("start-time")
.desc("signature starting time (default is now - 1 hour)").build());
opts.addOption(Option.builder("e").hasArg().argName("time/offset").longOpt("expire-time")
.desc("signature expiration time (default is start-time + 30 days)").build());
opts.addOption(
Option.builder("f").hasArg().argName("outfile").desc("file the signed keyset is written to").build());
}
// Argument options
opts.addOption(Option.builder("D").hasArg().argName("dir").longOpt("key-directory")
.desc("directory where key files are found (default '.').").build());
opts.addOption(Option.builder("s").hasArg().argName("time/offset").longOpt("start-time")
.desc("signature starting time (default is now - 1 hour)").build());
opts.addOption(Option.builder("e").hasArg().argName("time/offset").longOpt("expire-time")
.desc("signature expiration time (default is start-time + 30 days)").build());
opts.addOption(
Option.builder("f").hasArg().argName("outfile").desc("file the signed keyset is written to").build());
}
@Override
protected void processOptions(CommandLine cli)
throws org.apache.commons.cli.ParseException {
protected void processOptions() {
String[] verifyOptionKeys = { "verify_signatures", "verify" };
String[] keyDirectoryOptionKeys = { "key_directory", "keydir" };
String[] inceptionOptionKeys = { "inception", "start" };
String[] expireOptionKeys = { "expire" };
String optstr = null;
if (cli.hasOption('a'))
verifySigs = true;
if ((optstr = cli.getOptionValue('D')) != null) {
verifySigs = cliBooleanOption("a", verifyOptionKeys, false);
String keyDirectoryName = cliOption("D", keyDirectoryOptionKeys, null);
if (keyDirectoryName != null) {
keyDirectory = new File(optstr);
if (!keyDirectory.isDirectory()) {
System.err.println("error: " + optstr + " is not a directory");
usage();
fail("key directory " + optstr + " is not a directory");
}
}
if ((optstr = cli.getOptionValue('s')) != null) {
start = convertDuration(null, optstr);
} else {
// default is now - 1 hour.
start = Instant.now().minusSeconds(3600);
try {
optstr = cliOption("s", inceptionOptionKeys, null);
if (optstr != null) {
start = Utils.convertDuration(null, optstr);
} else {
// default is now - 1 hour.
start = Instant.now().minusSeconds(3600);
}
} catch (java.text.ParseException e) {
fail("Unable to parse start time specifiction: " + e);
}
if ((optstr = cli.getOptionValue('e')) != null) {
expire = convertDuration(start, optstr);
} else {
expire = convertDuration(start, "+2592000"); // 30 days
try {
optstr = cliOption("e", expireOptionKeys, null);
if (optstr != null) {
expire = Utils.convertDuration(start, optstr);
} else {
expire = Utils.convertDuration(start, "+2592000"); // 30 days
}
} catch (java.text.ParseException e) {
fail("Unable to parse expire time specification: " + e);
}
outputfile = cli.getOptionValue('f');
@@ -119,8 +125,7 @@ public class SignKeyset extends CLBase {
String[] files = cli.getArgs();
if (files.length < 1) {
System.err.println("error: missing zone file and/or key files");
usage();
fail("missing zone file and/or key files");
}
inputfile = files[0];
@@ -129,7 +134,6 @@ public class SignKeyset extends CLBase {
System.arraycopy(files, 1, keyFiles, 0, files.length - 1);
}
}
}
/**
* Verify the generated signatures.
@@ -138,7 +142,7 @@ public class SignKeyset extends CLBase {
* @param keypairs a list of keypairs used the sign the zone.
* @return true if all of the signatures validated.
*/
private static boolean verifySigs(List<Record> records,
private boolean verifySigs(List<Record> records,
List<DnsKeyPair> keypairs) {
boolean secure = true;
@@ -160,7 +164,7 @@ public class SignKeyset extends CLBase {
boolean result = verifier.verify(rrset);
if (!result) {
staticLog.fine("Signatures did not verify for RRset: " + rrset);
log.fine("Signatures did not verify for RRset: " + rrset);
secure = false;
}
}
@@ -179,7 +183,7 @@ public class SignKeyset extends CLBase {
* @param inDirectory the directory to look in (may be null).
* @return a list of keypair objects.
*/
private static List<DnsKeyPair> getKeys(String[] keyfiles, int startIndex,
private List<DnsKeyPair> getKeys(String[] keyfiles, int startIndex,
File inDirectory) throws IOException {
if (keyfiles == null)
return new ArrayList<>();
@@ -236,10 +240,9 @@ public class SignKeyset extends CLBase {
public void execute() throws Exception {
// Read in the zone
List<Record> records = ZoneUtils.readZoneFile(state.inputfile, null);
List<Record> records = ZoneUtils.readZoneFile(inputfile, null);
if (records == null || records.isEmpty()) {
System.err.println("error: empty keyset file");
state.usage();
fail("empty keyset file");
}
// Make sure that all records are DNSKEYs with the same name.
@@ -255,46 +258,42 @@ public class SignKeyset extends CLBase {
keysetName = r.getName();
}
if (!r.getName().equals(keysetName)) {
System.err.println("error: DNSKEY with a different name found!");
state.usage();
fail("DNSKEY with a different name found!");
}
keyset.addRR(r);
}
if (keyset.size() == 0) {
System.err.println("error: No DNSKEYs found in keyset file");
state.usage();
fail("error: No DNSKEYs found in keyset file");
}
// Load the key pairs.
List<DnsKeyPair> keypairs = getKeys(state.keyFiles, 0, state.keyDirectory);
List<DnsKeyPair> keypairs = getKeys(keyFiles, 0, 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);
keypairs = findZoneKeys(keyDirectory, keysetName);
}
// If there *still* aren't any ZSKs defined, bail.
if (keypairs == null || keypairs.isEmpty() || keysetName == null) {
System.err.println("error: No signing keys could be determined.");
state.usage();
return;
fail("no signing keys could be determined.");
}
// default the output file, if not set.
if (state.outputfile == null) {
if (outputfile == null) {
if (keysetName.isAbsolute()) {
state.outputfile = keysetName + "signed_keyset";
outputfile = keysetName + "signed_keyset";
} else {
state.outputfile = keysetName + ".signed_keyset";
outputfile = keysetName + ".signed_keyset";
}
}
JCEDnsSecSigner signer = new JCEDnsSecSigner();
List<RRSIGRecord> sigs = signer.signRRset(keyset, keypairs, state.start, state.expire);
List<RRSIGRecord> sigs = signer.signRRset(keyset, keypairs, start, expire);
for (RRSIGRecord s : sigs) {
keyset.addRR(s);
}
@@ -309,9 +308,9 @@ public class SignKeyset extends CLBase {
}
// write out the signed zone
ZoneUtils.writeZoneFile(signedRecords, state.outputfile);
ZoneUtils.writeZoneFile(signedRecords, outputfile);
if (state.verifySigs) {
if (verifySigs) {
log.fine("verifying generated signatures");
boolean res = verifySigs(signedRecords, keypairs);
@@ -325,9 +324,8 @@ public class SignKeyset extends CLBase {
}
public static void main(String[] args) {
SignKeyset tool = new SignKeyset();
tool.state = new CLIState();
SignKeyset tool = new SignKeyset("signkeyset", "jdnssec-signkeyset [..options..] dnskeyset_file [key_file ...]");
tool.run(tool.state, args);
tool.run(args);
}
}

View File

@@ -23,9 +23,7 @@ import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.xbill.DNS.Name;
import org.xbill.DNS.RRSIGRecord;
import org.xbill.DNS.RRset;
@@ -49,91 +47,100 @@ import com.verisignlabs.dnssec.security.ZoneUtils;
* @author David Blacka
*/
public class SignRRset extends CLBase {
private CLIState state;
private File keyDirectory = null;
private String[] keyFiles = null;
private Instant start = null;
private Instant expire = null;
private String inputfile = null;
private String outputfile = null;
private boolean verifySigs = false;
private boolean verboseSigning = false;
public SignRRset(String name, String usageStr) {
super(name, usageStr);
}
/**
* This is an inner class used to hold all of the command line option state.
* Set up the command line options.
*/
protected static class CLIState extends CLIStateBase {
private File keyDirectory = null;
public String[] keyFiles = null;
public Instant start = null;
public Instant expire = null;
public String inputfile = null;
public String outputfile = null;
public boolean verifySigs = false;
public boolean verboseSigning = false;
protected void setupOptions() {
// boolean options
opts.addOption("a", "verify", false, "verify generated signatures>");
opts.addOption("V", "verbose-signing", false, "Display verbose signing activity.");
public CLIState() {
super("jdnssec-signrrset [..options..] rrset_file key_file [key_file ...]");
}
opts.addOption(Option.builder("D").hasArg().argName("dir").longOpt("key-directory")
.desc("directory to find key files (default '.'").build());
opts.addOption(Option.builder("s").hasArg().argName("time/offset").longOpt("start-time")
.desc("signature starting time (default is now - 1 hour)").build());
opts.addOption(Option.builder("e").hasArg().argName("time/offset").longOpt("expire-time")
.desc("signature expiration time (default is start-time + 30 days)").build());
opts.addOption(
Option.builder("f").hasArg().argName("outfile").desc("file the the signed rrset is written to").build());
}
/**
* Set up the command line options.
*/
@Override
protected void setupOptions(Options opts) {
// boolean options
opts.addOption("a", "verify", false, "verify generated signatures>");
opts.addOption("V", "verbose-signing", false, "Display verbose signing activity.");
protected void processOptions() {
String[] verifyOptionKeys = { "verify_signatures", "verify" };
String[] verboseSigningOptionKeys = { "verbose_signing" };
String[] keyDirectoryOptionKeys = { "key_directory", "keydir" };
String[] inceptionOptionKeys = { "inception", "start" };
String[] expireOptionKeys = { "expire" };
opts.addOption(Option.builder("D").hasArg().argName("dir").longOpt("key-directory")
.desc("directory to find key files (default '.'").build());
opts.addOption(Option.builder("s").hasArg().argName("time/offset").longOpt("start-time")
.desc("signature starting time (default is now - 1 hour)").build());
opts.addOption(Option.builder("e").hasArg().argName("time/offset").longOpt("expire-time")
.desc("signature expiration time (default is start-time + 30 days)").build());
opts.addOption(
Option.builder("f").hasArg().argName("outfile").desc("file the the signed rrset is written to").build());
}
String optstr = null;
@Override
protected void processOptions(CommandLine cli) throws org.apache.commons.cli.ParseException {
String optstr = null;
verifySigs = cliBooleanOption("a", verifyOptionKeys, false);
if (cli.hasOption('a'))
verifySigs = true;
if (cli.hasOption('V'))
verboseSigning = true;
verboseSigning = cliBooleanOption("V", verboseSigningOptionKeys, false);
if ((optstr = cli.getOptionValue('D')) != null) {
keyDirectory = new File(optstr);
if (!keyDirectory.isDirectory()) {
System.err.println("error: " + optstr + " is not a directory");
usage();
}
optstr = cliOption("D", keyDirectoryOptionKeys, null);
if (optstr != null) {
keyDirectory = new File(optstr);
if (!keyDirectory.isDirectory()) {
fail("key directory " + optstr + " is not a directory");
}
}
if ((optstr = cli.getOptionValue('s')) != null) {
start = convertDuration(null, optstr);
try {
optstr = cliOption("s", inceptionOptionKeys, null);
if (optstr != null) {
start = Utils.convertDuration(null, optstr);
} else {
// default is now - 1 hour.
start = Instant.now().minusSeconds(3600);
}
} catch (java.text.ParseException e) {
fail("unable to parse start time specifiction: " + e);
}
if ((optstr = cli.getOptionValue('e')) != null) {
expire = convertDuration(start, optstr);
try {
optstr = cliOption("e", expireOptionKeys, null);
if (optstr != null) {
expire = Utils.convertDuration(start, optstr);
} else {
expire = convertDuration(start, "+2592000"); // 30 days
expire = Utils.convertDuration(start, "+2592000"); // 30 days
}
} catch (java.text.ParseException e) {
fail("Unable to parse expire time specification: " + e);
}
outputfile = cli.getOptionValue('f');
outputfile = cli.getOptionValue('f');
String[] files = cli.getArgs();
String[] files = cli.getArgs();
if (files.length < 1) {
System.err.println("error: missing zone file and/or key files");
usage();
}
if (files.length < 1) {
fail("missing zone file and/or key files");
}
inputfile = files[0];
if (files.length > 1) {
keyFiles = new String[files.length - 1];
System.arraycopy(files, 1, keyFiles, 0, files.length - 1);
}
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.
*
@@ -141,7 +148,7 @@ public class SignRRset extends CLBase {
* @param keypairs a list of keypairs used the sign the zone.
* @return true if all of the signatures validated.
*/
private static boolean verifySigs(List<Record> records, List<DnsKeyPair> keypairs) {
private boolean verifySigs(List<Record> records, List<DnsKeyPair> keypairs) {
boolean secure = true;
DnsSecVerifier verifier = new DnsSecVerifier();
@@ -163,7 +170,7 @@ public class SignRRset extends CLBase {
boolean result = verifier.verify(rrset);
if (!result) {
staticLog.fine("Signatures did not verify for RRset: " + rrset);
log.fine("Signatures did not verify for RRset: " + rrset);
secure = false;
}
}
@@ -182,7 +189,7 @@ public class SignRRset extends CLBase {
* @param inDirectory the directory to look in (may be null).
* @return a list of keypair objects.
*/
private static List<DnsKeyPair> getKeys(String[] keyfiles, int startIndex,
private List<DnsKeyPair> getKeys(String[] keyfiles, int startIndex,
File inDirectory) throws IOException {
if (keyfiles == null)
return new ArrayList<>();
@@ -204,10 +211,9 @@ public class SignRRset extends CLBase {
public void execute() throws Exception {
// Read in the zone
List<Record> records = ZoneUtils.readZoneFile(state.inputfile, null);
List<Record> records = ZoneUtils.readZoneFile(inputfile, null);
if (records == null || records.isEmpty()) {
System.err.println("error: empty RRset file");
state.usage();
fail("empty RRset file");
}
// Construct the RRset. Complain if the records in the input file
// consist of more than one RRset.
@@ -230,25 +236,21 @@ public class SignRRset extends CLBase {
&& rrset.getDClass() == r.getDClass()) {
rrset.addRR(r);
} else {
System.err.println("Records do not all belong to the same RRset.");
state.usage();
fail("records do not all belong to the same RRset");
}
}
if (rrset == null || rrset.size() == 0) {
System.err.println("No records found in inputfile.");
state.usage();
return;
fail("no records found in inputfile");
}
// Load the key pairs.
if (state.keyFiles.length == 0) {
System.err.println("error: at least one keyfile must be specified");
state.usage();
if (keyFiles.length == 0) {
fail("at least one keyfile must be specified");
}
List<DnsKeyPair> keypairs = getKeys(state.keyFiles, 0, state.keyDirectory);
List<DnsKeyPair> keypairs = getKeys(keyFiles, 0, keyDirectory);
// Make sure that all the keypairs have the same name.
// This will be used as the zone name, too.
@@ -260,19 +262,18 @@ public class SignRRset extends CLBase {
continue;
}
if (!pair.getDNSKEYName().equals(keysetName)) {
System.err.println("Keys do not all have the same name.");
state.usage();
fail("keys do not all have the same name");
}
}
// default the output file, if not set.
if (state.outputfile == null && !state.inputfile.equals("-")) {
state.outputfile = state.inputfile + ".signed";
if (outputfile == null && !inputfile.equals("-")) {
outputfile = inputfile + ".signed";
}
JCEDnsSecSigner signer = new JCEDnsSecSigner(state.verboseSigning);
JCEDnsSecSigner signer = new JCEDnsSecSigner(verboseSigning);
List<RRSIGRecord> sigs = signer.signRRset(rrset, keypairs, state.start, state.expire);
List<RRSIGRecord> sigs = signer.signRRset(rrset, keypairs, start, expire);
for (RRSIGRecord s : sigs) {
rrset.addRR(s);
}
@@ -287,9 +288,9 @@ public class SignRRset extends CLBase {
}
// write out the signed zone
ZoneUtils.writeZoneFile(signedRecords, state.outputfile);
ZoneUtils.writeZoneFile(signedRecords, outputfile);
if (state.verifySigs) {
if (verifySigs) {
log.fine("verifying generated signatures");
boolean res = verifySigs(signedRecords, keypairs);
@@ -303,9 +304,8 @@ public class SignRRset extends CLBase {
}
public static void main(String[] args) {
SignRRset tool = new SignRRset();
tool.state = new CLIState();
SignRRset tool = new SignRRset("signrrset", "jdnssec-signrrset [..options..] rrset_file key_file [key_file ...]");
tool.run(tool.state, args);
tool.run(args);
}
}

View File

@@ -28,10 +28,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Random;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.xbill.DNS.DNSKEYRecord;
import org.xbill.DNS.DNSSEC;
import org.xbill.DNS.Name;
@@ -54,219 +51,226 @@ import com.verisignlabs.dnssec.security.ZoneUtils;
* @author David Blacka
*/
public class SignZone extends CLBase {
private CLIState state;
private File keyDirectory = null;
private File keysetDirectory = null;
private String[] kskFiles = null;
private String[] keyFiles = null;
private String zonefile = null;
private Instant start = null;
private Instant expire = null;
private String outputfile = null;
private boolean verifySigs = false;
private boolean useOptOut = false;
private boolean fullySignKeyset = false;
private List<Name> includeNames = null;
private boolean useNsec3 = false;
private byte[] salt = null;
private int iterations = 0;
private int digestId = DNSSEC.Digest.SHA256;
private long nsec3paramttl = -1;
private boolean verboseSigning = false;
/**
* 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 Instant start = null;
public Instant 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 digestId = DNSSEC.Digest.SHA1;
public long nsec3paramttl = -1;
public boolean verboseSigning = false;
private static final Random rand = new Random();
private static final Random rand = new Random();
public SignZone(String name, String usageStr) {
super(name, usageStr);
}
public CLIState() {
super("jdnssec-signzone [..options..] zone_file [key_file ...]");
protected void setupOptions() {
// 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.");
opts.addOption(Option.builder("d").hasArg().argName("dir").longOpt("keyset-directory")
.desc("directory to find keyset files (default '.')").build());
opts.addOption(Option.builder("D").hasArg().argName("dir").longOpt("key-directory")
.desc("directory to find key files (default '.'").build());
opts.addOption(Option.builder("s").hasArg().argName("time/offset").longOpt("start-time")
.desc("signature starting time (default is now - 1 hour)").build());
opts.addOption(Option.builder("e").hasArg().argName("time/offset").longOpt("expire-time")
.desc("signature expiration time (default is start-time + 30 days)").build());
opts.addOption(
Option.builder("f").hasArg().argName("outfile").desc("file the the signed rrset is written to").build());
opts.addOption(Option.builder("k").hasArgs().argName("KSK file").longOpt("ksk-file")
.desc("This key is a Key-Signing Key (may repeat)").build());
opts.addOption(Option.builder("I").hasArg().argName("file").longOpt("include-file")
.desc("include names in the file in the NSEC/NSEC3 chain").build());
// 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).");
opts.addOption(
Option.builder("S").hasArg().argName("hex value").longOpt("salt").desc("Supply a salt value").build());
opts.addOption(Option.builder("R").hasArg().argName("length").longOpt("random-salt")
.desc("Generate a random salt of <length>").build());
opts.addOption(Option.builder("H").hasArg().argName("count").longOpt("iterations")
.desc("Use this many addtional iterations in NSEC3 (default 0)").build());
opts.addOption(Option.builder().hasArg().longOpt("nsec3paramttl").argName("ttl")
.desc("Use this TTL for the NSEC3PARAM record (default is min(soa.min, soa.ttl))").build());
opts.addOption(Option.builder().hasArg().argName("id").longOpt("ds-digest")
.desc("Digest algorithm to use for generated DS records").build());
}
protected void processOptions() {
String[] verifyOptionKeys = { "verify_signatures", "verify" };
String[] nsec3OptionKeys = { "use_nsec3", "nsec3" };
String[] optOutOptionKeys = { "use_opt_out", "opt_out" };
String[] verboseSigningOptionKeys = { "verbose_signing" };
String[] fullySignKeysetOptionKeys = { "fully_sign_keyset", "fully_sign" };
String[] keyDirectoryOptionKeys = { "key_directory", "keydir" };
String[] inceptionOptionKeys = { "inception", "start" };
String[] expireOptionKeys = { "expire" };
String[] nsec3SaltOptionKeys = { "nsec3_salt", "salt" };
String[] randomSaltOptionKeys = { "nsec3_random_salt_length", "nsec3_salt_length", "random_salt_length" };
String[] nsec3IterationsOptionKeys = { "nsec3_iterations", "iterations" };
String[] digestAlgOptionKeys = { "digest_algorithm", "digest_id" };
String[] nsec3paramTTLOptionKeys = { "nsec3param_ttl" };
String[] incudeNamesOptionKeys = { "include_names_file", "include_names" };
String optstr = null;
verifySigs = cliBooleanOption("a", verifyOptionKeys, false);
useNsec3 = cliBooleanOption("3", nsec3OptionKeys, false);
useOptOut = cliBooleanOption("O", optOutOptionKeys, false);
verboseSigning = cliBooleanOption("V", verboseSigningOptionKeys, false);
if (useOptOut && !useNsec3) {
System.err.println("Opt-Out not supported without NSEC3 -- ignored.");
useOptOut = false;
}
@Override
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.");
opts.addOption(Option.builder("d").hasArg().argName("dir").longOpt("keyset-directory")
.desc("directory to find keyset files (default '.')").build());
opts.addOption(Option.builder("D").hasArg().argName("dir").longOpt("key-directory")
.desc("directory to find key files (default '.'").build());
opts.addOption(Option.builder("s").hasArg().argName("time/offset").longOpt("start-time")
.desc("signature starting time (default is now - 1 hour)").build());
opts.addOption(Option.builder("e").hasArg().argName("time/offset").longOpt("expire-time")
.desc("signature expiration time (default is start-time + 30 days)").build());
opts.addOption(
Option.builder("f").hasArg().argName("outfile").desc("file the the signed rrset is written to").build());
opts.addOption(Option.builder("k").hasArgs().argName("KSK file").longOpt("ksk-file")
.desc("This key is a Key-Signing Key (may repeat)").build());
opts.addOption(Option.builder("I").hasArg().argName("file").longOpt("include-file")
.desc("include names in the file in the NSEC/NSEC3 chain").build());
// 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).");
opts.addOption(
Option.builder("S").hasArg().argName("hex value").longOpt("salt").desc("Supply a salt value").build());
opts.addOption(Option.builder("R").hasArg().argName("length").longOpt("random-salt")
.desc("Generate a random salt of <length>").build());
opts.addOption(Option.builder("H").hasArg().argName("count").longOpt("iterations")
.desc("Use this many addtional iterations in NSEC3 (default 0)").build());
opts.addOption(Option.builder().hasArg().longOpt("nsec3paramttl").argName("ttl")
.desc("Use this TTL for the NSEC3PARAM record (default is min(soa.min, soa.ttl))").build());
opts.addOption(Option.builder().hasArg().argName("id").longOpt("ds-digest")
.desc("Digest algorithm to use for generated DS records").build());
fullySignKeyset = cliBooleanOption("F", fullySignKeysetOptionKeys, false);
optstr = cliOption("D", keyDirectoryOptionKeys, null);
if (optstr != null) {
keyDirectory = new File(optstr);
if (!keyDirectory.isDirectory()) {
fail("key directory " + optstr + " is not a directory");
}
}
@Override
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);
try {
optstr = cliOption("s", inceptionOptionKeys, null);
if (optstr != null) {
start = Utils.convertDuration(null, optstr);
} else {
// default is now - 1 hour.
start = Instant.now().minusSeconds(3600);
}
} catch (java.text.ParseException e) {
fail("unable to parse start time specifiction: " + e);
}
if ((optstr = cli.getOptionValue('e')) != null) {
expire = CLBase.convertDuration(start, optstr);
try {
optstr = cliOption("e", expireOptionKeys, null);
if (optstr != null) {
expire = Utils.convertDuration(start, optstr);
} else {
expire = CLBase.convertDuration(start, "+2592000"); // 30 days
expire = Utils.convertDuration(start, "+2592000"); // 30 days
}
} catch (java.text.ParseException e) {
fail("missing zone file and/or key files");
}
outputfile = cli.getOptionValue('f');
outputfile = cli.getOptionValue('f');
kskFiles = cli.getOptionValues('k');
kskFiles = cli.getOptionValues('k');
if ((optstr = cli.getOptionValue('I')) != null) {
File includeNamesFile = new File(optstr);
try {
includeNames = CLIState.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) {
salt = new byte[length];
rand.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) {
digestId = parseInt(optstr, -1);
if (digestId < 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);
optstr = cliOption("S", nsec3SaltOptionKeys, null);
if (optstr != null) {
salt = base16.fromString(optstr);
if (salt == null && !optstr.equals("-")) {
fail("salt is not valid hexidecimal");
}
}
/**
* 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 {
try (BufferedReader br = new BufferedReader(new FileReader(nameListFile))) {
List<Name> res = new ArrayList<>();
String line = null;
while ((line = br.readLine()) != null) {
try {
Name n = Name.fromString(line);
// force the name to be absolute.
if (!n.isAbsolute())
n = Name.concatenate(n, Name.root);
res.add(n);
} catch (TextParseException e) {
staticLog.severe("DNS Name parsing error:" + e);
}
}
return res;
optstr = cliOption("R", randomSaltOptionKeys, null);
if (optstr != null) {
int length = Utils.parseInt(optstr, 0);
if (length > 0 && length <= 255) {
salt = new byte[length];
rand.nextBytes(salt);
}
}
iterations = cliIntOption("iterations", nsec3IterationsOptionKeys, 0);
if (iterations > 150) {
log.warning("NSEC3 iterations value is too high for normal use: " + iterations
+ " is greater than current accepted threshold of 150");
}
optstr = cliOption("ds-digest", digestAlgOptionKeys, Integer.toString(digestId));
digestId = DNSSEC.Digest.value(optstr);
nsec3paramttl = cliIntOption("nsec3paramttl", nsec3paramTTLOptionKeys, -1);
optstr = cliOption("I", incudeNamesOptionKeys, null);
if (optstr != null) {
File includeNamesFile = new File(optstr);
try {
includeNames = getNameList(includeNamesFile);
} catch (IOException e) {
fail("unable to load include-names file: " + e);
}
}
String[] files = cli.getArgs();
if (files.length < 1) {
fail("missing zone file and/or key files");
}
zonefile = files[0];
if (files.length > 1) {
keyFiles = new String[files.length - 1];
System.arraycopy(files, 1, keyFiles, 0, files.length - 1);
}
log.fine("SignZone settings => key_directory: " + keyDirectory +
", keyset_directory: " + keysetDirectory +
", start: " + start.getEpochSecond() +
", expire: " + expire.getEpochSecond() +
", verify_sigs: " + verifySigs +
", use_nsec3: " + useNsec3 +
", use_opt_out = " + useOptOut +
", salt: " + DnsKeyPair.toHex(salt) +
", iterations: " + iterations +
", nsec3param_ttl: " + nsec3paramttl +
", fully_sign_keyset: " + fullySignKeyset +
", digest_id: " + digestId +
", verbose_signing: " + verboseSigning);
}
/**
* 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 List<Name> getNameList(File nameListFile) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader(nameListFile))) {
List<Name> res = new ArrayList<>();
String line = null;
while ((line = br.readLine()) != null) {
try {
Name n = Name.fromString(line);
// force the name to be absolute.
if (!n.isAbsolute())
n = Name.concatenate(n, Name.root);
res.add(n);
} catch (TextParseException e) {
log.severe("DNS Name parsing error:" + e);
}
}
return res;
}
}
/**
@@ -276,7 +280,7 @@ public class SignZone extends CLBase {
* @param keypairs a list of keypairs used the sign the zone.
* @return true if all of the signatures validated.
*/
private static boolean verifyZoneSigs(List<Record> records,
private boolean verifyZoneSigs(List<Record> records,
List<DnsKeyPair> keypairs, List<DnsKeyPair> kskpairs) {
boolean secure = true;
@@ -302,7 +306,7 @@ public class SignZone extends CLBase {
if (!result) {
System.err.println("Signatures did not verify for RRset: " + rrset);
staticLog.fine("Signatures did not verify for RRset: " + rrset);
log.fine("Signatures did not verify for RRset: " + rrset);
secure = false;
}
}
@@ -321,7 +325,7 @@ public class SignZone extends CLBase {
* @param inDirectory the directory to look in (may be null).
* @return a list of keypair objects.
*/
private static List<DnsKeyPair> getKeys(String[] keyfiles, int startIndex,
private List<DnsKeyPair> getKeys(String[] keyfiles, int startIndex,
File inDirectory) throws IOException {
if (keyfiles == null)
return new ArrayList<>();
@@ -342,7 +346,7 @@ public class SignZone extends CLBase {
return keys;
}
private static List<DnsKeyPair> getKeys(List<Record> dnskeyrrs, File inDirectory)
private List<DnsKeyPair> getKeys(List<Record> dnskeyrrs, File inDirectory)
throws IOException {
List<DnsKeyPair> res = new ArrayList<>();
for (Record r : dnskeyrrs) {
@@ -376,7 +380,7 @@ public class SignZone extends CLBase {
}
}
private static List<DnsKeyPair> findZoneKeys(File inDirectory, Name zonename)
private List<DnsKeyPair> findZoneKeys(File inDirectory, Name zonename)
throws IOException {
if (inDirectory == null) {
inDirectory = new File(".");
@@ -419,7 +423,7 @@ public class SignZone extends CLBase {
* 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)
private List<Record> getKeysets(File inDirectory, Name zonename)
throws IOException {
if (inDirectory == null) {
inDirectory = new File(".");
@@ -470,38 +474,42 @@ public class SignZone extends CLBase {
}
public void execute() throws Exception {
// Do a basic existence check for the zonefile first.
if (!zonefile.equals("-")) {
File f = new File(zonefile);
if (!f.exists()) {
fail("zonefile '" + zonefile + "' does not exist");
}
}
// Read in the zone
List<Record> records = ZoneUtils.readZoneFile(state.zonefile, null);
List<Record> records = ZoneUtils.readZoneFile(zonefile, null);
if (records == null || records.isEmpty()) {
System.err.println("error: empty zone file");
state.usage();
return;
fail("empty zone file");
}
// calculate the zone name.
Name zonename = ZoneUtils.findZoneName(records);
if (zonename == null) {
System.err.println("error: invalid zone file - no SOA");
state.usage();
return;
fail("invalid zone file - no SOA");
}
// Load the key pairs. Note that getKeys() always returns an ArrayList,
// which may be empty.
List<DnsKeyPair> keypairs = getKeys(state.keyFiles, 0, state.keyDirectory);
List<DnsKeyPair> kskpairs = getKeys(state.kskFiles, 0, state.keyDirectory);
List<DnsKeyPair> keypairs = getKeys(keyFiles, 0, keyDirectory);
List<DnsKeyPair> kskpairs = getKeys(kskFiles, 0, keyDirectory);
// If we didn't get any keys on the command line, look at the zone apex for
// any public keys.
if (keypairs.isEmpty()) {
List<Record> dnskeys = ZoneUtils.findRRs(records, zonename, Type.DNSKEY);
keypairs = getKeys(dnskeys, state.keyDirectory);
keypairs = getKeys(dnskeys, keyDirectory);
}
// If we *still* don't have any key pairs, look for keys the key directory
// that match
if (keypairs.isEmpty()) {
keypairs = findZoneKeys(state.keyDirectory, zonename);
keypairs = findZoneKeys(keyDirectory, zonename);
}
// If we don't have any KSKs, but we do have more than one zone
@@ -521,9 +529,7 @@ public class SignZone extends CLBase {
// If we have zero keypairs at all, we are stuck.
if (keypairs.isEmpty() && kskpairs.isEmpty()) {
System.err.println("No zone signing keys could be determined.");
state.usage();
return;
fail("no zone signing keys could be determined");
}
// If we only have one type of key (all ZSKs or all KSKs), then these are
@@ -552,19 +558,18 @@ public class SignZone extends CLBase {
}
// default the output file, if not set.
if (state.outputfile == null && !state.zonefile.equals("-")) {
if (outputfile == null && !zonefile.equals("-")) {
if (zonename.isAbsolute()) {
state.outputfile = zonename + "signed";
outputfile = zonename + "signed";
} else {
state.outputfile = zonename + ".signed";
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();
fail("specified keypairs are not valid for the zone.");
}
// We force the signing keys to be in the zone by just appending
@@ -582,34 +587,34 @@ public class SignZone extends CLBase {
}
// read in the keysets, if any.
List<Record> keysetrecs = getKeysets(state.keysetDirectory, zonename);
List<Record> keysetrecs = getKeysets(keysetDirectory, zonename);
if (keysetrecs != null) {
records.addAll(keysetrecs);
}
JCEDnsSecSigner signer = new JCEDnsSecSigner(state.verboseSigning);
JCEDnsSecSigner signer = new JCEDnsSecSigner(verboseSigning);
// Sign the zone.
List<Record> signedRecords;
if (state.useNsec3) {
if (useNsec3) {
signedRecords = signer.signZoneNSEC3(zonename, records, kskpairs, keypairs,
state.start, state.expire,
state.fullySignKeyset, state.useOptOut,
state.includeNames, state.salt,
state.iterations, state.digestId,
state.nsec3paramttl);
start, expire,
fullySignKeyset, useOptOut,
includeNames, salt,
iterations, digestId,
nsec3paramttl);
} else {
signedRecords = signer.signZone(zonename, records, kskpairs, keypairs,
state.start, state.expire, state.fullySignKeyset,
state.digestId);
start, expire, fullySignKeyset,
digestId);
}
// write out the signed zone
ZoneUtils.writeZoneFile(signedRecords, state.outputfile);
ZoneUtils.writeZoneFile(signedRecords, outputfile);
System.out.println("zone signing complete");
if (state.verifySigs) {
if (verifySigs) {
log.fine("verifying generated signatures");
boolean res = verifyZoneSigs(signedRecords, keypairs, kskpairs);
@@ -622,9 +627,8 @@ public class SignZone extends CLBase {
}
public static void main(String[] args) {
SignZone tool = new SignZone();
tool.state = new CLIState();
SignZone tool = new SignZone("signzone", "jdnssec-signzone [..options..] zone_file [key_file ...]");
tool.run(tool.state, args);
tool.run(args);
}
}

View File

@@ -0,0 +1,103 @@
package com.verisignlabs.dnssec.cl;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.Date;
import java.util.TimeZone;
import com.verisignlabs.dnssec.security.DnsKeyAlgorithm;
public class Utils {
private Utils() {
}
/**
* Parse a string into an integer safely, using a default if the value does not
* parse cleanly
*
* @param s The string to parse
* @param def The default value
* @return either the parsed int or the default
*/
public static int parseInt(String s, int def) {
try {
return Integer.parseInt(s);
} catch (NumberFormatException e) {
return def;
}
}
/**
* Parse a string into a long safely, using a default if the value does not
* parse cleanly
*
* @param s The string to parse
* @param def The default value
* @return either the parsed long or the default
*/
public static long parseLong(String s, long def) {
try {
return Long.parseLong(s);
} catch (NumberFormatException e) {
return def;
}
}
/**
* Parse a DNSSEC algorithm number of mnemonic into the official algorithm number.
* @param s The arge value
* @return A DNSSEC algorithm number, or -1 if unrecognized.
*/
public static int parseAlg(String s) {
DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance();
int alg = Utils.parseInt(s, -1);
if (alg > 0) {
if (algs.supportedAlgorithm(alg))
return alg;
return -1;
}
return algs.stringToAlgorithm(s);
}
/**
* 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 Instant convertDuration(Instant start, String duration) throws java.text.ParseException {
if (start == null) {
start = Instant.now();
}
if (duration.startsWith("now")) {
start = Instant.now();
if (duration.indexOf("+") < 0)
return start;
duration = duration.substring(3);
}
if (duration.startsWith("+")) {
long offset = parseLong(duration.substring(1), 0);
return start.plusSeconds(offset);
}
// This is a heuristic to distinguish UNIX epoch times from the zone file
// format standard (which is length == 14)
if (duration.length() <= 10) {
long epoch = parseLong(duration, 0);
return Instant.ofEpochSecond(epoch);
}
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyyMMddHHmmss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT"));
Date parsedDate = dateFormatter.parse(duration);
return parsedDate.toInstant();
}
}

View File

@@ -20,10 +20,7 @@ package com.verisignlabs.dnssec.cl;
import java.time.Instant;
import java.util.List;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.ParseException;
import org.xbill.DNS.Record;
import com.verisignlabs.dnssec.security.ZoneUtils;
@@ -35,100 +32,75 @@ import com.verisignlabs.dnssec.security.ZoneVerifier;
* @author David Blacka
*/
public class VerifyZone extends CLBase {
private String zonefile = null;
private String[] keyfiles = null;
private int startfudge = 0;
private int expirefudge = 0;
private boolean ignoreTime = false;
private boolean ignoreDups = false;
private Instant currentTime = null;
private CLIState state;
public VerifyZone(String name, String usageStr) {
super(name, usageStr);
}
/**
* 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 Instant currentTime = null;
protected void setupOptions() {
opts.addOption(Option.builder("S").hasArg().argName("seconds").longOpt("sig-start-fudge")
.desc("'fudge' RRSIG inception ties by 'seconds'").build());
opts.addOption(Option.builder("E").hasArg().argName("seconds").longOpt("sig-expire-fudge")
.desc("'fudge' RRSIG expiration times by 'seconds'").build());
opts.addOption(Option.builder("t").hasArg().argName("time").longOpt("use-time")
.desc("Use 'time' as the time for verification purposes.").build());
public CLIState() {
super("jdnssec-verifyzone [..options..] zonefile");
opts.addOption(
Option.builder().longOpt("ignore-time").desc("Ignore RRSIG inception and expiration time errors.").build());
opts.addOption(Option.builder().longOpt("ignore-duplicate-rrs").desc("Ignore duplicate record errors.").build());
}
protected void processOptions() {
String[] ignoreTimeOptionKeys = { "ignore_time" };
String[] ignoreDuplicateOptionKeys = { "ingore_duplicate_rrs", "ignore_duplicates" };
String[] startFudgeOptionKeys = { "start_fudge" };
String[] expireFudgeOptionKeys = { "expire_fudge" };
String[] currentTimeOptionKeys = { "current_time" };
ignoreTime = cliBooleanOption("ignore-time", ignoreTimeOptionKeys, false);
ignoreDups = cliBooleanOption("ignore-duplicate-rrs", ignoreDuplicateOptionKeys, false);
startfudge = cliIntOption("S", startFudgeOptionKeys, 0);
expirefudge = cliIntOption("E", expireFudgeOptionKeys, 0);
String optstr = cliOption("t", currentTimeOptionKeys, null);
if (optstr != null) {
try {
currentTime = Utils.convertDuration(null, optstr);
} catch (java.text.ParseException e) {
fail("could not parse timespec");
}
}
@Override
protected void setupOptions(Options opts) {
opts.addOption(Option.builder("S").hasArg().argName("seconds").longOpt("sig-start-fudge")
.desc("'fudge' RRSIG inception ties by 'seconds'").build());
opts.addOption(Option.builder("E").hasArg().argName("seconds").longOpt("sig-expire-fudge")
.desc("'fudge' RRSIG expiration times by 'seconds'").build());
opts.addOption(Option.builder("t").hasArg().argName("time").longOpt("use-time")
.desc("Use 'time' as the time for verification purposes.").build());
String[] args = cli.getArgs();
opts.addOption(
Option.builder().longOpt("ignore-time").desc("Ignore RRSIG inception and expiration time errors.").build());
opts.addOption(Option.builder().longOpt("ignore-duplicate-rrs").desc("Ignore duplicate record errors.").build());
if (args.length < 1) {
fail("missing zone file");
}
@Override
protected void processOptions(CommandLine cli) {
if (cli.hasOption("ignore-time")) {
ignoreTime = true;
}
zonefile = args[0];
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);
}
if ((optstr = cli.getOptionValue('t')) != null) {
try {
currentTime = convertDuration(null, optstr);
} catch (ParseException e) {
System.err.println("error: could not parse timespec");
usage();
}
}
String[] optstrs = null;
if ((optstrs = cli.getOptionValues('A')) != null) {
for (int i = 0; i < optstrs.length; i++) {
addArgAlias(optstrs[i]);
}
}
String[] args = cli.getArgs();
if (args.length < 1) {
System.err.println("error: missing zone file");
usage();
}
zonefile = args[0];
if (args.length >= 2) {
keyfiles = new String[args.length - 1];
System.arraycopy(args, 1, keyfiles, 0, keyfiles.length);
}
if (args.length >= 2) {
keyfiles = new String[args.length - 1];
System.arraycopy(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.getVerifier().setCurrentTime(state.currentTime);
zoneverifier.setIgnoreDuplicateRRs(state.ignoreDups);
zoneverifier.getVerifier().setStartFudge(startfudge);
zoneverifier.getVerifier().setExpireFudge(expirefudge);
zoneverifier.getVerifier().setIgnoreTime(ignoreTime);
zoneverifier.getVerifier().setCurrentTime(currentTime);
zoneverifier.setIgnoreDuplicateRRs(ignoreDups);
List<Record> records = ZoneUtils.readZoneFile(state.zonefile, null);
List<Record> records = ZoneUtils.readZoneFile(zonefile, null);
log.fine("verifying zone...");
int errors = zoneverifier.verifyZone(records);
@@ -144,9 +116,8 @@ public class VerifyZone extends CLBase {
}
public static void main(String[] args) {
VerifyZone tool = new VerifyZone();
tool.state = new CLIState();
VerifyZone tool = new VerifyZone("verifyzone", "jdnssec-verifyzone [..options..] zonefile");
tool.run(tool.state, args);
tool.run(args);
}
}

View File

@@ -24,9 +24,6 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
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;
@@ -45,43 +42,33 @@ import com.verisignlabs.dnssec.security.RecordComparator;
* @author David Blacka
*/
public class ZoneFormat extends CLBase {
private CLIState state;
private String file;
private boolean assignNSEC3;
/**
* 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");
}
@Override
protected void setupOptions(Options opts) {
opts.addOption("N", "nsec3", false,
"attempt to determine the original ownernames for NSEC3 RRs.");
}
@Override
protected void processOptions(CommandLine cli) throws ParseException {
if (cli.hasOption('N'))
assignNSEC3 = true;
String[] args = cli.getArgs();
if (args.length < 1) {
System.err.println("error: must specify a zone file");
usage();
}
file = args[0];
}
public ZoneFormat(String name, String usageStr) {
super(name, usageStr);
}
private static List<Record> readZoneFile(String filename) throws IOException {
protected void setupOptions() {
opts.addOption("N", "nsec3", false,
"attempt to determine the original ownernames for NSEC3 RRs.");
}
protected void processOptions() {
String[] assignNsec3OwnersOptionKeys = { "assign_nsec3_owners", "assign_owners" };
assignNSEC3 = cliBooleanOption("N", assignNsec3OwnersOptionKeys, false);
String[] args = cli.getArgs();
if (args.length < 1) {
fail("must specify a zone file");
}
file = args[0];
}
private List<Record> readZoneFile(String filename) throws IOException {
try (Master master = new Master(filename)) {
List<Record> res = new ArrayList<>();
Record r = null;
@@ -98,14 +85,14 @@ public class ZoneFormat extends CLBase {
}
}
private static void formatZone(List<Record> zone) {
private void formatZone(List<Record> zone) {
for (Record r : zone) {
System.out.println(r.toString());
}
}
private static void determineNSEC3Owners(List<Record> zone)
private void determineNSEC3Owners(List<Record> zone)
throws NoSuchAlgorithmException {
// first, find the NSEC3PARAM record -- this is an inefficient linear
@@ -173,11 +160,11 @@ public class ZoneFormat extends CLBase {
}
public void execute() throws IOException, NoSuchAlgorithmException {
List<Record> z = readZoneFile(state.file);
List<Record> z = readZoneFile(file);
// Put the zone into a consistent (name and RR type) order.
Collections.sort(z, new RecordComparator());
if (state.assignNSEC3) {
if (assignNSEC3) {
determineNSEC3Owners(z);
} else {
formatZone(z);
@@ -185,10 +172,9 @@ public class ZoneFormat extends CLBase {
}
public static void main(String[] args) {
ZoneFormat tool = new ZoneFormat();
tool.state = new CLIState();
ZoneFormat tool = new ZoneFormat("zoneformat", "jdnssec-zoneformat [..options..] zonefile");
tool.run(tool.state, args);
tool.run(args);
}
}

View File

@@ -43,6 +43,7 @@ import java.security.spec.NamedParameterSpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.util.StringTokenizer;
import java.util.logging.Logger;
import javax.crypto.interfaces.DHPrivateKey;
import javax.crypto.interfaces.DHPublicKey;
@@ -67,6 +68,8 @@ public class DnsKeyConverter {
private KeyFactory mECKeyFactory;
private KeyFactory mEdKeyFactory;
private DnsKeyAlgorithm mAlgorithms;
private Logger log = Logger.getLogger(this.getClass().toString());
public DnsKeyConverter() {
mAlgorithms = DnsKeyAlgorithm.getInstance();
@@ -111,10 +114,16 @@ public class DnsKeyConverter {
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,
int origAlgorithm = mAlgorithms.originalAlgorithm(alg);
DNSKEYRecord keyrec = new DNSKEYRecord(name, dclass, ttl, flags, DNSKEYRecord.Protocol.DNSSEC, origAlgorithm,
key);
if (origAlgorithm == alg) {
return keyrec;
}
return new DNSKEYRecord(name, dclass, ttl, flags, DNSKEYRecord.Protocol.DNSSEC, alg, keyrec.getKey());
} catch (DNSSECException e) {
// FIXME: this mimics the behavior of KEYConverter.buildRecord(), which would
log.severe("Unable to generated a DNSKEYRecord: " + e);
// This mimics the behavior of KEYConverter.buildRecord(), which would
// return null if the algorithm was unknown.
return null;
}

View File

@@ -178,7 +178,7 @@ public class SignUtils {
if (n.labels() != labels) {
n = n.wild(n.labels() - labels);
wildcardName = true;
log.fine("Detected wildcard expansion: " + rrset.getName() + " changed to " + n);
log.finer("Detected wildcard expansion: " + rrset.getName() + " changed to " + n);
}
// now convert the wire format records in the RRset into a
@@ -1063,7 +1063,7 @@ public class SignUtils {
int ldiff = node.name.labels() - zonename.labels();
for (int i = 1; i < ldiff; i++) {
Name n = new Name(node.name, i);
log.fine("Generating ENT NSEC3 for " + n);
log.finer("Generating ENT NSEC3 for " + n);
ProtoNSEC3 nsec3 = generateNSEC3(n, zonename, node.ttl, salt, iterations, optIn,
null);
nsec3s.add(nsec3);
@@ -1127,11 +1127,11 @@ public class SignUtils {
// check to see if cur is a duplicate (by name)
if (prevNSEC3 != null
&& Arrays.equals(prevNSEC3.getOwner(), curNSEC3.getOwner())) {
log.fine("found duplicate NSEC3 (by name) -- merging type maps: "
log.finer("found duplicate NSEC3 (by name) -- merging type maps: "
+ prevNSEC3.getTypemap() + " and " + curNSEC3.getTypemap());
i.remove();
prevNSEC3.mergeTypes(curNSEC3.getTypemap());
log.fine("merged type map: " + prevNSEC3.getTypemap());
log.finer("merged type map: " + prevNSEC3.getTypemap());
continue;
}