10 Commits

36 changed files with 1295 additions and 1597 deletions

2
.gitignore vendored
View File

@@ -1,6 +1,5 @@
build build
bin/main bin/main
dist/
.classpath .classpath
.project .project
.gradle .gradle
@@ -9,4 +8,3 @@ docs
test-zones test-zones
settings.json settings.json
.settings .settings
.DS_Store

View File

@@ -1,35 +1,3 @@
2026-02-19 David Blacka <david@blacka.com>
* Update dnsjava to 3.6.4 (this adds a number of "new" RR types, including SVCB/HTTPS and ZONEMD.)
2024-11-02 David Blacka <david@blacka.com>
* Handle I/O errors from ZoneUtils.readZoneFile() correctly (issue #19).
2024-04-13 David Blacka <david@blacka.com>
* Remove support for ECC_GOST
* Create a new DSAlgorithm class, move DS creation into that
* Add support for DS algorithms 3 and 4 -- bouncycastle crypto
provider used for DS algorithm 3 (GOST R 34.11-94)
* Moved support for loading the bouncycastle provider to the new
DSAlgorithm class
2024-04-07 David Blacka <david@blacka.com>
* Released version 0.20
* Removed support for Gradle builds since the gradle config was
out-of-date
* Requires Java 15 or later for EdDSA support
* Supports DNSSEC algorithm 16 using the SunEC provider
* Supports a java properties-formatted config file. See
jdnssec-tools.properties.example for an example
* Updated to dnsjava 3.5.3
* Updated to commons-cli 1.6.0
* Added a "one-jar" distribution method, and a "univeral" CLI to
use with it.
* Formatting and linter suggestions
2024-03-25 David Blacka <davidb@verisign.com> 2024-03-25 David Blacka <davidb@verisign.com>
* Released version 0.19 * Released version 0.19

View File

@@ -11,3 +11,5 @@ This bit of code has been around since approximately 2005, and has been in "mini
* allowing for an external sort once the data is shown to be larger than X, and/or * allowing for an external sort once the data is shown to be larger than X, and/or
* allowing for a memory-constrained internal sort that uses disk, and/or, * allowing for a memory-constrained internal sort that uses disk, and/or,
* figuring out how to let the JVM use *a lot* of memory. * figuring out how to let the JVM use *a lot* of memory.
* Add support for algorithm 16, perhaps refactor algorithm 15 support using bouncycastle.
* Note that our current dnsjava version, 3.5.1 has some support, although it isn't clear if it has sign/verify support.

View File

@@ -8,13 +8,9 @@ This is a collection of DNSSEC tools written in Java. They are intended to be a
These tools depend upon DNSjava (<https://github.com/dnsjava/dnsjava>), the Jakarta Commons CLI and Logging libraries (<https://commons.apache.org/proper/commons-cli>), slf4j (<https://www.slf4j.org>), and Sun's Java Cryptography extensions. A copy of each of these libraries is included in the distribution. These tools depend upon DNSjava (<https://github.com/dnsjava/dnsjava>), the Jakarta Commons CLI and Logging libraries (<https://commons.apache.org/proper/commons-cli>), slf4j (<https://www.slf4j.org>), and Sun's Java Cryptography extensions. A copy of each of these libraries is included in the distribution.
See the "[licenses](https://github.com/dblacka/jdnssec-tools/tree/master/licenses)" directory for the licensing information of this package and the other packages that are distributed with it. See the "licenses" directory for the licensing information of this package and the other packages that are distributed with it.
## Getting Started Getting started:
### Using the binary distribution
The binary distributions can be downloaded from the [releases](https://github.com/dblacka/jdnssec-tools/releases) page. To use it;
1. Unpack the binary distribution: 1. Unpack the binary distribution:
@@ -25,11 +21,9 @@ The binary distributions can be downloaded from the [releases](https://github.co
cd java-dnssec-tools-x.x.x cd java-dnssec-tools-x.x.x
./bin/jdnssec-signzone -h ./bin/jdnssec-signzone -h
### Building from source Building from source:
There is a source distribution also downloadable from the [releases](https://github.com/dblacka/jdnssec-tools/releases) page, but this should work with a clone of this repo. 1. Unpack the source distribution, preferably into the same directory that the binary distribution was unpacked.
1. (If downloaded) Unpack the source distribution, preferably into the same directory that the binary distribution was unpacked.
tar zxvf java-dnssec-tools-x.x.x-src.tar.gz tar zxvf java-dnssec-tools-x.x.x-src.tar.gz
@@ -38,19 +32,17 @@ There is a source distribution also downloadable from the [releases](https://git
ant ant
4. You can build the distribution tarballs with 'ant dist', although the main `ant` build command will have built the primary jar file. 4. You can build the distribution tarballs with 'ant dist'. You can run the tools directly from the build area (without building the jdnssec-tools.jar file) by using the ./bin/\_jdnssec_* wrappers.
5. Alternatively, build the project using gradle:
gradlew clean
gradlew assemble -i
The resulting jar file gets generated in build/libs.
The source for this project is available in git on github: <https://github.com/dblacka/jdnssec-tools> The source for this project is available in git on github: <https://github.com/dblacka/jdnssec-tools>
### Using the one-jar distribution
As of version 0.20, there is a one-jar (aka an executable jar) as part of the distribution. It can also be downloaded from the [releases](https://github.com/dblacka/jdnssec-tools/releases) page.
1. Fetch the one-jar distribution.
2. Invoke with `java -jar jdnssec-tools-x.x.x.jar`
java -jar jdnssec-tools-x.x.x.jar signzone -h
--- ---
Questions or comments may be directed to the author (<mailto:davidb@verisign.com>), or by creating issues in the [github issue tracker](https://github.com/dblacka/jdnssec-tools/issues). Questions or comments may be directed to the author (<mailto:davidb@verisign.com>), or by creating issues in the [github issue tracker](https://github.com/dblacka/jdnssec-tools/issues).

View File

@@ -1 +1 @@
version=0.21 version=0.19

19
bin/_jdnssec-dstool Executable file
View File

@@ -0,0 +1,19 @@
#! /bin/sh
thisdir=`dirname $0`
basedir=`cd $thisdir/..; pwd`
ulimit_max=`ulimit -H -n`
if [ $ulimit_max != "unlimited" ]; then
ulimit -n $ulimit_max
fi
# set the classpath
CLASSPATH=$CLASSPATH:$basedir/build/classes
for i in $basedir/lib/*.jar $basedir/lib/*.zip; do
CLASSPATH="$CLASSPATH":"$i"
done
export CLASSPATH
exec java com.verisignlabs.dnssec.cl.DSTool "$@"

19
bin/_jdnssec-keygen Executable file
View File

@@ -0,0 +1,19 @@
#! /bin/sh
thisdir=`dirname $0`
basedir=`cd $thisdir/..; pwd`
ulimit_max=`ulimit -H -n`
if [ $ulimit_max != "unlimited" ]; then
ulimit -n $ulimit_max
fi
# set the classpath
CLASSPATH=$CLASSPATH:$basedir/build/classes
for i in $basedir/lib/*.jar $basedir/lib/*.zip; do
CLASSPATH="$CLASSPATH":"$i"
done
export CLASSPATH
exec java com.verisignlabs.dnssec.cl.KeyGen "$@"

19
bin/_jdnssec-keyinfo Executable file
View File

@@ -0,0 +1,19 @@
#! /bin/sh
thisdir=`dirname $0`
basedir=`cd $thisdir/..; pwd`
ulimit_max=`ulimit -H -n`
if [ $ulimit_max != "unlimited" ]; then
ulimit -n $ulimit_max
fi
# set the classpath
CLASSPATH=$CLASSPATH:$basedir/build/classes
for i in $basedir/lib/*.jar $basedir/lib/*.zip; do
CLASSPATH="$CLASSPATH":"$i"
done
export CLASSPATH
exec java com.verisignlabs.dnssec.cl.KeyInfoTool "$@"

19
bin/_jdnssec-signkeyset Executable file
View File

@@ -0,0 +1,19 @@
#! /bin/sh
thisdir=`dirname $0`
basedir=`cd $thisdir/..; pwd`
ulimit_max=`ulimit -H -n`
if [ $ulimit_max != "unlimited" ]; then
ulimit -n $ulimit_max
fi
# set the classpath
CLASSPATH=$CLASSPATH:$basedir/build/classes
for i in $basedir/lib/*.jar $basedir/lib/*.zip; do
CLASSPATH="$CLASSPATH":"$i"
done
export CLASSPATH
exec java com.verisignlabs.dnssec.cl.SignKeyset "$@"

19
bin/_jdnssec-signzone Executable file
View File

@@ -0,0 +1,19 @@
#! /bin/sh
thisdir=`dirname $0`
basedir=`cd $thisdir/..; pwd`
ulimit_max=`ulimit -H -n`
if [ $ulimit_max != "unlimited" ]; then
ulimit -n $ulimit_max
fi
# set the classpath
CLASSPATH=$CLASSPATH:$basedir/build/classes
for i in $basedir/lib/*.jar $basedir/lib/*.zip; do
CLASSPATH="$CLASSPATH":"$i"
done
export CLASSPATH
exec java com.verisignlabs.dnssec.cl.SignZone "$@"

19
bin/_jdnssec-verifyzone Executable file
View File

@@ -0,0 +1,19 @@
#! /bin/sh
thisdir=`dirname $0`
basedir=`cd $thisdir/..; pwd`
ulimit_max=`ulimit -H -n`
if [ $ulimit_max != "unlimited" ]; then
ulimit -n $ulimit_max
fi
# set the classpath
CLASSPATH=$CLASSPATH:$basedir/build/classes
for i in $basedir/lib/*.jar $basedir/lib/*.zip; do
CLASSPATH="$CLASSPATH":"$i"
done
export CLASSPATH
exec java com.verisignlabs.dnssec.cl.VerifyZone "$@"

19
bin/_jdnssec-zoneformat Executable file
View File

@@ -0,0 +1,19 @@
#! /bin/sh
thisdir=`dirname $0`
basedir=`cd $thisdir/..; pwd`
ulimit_max=`ulimit -H -n`
if [ $ulimit_max != "unlimited" ]; then
ulimit -n $ulimit_max
fi
# set the classpath
CLASSPATH=$CLASSPATH:$basedir/build/classes
for i in $basedir/lib/*.jar $basedir/lib/*.zip; do
CLASSPATH="$CLASSPATH":"$i"
done
export CLASSPATH
exec java com.verisignlabs.dnssec.cl.ZoneFormat "$@"

View File

@@ -1,22 +0,0 @@
#! /bin/sh
thisdir=$(dirname $0)
basedir=$(cd $thisdir/.. || exit; pwd)
ulimit_max=$(ulimit -H -n)
if [ $ulimit_max != "unlimited" ]; then
ulimit -n $ulimit_max
fi
# set the classpath
for i in "$basedir"/lib/*.jar "$basedir"/lib/*.zip "$basedir"/build/libs/*.jar; do
if ! [ -f $i ]; then continue; fi
if [ -z "$CLASSPATH" ]; then
CLASSPATH=$i
else
CLASSPATH="$CLASSPATH":"$i"
fi
done
export CLASSPATH
exec java com.verisignlabs.dnssec.cl.CLI "$@"

View File

@@ -1,3 +1,2 @@
build.deprecation=true build.deprecation=true
build.debug=false build.debug=true
build.java_version=17

View File

@@ -10,20 +10,18 @@
--> -->
<project default="build" basedir="."> <project default="compile" basedir=".">
<property file="build.properties" /> <property file="build.properties" />
<property file="VERSION" /> <property file="VERSION" />
<property name="sectools-distname" value="jdnssec-tools-${version}" />
<property name="build.dir" value="build" /> <property name="build.dir" value="build" />
<property name="build.dest" value="${build.dir}/classes" /> <property name="build.dest" value="${build.dir}/classes" />
<property name="build.lib.dest" value="${build.dir}/libs" /> <property name="build.lib.dest" value="${build.dir}/libs" />
<property name="build.src" value="src/main/java" /> <property name="build.src" value="src/main/java" />
<property name="dist.dir" value="dist"/>
<property name="dist.name" value="jdnssec-tools-${version}" />
<property name="packages" value="com.verisignlabs.dnssec.*" /> <property name="packages" value="com.verisignlabs.dnssec.*" />
<property name="doc.dir" value="docs" /> <property name="doc.dir" value="docs" />
<property name="javadoc.dest" value="${doc.dir}/javadoc" /> <property name="javadoc.dest" value="${doc.dir}/javadoc" />
@@ -35,7 +33,6 @@
<pathelement location="${build.dest}" /> <pathelement location="${build.dest}" />
<fileset dir="${lib.dir}" includes="*.jar,*.zip" /> <fileset dir="${lib.dir}" includes="*.jar,*.zip" />
</path> </path>
<property name="project.classpath" refid="project.classpath" /> <property name="project.classpath" refid="project.classpath" />
<target name="prepare-src"> <target name="prepare-src">
@@ -43,7 +40,7 @@
<mkdir dir="${build.lib.dest}" /> <mkdir dir="${build.lib.dest}" />
</target> </target>
<target name="compile" depends="prepare-src" > <target name="sectools" depends="prepare-src" >
<javac srcdir="${build.src}" <javac srcdir="${build.src}"
destdir="${build.dest}" destdir="${build.dest}"
classpathref="project.classpath" classpathref="project.classpath"
@@ -51,32 +48,17 @@
includeantruntime="false" includeantruntime="false"
includes="com/verisignlabs/dnssec/" includes="com/verisignlabs/dnssec/"
debug="${build.debug}" debug="${build.debug}"
release="${build.java_version}" /> release="17" />
</target> </target>
<target name="build-jar" depends="usage, compile"> <target name="sectools-jar" depends="usage,sectools">
<jar jarfile="${build.lib.dest}/jdnssec-tools.jar" <jar jarfile="${build.lib.dest}/jdnssec-tools.jar"
basedir="${build.dest}" basedir="${build.dest}"
includes="com/verisignlabs/dnssec/" /> includes="com/verisignlabs/dnssec/" />
</target> </target>
<target name="build" <target name="compile"
depends="usage,build-jar"> depends="usage,sectools-jar">
</target>
<target name="build-onejar" depends="compile">
<jar destfile="${dist.dir}/${dist.name}.jar">
<zipfileset dir="${build.dest}" includes="**/*.class" />
<zipfileset src="${lib.dir}/dnsjava-3.6.4.jar" />
<zipfileset src="${lib.dir}/commons-cli-1.6.0.jar" />
<zipfileset src="${lib.dir}/slf4j-api-1.7.36.jar" />
<zipfileset src="${lib.dir}/slf4j-simple-1.7.36.jar" />
<manifest>
<attribute name="Main-Class"
value="com.verisignlabs.dnssec.cl.CLI" />
</manifest>
</jar>
</target> </target>
<target name="javadoc" depends="usage"> <target name="javadoc" depends="usage">
@@ -93,21 +75,16 @@
</javadoc> </javadoc>
</target> </target>
<target name="clean" depends="usage"> <target name="clean" depends="usage">
<delete dir="${build.dest}" /> <delete dir="${build.dest}" />
<delete dir="${build.lib.dest}" /> <delete dir="${build.lib.dest}" />
<delete dir="${dist.dir}" />
</target> </target>
<target name="dist-clean" depends="usage"> <target name="sectools-dist-prepare" depends="usage, compile, javadoc">
<delete dir="${dist.name}" /> <mkdir dir="${sectools-distname}" />
</target>
<target name="dist-prepare" depends="usage, build, javadoc"> <copy todir="${sectools-distname}">
<mkdir dir="${dist.dir}" />
<mkdir dir="${dist.name}" />
<copy todir="${dist.name}">
<fileset dir="."> <fileset dir=".">
<include name="bin/jdnssec-*" /> <include name="bin/jdnssec-*" />
<include name="lib/*.jar" /> <include name="lib/*.jar" />
@@ -122,33 +99,37 @@
</fileset> </fileset>
</copy> </copy>
<copy todir="${dist.name}/lib"> <copy todir="${sectools-distname}/lib">
<fileset dir="${build.lib.dest}"> <fileset dir="${build.lib.dest}">
<include name="*.jar" /> <include name="*.jar" />
</fileset> </fileset>
</copy> </copy>
</target> </target>
<target name="sectools-dist-clean">
<delete dir="${sectools-distname}" />
</target>
<patternset id="exec.files"> <patternset id="exec.files">
<include name="${dist.name}/bin/jdnssec-*" /> <include name="${sectools-distname}/bin/jdnssec-*" />
</patternset> </patternset>
<patternset id="src.files"> <patternset id="src.files">
<include name="${dist.name}/src/" /> <include name="${sectools-distname}/src/" />
<include name="${dist.name}/build.xml" /> <include name="${sectools-distname}/build.xml" />
<include name="${dist.name}/build.properties" /> <include name="${sectools-distname}/build.properties" />
</patternset> </patternset>
<patternset id="bin.files"> <patternset id="bin.files">
<include name="${dist.name}/doc/" /> <include name="${sectools-distname}/doc/" />
<include name="${dist.name}/lib/" /> <include name="${sectools-distname}/lib/" />
<include name="${dist.name}/licenses/" /> <include name="${sectools-distname}/licenses/" />
<include name="${dist.name}/VERSION" /> <include name="${sectools-distname}/VERSION" />
<include name="${dist.name}/README" /> <include name="${sectools-distname}/README" />
</patternset> </patternset>
<target name="bin-dist" depends="dist-prepare"> <target name="sectools-bin-dist" depends="sectools-dist-prepare">
<tar destfile="${dist.dir}/${dist.name}.tar.gz" compression="gzip"> <tar destfile="${sectools-distname}.tar.gz" compression="gzip">
<tarfileset mode="755" dir="."> <tarfileset mode="755" dir=".">
<patternset refid="exec.files" /> <patternset refid="exec.files" />
</tarfileset> </tarfileset>
@@ -158,20 +139,21 @@
</tar> </tar>
</target> </target>
<target name="src-dist" depends="dist-prepare"> <target name="sectools-src-dist" depends="sectools-dist-prepare">
<tar destfile="${dist.dir}/${dist.name}-src.tar.gz" compression="gzip"> <tar destfile="${sectools-distname}-src.tar.gz"
compression="gzip">
<tarfileset dir="."> <tarfileset dir=".">
<patternset refid="src.files" /> <patternset refid="src.files" />
</tarfileset> </tarfileset>
</tar> </tar>
</target> </target>
<target name="onejar" <target name="sectools-dist"
depends="build-onejar"> depends="sectools-bin-dist,sectools-src-dist, sectools-dist-clean">
</target> </target>
<target name="dist"
depends="bin-dist, src-dist, build-onejar, dist-clean"> <target name="dist" depends="sectools-dist">
</target> </target>
<target name="usage"> <target name="usage">
@@ -179,11 +161,10 @@
<echo message="jdnssec-tools v. ${version} Build System" /> <echo message="jdnssec-tools v. ${version} Build System" />
<echo message="--------------------------------" /> <echo message="--------------------------------" />
<echo message="Available Targets:" /> <echo message="Available Targets:" />
<echo message=" build (default) - compiles the source code, creates main jar" /> <echo message=" compile (default) - compiles the source code, creates jar" />
<echo message=" javadoc - create javadoc from source" /> <echo message=" javadoc - create javadoc from source" />
<echo message=" clean - delete class files" /> <echo message=" clean - delete class files" />
<echo message=" dist - package it up" /> <echo message=" dist - package it up" />
<echo message=" onejar - build the executable jar" />
<echo message=" usage - this help message" /> <echo message=" usage - this help message" />
<echo message=" " /> <echo message=" " />
</target> </target>

View File

@@ -1,93 +0,0 @@
# An example properties file for jdnssec-tools
# Properties may be be scoped by the tool name, which is the name minus "jdnssec-"
# If unscoped, the same named property will be used by multiple tools
# Common properties
# log_level = warning
# verbose = true # same as log_level = fine (true) or log_level = warning (false)
# multiline = false
# algorithm aliasing is <scope>.alias.<new-mnemonic> = <orig-alg-id>:<alias-alg-id>
# alias.NEWALG = 8:100
# jdnssec-dstool properties
## These are all equivalent. Unscoped properties might apply to other tools
# dstool.digest_algorithm = 4
# digest_algorithm = 4 # applies to jdnssec-signzone, too
# dstool.digest_id = 4
# jdnssec-keygen properties
# keygen.use_large_exponent = true
# keygen.key_directory = .
# key_directory = /path/to/dnskey_files # applies to jdnssec-sign*
# keygen.algorithm = ED448
# keygen.keylength = 2048
# keygen.keylen = 2048 # same thing
# keygen.ttl = 3600
# jdnssec-keyinfotool
# no additional keys
# jdnssec-signkeyset
# signkeyset.verify = false
# signkeyset.key_directory = .
# signkeyset.start = -300
# signkeyset.inception = 1712424863
# signkeyset.expire = +604800
# jdnssec-signrrset
# signrrset.verify_signatures = false
# signrrset.verify = false # same thing
# signrrset.key_directory = .
# signrrset.start = now
# signrrset.inception = now # same thing
# signrrset.expire = now+3600
# jdnssec-signzone
# signzone.verify_signatures = false
# signzone.verify = false # same thing
# signzone.use_nsec3 = false
# signzone.nsec3 = false # same thing
# signzone.use_opt_out = false
# signzone.opt_out = false # same thing
# signzone.verbose_signing = false
# signzone.fully_sign_keyset = false
# signzone.fully_sign = false # same thing
# signzone.key_directory = .
# signzone.keydir = . # same thing
# signzone.start = now
# signzone.inception = now
# signzone.expire = now+3600
# signzone.nsec3_salt = DEADBEEF
# signzone.salt = DEADBEEF # same thing
# signzone.nsec3_random_salt_length = 6
# signzone.nsec3_salt_length = 6 # same thing
# signzone.random_salt_length = 6 # same thing
# signzone.nsec3_iterations = 0
# signzone.iterations = 0 # same thing
# signzone.digest_algorithm = 4
# signzone.digest_id = 4 # same thing
# signzone.nsec3param_ttl = 86400
# signzone.include_names_file = /path/to/include-names
# signzone.include_names = /path/to/include-names # same thing
# jdnssec-verifyzone
# verifyzone.ignore_time = false
# verifyzone.ignore_duplicate_rrs = false
# verifyzone.ignore_duplicates = false # same thing
# verifyzone.start_fudge = 0
# verifyzone.expire_fudge = 0
# verifyzone.current_time = now
# jdnssec-zoneformat
# zoneformat.assign_nsec3_owners = false
# zoneformat.assign_owners = false # same thing

BIN
lib/dnsjava-3.5.3.jar Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -17,11 +17,11 @@
package com.verisignlabs.dnssec.cl; package com.verisignlabs.dnssec.cl;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.Properties; import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.Date;
import java.util.TimeZone;
import java.util.logging.Formatter; import java.util.logging.Formatter;
import java.util.logging.Handler; import java.util.logging.Handler;
import java.util.logging.Level; import java.util.logging.Level;
@@ -48,19 +48,8 @@ import com.verisignlabs.dnssec.security.DnsKeyAlgorithm;
* subclass variant of the CLIState and call run(). * subclass variant of the CLIState and call run().
*/ */
public abstract class CLBase { public abstract class CLBase {
protected Logger log = Logger.getLogger(this.getClass().toString()); protected static Logger staticLog = Logger.getLogger(CLBase.class.getName());
protected Options opts; protected Logger log;
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 * This is a very simple log formatter that simply outputs the log level and
@@ -81,319 +70,253 @@ 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 an overridable method for subclasses to add their own command line * This is a base class for command line parsing state. Subclasses should
* options. * override setupOptions and processOptions.
*/ */
protected abstract void setupOptions(); public static class CLIStateBase {
protected Options opts;
protected String usageStr;
/** /**
* Initialize the command line options * The base constructor. This will setup the command line options.
*/ *
public void setup() { * @param usage
opts = new Options(); * The command line usage string (e.g.,
setupCommonOptions(); * "jdnssec-foo [..options..] zonefile")
setupOptions(); */
} public CLIStateBase(String usage) {
usageStr = usage;
/** setup();
* 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);
} }
if (cli.hasOption('h')) { /** This is the base set of command line options provided to all subclasses. */
usage(); 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);
} }
String loadedConfig = loadConfig(cli.getOptionValue('c')); /**
* This is an overridable method for subclasses to add their own command
Logger rootLogger = Logger.getLogger(""); * line options.
*
// we set log level with both --log-level and -v/--verbose. * @param opts
String logLevel = cliOption("log-level", logLevelOptionKeys, null); * the options object to add (via OptionBuilder, typically) new
if (logLevel == null) { * options to.
logLevel = cli.hasOption("v") ? "fine" : "warning"; */
} protected void setupOptions(Options opts) {
setLogLevel(rootLogger, logLevel); // Subclasses generally override this.
for (Handler h : rootLogger.getHandlers()) {
h.setLevel(rootLogger.getLevel());
h.setFormatter(new BareLogFormatter());
} }
if (loadedConfig != null) { /**
log.info("Loaded config file: " + loadedConfig); * 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 (cliBooleanOption("m", multilineOptionKeys, false)) { if (cli.hasOption('h')) {
org.xbill.DNS.Options.set("multiline"); usage();
}
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;
} }
try (FileInputStream stream = new FileInputStream(f)) { Logger rootLogger = Logger.getLogger("");
props.load(stream); int value = parseInt(cli.getOptionValue('v'), -1);
break; // load the first config file found in our list
} catch (IOException e) {
log.warning("Could not read config file " + f.getName() + ": " + e);
}
}
if (f != null) { switch (value) {
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: case 0:
level = Level.OFF; rootLogger.setLevel(Level.OFF);
break; break;
case 1: case 1:
level = Level.SEVERE; rootLogger.setLevel(Level.SEVERE);
break; break;
case 2: case 2:
default: default:
level = Level.WARNING; rootLogger.setLevel(Level.WARNING);
break; break;
case 3: case 3:
level = Level.INFO; rootLogger.setLevel(Level.INFO);
break; break;
case 4: case 4:
level = Level.FINE; rootLogger.setLevel(Level.CONFIG);
break;
case 5: case 5:
rootLogger.setLevel(Level.FINE);
break;
case 6: case 6:
level = Level.ALL; rootLogger.setLevel(Level.ALL);
break;
} }
} else {
try { // I hate java.util.logging, btw.
level = Level.parse(levelStr.toUpperCase()); for (Handler h : rootLogger.getHandlers()) {
} catch (IllegalArgumentException e) { h.setLevel(rootLogger.getLevel());
System.err.println("Verbosity level '" + levelStr + "' not recognized"); h.setFormatter(new BareLogFormatter());
level = Level.WARNING;
} }
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);
} }
logger.setLevel(level);
} }
/** public static int parseInt(String s, int def) {
* Process both property file based alias definitions and command line alias try {
* definitions return Integer.parseInt(s);
*/ } catch (NumberFormatException e) {
protected void processAliasOptions() { return def;
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];
if (mnemonic != null && origAlg >= 0 && aliasAlg >= 0) {
algs.addAlias(aliasAlg, mnemonic, origAlg);
}
}
} }
}
// Next see if we have any alias options in properties public static long parseLong(String s, long def) {
// Those look like 'signzone.alias.<alias-mnemonic> = try {
// <orig-alg-num>:<alias-alg-num>' return Long.parseLong(s);
for (String key : props.stringPropertyNames()) { } catch (NumberFormatException e) {
if (key.startsWith(name + ".alias.") || key.startsWith("alias.")) { return def;
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);
if (mnemonic != null && origAlg >= 0 && aliasAlg >= 0) {
algs.addAlias(aliasAlg, mnemonic, origAlg);
}
}
} }
} }
/** /**
* Given a parsed command line, option, and list of possible config * Calculate a date/time from a command line time/offset duration string.
* properties, and a default value, determine value for the option
* *
* @param option The option name * @param start
* @param properties A list of configuration parameters that we would like * the start time to calculate offsets from.
* to use for this option, from most preferred to least. * @param duration
* @param defaultValue A default value to return if either the option or * the time/offset string to parse.
* config value cannot be parsed, or neither are present. * @return the calculated time.
* @return The found value, or the default value.
*/ */
protected String cliOption(String option, String[] properties, String defaultValue) { public static Instant convertDuration(Instant start, String duration) throws ParseException {
if (cli.hasOption(option)) { if (start == null) {
return cli.getOptionValue(option); start = Instant.now();
} }
for (String property : properties) {
// first look up the scoped version of the property if (duration.startsWith("now")) {
String value = props.getProperty(name + "." + property); start = Instant.now();
if (value != null) { if (duration.indexOf("+") < 0)
return value; return start;
}
value = props.getProperty(property); duration = duration.substring(3);
if (value != null) {
return value;
}
} }
return defaultValue;
}
/** if (duration.startsWith("+")) {
* Given a parsed command line, option, and list of possible config long offset = parseLong(duration.substring(1), 0);
* properties, determine the value for the option, converting the value to return start.plusSeconds(offset);
* long. }
*/
protected long cliLongOption(String option, String[] properties, long defaultValue) { // This is a heuristic to distinguish UNIX epoch times from the zone file
String value = cliOption(option, properties, Long.toString(defaultValue)); // format standard (which is length == 14)
return Utils.parseLong(value, defaultValue); 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 SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyyMMddHHmmss");
* int. dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT"));
*/ try {
protected int cliIntOption(String option, String[] properties, int defaultValue) { Date parsedDate = dateFormatter.parse(duration);
String value = cliOption(option, properties, Integer.toString(defaultValue)); return parsedDate.toInstant();
return Utils.parseInt(value, defaultValue); } 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 abstract void execute() throws Exception;
public void run(String[] args) { 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();
}
parseCommandLine(args);
log = Logger.getLogger(this.getClass().toString()); log = Logger.getLogger(this.getClass().toString());
try { try {

View File

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

View File

@@ -19,7 +19,9 @@ package com.verisignlabs.dnssec.cl;
import java.io.File; import java.io.File;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option; import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.xbill.DNS.DClass; import org.xbill.DNS.DClass;
import org.xbill.DNS.DNSKEYRecord; import org.xbill.DNS.DNSKEYRecord;
import org.xbill.DNS.Name; import org.xbill.DNS.Name;
@@ -35,144 +37,177 @@ import com.verisignlabs.dnssec.security.JCEDnsSecSigner;
* @author David Blacka * @author David Blacka
*/ */
public class KeyGen extends CLBase { public class KeyGen extends CLBase {
private int algorithm = 13; private CLIState state;
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);
}
/** /**
* Set up the command line options. * This is a small inner class used to hold all of the command line option
* state.
*/ */
protected void setupOptions() { protected static class CLIState extends CLIStateBase {
// boolean options public int algorithm = 13;
opts.addOption("k", "kskflag", false, public int keylength = 2048;
"Key is a key-signing-key (sets the SEP flag)."); public boolean useLargeE = true;
opts.addOption("e", "large-exponent", false, "Use large RSA exponent (default)"); public String outputfile = null;
opts.addOption("E", "small-exponent", false, "Use small RSA exponent"); public File keydir = null;
public boolean zoneKey = true;
public boolean kskFlag = false;
public String owner = null;
public long ttl = 86400;
public int givenKeyTag = -1;
// Argument options public CLIState() {
opts.addOption( super("jdnssec-keygen [..options..] name");
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 + " | aliases, 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);
} }
outputfile = cli.getOptionValue('f'); /**
* 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());
String keydirName = cliOption("d", keyDirectoryOptionKeys, null);
if (keydirName != null) {
keydir = new File(keydirName);
} }
String algString = cliOption("a", algorithmOptionKeys, Integer.toString(algorithm)); @Override
algorithm = Utils.parseAlg(algString); protected void processOptions(CommandLine cli)
if (algorithm < 0) { throws org.apache.commons.cli.ParseException {
fail("DNSSEC algorithm " + algString + " is not supported"); 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];
} }
keylength = cliIntOption("b", keyLengthOptionKeys, keylength); private static int parseAlg(String s) {
ttl = cliLongOption("ttl", ttlOptionKeys, ttl); DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance();
givenKeyTag = Utils.parseInt(cli.getOptionValue("with-tag"), -1);
String[] args = cli.getArgs(); int alg = parseInt(s, -1);
if (alg > 0) {
if (algs.supportedAlgorithm(alg))
return alg;
return -1;
}
if (args.length < 1) { return algs.stringToAlgorithm(s);
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 { public void execute() throws Exception {
JCEDnsSecSigner signer = new JCEDnsSecSigner(); JCEDnsSecSigner signer = new JCEDnsSecSigner();
// Minor hack to make the owner name absolute. // Minor hack to make the owner name absolute.
if (!owner.endsWith(".")) { if (!state.owner.endsWith(".")) {
owner = owner + "."; state.owner = state.owner + ".";
} }
Name ownerName = Name.fromString(owner); Name ownerName = Name.fromString(state.owner);
// Calculate our flags // Calculate our flags
int flags = 0; int flags = 0;
if (zoneKey) { if (state.zoneKey) {
flags |= DNSKEYRecord.Flags.ZONE_KEY; flags |= DNSKEYRecord.Flags.ZONE_KEY;
} }
if (kskFlag) { if (state.kskFlag) {
flags |= DNSKEYRecord.Flags.SEP_KEY; flags |= DNSKEYRecord.Flags.SEP_KEY;
} }
log.fine("create key pair with (name = " + ownerName + ", ttl = " + ttl log.fine("create key pair with (name = " + ownerName + ", ttl = " + state.ttl
+ ", alg = " + algorithm + ", flags = " + flags + ", length = " + ", alg = " + state.algorithm + ", flags = " + flags + ", length = "
+ keylength + ")"); + state.keylength + ")");
DnsKeyPair pair = signer.generateKey(ownerName, ttl, DClass.IN, DnsKeyPair pair = signer.generateKey(ownerName, state.ttl, DClass.IN,
algorithm, flags, keylength, state.algorithm, flags, state.keylength,
useLargeE); state.useLargeE);
// If we were asked to generate a duplicate keytag, keep trying until we get one // If we were asked to generate a duplicate keytag, keep trying until we get one
// This can take a long time, depending on our key generation speed while (state.givenKeyTag >= 0 && pair.getDNSKEYFootprint() != state.givenKeyTag) {
while (givenKeyTag >= 0 && pair.getDNSKEYFootprint() != givenKeyTag) { pair = signer.generateKey(ownerName, state.ttl, DClass.IN, state.algorithm, flags, state.keylength,
pair = signer.generateKey(ownerName, ttl, DClass.IN, algorithm, flags, keylength, state.useLargeE);
useLargeE);
} }
if (outputfile != null) { if (state.outputfile != null) {
BINDKeyUtils.writeKeyFiles(outputfile, pair, keydir); BINDKeyUtils.writeKeyFiles(state.outputfile, pair, state.keydir);
} else { } else {
BINDKeyUtils.writeKeyFiles(pair, keydir); BINDKeyUtils.writeKeyFiles(pair, state.keydir);
System.out.println(BINDKeyUtils.keyFileBase(pair)); System.out.println(BINDKeyUtils.keyFileBase(pair));
} }
} }
public static void main(String[] args) { public static void main(String[] args) {
KeyGen tool = new KeyGen("keygen", "jdnssec-keygen [..options..] zonename"); KeyGen tool = new KeyGen();
tool.state = new CLIState();
tool.run(args); tool.run(tool.state, args);
} }
} }

View File

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

View File

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

View File

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

View File

@@ -28,7 +28,10 @@ import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Random; import java.util.Random;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option; 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.DNSKEYRecord;
import org.xbill.DNS.DNSSEC; import org.xbill.DNS.DNSSEC;
import org.xbill.DNS.Name; import org.xbill.DNS.Name;
@@ -51,225 +54,218 @@ import com.verisignlabs.dnssec.security.ZoneUtils;
* @author David Blacka * @author David Blacka
*/ */
public class SignZone extends CLBase { public class SignZone extends CLBase {
private File keyDirectory = null; private CLIState state;
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 = {};
private int iterations = 0;
private int digestId = DNSSEC.Digest.SHA256;
private long nsec3paramttl = -1;
private boolean verboseSigning = false;
private static final Random rand = new Random(); /**
* 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;
public SignZone(String name, String usageStr) { private static final Random rand = new Random();
super(name, usageStr);
}
protected void setupOptions() { public CLIState() {
// boolean options super("jdnssec-signzone [..options..] zone_file [key_file ...]");
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;
} }
fullySignKeyset = cliBooleanOption("F", fullySignKeysetOptionKeys, 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.");
optstr = cliOption("D", keyDirectoryOptionKeys, null); opts.addOption(Option.builder("d").hasArg().argName("dir").longOpt("keyset-directory")
if (optstr != null) { .desc("directory to find keyset files (default '.')").build());
keyDirectory = new File(optstr); opts.addOption(Option.builder("D").hasArg().argName("dir").longOpt("key-directory")
if (!keyDirectory.isDirectory()) { .desc("directory to find key files (default '.'").build());
fail("key directory " + optstr + " is not a directory"); 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());
}
@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;
} }
}
try { if (cli.hasOption('F'))
optstr = cliOption("s", inceptionOptionKeys, null); fullySignKeyset = true;
if (optstr != null) {
start = Utils.convertDuration(null, optstr); 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 { } else {
// default is now - 1 hour. // default is now - 1 hour.
start = Instant.now().minusSeconds(3600); start = Instant.now().minusSeconds(3600);
} }
} catch (java.text.ParseException e) {
fail("unable to parse start time specifiction: " + e.getMessage());
}
try { if ((optstr = cli.getOptionValue('e')) != null) {
optstr = cliOption("e", expireOptionKeys, null); expire = CLBase.convertDuration(start, optstr);
if (optstr != null) {
expire = Utils.convertDuration(start, optstr);
} else { } else {
expire = Utils.convertDuration(start, "+2592000"); // 30 days expire = CLBase.convertDuration(start, "+2592000"); // 30 days
} }
} catch (java.text.ParseException e) {
fail("unable to parse end time specficiation: " + e.getMessage());
}
outputfile = cli.getOptionValue('f'); outputfile = cli.getOptionValue('f');
kskFiles = cli.getOptionValues('k'); kskFiles = cli.getOptionValues('k');
optstr = cliOption("S", nsec3SaltOptionKeys, null); if ((optstr = cli.getOptionValue('I')) != null) {
if (optstr != null) { File includeNamesFile = new File(optstr);
salt = base16.fromString(optstr);
if (salt == null && !optstr.equals("-")) {
fail("salt is not valid hexidecimal");
}
}
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 { try {
Name n = Name.fromString(line); includeNames = CLIState.getNameList(includeNamesFile);
// force the name to be absolute. } catch (IOException e) {
if (!n.isAbsolute()) throw new ParseException(e.getMessage());
n = Name.concatenate(n, Name.root);
res.add(n);
} catch (TextParseException e) {
log.severe("DNS Name parsing error:" + e);
} }
} }
return res; 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);
}
}
/**
* 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;
}
} }
} }
@@ -280,7 +276,7 @@ public class SignZone extends CLBase {
* @param keypairs a list of keypairs used the sign the zone. * @param keypairs a list of keypairs used the sign the zone.
* @return true if all of the signatures validated. * @return true if all of the signatures validated.
*/ */
private boolean verifyZoneSigs(List<Record> records, private static boolean verifyZoneSigs(List<Record> records,
List<DnsKeyPair> keypairs, List<DnsKeyPair> kskpairs) { List<DnsKeyPair> keypairs, List<DnsKeyPair> kskpairs) {
boolean secure = true; boolean secure = true;
@@ -306,7 +302,7 @@ public class SignZone extends CLBase {
if (!result) { if (!result) {
System.err.println("Signatures did not verify for RRset: " + rrset); System.err.println("Signatures did not verify for RRset: " + rrset);
log.fine("Signatures did not verify for RRset: " + rrset); staticLog.fine("Signatures did not verify for RRset: " + rrset);
secure = false; secure = false;
} }
} }
@@ -325,7 +321,7 @@ public class SignZone extends CLBase {
* @param inDirectory the directory to look in (may be null). * @param inDirectory the directory to look in (may be null).
* @return a list of keypair objects. * @return a list of keypair objects.
*/ */
private List<DnsKeyPair> getKeys(String[] keyfiles, int startIndex, private static List<DnsKeyPair> getKeys(String[] keyfiles, int startIndex,
File inDirectory) throws IOException { File inDirectory) throws IOException {
if (keyfiles == null) if (keyfiles == null)
return new ArrayList<>(); return new ArrayList<>();
@@ -346,7 +342,7 @@ public class SignZone extends CLBase {
return keys; return keys;
} }
private List<DnsKeyPair> getKeys(List<Record> dnskeyrrs, File inDirectory) private static List<DnsKeyPair> getKeys(List<Record> dnskeyrrs, File inDirectory)
throws IOException { throws IOException {
List<DnsKeyPair> res = new ArrayList<>(); List<DnsKeyPair> res = new ArrayList<>();
for (Record r : dnskeyrrs) { for (Record r : dnskeyrrs) {
@@ -380,7 +376,7 @@ public class SignZone extends CLBase {
} }
} }
private List<DnsKeyPair> findZoneKeys(File inDirectory, Name zonename) private static List<DnsKeyPair> findZoneKeys(File inDirectory, Name zonename)
throws IOException { throws IOException {
if (inDirectory == null) { if (inDirectory == null) {
inDirectory = new File("."); inDirectory = new File(".");
@@ -423,7 +419,7 @@ public class SignZone extends CLBase {
* keysets that do not belong in the zone. * keysets that do not belong in the zone.
* @return a list of {@link org.xbill.DNS.Record}s found in the keyset files. * @return a list of {@link org.xbill.DNS.Record}s found in the keyset files.
*/ */
private List<Record> getKeysets(File inDirectory, Name zonename) private static List<Record> getKeysets(File inDirectory, Name zonename)
throws IOException { throws IOException {
if (inDirectory == null) { if (inDirectory == null) {
inDirectory = new File("."); inDirectory = new File(".");
@@ -474,42 +470,37 @@ public class SignZone extends CLBase {
} }
public void execute() throws Exception { 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 // Read in the zone
List<Record> records = ZoneUtils.readZoneFile(zonefile, null); List<Record> records = ZoneUtils.readZoneFile(state.zonefile, null);
if (records == null || records.isEmpty()) { if (records == null || records.isEmpty()) {
fail("empty zone file"); System.err.println("error: empty zone file");
state.usage();
return;
} }
// calculate the zone name. // calculate the zone name.
Name zonename = ZoneUtils.findZoneName(records); Name zonename = ZoneUtils.findZoneName(records);
if (zonename == null) { if (zonename == null) {
fail("invalid zone file - no SOA"); System.err.println("error: invalid zone file - no SOA");
state.usage();
return;
} }
// Load the key pairs. Note that getKeys() always returns an ArrayList, // Load the key pairs. Note that getKeys() always returns array.
// which may be empty. List<DnsKeyPair> keypairs = getKeys(state.keyFiles, 0, state.keyDirectory);
List<DnsKeyPair> keypairs = getKeys(keyFiles, 0, keyDirectory); List<DnsKeyPair> kskpairs = getKeys(state.kskFiles, 0, state.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 // If we didn't get any keys on the command line, look at the zone apex for
// any public keys. // any public keys.
if (keypairs.isEmpty()) { if (keypairs.isEmpty()) {
List<Record> dnskeys = ZoneUtils.findRRs(records, zonename, Type.DNSKEY); List<Record> dnskeys = ZoneUtils.findRRs(records, zonename, Type.DNSKEY);
keypairs = getKeys(dnskeys, keyDirectory); keypairs = getKeys(dnskeys, state.keyDirectory);
} }
// If we *still* don't have any key pairs, look for keys the key directory // If we *still* don't have any key pairs, look for keys the key directory
// that match // that match
if (keypairs.isEmpty()) { if (keypairs.isEmpty()) {
keypairs = findZoneKeys(keyDirectory, zonename); keypairs = findZoneKeys(state.keyDirectory, zonename);
} }
// If we don't have any KSKs, but we do have more than one zone // If we don't have any KSKs, but we do have more than one zone
@@ -529,7 +520,9 @@ public class SignZone extends CLBase {
// If we have zero keypairs at all, we are stuck. // If we have zero keypairs at all, we are stuck.
if (keypairs.isEmpty() && kskpairs.isEmpty()) { if (keypairs.isEmpty() && kskpairs.isEmpty()) {
fail("no zone signing keys could be determined"); System.err.println("No zone signing keys could be determined.");
state.usage();
return;
} }
// If we only have one type of key (all ZSKs or all KSKs), then these are // If we only have one type of key (all ZSKs or all KSKs), then these are
@@ -558,18 +551,19 @@ public class SignZone extends CLBase {
} }
// default the output file, if not set. // default the output file, if not set.
if (outputfile == null && !zonefile.equals("-")) { if (state.outputfile == null && !state.zonefile.equals("-")) {
if (zonename.isAbsolute()) { if (zonename.isAbsolute()) {
outputfile = zonename + "signed"; state.outputfile = zonename + "signed";
} else { } else {
outputfile = zonename + ".signed"; state.outputfile = zonename + ".signed";
} }
} }
// Verify that the keys can be in the zone. // Verify that the keys can be in the zone.
if (!keyPairsValidForZone(zonename, keypairs) if (!keyPairsValidForZone(zonename, keypairs)
|| !keyPairsValidForZone(zonename, kskpairs)) { || !keyPairsValidForZone(zonename, kskpairs)) {
fail("specified keypairs are not valid for the zone."); 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 // We force the signing keys to be in the zone by just appending
@@ -587,34 +581,34 @@ public class SignZone extends CLBase {
} }
// read in the keysets, if any. // read in the keysets, if any.
List<Record> keysetrecs = getKeysets(keysetDirectory, zonename); List<Record> keysetrecs = getKeysets(state.keysetDirectory, zonename);
if (keysetrecs != null) { if (keysetrecs != null) {
records.addAll(keysetrecs); records.addAll(keysetrecs);
} }
JCEDnsSecSigner signer = new JCEDnsSecSigner(verboseSigning); JCEDnsSecSigner signer = new JCEDnsSecSigner(state.verboseSigning);
// Sign the zone. // Sign the zone.
List<Record> signedRecords; List<Record> signedRecords;
if (useNsec3) { if (state.useNsec3) {
signedRecords = signer.signZoneNSEC3(zonename, records, kskpairs, keypairs, signedRecords = signer.signZoneNSEC3(zonename, records, kskpairs, keypairs,
start, expire, state.start, state.expire,
fullySignKeyset, useOptOut, state.fullySignKeyset, state.useOptOut,
includeNames, salt, state.includeNames, state.salt,
iterations, digestId, state.iterations, state.digestId,
nsec3paramttl); state.nsec3paramttl);
} else { } else {
signedRecords = signer.signZone(zonename, records, kskpairs, keypairs, signedRecords = signer.signZone(zonename, records, kskpairs, keypairs,
start, expire, fullySignKeyset, state.start, state.expire, state.fullySignKeyset,
digestId); state.digestId);
} }
// write out the signed zone // write out the signed zone
ZoneUtils.writeZoneFile(signedRecords, outputfile); ZoneUtils.writeZoneFile(signedRecords, state.outputfile);
System.out.println("zone signing complete"); System.out.println("zone signing complete");
if (verifySigs) { if (state.verifySigs) {
log.fine("verifying generated signatures"); log.fine("verifying generated signatures");
boolean res = verifyZoneSigs(signedRecords, keypairs, kskpairs); boolean res = verifyZoneSigs(signedRecords, keypairs, kskpairs);
@@ -627,8 +621,9 @@ public class SignZone extends CLBase {
} }
public static void main(String[] args) { public static void main(String[] args) {
SignZone tool = new SignZone("signzone", "jdnssec-signzone [..options..] zone_file [key_file ...]"); SignZone tool = new SignZone();
tool.state = new CLIState();
tool.run(args); tool.run(tool.state, args);
} }
} }

View File

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

View File

@@ -24,6 +24,9 @@ import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; 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.Master;
import org.xbill.DNS.NSEC3PARAMRecord; import org.xbill.DNS.NSEC3PARAMRecord;
import org.xbill.DNS.NSEC3Record; import org.xbill.DNS.NSEC3Record;
@@ -42,33 +45,43 @@ import com.verisignlabs.dnssec.security.RecordComparator;
* @author David Blacka * @author David Blacka
*/ */
public class ZoneFormat extends CLBase { public class ZoneFormat extends CLBase {
private String file; private CLIState state;
private boolean assignNSEC3;
public ZoneFormat(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 file;
public boolean assignNSEC3;
protected void setupOptions() { public CLIState() {
opts.addOption("N", "nsec3", false, super("jdnssec-zoneformat [..options..] zonefile");
"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]; @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];
}
} }
private List<Record> readZoneFile(String filename) throws IOException { private static List<Record> readZoneFile(String filename) throws IOException {
try (Master master = new Master(filename)) { try (Master master = new Master(filename)) {
List<Record> res = new ArrayList<>(); List<Record> res = new ArrayList<>();
Record r = null; Record r = null;
@@ -85,14 +98,14 @@ public class ZoneFormat extends CLBase {
} }
} }
private void formatZone(List<Record> zone) { private static void formatZone(List<Record> zone) {
for (Record r : zone) { for (Record r : zone) {
System.out.println(r.toString()); System.out.println(r.toString());
} }
} }
private void determineNSEC3Owners(List<Record> zone) private static void determineNSEC3Owners(List<Record> zone)
throws NoSuchAlgorithmException { throws NoSuchAlgorithmException {
// first, find the NSEC3PARAM record -- this is an inefficient linear // first, find the NSEC3PARAM record -- this is an inefficient linear
@@ -160,11 +173,11 @@ public class ZoneFormat extends CLBase {
} }
public void execute() throws IOException, NoSuchAlgorithmException { public void execute() throws IOException, NoSuchAlgorithmException {
List<Record> z = readZoneFile(file); List<Record> z = readZoneFile(state.file);
// Put the zone into a consistent (name and RR type) order. // Put the zone into a consistent (name and RR type) order.
Collections.sort(z, new RecordComparator()); Collections.sort(z, new RecordComparator());
if (assignNSEC3) { if (state.assignNSEC3) {
determineNSEC3Owners(z); determineNSEC3Owners(z);
} else { } else {
formatZone(z); formatZone(z);
@@ -172,9 +185,10 @@ public class ZoneFormat extends CLBase {
} }
public static void main(String[] args) { public static void main(String[] args) {
ZoneFormat tool = new ZoneFormat("zoneformat", "jdnssec-zoneformat [..options..] zonefile"); ZoneFormat tool = new ZoneFormat();
tool.state = new CLIState();
tool.run(args); tool.run(tool.state, args);
} }
} }

View File

@@ -134,7 +134,8 @@ public class BINDKeyUtils {
public static DnsKeyPair loadKeyPair(String keyFileBasePath, File inDirectory) public static DnsKeyPair loadKeyPair(String keyFileBasePath, File inDirectory)
throws IOException { throws IOException {
keyFileBasePath = fixKeyFileBasePath(keyFileBasePath); 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 publicKeyFile = new File(inDirectory, keyFileBasePath + ".key");
File privateKeyFile = new File(inDirectory, keyFileBasePath + ".private"); File privateKeyFile = new File(inDirectory, keyFileBasePath + ".private");
@@ -250,6 +251,8 @@ public class BINDKeyUtils {
if (privateKeyString == null) if (privateKeyString == null)
return null; return null;
// FIXME: should this swallow exceptions or actually propagate
// them?
try { try {
DnsKeyConverter conv = new DnsKeyConverter(); DnsKeyConverter conv = new DnsKeyConverter();
return conv.parsePrivateKeyString(privateKeyString); return conv.parsePrivateKeyString(privateKeyString);

View File

@@ -1,167 +0,0 @@
/*
* Copyright (c) 2006, 2022 Verisign. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer. 2. Redistributions in
* binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other
* materials provided with the distribution. 3. The name of the author may not
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
* NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
package com.verisignlabs.dnssec.security;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.Security;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.logging.Logger;
import org.xbill.DNS.CDSRecord;
import org.xbill.DNS.DLVRecord;
import org.xbill.DNS.DNSKEYRecord;
import org.xbill.DNS.DNSOutput;
import org.xbill.DNS.DNSSEC;
import org.xbill.DNS.DSRecord;
/**
* This class handles the implementation behind converting DNSKEYs into
* DSRecords. It primarily exists to bootstrap whatever crypto libraries we
* might need to do so.
*
* @author David Blacka
*/
public class DSAlgorithm {
private Logger log = Logger.getLogger(this.getClass().toString());
HashSet<Integer> mSupportedAlgorithms = null;
private static DSAlgorithm mInstance = null;
public DSAlgorithm() {
mSupportedAlgorithms = new HashSet<>();
mSupportedAlgorithms.add(DNSSEC.Digest.SHA1);
mSupportedAlgorithms.add(DNSSEC.Digest.SHA256);
mSupportedAlgorithms.add(DNSSEC.Digest.SHA384);
// 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<?> bcProviderClass = Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider");
Provider bcProvider = (Provider) bcProviderClass.getDeclaredConstructor().newInstance();
Security.addProvider(bcProvider);
log.fine("bouncycastle crypto provider loaded");
mSupportedAlgorithms.add(DNSSEC.Digest.GOST3411);
} catch (ReflectiveOperationException e) {
// do nothing, this is the normal case
}
}
public String[] supportedAlgorithmMnemonics() {
ArrayList<String> algs = new ArrayList<>();
for (int digestId : mSupportedAlgorithms) {
algs.add(DNSSEC.Digest.string(digestId));
}
String[] result = new String[algs.size()];
return algs.toArray(result);
}
/**
* Given a DNSKEY record, generate the DS record from it.
*
* @param keyrec the KEY record in question.
* @param digestAlg The digest algorithm (SHA-1, SHA-256, etc.).
* @param ttl the desired TTL for the generated DS record. If zero, or
* negative, the original KEY RR's TTL will be used.
* @return the corresponding {@link org.xbill.DNS.DSRecord}
*/
public DSRecord calculateDSRecord(DNSKEYRecord keyrec, int digestAlg, long ttl) {
if (keyrec == null)
return null;
if (ttl <= 0)
ttl = keyrec.getTTL();
DNSOutput os = new DNSOutput();
os.writeByteArray(keyrec.getName().toWireCanonical());
os.writeByteArray(keyrec.rdataToWireCanonical());
try {
byte[] digest;
MessageDigest md;
switch (digestAlg) {
case DNSSEC.Digest.SHA1:
md = MessageDigest.getInstance("SHA");
digest = md.digest(os.toByteArray());
break;
case DNSSEC.Digest.SHA256:
md = MessageDigest.getInstance("SHA-256");
digest = md.digest(os.toByteArray());
break;
case DNSSEC.Digest.GOST3411:
// The standard Java crypto providers don't have this, but bouncycastle does
if (java.security.Security.getProviders("MessageDigest.GOST3411") != null) {
md = MessageDigest.getInstance("GOST3411");
digest = md.digest(os.toByteArray());
} else {
throw new IllegalArgumentException("Unsupported digest id: " + digestAlg);
}
break;
case DNSSEC.Digest.SHA384:
md = MessageDigest.getInstance("SHA-384");
digest = md.digest(os.toByteArray());
break;
default:
throw new IllegalArgumentException("Unknown digest id: " + digestAlg);
}
return new DSRecord(keyrec.getName(), keyrec.getDClass(), ttl,
keyrec.getFootprint(), keyrec.getAlgorithm(), digestAlg,
digest);
} catch (NoSuchAlgorithmException e) {
log.severe(e.toString());
return null;
}
}
public DLVRecord dsToDLV(DSRecord ds) {
return new DLVRecord(ds.getName(), ds.getDClass(), ds.getTTL(), ds.getFootprint(), ds.getAlgorithm(),
ds.getDigestID(), ds.getDigest());
}
public CDSRecord dstoCDS(DSRecord ds) {
return new CDSRecord(ds.getName(), ds.getDClass(), ds.getTTL(), ds.getFootprint(), ds.getAlgorithm(),
ds.getDClass(), ds.getDigest());
}
public static DSAlgorithm getInstance() {
if (mInstance == null) {
mInstance = new DSAlgorithm();
}
return mInstance;
}
}

View File

@@ -27,14 +27,20 @@
package com.verisignlabs.dnssec.security; package com.verisignlabs.dnssec.security;
import java.math.BigInteger;
import java.security.AlgorithmParameters; import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException; import java.security.InvalidAlgorithmParameterException;
import java.security.KeyPair; import java.security.KeyPair;
import java.security.KeyPairGenerator; import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.Security;
import java.security.Signature; import java.security.Signature;
import java.security.spec.ECFieldFp;
import java.security.spec.ECGenParameterSpec; import java.security.spec.ECGenParameterSpec;
import java.security.spec.ECParameterSpec; import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.EllipticCurve;
import java.security.spec.InvalidParameterSpecException; import java.security.spec.InvalidParameterSpecException;
import java.security.spec.NamedParameterSpec; import java.security.spec.NamedParameterSpec;
import java.security.spec.RSAKeyGenParameterSpec; import java.security.spec.RSAKeyGenParameterSpec;
@@ -60,13 +66,14 @@ public class DnsKeyAlgorithm {
// Our base algorithm numbers. This is a normalization of the DNSSEC // Our base algorithm numbers. This is a normalization of the DNSSEC
// algorithms (which are really signature algorithms). Thus RSASHA1, // algorithms (which are really signature algorithms). Thus RSASHA1,
// RSASHA256, etc. all boil down to 'RSA' here. Similarly, ECDSAP256SHA256 and // RSASHA256, etc. all boil down to 'RSA' here. Similary, ECDSAP256SHA256 and
// ECDSAP384SHA384 both become 'ECDSA'. // ECDSAP384SHA384 both become 'ECDSA'.
public enum BaseAlgorithm { public enum BaseAlgorithm {
UNKNOWN, UNKNOWN,
RSA, RSA,
DH, DH,
DSA, DSA,
ECC_GOST,
ECDSA, ECDSA,
EDDSA; EDDSA;
} }
@@ -122,8 +129,12 @@ public class DnsKeyAlgorithm {
private KeyPairGenerator mRSAKeyGenerator; private KeyPairGenerator mRSAKeyGenerator;
/** This is a cached key pair generator for DSA keys. */ /** This is a cached key pair generator for DSA keys. */
private KeyPairGenerator mDSAKeyGenerator; 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. */ /** This is a cached key pair generator for ECDSA_P256 keys. */
private KeyPairGenerator mECKeyGenerator; private KeyPairGenerator mECKeyGenerator;
/** This is a cached key pair generator for EdDSA keys. */
private KeyPairGenerator mEdKeyGenerator;
private Logger log = Logger.getLogger(this.getClass().toString()); private Logger log = Logger.getLogger(this.getClass().toString());
@@ -131,6 +142,17 @@ public class DnsKeyAlgorithm {
private static DnsKeyAlgorithm mInstance = null; private static DnsKeyAlgorithm mInstance = null;
public DnsKeyAlgorithm() { 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<?> bcProviderClass = Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider");
Provider bcProvider = (Provider) bcProviderClass.getDeclaredConstructor().newInstance();
Security.addProvider(bcProvider);
} catch (ReflectiveOperationException e) {
log.fine("Unable to load BC provider");
}
initialize(); initialize();
} }
@@ -167,6 +189,14 @@ public class DnsKeyAlgorithm {
addAlgorithm(DNSSEC.Algorithm.RSASHA512, "SHA512withRSA", BaseAlgorithm.RSA); addAlgorithm(DNSSEC.Algorithm.RSASHA512, "SHA512withRSA", BaseAlgorithm.RSA);
addMnemonic("RSASHA512", DNSSEC.Algorithm.RSASHA512); addMnemonic("RSASHA512", DNSSEC.Algorithm.RSASHA512);
// ECC-GOST is not supported by Java 1.8's Sun crypto provider. The
// bouncycastle.org provider, however, does support it.
// 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", BaseAlgorithm.ECC_GOST, null);
addMnemonic("ECCGOST", DNSSEC.Algorithm.ECC_GOST);
addMnemonic("ECC-GOST", DNSSEC.Algorithm.ECC_GOST);
addAlgorithm(DNSSEC.Algorithm.ECDSAP256SHA256, "SHA256withECDSA", BaseAlgorithm.ECDSA, "secp256r1"); addAlgorithm(DNSSEC.Algorithm.ECDSAP256SHA256, "SHA256withECDSA", BaseAlgorithm.ECDSA, "secp256r1");
addMnemonic("ECDSAP256SHA256", DNSSEC.Algorithm.ECDSAP256SHA256); addMnemonic("ECDSAP256SHA256", DNSSEC.Algorithm.ECDSAP256SHA256);
addMnemonic("ECDSA-P256", DNSSEC.Algorithm.ECDSAP256SHA256); addMnemonic("ECDSA-P256", DNSSEC.Algorithm.ECDSAP256SHA256);
@@ -196,7 +226,9 @@ public class DnsKeyAlgorithm {
* library (SunEC). * library (SunEC).
*/ */
private void addECDSAAlgorithm(int algorithm, String sigName, String curveName) { private void addECDSAAlgorithm(int algorithm, String sigName, String curveName) {
ECParameterSpec ecSpec = ECSpecFromName(curveName); ECParameterSpec ecSpec = ECSpecFromAlgorithm(algorithm);
if (ecSpec == null)
ecSpec = ECSpecFromName(curveName);
if (ecSpec == null) if (ecSpec == null)
return; return;
@@ -246,15 +278,10 @@ public class DnsKeyAlgorithm {
* @param curveName the name of the curve. * @param curveName the name of the curve.
*/ */
private void addAlgorithm(int algorithm, String sigName, BaseAlgorithm baseType, String curveName) { private void addAlgorithm(int algorithm, String sigName, BaseAlgorithm baseType, String curveName) {
switch (baseType) { if (baseType == BaseAlgorithm.ECDSA) {
case ECDSA: addECDSAAlgorithm(algorithm, sigName, curveName);
addECDSAAlgorithm(algorithm, sigName, curveName); } else if (baseType == BaseAlgorithm.EDDSA) {
break; addEdDSAAlgorithm(algorithm, sigName, curveName);
case EDDSA:
addEdDSAAlgorithm(algorithm, sigName, curveName);
break;
default:
throw new IllegalArgumentException("Non-Ellipic curve algorithm passed.");
} }
} }
@@ -297,6 +324,25 @@ public class DnsKeyAlgorithm {
return mAlgorithmMap.get(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) {
if (algorithm == 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);
}
return null;
}
// Fetch the curve parameters from a named ECDSA curve. // Fetch the curve parameters from a named ECDSA curve.
private ECParameterSpec ECSpecFromName(String stdName) { private ECParameterSpec ECSpecFromName(String stdName) {
try { try {
@@ -483,6 +529,22 @@ public class DnsKeyAlgorithm {
pair = mDSAKeyGenerator.generateKeyPair(); pair = mDSAKeyGenerator.generateKeyPair();
break; break;
} }
case ECC_GOST: {
if (mECGOSTKeyGenerator == null) {
mECGOSTKeyGenerator = KeyPairGenerator.getInstance("ECGOST3410");
}
ECParameterSpec ecSpec = getEllipticCurveParams(algorithm);
try {
mECGOSTKeyGenerator.initialize(ecSpec);
} 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: { case ECDSA: {
if (mECKeyGenerator == null) { if (mECKeyGenerator == null) {
mECKeyGenerator = KeyPairGenerator.getInstance("EC"); mECKeyGenerator = KeyPairGenerator.getInstance("EC");
@@ -501,9 +563,9 @@ public class DnsKeyAlgorithm {
} }
case EDDSA: { case EDDSA: {
EdAlgEntry entry = (EdAlgEntry) getEntry(algorithm); EdAlgEntry entry = (EdAlgEntry) getEntry(algorithm);
KeyPairGenerator edKeyGenerator = KeyPairGenerator.getInstance(entry.curveName); mEdKeyGenerator = KeyPairGenerator.getInstance(entry.curveName);
pair = edKeyGenerator.generateKeyPair(); pair = mEdKeyGenerator.generateKeyPair();
break; break;
} }
default: default:

View File

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

View File

@@ -72,7 +72,8 @@ public class JCEDnsSecSigner {
* @param ttl the KEY RR's TTL. * @param ttl the KEY RR's TTL.
* @param dclass the KEY RR's DNS class. * @param dclass the KEY RR's DNS class.
* @param algorithm the DNSSEC algorithm (RSASHA258, RSASHA512, * @param algorithm the DNSSEC algorithm (RSASHA258, RSASHA512,
* ECDSAP256, etc.) * ECDSAP256,
* etc.)
* @param flags any flags for the KEY RR. * @param flags any flags for the KEY RR.
* @param keysize the size of the key to generate. * @param keysize the size of the key to generate.
* @param useLargeExponent if generating an RSA key, use the large exponent. * @param useLargeExponent if generating an RSA key, use the large exponent.

View File

@@ -178,7 +178,7 @@ public class SignUtils {
if (n.labels() != labels) { if (n.labels() != labels) {
n = n.wild(n.labels() - labels); n = n.wild(n.labels() - labels);
wildcardName = true; wildcardName = true;
log.finer("Detected wildcard expansion: " + rrset.getName() + " changed to " + n); log.fine("Detected wildcard expansion: " + rrset.getName() + " changed to " + n);
} }
// now convert the wire format records in the RRset into a // 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(); int ldiff = node.name.labels() - zonename.labels();
for (int i = 1; i < ldiff; i++) { for (int i = 1; i < ldiff; i++) {
Name n = new Name(node.name, i); Name n = new Name(node.name, i);
log.finer("Generating ENT NSEC3 for " + n); log.fine("Generating ENT NSEC3 for " + n);
ProtoNSEC3 nsec3 = generateNSEC3(n, zonename, node.ttl, salt, iterations, optIn, ProtoNSEC3 nsec3 = generateNSEC3(n, zonename, node.ttl, salt, iterations, optIn,
null); null);
nsec3s.add(nsec3); nsec3s.add(nsec3);
@@ -1127,11 +1127,11 @@ public class SignUtils {
// check to see if cur is a duplicate (by name) // check to see if cur is a duplicate (by name)
if (prevNSEC3 != null if (prevNSEC3 != null
&& Arrays.equals(prevNSEC3.getOwner(), curNSEC3.getOwner())) { && Arrays.equals(prevNSEC3.getOwner(), curNSEC3.getOwner())) {
log.finer("found duplicate NSEC3 (by name) -- merging type maps: " log.fine("found duplicate NSEC3 (by name) -- merging type maps: "
+ prevNSEC3.getTypemap() + " and " + curNSEC3.getTypemap()); + prevNSEC3.getTypemap() + " and " + curNSEC3.getTypemap());
i.remove(); i.remove();
prevNSEC3.mergeTypes(curNSEC3.getTypemap()); prevNSEC3.mergeTypes(curNSEC3.getTypemap());
log.finer("merged type map: " + prevNSEC3.getTypemap()); log.fine("merged type map: " + prevNSEC3.getTypemap());
continue; continue;
} }
@@ -1317,8 +1317,6 @@ public class SignUtils {
*/ */
public static void generateDSRecords(Name zonename, List<Record> records, int digestAlg) { public static void generateDSRecords(Name zonename, List<Record> records, int digestAlg) {
DSAlgorithm dsAlgorithm = DSAlgorithm.getInstance();
for (ListIterator<Record> i = records.listIterator(); i.hasNext();) { for (ListIterator<Record> i = records.listIterator(); i.hasNext();) {
Record r = i.next(); Record r = i.next();
if (r == null) if (r == null)
@@ -1330,7 +1328,7 @@ public class SignUtils {
// Convert non-zone level KEY records into DS records. // Convert non-zone level KEY records into DS records.
if (r.getType() == Type.DNSKEY && !rName.equals(zonename)) { if (r.getType() == Type.DNSKEY && !rName.equals(zonename)) {
DSRecord ds = dsAlgorithm.calculateDSRecord((DNSKEYRecord) r, digestAlg, r.getTTL()); DSRecord ds = calculateDSRecord((DNSKEYRecord) r, digestAlg, r.getTTL());
i.set(ds); i.set(ds);
} }
@@ -1378,6 +1376,53 @@ public class SignUtils {
} }
} }
/**
* Given a DNSKEY record, generate the DS record from it.
*
* @param keyrec the KEY record in question.
* @param digestAlg The digest algorithm (SHA-1, SHA-256, etc.).
* @param ttl the desired TTL for the generated DS record. If zero, or
* negative, the original KEY RR's TTL will be used.
* @return the corresponding {@link org.xbill.DNS.DSRecord}
*/
public static DSRecord calculateDSRecord(DNSKEYRecord keyrec, int digestAlg, long ttl) {
if (keyrec == null)
return null;
if (ttl <= 0)
ttl = keyrec.getTTL();
DNSOutput os = new DNSOutput();
os.writeByteArray(keyrec.getName().toWireCanonical());
os.writeByteArray(keyrec.rdataToWireCanonical());
try {
byte[] digest;
MessageDigest md;
switch (digestAlg) {
case DNSSEC.Digest.SHA1:
md = MessageDigest.getInstance("SHA");
digest = md.digest(os.toByteArray());
break;
case DNSSEC.Digest.SHA256:
md = MessageDigest.getInstance("SHA-256");
digest = md.digest(os.toByteArray());
break;
default:
throw new IllegalArgumentException("Unknown digest id: " + digestAlg);
}
return new DSRecord(keyrec.getName(), keyrec.getDClass(), ttl,
keyrec.getFootprint(), keyrec.getAlgorithm(), digestAlg,
digest);
} catch (NoSuchAlgorithmException e) {
log.severe(e.toString());
return null;
}
}
/** /**
* Calculate an NSEC3 hash based on a DNS name and NSEC3 hash parameters. * Calculate an NSEC3 hash based on a DNS name and NSEC3 hash parameters.

View File

@@ -58,10 +58,13 @@ public class ZoneUtils {
*/ */
public static List<Record> readZoneFile(String zonefile, Name origin) throws IOException { public static List<Record> readZoneFile(String zonefile, Name origin) throws IOException {
ArrayList<Record> records = new ArrayList<>(); ArrayList<Record> records = new ArrayList<>();
Master m = zonefile.equals("-") ? new Master(System.in) : new Master(zonefile, origin); try (Master m = zonefile.equals("-") ? new Master(System.in) : new Master(zonefile, origin)) {
Record r = null; Record r = null;
while ((r = m.nextRecord()) != null) { while ((r = m.nextRecord()) != null) {
records.add(r); records.add(r);
}
} catch (IOException e) {
e.printStackTrace();
} }
return records; return records;