Port to DNSJava 3.5.1, Java 8, linter fixes (#13)

* Initial port to dnsjava 3.5.1

* java.util.Date -> java.time.Instant
* for (Iterator ..) to for ( Object : List )
* DSRecord.<digest type> -> DNSSEC.Digest.<type>
* source to java 8

* formatting overhaul; copyright; author

* add slf4j jars for dnsjava 3.5.1

* NSEC/NSEC3 ttls are now min(soa.min, soa.ttl)

* Upgrade to commons-cli-1.5; some linter fixes

* Add CDS support of jdnssec-dstool

* linter suggestions

* add a TODO list

* Add a TODO list
This commit is contained in:
David Blacka 2022-09-21 14:24:42 -04:00 committed by GitHub
parent ce1189703f
commit e322186112
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 2627 additions and 3332 deletions

6
.gitattributes vendored Normal file
View File

@ -0,0 +1,6 @@
#
# https://help.github.com/articles/dealing-with-line-endings/
#
# These are explicitly windows files and should use crlf
*.bat text eol=crlf

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
build build
bin/main
.classpath .classpath
.project .project
.gradle .gradle

15
README.TODO.md Normal file
View File

@ -0,0 +1,15 @@
# jdnssec-tools TODO List
This bit of code has been around since approximately 2005, and has been in "minimal maintenance" mode for much of that time. But that doesn't mean there aren't features that we *want* to do, if we could arrange time and attention. Here is a partial list:
* More feature parity with the current BIND 9 tools
* Support the "v1.3" private key format. This basically means supporting the timing parameters that BiND 9 added.
* Have `jdnssec-signzone` support incremental signing, including key rollovers
* Rewrite `jdnssec-signzone` to use a "TreeMap" and arrange the data into a map of RRsets, rather than a sorted list of Record objects. This wouldn't be more efficient, but might be easier to understand.
* Allow `jdnssec-signzone` to scale by either:
* Allowing for pre-sorted zone data, 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,
* 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

@ -1,12 +1,12 @@
# jdnssec-tools # jdnssec-tools
* https://github.com/dblacka/jdnssec-tools/wiki * <https://github.com/dblacka/jdnssec-tools/wiki>
Author: David Blacka (davidb@verisign.com) Author: David Blacka (davidb@verisign.com)
This is a collection of DNSSEC tools written in Java. They are intended to be an addition or replacement for the DNSSEC tools that are part of BIND 9. This is a collection of DNSSEC tools written in Java. They are intended to be an addition or replacement for the DNSSEC tools that are part of BIND 9.
These tools depend upon DNSjava (https://github.com/dnsjava/dnsjava), the Jakarta Commons CLI and Logging libraries (https://commons.apache.org/proper/commons-cli), and Sun's Java Cryptography extensions. A copy of each of these libraries is included in the distribution. Currently, these tools use a custom version of the DNSjava library with minor modifications, which is provided. 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" 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.
@ -21,7 +21,6 @@ Getting started:
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:
1. Unpack the source distribution, preferably into the same directory that the binary distribution was unpacked. 1. Unpack the source distribution, preferably into the same directory that the binary distribution was unpacked.
@ -29,7 +28,7 @@ Building from source:
tar zxvf java-dnssec-tools-x.x.x-src.tar.gz tar zxvf java-dnssec-tools-x.x.x-src.tar.gz
2. Edit the build.properties file to suit your environment. 2. Edit the build.properties file to suit your environment.
3. Run Ant (see http://ant.apache.org for information about the Ant build tool). 3. Run Ant (see <http://ant.apache.org> for information about the Ant build tool).
ant ant
@ -42,9 +41,7 @@ Building from source:
The resulting jar file gets generated in build/libs. 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>
Source for the modified DNSjava library can be found on github as well: https://github.com/dblacka/jdnssec-dnsjava
--- ---

View File

@ -1 +1 @@
version=0.16.1 version=0.17

View File

@ -10,15 +10,15 @@ apply plugin: 'idea'
jar { jar {
baseName = 'jdnssec-tools' baseName = 'jdnssec-tools'
version = '0.16' version = '0.17'
} }
repositories { repositories {
mavenCentral() mavenCentral()
} }
sourceCompatibility = 1.7 sourceCompatibility = 1.8
targetCompatibility = 1.7 targetCompatibility = 1.8
dependencies { dependencies {
compile fileTree(dir: 'lib', include: '*.jar') compile fileTree(dir: 'lib', include: '*.jar')

View File

@ -47,8 +47,8 @@
deprecation="true" deprecation="true"
includeantruntime="false" includeantruntime="false"
includes="com/verisignlabs/dnssec/" includes="com/verisignlabs/dnssec/"
source="1.7" source="8"
target="1.7" /> target="8" />
</target> </target>
<target name="sectools-jar" depends="usage,sectools"> <target name="sectools-jar" depends="usage,sectools">

Binary file not shown.

BIN
lib/commons-cli-1.5.0.jar Normal file

Binary file not shown.

Binary file not shown.

BIN
lib/dnsjava-3.5.1.jar Normal file

Binary file not shown.

BIN
lib/slf4j-api-1.7.36.jar Normal file

Binary file not shown.

BIN
lib/slf4j-simple-1.7.36.jar Normal file

Binary file not shown.

View File

@ -0,0 +1,21 @@
Copyright (c) 2004-2017 QOS.ch
All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,7 +1,25 @@
// Copyright (C) 2022 Verisign, Inc.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
// USA
package com.verisignlabs.dnssec.cl; package com.verisignlabs.dnssec.cl;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.Date; import java.util.Date;
import java.util.TimeZone; import java.util.TimeZone;
import java.util.logging.Formatter; import java.util.logging.Formatter;
@ -13,11 +31,11 @@ import java.util.logging.Logger;
import org.apache.commons.cli.AlreadySelectedException; import org.apache.commons.cli.AlreadySelectedException;
import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.OptionBuilder; import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options; import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException; import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
import org.apache.commons.cli.UnrecognizedOptionException; import org.apache.commons.cli.UnrecognizedOptionException;
import com.verisignlabs.dnssec.security.DnsKeyAlgorithm; import com.verisignlabs.dnssec.security.DnsKeyAlgorithm;
@ -29,19 +47,17 @@ import com.verisignlabs.dnssec.security.DnsKeyAlgorithm;
* Subclasses also have their own main() methods, which should just create the * Subclasses also have their own main() methods, which should just create the
* 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 static Logger staticLog = Logger.getLogger(CLBase.class.getName());
protected static Logger log; protected Logger log;
/** /**
* 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
* log string. * log string.
*/ */
public static class BareLogFormatter extends Formatter public static class BareLogFormatter extends Formatter {
{
@Override public String format(LogRecord arg0) {
public String format(LogRecord arg0)
{
StringBuilder out = new StringBuilder(); StringBuilder out = new StringBuilder();
String lvl = arg0.getLevel().getName(); String lvl = arg0.getLevel().getName();
@ -58,8 +74,7 @@ public abstract class CLBase
* This is a base class for command line parsing state. Subclasses should * This is a base class for command line parsing state. Subclasses should
* override setupOptions and processOptions. * override setupOptions and processOptions.
*/ */
public static class CLIStateBase public static class CLIStateBase {
{
protected Options opts; protected Options opts;
protected String usageStr; protected String usageStr;
@ -70,16 +85,15 @@ public abstract class CLBase
* The command line usage string (e.g., * The command line usage string (e.g.,
* "jdnssec-foo [..options..] zonefile") * "jdnssec-foo [..options..] zonefile")
*/ */
public CLIStateBase(String usage) public CLIStateBase(String usage) {
{
usageStr = usage; usageStr = usage;
setup(); setup();
} }
/** This is the base set of command line options provided to all subclasses. */ /** This is the base set of command line options provided to all subclasses. */
private void setup() private void setup() {
{ // Set up the standard set of options that all jdnssec command line tools will
// Set up the standard set of options that all jdnssec command line tools will implement. // implement.
opts = new Options(); opts = new Options();
// boolean options // boolean options
@ -87,18 +101,12 @@ public abstract class CLBase
opts.addOption("m", "multiline", false, opts.addOption("m", "multiline", false,
"Output DNS records using 'multiline' format"); "Output DNS records using 'multiline' format");
OptionBuilder.hasOptionalArg(); opts.addOption(Option.builder("v").longOpt("verbose").argName("level").optionalArg(true).desc(
OptionBuilder.withLongOpt("verbose"); "verbosity level -- 0 is silence, 3 is info, 5 is debug information, 6 is trace information. default is level 2 (warning)")
OptionBuilder.withArgName("level"); .build());
OptionBuilder.withDescription("verbosity level -- 0 is silence, 3 is info, "
+ "5 is debug information, 6 is trace information. default is level 2 (warning)");
opts.addOption(OptionBuilder.create('v'));
OptionBuilder.hasArg(); opts.addOption(Option.builder("A").hasArg().argName("alias:original:mnemonic").longOpt("alg-alias")
OptionBuilder.withArgName("alias:original:mnemonic"); .desc("Define an alias for an algorithm").build());
OptionBuilder.withLongOpt("alg-alias");
OptionBuilder.withDescription("Define an alias for an algorithm");
opts.addOption(OptionBuilder.create('A'));
setupOptions(opts); setupOptions(opts);
} }
@ -111,8 +119,7 @@ public abstract class CLBase
* the options object to add (via OptionBuilder, typically) new * the options object to add (via OptionBuilder, typically) new
* options to. * options to.
*/ */
protected void setupOptions(Options opts) protected void setupOptions(Options opts) {
{
// Subclasses generally override this. // Subclasses generally override this.
} }
@ -126,18 +133,18 @@ public abstract class CLBase
* The command line arguments. * The command line arguments.
* @throws ParseException * @throws ParseException
*/ */
public void parseCommandLine(String args[]) throws ParseException public void parseCommandLine(String[] args) throws ParseException {
{ CommandLineParser parser = new DefaultParser();
CommandLineParser cli_parser = new PosixParser(); CommandLine cli = parser.parse(opts, args);
CommandLine cli = cli_parser.parse(opts, args);
if (cli.hasOption('h')) usage(); if (cli.hasOption('h')) {
usage();
}
Logger rootLogger = Logger.getLogger(""); Logger rootLogger = Logger.getLogger("");
int value = parseInt(cli.getOptionValue('v'), -1); int value = parseInt(cli.getOptionValue('v'), -1);
switch (value) switch (value) {
{
case 0: case 0:
rootLogger.setLevel(Level.OFF); rootLogger.setLevel(Level.OFF);
break; break;
@ -162,22 +169,18 @@ public abstract class CLBase
} }
// I hate java.util.logging, btw. // I hate java.util.logging, btw.
for (Handler h : rootLogger.getHandlers()) for (Handler h : rootLogger.getHandlers()) {
{
h.setLevel(rootLogger.getLevel()); h.setLevel(rootLogger.getLevel());
h.setFormatter(new BareLogFormatter()); h.setFormatter(new BareLogFormatter());
} }
if (cli.hasOption('m')) if (cli.hasOption('m')) {
{
org.xbill.DNS.Options.set("multiline"); org.xbill.DNS.Options.set("multiline");
} }
String[] optstrs = null; String[] optstrs = null;
if ((optstrs = cli.getOptionValues('A')) != null) if ((optstrs = cli.getOptionValues('A')) != null) {
{ for (int i = 0; i < optstrs.length; i++) {
for (int i = 0; i < optstrs.length; i++)
{
addArgAlias(optstrs[i]); addArgAlias(optstrs[i]);
} }
} }
@ -193,14 +196,12 @@ public abstract class CLBase
* The {@link CommandLine} object containing the parsed command * The {@link CommandLine} object containing the parsed command
* line state. * line state.
*/ */
protected void processOptions(CommandLine cli) throws ParseException protected void processOptions(CommandLine cli) throws ParseException {
{
// Subclasses generally override this. // Subclasses generally override this.
} }
/** Print out the usage and help statements, then quit. */ /** Print out the usage and help statements, then quit. */
public void usage() public void usage() {
{
HelpFormatter f = new HelpFormatter(); HelpFormatter f = new HelpFormatter();
PrintWriter out = new PrintWriter(System.err); PrintWriter out = new PrintWriter(System.err);
@ -214,48 +215,42 @@ public abstract class CLBase
} }
protected void addArgAlias(String s) protected void addArgAlias(String s) {
{ if (s == null)
if (s == null) return; return;
DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance(); DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance();
String[] v = s.split(":"); String[] v = s.split(":");
if (v.length < 2) return; if (v.length < 2)
return;
int alias = parseInt(v[0], -1); int alias = parseInt(v[0], -1);
if (alias <= 0) return; if (alias <= 0)
return;
int orig = parseInt(v[1], -1); int orig = parseInt(v[1], -1);
if (orig <= 0) return; if (orig <= 0)
return;
String mn = null; String mn = null;
if (v.length > 2) mn = v[2]; if (v.length > 2)
mn = v[2];
algs.addAlias(alias, mn, orig); algs.addAlias(alias, mn, orig);
} }
} }
public static int parseInt(String s, int def) public static int parseInt(String s, int def) {
{ try {
try return Integer.parseInt(s);
{ } catch (NumberFormatException e) {
int v = Integer.parseInt(s);
return v;
}
catch (NumberFormatException e)
{
return def; return def;
} }
} }
public static long parseLong(String s, long def) public static long parseLong(String s, long def) {
{ try {
try return Long.parseLong(s);
{ } catch (NumberFormatException e) {
long v = Long.parseLong(s);
return v;
}
catch (NumberFormatException e)
{
return def; return def;
} }
} }
@ -269,61 +264,54 @@ public abstract class CLBase
* the time/offset string to parse. * the time/offset string to parse.
* @return the calculated time. * @return the calculated time.
*/ */
public static Date convertDuration(Date start, String duration) throws ParseException public static Instant convertDuration(Instant start, String duration) throws ParseException {
{ if (start == null) {
if (start == null) start = new Date(); start = Instant.now();
if (duration.startsWith("now")) }
{
start = new Date(); if (duration.startsWith("now")) {
if (duration.indexOf("+") < 0) return start; start = Instant.now();
if (duration.indexOf("+") < 0)
return start;
duration = duration.substring(3); duration = duration.substring(3);
} }
if (duration.startsWith("+")) if (duration.startsWith("+")) {
{ long offset = parseLong(duration.substring(1), 0);
long offset = (long) parseInt(duration.substring(1), 0) * 1000; return start.plusSeconds(offset);
return new Date(start.getTime() + offset);
} }
if (duration.length() <= 10)
{ // This is a heuristic to distinguish UNIX epoch times from the zone file
long epoch = parseLong(duration, 0) * 1000; // format standard (which is length == 14)
return new Date(epoch); if (duration.length() <= 10) {
long epoch = parseLong(duration, 0);
return Instant.ofEpochSecond(epoch);
} }
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyyMMddHHmmss"); SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyyMMddHHmmss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT")); dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT"));
try try {
{ Date parsedDate = dateFormatter.parse(duration);
return dateFormatter.parse(duration); return parsedDate.toInstant();
} } catch (java.text.ParseException e) {
catch (java.text.ParseException e)
{
throw new ParseException(e.getMessage()); throw new ParseException(e.getMessage());
} }
} }
public abstract void execute() throws Exception; public abstract void execute() throws Exception;
public void run(CLIStateBase state, String[] args) public void run(CLIStateBase state, String[] args) {
{ try {
try
{
state.parseCommandLine(args); state.parseCommandLine(args);
} } catch (UnrecognizedOptionException e) {
catch (UnrecognizedOptionException e)
{
System.err.println("error: unknown option encountered: " + e.getMessage()); System.err.println("error: unknown option encountered: " + e.getMessage());
state.usage(); state.usage();
} } catch (AlreadySelectedException e) {
catch (AlreadySelectedException e)
{
System.err.println("error: mutually exclusive options have " System.err.println("error: mutually exclusive options have "
+ "been selected:\n " + e.getMessage()); + "been selected:\n " + e.getMessage());
state.usage(); state.usage();
} } catch (Exception e) {
catch (Exception e)
{
System.err.println("error: unknown command line parsing exception:"); System.err.println("error: unknown command line parsing exception:");
e.printStackTrace(); e.printStackTrace();
state.usage(); state.usage();
@ -331,12 +319,9 @@ public abstract class CLBase
log = Logger.getLogger(this.getClass().toString()); log = Logger.getLogger(this.getClass().toString());
try try {
{
execute(); execute();
} } catch (Exception e) {
catch (Exception e)
{
e.printStackTrace(); e.printStackTrace();
System.exit(1); System.exit(1);
} }

View File

@ -1,4 +1,4 @@
// Copyright (C) 2001-2003, 2011 VeriSign, Inc. // Copyright (C) 2001-2003, 2011, 2022 VeriSign, Inc.
// //
// This library is free software; you can redistribute it and/or // This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public // modify it under the terms of the GNU Lesser General Public
@ -20,36 +20,45 @@ package com.verisignlabs.dnssec.cl;
import java.io.FileWriter; import java.io.FileWriter;
import java.io.PrintWriter; import java.io.PrintWriter;
import org.apache.commons.cli.*; import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.xbill.DNS.CDSRecord;
import org.xbill.DNS.DLVRecord; import org.xbill.DNS.DLVRecord;
import org.xbill.DNS.DNSKEYRecord; import org.xbill.DNS.DNSKEYRecord;
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.*; import com.verisignlabs.dnssec.security.BINDKeyUtils;
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
* *
* @author David Blacka * @author David Blacka
*/ */
public class DSTool extends CLBase public class DSTool extends CLBase {
{
private CLIState state; private CLIState state;
/** There are several records that are based on DS. */
protected enum dsType {
DS, CDS, DLV;
}
/** /**
* This is a small inner class used to hold all of the command line option * This is a small inner class used to hold all of the command line option
* state. * state.
*/ */
protected static class CLIState extends CLIStateBase
{ protected static class CLIState extends CLIStateBase {
public boolean createDLV = false; public dsType createType = dsType.DS;
public String outputfile = null; public String outputfile = null;
public String keyname = null; public String keyname = null;
public int digest_id = DSRecord.SHA1_DIGEST_ID; public int digestId = DNSSEC.Digest.SHA1;
public CLIState() public CLIState() {
{
super("jdnssec-dstool [..options..] keyfile"); super("jdnssec-dstool [..options..] keyfile");
} }
@ -58,76 +67,72 @@ public class DSTool extends CLBase
* *
* @return a set of command line options. * @return a set of command line options.
*/ */
protected void setupOptions(Options opts) @Override
{ protected void setupOptions(Options opts) {
OptionBuilder.withLongOpt("dlv"); opts.addOption(Option.builder("D").longOpt("dlv").desc("Generate a DLV record instead.").build());
OptionBuilder.withDescription("Generate a DLV record instead."); opts.addOption(Option.builder("C").longOpt("cds").desc("Generate a CDS record instead").build());
opts.addOption(OptionBuilder.create()); opts.addOption(Option.builder("d").hasArg().argName("id").longOpt("digest").desc("The digest algorithm to use").build());
opts.addOption(Option.builder("f").hasArg().argName("file").longOpt("output").desc("output to file").build());
OptionBuilder.hasArg();
OptionBuilder.withLongOpt("digest");
OptionBuilder.withArgName("id");
OptionBuilder.withDescription("The Digest ID to use (numerically): either 1 for SHA1 or 2 for SHA256");
opts.addOption(OptionBuilder.create('d'));
} }
@Override
protected void processOptions(CommandLine cli) protected void processOptions(CommandLine cli)
throws org.apache.commons.cli.ParseException throws org.apache.commons.cli.ParseException {
{
outputfile = cli.getOptionValue('f'); outputfile = cli.getOptionValue('f');
createDLV = cli.hasOption("dlv"); if (cli.hasOption("dlv")) {
createType = dsType.DLV;
} else if (cli.hasOption("cds")) {
createType = dsType.CDS;
}
String optstr = cli.getOptionValue('d'); String optstr = cli.getOptionValue('d');
if (optstr != null) digest_id = parseInt(optstr, digest_id); if (optstr != null)
digestId = DNSSEC.Digest.value(optstr);
String[] cl_args = cli.getArgs(); String[] args = cli.getArgs();
if (cl_args.length < 1) if (args.length < 1) {
{
System.err.println("error: missing key file "); System.err.println("error: missing key file ");
usage(); usage();
} }
keyname = cl_args[0]; keyname = args[0];
} }
} }
public void execute() throws Exception public void execute() throws Exception {
{
DnsKeyPair key = BINDKeyUtils.loadKey(state.keyname, null); DnsKeyPair key = BINDKeyUtils.loadKey(state.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 is not an SEP-flagged key."); log.warning("DNSKEY is not an SEP-flagged key.");
} }
DSRecord ds = SignUtils.calculateDSRecord(dnskey, state.digest_id, dnskey.getTTL()); DSRecord ds = SignUtils.calculateDSRecord(dnskey, state.digestId, dnskey.getTTL());
Record res = ds; Record res = ds;
if (state.createDLV) if (state.createType == dsType.DLV) {
{
log.fine("creating DLV."); log.fine("creating DLV.");
DLVRecord dlv = new DLVRecord(ds.getName(), ds.getDClass(), ds.getTTL(), DLVRecord dlv = new DLVRecord(ds.getName(), ds.getDClass(), ds.getTTL(), ds.getFootprint(), ds.getAlgorithm(),
ds.getFootprint(), ds.getAlgorithm(),
ds.getDigestID(), ds.getDigest()); ds.getDigestID(), ds.getDigest());
res = dlv; res = dlv;
} else if (state.createType == dsType.CDS) {
log.fine("creating CDS.");
CDSRecord cds = new CDSRecord(ds.getName(), ds.getDClass(), ds.getTTL(), ds.getFootprint(), ds.getAlgorithm(),
ds.getDClass(), ds.getDigest());
res = cds;
} }
if (state.outputfile != null) if (state.outputfile != null && !state.outputfile.equals("-")) {
{ try (PrintWriter out = new PrintWriter(new FileWriter(state.outputfile))) {
PrintWriter out = new PrintWriter(new FileWriter(state.outputfile));
out.println(res); out.println(res);
out.close();
} }
else } else {
{
System.out.println(res); System.out.println(res);
} }
} }
public static void main(String[] args) public static void main(String[] args) {
{
DSTool tool = new DSTool(); DSTool tool = new DSTool();
tool.state = new CLIState(); tool.state = new CLIState();

View File

@ -1,4 +1,4 @@
// Copyright (C) 2001-2003, 2011 VeriSign, Inc. // Copyright (C) 2001-2003, 2011, 2022 VeriSign, Inc.
// //
// This library is free software; you can redistribute it and/or // This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public // modify it under the terms of the GNU Lesser General Public
@ -19,28 +19,31 @@ package com.verisignlabs.dnssec.cl;
import java.io.File; import java.io.File;
import org.apache.commons.cli.*; import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.xbill.DNS.DClass; import org.xbill.DNS.DClass;
import org.xbill.DNS.DNSKEYRecord; import org.xbill.DNS.DNSKEYRecord;
import org.xbill.DNS.Name; import org.xbill.DNS.Name;
import com.verisignlabs.dnssec.security.*; import com.verisignlabs.dnssec.security.BINDKeyUtils;
import com.verisignlabs.dnssec.security.DnsKeyAlgorithm;
import com.verisignlabs.dnssec.security.DnsKeyPair;
import com.verisignlabs.dnssec.security.JCEDnsSecSigner;
/** /**
* This class forms the command line implementation of a DNSSEC key generator * This class forms the command line implementation of a DNSSEC key generator
* *
* @author David Blacka * @author David Blacka
*/ */
public class KeyGen extends CLBase public class KeyGen extends CLBase {
{
private CLIState state; private CLIState state;
/** /**
* This is a small inner class used to hold all of the command line option * This is a small inner class used to hold all of the command line option
* state. * state.
*/ */
protected static class CLIState extends CLIStateBase protected static class CLIState extends CLIStateBase {
{
public int algorithm = 8; public int algorithm = 8;
public int keylength = 1024; public int keylength = 1024;
public boolean useLargeE = true; public boolean useLargeE = true;
@ -51,16 +54,15 @@ public class KeyGen extends CLBase
public String owner = null; public String owner = null;
public long ttl = 86400; public long ttl = 86400;
public CLIState() public CLIState() {
{
super("jdnssec-keygen [..options..] name"); super("jdnssec-keygen [..options..] name");
} }
/** /**
* Set up the command line options. * Set up the command line options.
*/ */
protected void setupOptions(Options opts) @Override
{ protected void setupOptions(Options opts) {
// boolean options // boolean options
opts.addOption("k", "kskflag", false, opts.addOption("k", "kskflag", false,
"Key is a key-signing-key (sets the SEP flag)."); "Key is a key-signing-key (sets the SEP flag).");
@ -68,156 +70,127 @@ public class KeyGen extends CLBase
opts.addOption("E", "small-exponent", false, "Use small RSA exponent"); opts.addOption("E", "small-exponent", false, "Use small RSA exponent");
// Argument options // Argument options
OptionBuilder.hasArg(); opts.addOption(
OptionBuilder.withLongOpt("nametype"); Option.builder("n").longOpt("nametype").hasArg().argName("type").desc("ZONE | OTHER (default ZONE)").build());
OptionBuilder.withArgName("type");
OptionBuilder.withDescription("ZONE | OTHER (default ZONE)");
opts.addOption(OptionBuilder.create('n'));
String[] algStrings = DnsKeyAlgorithm.getInstance().supportedAlgMnemonics(); String[] algStrings = DnsKeyAlgorithm.getInstance().supportedAlgMnemonics();
OptionBuilder.hasArg(); String algStringSet = String.join(" | ", algStrings);
OptionBuilder.withArgName("algorithm"); opts.addOption(Option.builder("a").hasArg().argName("algorithm")
OptionBuilder.withDescription(String.join(" | ", algStrings) + .desc(algStringSet + " | alias, RSASHA256 is default.").build());
" | alias, RSASHA256 is default.");
opts.addOption(OptionBuilder.create('a'));
OptionBuilder.hasArg(); opts.addOption(Option.builder("b").hasArg().argName("size").desc(
OptionBuilder.withArgName("size"); "key size, in bits (default 1024). RSA: [512..4096], DSA: [512..1024], DH: [128..4096], ECDSA: ignored, EdDSA: ignored")
OptionBuilder.withDescription("key size, in bits. default is 1024. " .build());
+ "RSA: [512..4096], DSA: [512..1024], DH: [128..4096], " opts.addOption(Option.builder("f").hasArg().argName("file").longOpt("output-file")
+ "ECDSA: ignored"); .desc("base filename from the public/private key files").build());
opts.addOption(OptionBuilder.create('b')); 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());
OptionBuilder.hasArg();
OptionBuilder.withArgName("file");
OptionBuilder.withLongOpt("output-file");
OptionBuilder.withDescription("base filename for the public/private key files");
opts.addOption(OptionBuilder.create('f'));
OptionBuilder.hasArg();
OptionBuilder.withLongOpt("keydir");
OptionBuilder.withArgName("dir");
OptionBuilder.withDescription("place generated key files in this " + "directory");
opts.addOption(OptionBuilder.create('d'));
} }
@Override
protected void processOptions(CommandLine cli) protected void processOptions(CommandLine cli)
throws org.apache.commons.cli.ParseException throws org.apache.commons.cli.ParseException {
{
String optstr = null; String optstr = null;
String[] optstrs = null; String[] optstrs = null;
if (cli.hasOption('k')) kskFlag = true; if (cli.hasOption('k'))
if (cli.hasOption('e')) useLargeE = true; kskFlag = true;
if (cli.hasOption('e'))
useLargeE = true;
outputfile = cli.getOptionValue('f'); outputfile = cli.getOptionValue('f');
if ((optstr = cli.getOptionValue('d')) != null) if ((optstr = cli.getOptionValue('d')) != null) {
{
keydir = new File(optstr); keydir = new File(optstr);
} }
if ((optstr = cli.getOptionValue('n')) != null) if ((optstr = cli.getOptionValue('n')) != null && !optstr.equalsIgnoreCase("ZONE")) {
{
if (!optstr.equalsIgnoreCase("ZONE"))
{
zoneKey = false; zoneKey = false;
} }
}
if ((optstrs = cli.getOptionValues('A')) != null) if ((optstrs = cli.getOptionValues('A')) != null) {
{ for (int i = 0; i < optstrs.length; i++) {
for (int i = 0; i < optstrs.length; i++)
{
addArgAlias(optstrs[i]); addArgAlias(optstrs[i]);
} }
} }
if ((optstr = cli.getOptionValue('a')) != null) if ((optstr = cli.getOptionValue('a')) != null) {
{ algorithm = CLIState.parseAlg(optstr);
algorithm = parseAlg(optstr); if (algorithm < 0) {
if (algorithm < 0)
{
System.err.println("DNSSEC algorithm " + optstr + " is not supported"); System.err.println("DNSSEC algorithm " + optstr + " is not supported");
usage(); usage();
} }
} }
if ((optstr = cli.getOptionValue('b')) != null) if ((optstr = cli.getOptionValue('b')) != null) {
{
keylength = parseInt(optstr, 1024); keylength = parseInt(optstr, 1024);
} }
if ((optstr = cli.getOptionValue("ttl")) != null) if ((optstr = cli.getOptionValue("ttl")) != null) {
{
ttl = parseInt(optstr, 86400); ttl = parseInt(optstr, 86400);
} }
String[] cl_args = cli.getArgs(); String[] args = cli.getArgs();
if (cl_args.length < 1) if (args.length < 1) {
{
System.err.println("error: missing key owner name"); System.err.println("error: missing key owner name");
usage(); usage();
} }
owner = cl_args[0]; owner = args[0];
}
} }
private static int parseAlg(String s) {
private static int parseAlg(String s)
{
DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance(); DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance();
int alg = parseInt(s, -1); int alg = parseInt(s, -1);
if (alg > 0) if (alg > 0) {
{ if (algs.supportedAlgorithm(alg))
if (algs.supportedAlgorithm(alg)) return alg; return alg;
return -1; return -1;
} }
return algs.stringToAlgorithm(s); return algs.stringToAlgorithm(s);
} }
}
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 (!state.owner.endsWith(".")) if (!state.owner.endsWith(".")) {
{
state.owner = state.owner + "."; state.owner = state.owner + ".";
} }
Name owner_name = Name.fromString(state.owner); Name ownerName = Name.fromString(state.owner);
// Calculate our flags // Calculate our flags
int flags = 0; int flags = 0;
if (state.zoneKey) flags |= DNSKEYRecord.Flags.ZONE_KEY; if (state.zoneKey)
if (state.kskFlag) flags |= DNSKEYRecord.Flags.SEP_KEY; flags |= DNSKEYRecord.Flags.ZONE_KEY;
if (state.kskFlag)
flags |= DNSKEYRecord.Flags.SEP_KEY;
log.fine("create key pair with (name = " + owner_name + ", ttl = " + state.ttl log.fine("create key pair with (name = " + ownerName + ", ttl = " + state.ttl
+ ", alg = " + state.algorithm + ", flags = " + flags + ", length = " + ", alg = " + state.algorithm + ", flags = " + flags + ", length = "
+ state.keylength + ")"); + state.keylength + ")");
DnsKeyPair pair = signer.generateKey(owner_name, state.ttl, DClass.IN, DnsKeyPair pair = signer.generateKey(ownerName, state.ttl, DClass.IN,
state.algorithm, flags, state.keylength, state.algorithm, flags, state.keylength,
state.useLargeE); state.useLargeE);
if (state.outputfile != null) if (state.outputfile != null) {
{
BINDKeyUtils.writeKeyFiles(state.outputfile, pair, state.keydir); BINDKeyUtils.writeKeyFiles(state.outputfile, pair, state.keydir);
} } else {
else
{
BINDKeyUtils.writeKeyFiles(pair, state.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 tool = new KeyGen();
tool.state = new CLIState(); tool.state = new CLIState();

View File

@ -1,4 +1,4 @@
// Copyright (C) 2001-2003, 2011 VeriSign, Inc. // Copyright (C) 2001-2003, 2011, 2022 VeriSign, Inc.
// //
// This library is free software; you can redistribute it and/or // This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public // modify it under the terms of the GNU Lesser General Public
@ -20,57 +20,55 @@ 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.*; 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.*; import com.verisignlabs.dnssec.security.BINDKeyUtils;
import com.verisignlabs.dnssec.security.DnsKeyAlgorithm;
import com.verisignlabs.dnssec.security.DnsKeyPair;
/** /**
* This class forms the command line implementation of a key introspection tool. * This class forms the command line implementation of a key introspection tool.
* *
* @author David Blacka * @author David Blacka
*/ */
public class KeyInfoTool extends CLBase public class KeyInfoTool extends CLBase {
{
private CLIState state; private CLIState state;
/** /**
* This is a small inner class used to hold all of the command line option * This is a small inner class used to hold all of the command line option
* state. * state.
*/ */
protected static class CLIState extends CLIStateBase protected static class CLIState extends CLIStateBase {
{
public String[] keynames = null; public String[] keynames = null;
public CLIState() public CLIState() {
{
super("jdnssec-keyinfo [..options..] keyfile"); super("jdnssec-keyinfo [..options..] keyfile");
} }
/** /**
* Set up the command line options. * Set up the command line options.
*/ */
protected void setupOptions(Options opts) @Override
{ protected void setupOptions(Options opts) {
// no special options at the moment. // no special options at the moment.
} }
protected void processOptions(CommandLine cli) throws ParseException @Override
{ protected void processOptions(CommandLine cli) throws ParseException {
keynames = cli.getArgs(); keynames = cli.getArgs();
if (keynames.length < 1) if (keynames.length < 1) {
{
System.err.println("error: missing key file "); System.err.println("error: missing key file ");
usage(); usage();
} }
} }
} }
public void execute() throws Exception public void execute() throws Exception {
{ for (int i = 0; i < state.keynames.length; ++i) {
for (int i = 0; i < state.keynames.length; ++i)
{
String keyname = state.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();
@ -87,32 +85,25 @@ public class KeyInfoTool extends CLBase
System.out.println("ID: " + dnskey.getFootprint()); System.out.println("ID: " + dnskey.getFootprint());
System.out.println("KeyFileBase: " + BINDKeyUtils.keyFileBase(key)); System.out.println("KeyFileBase: " + BINDKeyUtils.keyFileBase(key));
int basetype = dnskeyalg.baseType(dnskey.getAlgorithm()); int basetype = dnskeyalg.baseType(dnskey.getAlgorithm());
switch (basetype)
{ if (basetype == DnsKeyAlgorithm.RSA) {
case DnsKeyAlgorithm.RSA: {
RSAPublicKey pub = (RSAPublicKey) key.getPublic(); RSAPublicKey pub = (RSAPublicKey) key.getPublic();
System.out.println("RSA Public Exponent: " + pub.getPublicExponent()); System.out.println("RSA Public Exponent: " + pub.getPublicExponent());
System.out.println("RSA Modulus: " + pub.getModulus()); System.out.println("RSA Modulus: " + pub.getModulus());
break; } else if (basetype == DnsKeyAlgorithm.DSA) {
}
case DnsKeyAlgorithm.DSA: {
DSAPublicKey pub = (DSAPublicKey) key.getPublic(); DSAPublicKey pub = (DSAPublicKey) key.getPublic();
System.out.println("DSA base (G): " + pub.getParams().getG()); System.out.println("DSA base (G): " + pub.getParams().getG());
System.out.println("DSA prime (P): " + pub.getParams().getP()); System.out.println("DSA prime (P): " + pub.getParams().getP());
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());
break;
} }
} if (state.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 tool = new KeyInfoTool();
tool.state = new CLIState(); tool.state = new CLIState();

View File

@ -1,4 +1,4 @@
// Copyright (C) 2001-2003, 2011 VeriSign, Inc. // Copyright (C) 2001-2003, 2011, 2022 VeriSign, Inc.
// //
// This library is free software; you can redistribute it and/or // This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public // modify it under the terms of the GNU Lesser General Public
@ -20,13 +20,13 @@ package com.verisignlabs.dnssec.cl;
import java.io.File; import java.io.File;
import java.io.FileFilter; import java.io.FileFilter;
import java.io.IOException; import java.io.IOException;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Collections;
import java.util.Iterator;
import java.util.List; import java.util.List;
import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.OptionBuilder; import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options; 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;
@ -34,7 +34,12 @@ import org.xbill.DNS.RRset;
import org.xbill.DNS.Record; import org.xbill.DNS.Record;
import org.xbill.DNS.Type; import org.xbill.DNS.Type;
import com.verisignlabs.dnssec.security.*; import com.verisignlabs.dnssec.security.BINDKeyUtils;
import com.verisignlabs.dnssec.security.DnsKeyPair;
import com.verisignlabs.dnssec.security.DnsSecVerifier;
import com.verisignlabs.dnssec.security.JCEDnsSecSigner;
import com.verisignlabs.dnssec.security.SignUtils;
import com.verisignlabs.dnssec.security.ZoneUtils;
/** /**
* This class forms the command line implementation of a DNSSEC keyset signer. * This class forms the command line implementation of a DNSSEC keyset signer.
@ -43,94 +48,66 @@ import com.verisignlabs.dnssec.security.*;
* *
* @author David Blacka * @author David Blacka
*/ */
public class SignKeyset extends CLBase public class SignKeyset extends CLBase {
{
private CLIState state; private CLIState state;
/** /**
* This is an inner class used to hold all of the command line option state. * This is an inner class used to hold all of the command line option state.
*/ */
protected static class CLIState extends CLIStateBase protected static class CLIState extends CLIStateBase {
{
public File keyDirectory = null; public File keyDirectory = null;
public String[] keyFiles = null; public String[] keyFiles = null;
public Date start = null; public Instant start = null;
public Date expire = null; public Instant expire = null;
public String inputfile = null; public String inputfile = null;
public String outputfile = null; public String outputfile = null;
public boolean verifySigs = false; public boolean verifySigs = false;
public CLIState() public CLIState() {
{
super("jdnssec-signkeyset [..options..] dnskeyset_file [key_file ...]"); super("jdnssec-signkeyset [..options..] dnskeyset_file [key_file ...]");
} }
/** /**
* Set up the command line options. * Set up the command line options.
*/ */
protected void setupOptions(Options opts) @Override
{ protected void setupOptions(Options opts) {
// boolean options // boolean options
opts.addOption("a", "verify", false, "verify generated signatures>"); opts.addOption("a", "verify", false, "verify generated signatures>");
// Argument options // Argument options
OptionBuilder.hasArg(); opts.addOption(Option.builder("D").hasArg().argName("dir").longOpt("key-directory").desc("directory where key files are found (default '.').").build());
OptionBuilder.withArgName("dir"); opts.addOption(Option.builder("s").hasArg().argName("time/offset").longOpt("start-time").desc("signature starting time (default is now - 1 hour)").build());
OptionBuilder.withLongOpt("key-directory"); opts.addOption(Option.builder("e").hasArg().argName("time/offset").longOpt("expire-time").desc("signature expiration time (default is start-time + 30 days)").build());
OptionBuilder.withDescription("directory to find key files (default '.')."); opts.addOption(Option.builder("f").hasArg().argName("outfile").desc("file the signed keyset is written to").build());
opts.addOption(OptionBuilder.create('D'));
OptionBuilder.hasArg();
OptionBuilder.withArgName("time/offset");
OptionBuilder.withLongOpt("start-time");
OptionBuilder.withDescription("signature starting time (default is now - 1 hour)");
opts.addOption(OptionBuilder.create('s'));
OptionBuilder.hasArg();
OptionBuilder.withArgName("time/offset");
OptionBuilder.withLongOpt("expire-time");
OptionBuilder.withDescription("signature expiration time (default is start-time + 30 days).");
opts.addOption(OptionBuilder.create('e'));
OptionBuilder.hasArg();
OptionBuilder.withArgName("outfile");
OptionBuilder.withDescription("file the signed keyset is written to.");
opts.addOption(OptionBuilder.create('f'));
} }
@Override
protected void processOptions(CommandLine cli) protected void processOptions(CommandLine cli)
throws org.apache.commons.cli.ParseException throws org.apache.commons.cli.ParseException {
{
String optstr = null; String optstr = null;
if (cli.hasOption('a')) verifySigs = true; if (cli.hasOption('a'))
verifySigs = true;
if ((optstr = cli.getOptionValue('D')) != null) if ((optstr = cli.getOptionValue('D')) != null) {
{
keyDirectory = new File(optstr); keyDirectory = new File(optstr);
if (!keyDirectory.isDirectory()) if (!keyDirectory.isDirectory()) {
{
System.err.println("error: " + optstr + " is not a directory"); System.err.println("error: " + optstr + " is not a directory");
usage(); usage();
} }
} }
if ((optstr = cli.getOptionValue('s')) != null) if ((optstr = cli.getOptionValue('s')) != null) {
{
start = convertDuration(null, optstr); start = convertDuration(null, optstr);
} } else {
else
{
// default is now - 1 hour. // default is now - 1 hour.
start = new Date(System.currentTimeMillis() - (3600 * 1000)); start = Instant.now().minusSeconds(3600);
} }
if ((optstr = cli.getOptionValue('e')) != null) if ((optstr = cli.getOptionValue('e')) != null) {
{
expire = convertDuration(start, optstr); expire = convertDuration(start, optstr);
} } else {
else
{
expire = convertDuration(start, "+2592000"); // 30 days expire = convertDuration(start, "+2592000"); // 30 days
} }
@ -138,15 +115,13 @@ public class SignKeyset extends CLBase
String[] files = cli.getArgs(); String[] files = cli.getArgs();
if (files.length < 1) if (files.length < 1) {
{
System.err.println("error: missing zone file and/or key files"); System.err.println("error: missing zone file and/or key files");
usage(); 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);
} }
@ -156,23 +131,19 @@ public class SignKeyset extends CLBase
/** /**
* Verify the generated signatures. * Verify the generated signatures.
* *
* @param zonename
* the origin name of the zone.
* @param records * @param records
* a list of {@link org.xbill.DNS.Record}s. * a list of {@link org.xbill.DNS.Record}s.
* @param keypairs * @param keypairs
* a list of keypairs used the sign the zone. * 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 static boolean verifySigs(Name zonename, List<Record> records, private static boolean verifySigs(List<Record> records,
List<DnsKeyPair> keypairs) List<DnsKeyPair> keypairs) {
{
boolean secure = true; boolean secure = true;
DnsSecVerifier verifier = new DnsSecVerifier(); DnsSecVerifier verifier = new DnsSecVerifier();
for (DnsKeyPair pair : keypairs) for (DnsKeyPair pair : keypairs) {
{
verifier.addTrustedKey(pair); verifier.addTrustedKey(pair);
} }
@ -180,16 +151,15 @@ public class SignKeyset extends CLBase
List<RRset> rrsets = SignUtils.assembleIntoRRsets(records); List<RRset> rrsets = SignUtils.assembleIntoRRsets(records);
for (RRset rrset : rrsets) for (RRset rrset : rrsets) {
{
// skip unsigned rrsets. // skip unsigned rrsets.
if (!rrset.sigs().hasNext()) continue; if (rrset.sigs().isEmpty())
continue;
boolean result = verifier.verify(rrset); boolean result = verifier.verify(rrset);
if (!result) if (!result) {
{ staticLog.fine("Signatures did not verify for RRset: " + rrset);
log.fine("Signatures did not verify for RRset: " + rrset);
secure = false; secure = false;
} }
} }
@ -201,57 +171,54 @@ public class SignKeyset extends CLBase
* Load the key pairs from the key files. * Load the key pairs from the key files.
* *
* @param keyfiles * @param keyfiles
* a string array containing the base names or paths of the keys * a string array containing the base names or paths of the
* keys
* to be loaded. * to be loaded.
* @param start_index * @param startIndex
* the starting index of keyfiles string array to use. This * the starting index of keyfiles string array to use. This
* allows us to use the straight command line argument array. * allows us to use the straight command line argument array.
* @param inDirectory * @param inDirectory
* the directory to look in (may be null). * the directory to look in (may be null).
* @return a list of keypair objects. * @return a list of keypair objects.
*/ */
private static List<DnsKeyPair> getKeys(String[] keyfiles, int start_index, private static List<DnsKeyPair> getKeys(String[] keyfiles, int startIndex,
File inDirectory) throws IOException File inDirectory) throws IOException {
{ if (keyfiles == null)
if (keyfiles == null) return null; return Collections.emptyList();
int len = keyfiles.length - start_index; int len = keyfiles.length - startIndex;
if (len <= 0) return null; if (len <= 0)
return Collections.emptyList();
ArrayList<DnsKeyPair> keys = new ArrayList<DnsKeyPair>(len); ArrayList<DnsKeyPair> keys = new ArrayList<>(len);
for (int i = start_index; i < keyfiles.length; i++) for (int i = startIndex; i < keyfiles.length; i++) {
{
DnsKeyPair k = BINDKeyUtils.loadKeyPair(keyfiles[i], inDirectory); DnsKeyPair k = BINDKeyUtils.loadKeyPair(keyfiles[i], inDirectory);
if (k != null) keys.add(k); if (k != null)
keys.add(k);
} }
return keys; return keys;
} }
private static class KeyFileFilter implements FileFilter private static class KeyFileFilter implements FileFilter {
{
private String prefix; private String prefix;
public KeyFileFilter(Name origin) public KeyFileFilter(Name origin) {
{
prefix = "K" + origin.toString(); prefix = "K" + origin.toString();
} }
public boolean accept(File pathname) public boolean accept(File pathname) {
{ if (!pathname.isFile())
if (!pathname.isFile()) return false;
String name = pathname.getName();
if (name.startsWith(prefix) && name.endsWith(".private")) return true;
return false; return false;
String name = pathname.getName();
return (name.startsWith(prefix) && name.endsWith(".private"));
} }
} }
private static 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(".");
} }
@ -260,24 +227,19 @@ public class SignKeyset extends CLBase
File[] files = inDirectory.listFiles(filter); File[] files = inDirectory.listFiles(filter);
// read in all of the records // read in all of the records
ArrayList<DnsKeyPair> keys = new ArrayList<DnsKeyPair>(); ArrayList<DnsKeyPair> keys = new ArrayList<>();
for (int i = 0; i < files.length; i++) for (int i = 0; i < files.length; i++) {
{
DnsKeyPair p = BINDKeyUtils.loadKeyPair(files[i].getName(), inDirectory); DnsKeyPair p = BINDKeyUtils.loadKeyPair(files[i].getName(), inDirectory);
keys.add(p); keys.add(p);
} }
if (keys.size() > 0) return keys; return keys;
return null;
} }
@SuppressWarnings("unchecked") public void execute() throws Exception {
public void execute() throws Exception
{
// Read in the zone // Read in the zone
List<Record> records = ZoneUtils.readZoneFile(state.inputfile, null); List<Record> records = ZoneUtils.readZoneFile(state.inputfile, null);
if (records == null || records.size() == 0) if (records == null || records.isEmpty()) {
{
System.err.println("error: empty keyset file"); System.err.println("error: empty keyset file");
state.usage(); state.usage();
} }
@ -286,27 +248,22 @@ public class SignKeyset extends CLBase
Name keysetName = null; Name keysetName = null;
RRset keyset = new RRset(); RRset keyset = new RRset();
for (Record r : records) for (Record r : records) {
{ if (r.getType() != Type.DNSKEY) {
if (r.getType() != Type.DNSKEY)
{
System.err.println("error: Non DNSKEY RR found in keyset: " + r); System.err.println("error: Non DNSKEY RR found in keyset: " + r);
continue; continue;
} }
if (keysetName == null) if (keysetName == null) {
{
keysetName = r.getName(); keysetName = r.getName();
} }
if (!r.getName().equals(keysetName)) if (!r.getName().equals(keysetName)) {
{
System.err.println("error: DNSKEY with a different name found!"); System.err.println("error: DNSKEY with a different name found!");
state.usage(); state.usage();
} }
keyset.addRR(r); keyset.addRR(r);
} }
if (keyset.size() == 0) if (keyset.size() == 0) {
{
System.err.println("error: No DNSKEYs found in keyset file"); System.err.println("error: No DNSKEYs found in keyset file");
state.usage(); state.usage();
} }
@ -317,27 +274,22 @@ public class SignKeyset extends CLBase
// 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(state.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.size() == 0) if (keypairs == null || keypairs.isEmpty() || keysetName == null) {
{
System.err.println("error: No signing keys could be determined."); System.err.println("error: No signing keys could be determined.");
state.usage(); state.usage();
return;
} }
// default the output file, if not set. // default the output file, if not set.
if (state.outputfile == null) if (state.outputfile == null) {
{ if (keysetName.isAbsolute()) {
if (keysetName.isAbsolute())
{
state.outputfile = keysetName + "signed_keyset"; state.outputfile = keysetName + "signed_keyset";
} } else {
else
{
state.outputfile = keysetName + ".signed_keyset"; state.outputfile = keysetName + ".signed_keyset";
} }
} }
@ -345,46 +297,36 @@ public class SignKeyset extends CLBase
JCEDnsSecSigner signer = new JCEDnsSecSigner(); JCEDnsSecSigner signer = new JCEDnsSecSigner();
List<RRSIGRecord> sigs = signer.signRRset(keyset, keypairs, state.start, state.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);
} }
// write out the signed RRset // write out the signed RRset
List<Record> signed_records = new ArrayList<Record>(); List<Record> signedRecords = new ArrayList<>();
for (Iterator<Record> i = keyset.rrs(); i.hasNext();) for (Record r : keyset.rrs()) {
{ signedRecords.add(r);
signed_records.add(i.next());
} }
for (Iterator<Record> i = keyset.sigs(); i.hasNext();) for (RRSIGRecord s : keyset.sigs()) {
{ signedRecords.add(s);
signed_records.add(i.next());
} }
// write out the signed zone // write out the signed zone
ZoneUtils.writeZoneFile(signed_records, state.outputfile); ZoneUtils.writeZoneFile(signedRecords, state.outputfile);
if (state.verifySigs) if (state.verifySigs) {
{
log.fine("verifying generated signatures"); log.fine("verifying generated signatures");
boolean res = verifySigs(keysetName, signed_records, keypairs); boolean res = verifySigs(signedRecords, keypairs);
if (res) if (res) {
{
System.out.println("Generated signatures verified"); System.out.println("Generated signatures verified");
// log.info("Generated signatures verified"); } else {
}
else
{
System.out.println("Generated signatures did not verify."); System.out.println("Generated signatures did not verify.");
// log.warn("Generated signatures did not verify.");
} }
} }
} }
public static void main(String[] args) public static void main(String[] args) {
{
SignKeyset tool = new SignKeyset(); SignKeyset tool = new SignKeyset();
tool.state = new CLIState(); tool.state = new CLIState();

View File

@ -1,4 +1,4 @@
// Copyright (C) 2001-2003, 2011 VeriSign, Inc. // Copyright (C) 2001-2003, 2011, 2022 VeriSign, Inc.
// //
// This library is free software; you can redistribute it and/or // This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public // modify it under the terms of the GNU Lesser General Public
@ -19,22 +19,26 @@ package com.verisignlabs.dnssec.cl;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Collections;
import java.util.Iterator;
import java.util.List; import java.util.List;
import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.OptionBuilder; import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options; 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;
import org.xbill.DNS.Record; import org.xbill.DNS.Record;
import org.xbill.DNS.Type; import org.xbill.DNS.Type;
import com.verisignlabs.dnssec.security.*; import com.verisignlabs.dnssec.security.BINDKeyUtils;
import com.verisignlabs.dnssec.security.DnsKeyPair;
import com.verisignlabs.dnssec.security.DnsSecVerifier;
import com.verisignlabs.dnssec.security.JCEDnsSecSigner;
import com.verisignlabs.dnssec.security.SignUtils;
import com.verisignlabs.dnssec.security.ZoneUtils;
/** /**
* This class forms the command line implementation of a DNSSEC RRset signer. * This class forms the command line implementation of a DNSSEC RRset signer.
@ -45,95 +49,72 @@ import com.verisignlabs.dnssec.security.*;
* *
* @author David Blacka * @author David Blacka
*/ */
public class SignRRset extends CLBase public class SignRRset extends CLBase {
{
private CLIState state; private CLIState state;
/** /**
* This is an inner class used to hold all of the command line option state. * This is an inner class used to hold all of the command line option state.
*/ */
protected static class CLIState extends CLIStateBase protected static class CLIState extends CLIStateBase {
{
private File keyDirectory = null; private File keyDirectory = null;
public String[] keyFiles = null; public String[] keyFiles = null;
public Date start = null; public Instant start = null;
public Date expire = null; public Instant expire = null;
public String inputfile = null; public String inputfile = null;
public String outputfile = null; public String outputfile = null;
public boolean verifySigs = false; public boolean verifySigs = false;
public boolean verboseSigning = false; public boolean verboseSigning = false;
public CLIState() public CLIState() {
{
super("jdnssec-signrrset [..options..] rrset_file key_file [key_file ...]"); super("jdnssec-signrrset [..options..] rrset_file key_file [key_file ...]");
} }
/** /**
* Set up the command line options. * Set up the command line options.
*/ */
protected void setupOptions(Options opts) @Override
{ protected void setupOptions(Options opts) {
// boolean options // boolean options
opts.addOption("a", "verify", false, "verify generated signatures>"); opts.addOption("a", "verify", false, "verify generated signatures>");
opts.addOption("V", "verbose-signing", false, "Display verbose signing activity."); opts.addOption("V", "verbose-signing", false, "Display verbose signing activity.");
OptionBuilder.hasArg(); opts.addOption(Option.builder("D").hasArg().argName("dir").longOpt("key-directory")
OptionBuilder.withArgName("dir"); .desc("directory to find key files (default '.'").build());
OptionBuilder.withLongOpt("key-directory"); opts.addOption(Option.builder("s").hasArg().argName("time/offset").longOpt("start-time")
OptionBuilder.withDescription("directory to find key files (default '.')."); .desc("signature starting time (default is now - 1 hour)").build());
opts.addOption(OptionBuilder.create('D')); opts.addOption(Option.builder("e").hasArg().argName("time/offset").longOpt("expire-time")
.desc("signature expiration time (default is start-time + 30 days)").build());
OptionBuilder.hasArg(); opts.addOption(
OptionBuilder.withArgName("time/offset"); Option.builder("f").hasArg().argName("outfile").desc("file the the signed rrset is written to").build());
OptionBuilder.withLongOpt("start-time");
OptionBuilder.withDescription("signature starting time (default is now - 1 hour)");
opts.addOption(OptionBuilder.create('s'));
OptionBuilder.hasArg();
OptionBuilder.withArgName("time/offset");
OptionBuilder.withLongOpt("expire-time");
OptionBuilder.withDescription("signature expiration time (default is start-time + 30 days).");
opts.addOption(OptionBuilder.create('e'));
OptionBuilder.hasArg();
OptionBuilder.withArgName("outfile");
OptionBuilder.withDescription("file the signed rrset is written to.");
opts.addOption(OptionBuilder.create('f'));
} }
protected void processOptions(CommandLine cli) throws org.apache.commons.cli.ParseException @Override
{ protected void processOptions(CommandLine cli) throws org.apache.commons.cli.ParseException {
String optstr = null; String optstr = null;
if (cli.hasOption('a')) verifySigs = true; if (cli.hasOption('a'))
if (cli.hasOption('V')) verboseSigning = true; verifySigs = true;
if (cli.hasOption('V'))
verboseSigning = true;
if ((optstr = cli.getOptionValue('D')) != null) if ((optstr = cli.getOptionValue('D')) != null) {
{
keyDirectory = new File(optstr); keyDirectory = new File(optstr);
if (!keyDirectory.isDirectory()) if (!keyDirectory.isDirectory()) {
{
System.err.println("error: " + optstr + " is not a directory"); System.err.println("error: " + optstr + " is not a directory");
usage(); usage();
} }
} }
if ((optstr = cli.getOptionValue('s')) != null) if ((optstr = cli.getOptionValue('s')) != null) {
{
start = convertDuration(null, optstr); start = convertDuration(null, optstr);
} } else {
else
{
// default is now - 1 hour. // default is now - 1 hour.
start = new Date(System.currentTimeMillis() - (3600 * 1000)); start = Instant.now().minusSeconds(3600);
} }
if ((optstr = cli.getOptionValue('e')) != null) if ((optstr = cli.getOptionValue('e')) != null) {
{
expire = convertDuration(start, optstr); expire = convertDuration(start, optstr);
} } else {
else
{
expire = convertDuration(start, "+2592000"); // 30 days expire = convertDuration(start, "+2592000"); // 30 days
} }
@ -141,15 +122,13 @@ public class SignRRset extends CLBase
String[] files = cli.getArgs(); String[] files = cli.getArgs();
if (files.length < 1) if (files.length < 1) {
{
System.err.println("error: missing zone file and/or key files"); System.err.println("error: missing zone file and/or key files");
usage(); 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);
} }
@ -159,22 +138,18 @@ public class SignRRset extends CLBase
/** /**
* Verify the generated signatures. * Verify the generated signatures.
* *
* @param zonename
* the origin name of the zone.
* @param records * @param records
* a list of {@link org.xbill.DNS.Record}s. * a list of {@link org.xbill.DNS.Record}s.
* @param keypairs * @param keypairs
* a list of keypairs used the sign the zone. * 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 static boolean verifySigs(Name zonename, 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();
for (DnsKeyPair pair : keypairs) for (DnsKeyPair pair : keypairs) {
{
verifier.addTrustedKey(pair); verifier.addTrustedKey(pair);
} }
@ -182,16 +157,16 @@ public class SignRRset extends CLBase
List<RRset> rrsets = SignUtils.assembleIntoRRsets(records); List<RRset> rrsets = SignUtils.assembleIntoRRsets(records);
for (RRset rrset : rrsets) for (RRset rrset : rrsets) {
{
// skip unsigned rrsets. // skip unsigned rrsets.
if (!rrset.sigs().hasNext()) continue; if (rrset.sigs().isEmpty()) {
continue;
}
boolean result = verifier.verify(rrset); boolean result = verifier.verify(rrset);
if (!result) if (!result) {
{ staticLog.fine("Signatures did not verify for RRset: " + rrset);
log.fine("Signatures did not verify for RRset: " + rrset);
secure = false; secure = false;
} }
} }
@ -203,41 +178,40 @@ public class SignRRset extends CLBase
* Load the key pairs from the key files. * Load the key pairs from the key files.
* *
* @param keyfiles * @param keyfiles
* a string array containing the base names or paths of the keys * a string array containing the base names or paths of the
* keys
* to be loaded. * to be loaded.
* @param start_index * @param startIndex
* the starting index of keyfiles string array to use. This * the starting index of keyfiles string array to use. This
* allows us to use the straight command line argument array. * allows us to use the straight command line argument array.
* @param inDirectory * @param inDirectory
* the directory to look in (may be null). * the directory to look in (may be null).
* @return a list of keypair objects. * @return a list of keypair objects.
*/ */
private static List<DnsKeyPair> getKeys(String[] keyfiles, int start_index, private static List<DnsKeyPair> getKeys(String[] keyfiles, int startIndex,
File inDirectory) throws IOException File inDirectory) throws IOException {
{ if (keyfiles == null)
if (keyfiles == null) return null; return Collections.emptyList();
int len = keyfiles.length - start_index; int len = keyfiles.length - startIndex;
if (len <= 0) return null; if (len <= 0)
return Collections.emptyList();
ArrayList<DnsKeyPair> keys = new ArrayList<DnsKeyPair>(len); ArrayList<DnsKeyPair> keys = new ArrayList<>(len);
for (int i = start_index; i < keyfiles.length; i++) for (int i = startIndex; i < keyfiles.length; i++) {
{
DnsKeyPair k = BINDKeyUtils.loadKeyPair(keyfiles[i], inDirectory); DnsKeyPair k = BINDKeyUtils.loadKeyPair(keyfiles[i], inDirectory);
if (k != null) keys.add(k); if (k != null)
keys.add(k);
} }
return keys; return keys;
} }
@SuppressWarnings("unchecked") public void execute() throws Exception {
public void execute() throws Exception
{
// Read in the zone // Read in the zone
List<Record> records = ZoneUtils.readZoneFile(state.inputfile, null); List<Record> records = ZoneUtils.readZoneFile(state.inputfile, null);
if (records == null || records.size() == 0) if (records == null || records.isEmpty()) {
{
System.err.println("error: empty RRset file"); System.err.println("error: empty RRset file");
state.usage(); state.usage();
} }
@ -245,44 +219,37 @@ public class SignRRset extends CLBase
// consist of more than one RRset. // consist of more than one RRset.
RRset rrset = null; RRset rrset = null;
for (Record r : records) for (Record r : records) {
{
// skip RRSIGs // skip RRSIGs
if (r.getType() == Type.RRSIG || r.getType() == Type.SIG) if (r.getType() == Type.RRSIG || r.getType() == Type.SIG) {
{
continue; continue;
} }
// Handle the first record. // Handle the first record.
if (rrset == null) if (rrset == null) {
{
rrset = new RRset(); rrset = new RRset();
rrset.addRR(r); rrset.addRR(r);
continue; continue;
} }
// Ensure that the remaining records all belong to the same rrset. // Ensure that the remaining records all belong to the same rrset.
if (rrset.getName().equals(r.getName()) && rrset.getType() == r.getType() if (rrset.getName().equals(r.getName()) && rrset.getType() == r.getType()
&& rrset.getDClass() == r.getDClass()) && rrset.getDClass() == r.getDClass()) {
{
rrset.addRR(r); rrset.addRR(r);
} } else {
else
{
System.err.println("Records do not all belong to the same RRset."); System.err.println("Records do not all belong to the same RRset.");
state.usage(); state.usage();
} }
} }
if (rrset.size() == 0) if (rrset == null || rrset.size() == 0) {
{
System.err.println("No records found in inputfile."); System.err.println("No records found in inputfile.");
state.usage(); state.usage();
return;
} }
// Load the key pairs. // Load the key pairs.
if (state.keyFiles.length == 0) if (state.keyFiles.length == 0) {
{
System.err.println("error: at least one keyfile must be specified"); System.err.println("error: at least one keyfile must be specified");
state.usage(); state.usage();
} }
@ -293,69 +260,55 @@ public class SignRRset extends CLBase
// This will be used as the zone name, too. // This will be used as the zone name, too.
Name keysetName = null; Name keysetName = null;
for (DnsKeyPair pair : keypairs) for (DnsKeyPair pair : keypairs) {
{ if (keysetName == null) {
if (keysetName == null)
{
keysetName = pair.getDNSKEYName(); keysetName = pair.getDNSKEYName();
continue; continue;
} }
if (!pair.getDNSKEYName().equals(keysetName)) if (!pair.getDNSKEYName().equals(keysetName)) {
{
System.err.println("Keys do not all have the same name."); System.err.println("Keys do not all have the same name.");
state.usage(); state.usage();
} }
} }
// default the output file, if not set. // default the output file, if not set.
if (state.outputfile == null && !state.inputfile.equals("-")) if (state.outputfile == null && !state.inputfile.equals("-")) {
{
state.outputfile = state.inputfile + ".signed"; state.outputfile = state.inputfile + ".signed";
} }
JCEDnsSecSigner signer = new JCEDnsSecSigner(state.verboseSigning); JCEDnsSecSigner signer = new JCEDnsSecSigner(state.verboseSigning);
List<RRSIGRecord> sigs = signer.signRRset(rrset, keypairs, state.start, state.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);
} }
// write out the signed RRset // write out the signed RRset
List<Record> signed_records = new ArrayList<Record>(); List<Record> signedRecords = new ArrayList<>();
for (Iterator<Record> i = rrset.rrs(); i.hasNext();) for (Record r : rrset.rrs()) {
{ signedRecords.add(r);
signed_records.add(i.next());
} }
for (Iterator<Record> i = rrset.sigs(); i.hasNext();) for (RRSIGRecord sigrec : rrset.sigs()) {
{ signedRecords.add(sigrec);
signed_records.add(i.next());
} }
// write out the signed zone // write out the signed zone
ZoneUtils.writeZoneFile(signed_records, state.outputfile); ZoneUtils.writeZoneFile(signedRecords, state.outputfile);
if (state.verifySigs) if (state.verifySigs) {
{
log.fine("verifying generated signatures"); log.fine("verifying generated signatures");
boolean res = verifySigs(keysetName, signed_records, keypairs); boolean res = verifySigs(signedRecords, keypairs);
if (res) if (res) {
{
System.out.println("Generated signatures verified"); System.out.println("Generated signatures verified");
// log.info("Generated signatures verified"); } else {
}
else
{
System.out.println("Generated signatures did not verify."); System.out.println("Generated signatures did not verify.");
// log.warn("Generated signatures did not verify.");
} }
} }
} }
public static void main(String[] args) public static void main(String[] args) {
{
SignRRset tool = new SignRRset(); SignRRset tool = new SignRRset();
tool.state = new CLIState(); tool.state = new CLIState();

View File

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

View File

@ -1,4 +1,4 @@
// Copyright (C) 2011 VeriSign, Inc. // Copyright (C) 2011, 2022 VeriSign, Inc.
// //
// This library is free software; you can redistribute it and/or // This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public // modify it under the terms of the GNU Lesser General Public
@ -20,8 +20,8 @@ package com.verisignlabs.dnssec.cl;
import java.util.List; import java.util.List;
import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options; import org.apache.commons.cli.Options;
import org.apache.commons.cli.Option;
import org.xbill.DNS.Record; import org.xbill.DNS.Record;
import com.verisignlabs.dnssec.security.ZoneUtils; import com.verisignlabs.dnssec.security.ZoneUtils;
@ -32,8 +32,7 @@ import com.verisignlabs.dnssec.security.ZoneVerifier;
* *
* @author David Blacka * @author David Blacka
*/ */
public class VerifyZone extends CLBase public class VerifyZone extends CLBase {
{
private CLIState state; private CLIState state;
@ -41,8 +40,7 @@ public class VerifyZone extends CLBase
* This is a small inner class used to hold all of the command line option * This is a small inner class used to hold all of the command line option
* state. * state.
*/ */
protected static class CLIState extends CLIStateBase protected static class CLIState extends CLIStateBase {
{
public String zonefile = null; public String zonefile = null;
public String[] keyfiles = null; public String[] keyfiles = null;
public int startfudge = 0; public int startfudge = 0;
@ -50,88 +48,64 @@ public class VerifyZone extends CLBase
public boolean ignoreTime = false; public boolean ignoreTime = false;
public boolean ignoreDups = false; public boolean ignoreDups = false;
public CLIState() public CLIState() {
{
super("jdnssec-verifyzone [..options..] zonefile"); super("jdnssec-verifyzone [..options..] zonefile");
} }
protected void setupOptions(Options opts) @Override
{ protected void setupOptions(Options opts) {
OptionBuilder.hasOptionalArg(); opts.addOption(Option.builder("S").optionalArg(true).argName("seconds").longOpt("sig-start-fudge")
OptionBuilder.withLongOpt("sig-start-fudge"); .desc("'fudge' RRSIG inception ties by 'seconds'").build());
OptionBuilder.withArgName("seconds"); opts.addOption(Option.builder("E").optionalArg(true).argName("seconds").longOpt("sig-expire-fudge")
OptionBuilder.withDescription("'fudge' RRSIG inception times by 'seconds' seconds."); .desc("'fudge' RRSIG expiration times by 'seconds'").build());
opts.addOption(OptionBuilder.create('S')); opts.addOption(
Option.builder().longOpt("ignore-time").desc("Ignore RRSIG inception and expiration time errors.").build());
OptionBuilder.hasOptionalArg(); opts.addOption(Option.builder().longOpt("ignore-duplicate-rrs").desc("Ignore duplicate record errors.").build());
OptionBuilder.withLongOpt("sig-expire-fudge");
OptionBuilder.withArgName("seconds");
OptionBuilder.withDescription("'fudge' RRSIG expiration times by 'seconds' seconds.");
opts.addOption(OptionBuilder.create('E'));
OptionBuilder.withLongOpt("ignore-time");
OptionBuilder.withDescription("Ignore RRSIG inception and expiration time errors.");
opts.addOption(OptionBuilder.create());
OptionBuilder.withLongOpt("ignore-duplicate-rrs");
OptionBuilder.withDescription("Ignore duplicate record errors.");
opts.addOption(OptionBuilder.create());
} }
protected void processOptions(CommandLine cli) @Override
{ protected void processOptions(CommandLine cli) {
if (cli.hasOption("ignore-time")) if (cli.hasOption("ignore-time")) {
{
ignoreTime = true; ignoreTime = true;
} }
if (cli.hasOption("ignore-duplicate-rrs")) if (cli.hasOption("ignore-duplicate-rrs")) {
{
ignoreDups = true; ignoreDups = true;
} }
String optstr = null; String optstr = null;
if ((optstr = cli.getOptionValue('S')) != null) if ((optstr = cli.getOptionValue('S')) != null) {
{
startfudge = parseInt(optstr, 0); startfudge = parseInt(optstr, 0);
} }
if ((optstr = cli.getOptionValue('E')) != null) if ((optstr = cli.getOptionValue('E')) != null) {
{
expirefudge = parseInt(optstr, 0); expirefudge = parseInt(optstr, 0);
} }
String[] optstrs = null; String[] optstrs = null;
if ((optstrs = cli.getOptionValues('A')) != null) if ((optstrs = cli.getOptionValues('A')) != null) {
{ for (int i = 0; i < optstrs.length; i++) {
for (int i = 0; i < optstrs.length; i++)
{
addArgAlias(optstrs[i]); addArgAlias(optstrs[i]);
} }
} }
String[] cl_args = cli.getArgs(); String[] args = cli.getArgs();
if (cl_args.length < 1) if (args.length < 1) {
{
System.err.println("error: missing zone file"); System.err.println("error: missing zone file");
usage(); usage();
} }
zonefile = cl_args[0]; zonefile = args[0];
if (cl_args.length >= 2) if (args.length >= 2) {
{ keyfiles = new String[args.length - 1];
keyfiles = new String[cl_args.length - 1]; System.arraycopy(args, 1, keyfiles, 0, keyfiles.length);
System.arraycopy(cl_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(state.startfudge); zoneverifier.getVerifier().setStartFudge(state.startfudge);
zoneverifier.getVerifier().setExpireFudge(state.expirefudge); zoneverifier.getVerifier().setExpireFudge(state.expirefudge);
@ -144,20 +118,16 @@ public class VerifyZone extends CLBase
int errors = zoneverifier.verifyZone(records); int errors = zoneverifier.verifyZone(records);
log.fine("completed verification process."); log.fine("completed verification process.");
if (errors > 0) if (errors > 0) {
{
System.out.println("zone did not verify."); System.out.println("zone did not verify.");
System.exit(1); System.exit(1);
} } else {
else
{
System.out.println("zone verified."); System.out.println("zone verified.");
System.exit(0); System.exit(0);
} }
} }
public static void main(String[] args) public static void main(String[] args) {
{
VerifyZone tool = new VerifyZone(); VerifyZone tool = new VerifyZone();
tool.state = new CLIState(); tool.state = new CLIState();

View File

@ -1,4 +1,4 @@
// Copyright (C) 2011 VeriSign, Inc. // Copyright (C) 2011, 2022 VeriSign, Inc.
// //
// This library is free software; you can redistribute it and/or // This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public // modify it under the terms of the GNU Lesser General Public
@ -23,7 +23,6 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.ListIterator;
import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Options; import org.apache.commons.cli.Options;
@ -43,168 +42,150 @@ import com.verisignlabs.dnssec.security.RecordComparator;
* This class forms the command line implementation of a zone file normalizer. * This class forms the command line implementation of a zone file normalizer.
* That is, a tool to rewrite zones in a consistent, comparable format. * That is, a tool to rewrite zones in a consistent, comparable format.
* *
* @author David Blacka (original) * @author David Blacka
* @author $Author: davidb $
* @version $Revision: 2218 $
*/ */
public class ZoneFormat extends CLBase public class ZoneFormat extends CLBase {
{
private CLIState state; private CLIState state;
/** /**
* This is a small inner class used to hold all of the command line option * This is a small inner class used to hold all of the command line option
* state. * state.
*/ */
protected static class CLIState extends CLIStateBase protected static class CLIState extends CLIStateBase {
{
public String file; public String file;
public boolean assignNSEC3; public boolean assignNSEC3;
public CLIState() public CLIState() {
{
super("jdnssec-zoneformat [..options..] zonefile"); super("jdnssec-zoneformat [..options..] zonefile");
} }
protected void setupOptions(Options opts) @Override
{ protected void setupOptions(Options opts) {
opts.addOption("N", "nsec3", false, opts.addOption("N", "nsec3", false,
"attempt to determine the original ownernames for NSEC3 RRs."); "attempt to determine the original ownernames for NSEC3 RRs.");
} }
protected void processOptions(CommandLine cli) throws ParseException @Override
{ protected void processOptions(CommandLine cli) throws ParseException {
if (cli.hasOption('N')) assignNSEC3 = true; if (cli.hasOption('N'))
assignNSEC3 = true;
String[] cl_args = cli.getArgs(); String[] args = cli.getArgs();
if (cl_args.length < 1) if (args.length < 1) {
{
System.err.println("error: must specify a zone file"); System.err.println("error: must specify a zone file");
usage(); usage();
} }
file = cl_args[0]; file = args[0];
} }
} }
private static List<Record> readZoneFile(String filename) throws IOException private static List<Record> readZoneFile(String filename) throws IOException {
{ try (Master master = new Master(filename)) {
Master master = new Master(filename); List<Record> res = new ArrayList<>();
List<Record> res = new ArrayList<Record>();
Record r = null; Record r = null;
while ((r = master.nextRecord()) != null) while ((r = master.nextRecord()) != null) {
{
// Normalize each record by round-tripping it through canonical wire line // Normalize each record by round-tripping it through canonical wire line
// format. Mostly this just lowercases names that are subject to it. // format. Mostly this just lowercases names that are subject to it.
byte[] wire = r.toWireCanonical(); byte[] wire = r.toWireCanonical();
Record canon_record = Record.fromWire(wire, Section.ANSWER); Record canonRec = Record.fromWire(wire, Section.ANSWER);
res.add(canon_record); res.add(canonRec);
} }
return res; return res;
} }
}
private static void formatZone(List<Record> zone) private static void formatZone(List<Record> zone) {
{
// Put the zone into a consistent (name and RR type) order.
RecordComparator cmp = new RecordComparator();
Collections.sort(zone, cmp);
for (Record r : zone) for (Record r : zone) {
{
System.out.println(r.toString()); System.out.println(r.toString());
} }
} }
private static void determineNSEC3Owners(List<Record> zone) private static void determineNSEC3Owners(List<Record> zone)
throws NoSuchAlgorithmException throws NoSuchAlgorithmException {
{
// Put the zone into a consistent (name and RR type) order.
Collections.sort(zone, new RecordComparator());
// first, find the NSEC3PARAM record -- this is an inefficient linear // first, find the NSEC3PARAM record -- this is an inefficient linear
// search, although it should be near the head of the list. // search, although it should be near the head of the list.
NSEC3PARAMRecord nsec3param = null; NSEC3PARAMRecord nsec3param = null;
HashMap<String, String> map = new HashMap<String, String>(); HashMap<String, String> map = new HashMap<>();
base32 b32 = new base32(base32.Alphabet.BASE32HEX, false, true); base32 b32 = new base32(base32.Alphabet.BASE32HEX, false, true);
Name zonename = null; Name zonename = null;
for (Record r : zone) for (Record r : zone) {
{ if (r.getType() == Type.SOA) {
if (r.getType() == Type.SOA)
{
zonename = r.getName(); zonename = r.getName();
continue; continue;
} }
if (r.getType() == Type.NSEC3PARAM) if (r.getType() == Type.NSEC3PARAM) {
{
nsec3param = (NSEC3PARAMRecord) r; nsec3param = (NSEC3PARAMRecord) r;
break; break;
} }
} }
// If we couldn't determine a zone name, we have an issue. // If we couldn't determine a zone name, we have an issue.
if (zonename == null) return; if (zonename == null || nsec3param == null) {
// If there wasn't one, we have nothing to do. formatZone(zone);
if (nsec3param == null) return; return;
}
// Next pass, calculate a mapping between ownernames and hashnames // Next pass, calculate a mapping between ownernames and hashnames
Name last_name = null; Name lastName = null;
for (Record r : zone) for (Record r : zone) {
{ if (r.getName().equals(lastName))
if (r.getName().equals(last_name)) continue; continue;
if (r.getType() == Type.NSEC3) continue; if (r.getType() == Type.NSEC3)
continue;
Name n = r.getName(); Name n = r.getName();
byte[] hash = nsec3param.hashName(n); byte[] hash = nsec3param.hashName(n);
String hashname = b32.toString(hash); String hashname = b32.toString(hash);
map.put(hashname, n.toString().toLowerCase()); map.put(hashname, n.toString().toLowerCase());
last_name = n; lastName = n;
// inefficiently create hashes for the possible ancestor ENTs // inefficiently create hashes for the possible ancestor ENTs
for (int i = zonename.labels() + 1; i < n.labels(); ++i) for (int i = zonename.labels() + 1; i < n.labels(); ++i) {
{
Name parent = new Name(n, n.labels() - i); Name parent = new Name(n, n.labels() - i);
byte[] parent_hash = nsec3param.hashName(parent); byte[] parentHash = nsec3param.hashName(parent);
String parent_hashname = b32.toString(parent_hash); String parentHashName = b32.toString(parentHash);
if (!map.containsKey(parent_hashname)) if (!map.containsKey(parentHashName)) {
{ map.put(parentHashName, parent.toString().toLowerCase());
map.put(parent_hashname, parent.toString().toLowerCase());
} }
} }
} }
// Final pass, assign the names if we can // Final pass, output the zone with added comments for the NSEC3 records
for (ListIterator<Record> i = zone.listIterator(); i.hasNext();) for (Record r : zone) {
{ if (r.getType() != Type.NSEC3) {
Record r = i.next(); System.out.println(r.toString());
if (r.getType() != Type.NSEC3) continue; continue;
}
NSEC3Record nsec3 = (NSEC3Record) r; NSEC3Record nsec3 = (NSEC3Record) r;
String hashname = nsec3.getName().getLabelString(0).toLowerCase(); String hashname = nsec3.getName().getLabelString(0).toLowerCase();
String ownername = (String) map.get(hashname); String ownername = map.get(hashname);
System.out.println(r.toString() + " ; " + ownername);
NSEC3Record new_nsec3 = new NSEC3Record(nsec3.getName(), nsec3.getDClass(),
nsec3.getTTL(), nsec3.getHashAlgorithm(),
nsec3.getFlags(), nsec3.getIterations(),
nsec3.getSalt(), nsec3.getNext(),
nsec3.getTypes(), ownername);
i.set(new_nsec3);
} }
} }
public void execute() throws IOException, NoSuchAlgorithmException public void execute() throws IOException, NoSuchAlgorithmException {
{
List<Record> z = readZoneFile(state.file); List<Record> z = readZoneFile(state.file);
if (state.assignNSEC3) determineNSEC3Owners(z); // Put the zone into a consistent (name and RR type) order.
Collections.sort(z, new RecordComparator());
if (state.assignNSEC3) {
determineNSEC3Owners(z);
} else {
formatZone(z); formatZone(z);
} }
}
public static void main(String[] args) public static void main(String[] args) {
{
ZoneFormat tool = new ZoneFormat(); ZoneFormat tool = new ZoneFormat();
tool.state = new CLIState(); tool.state = new CLIState();

View File

@ -1,6 +1,4 @@
// $Id$ // Copyright (C) 2001-2003, 2022 VeriSign, Inc.
//
// Copyright (C) 2001-2003 VeriSign, Inc.
// //
// This library is free software; you can redistribute it and/or // This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public // modify it under the terms of the GNU Lesser General Public
@ -28,7 +26,6 @@ import java.io.PrintWriter;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.PublicKey; import java.security.PublicKey;
import java.text.NumberFormat;
import org.xbill.DNS.DNSKEYRecord; import org.xbill.DNS.DNSKEYRecord;
import org.xbill.DNS.Master; import org.xbill.DNS.Master;
@ -45,58 +42,31 @@ import org.xbill.DNS.utils.base64;
* In this class, the "base" key path or name is the file name without the * In this class, the "base" key path or name is the file name without the
* trailing ".key" or ".private". * trailing ".key" or ".private".
* *
* @author David Blacka (original) * @author David Blacka
* @author $Author$
* @version $Revision$
*/ */
public class BINDKeyUtils public class BINDKeyUtils {
{
// formatters used to generated the BIND key file names private BINDKeyUtils() { }
private static NumberFormat mAlgNumberFormatter;
private static NumberFormat mKeyIdNumberFormatter;
/** /**
* Calculate the BIND9 key file base name (i.e., without the ".key" or * Calculate the BIND9 key file base name (i.e., without the ".key" or
* ".private" extensions) * ".private" extensions)
*/ */
private static String getKeyFileBase(Name signer, int algorithm, int keyid) private static String getKeyFileBase(Name signer, int algorithm, int keyid) {
{
if (mAlgNumberFormatter == null)
{
mAlgNumberFormatter = NumberFormat.getNumberInstance();
mAlgNumberFormatter.setMaximumIntegerDigits(3);
mAlgNumberFormatter.setMinimumIntegerDigits(3);
}
if (mKeyIdNumberFormatter == null)
{
mKeyIdNumberFormatter = NumberFormat.getNumberInstance();
mKeyIdNumberFormatter.setMaximumIntegerDigits(5);
mKeyIdNumberFormatter.setMinimumIntegerDigits(5);
mKeyIdNumberFormatter.setGroupingUsed(false);
}
keyid &= 0xFFFF; keyid &= 0xFFFF;
return String.format("K%1$s+%2$03d+%3$05d", signer, algorithm, keyid);
String fn = "K" + signer + "+" + mAlgNumberFormatter.format(algorithm)
+ "+" + mKeyIdNumberFormatter.format(keyid);
return fn;
} }
/** Reads in the DNSKEYRecord from the public key file */ /** Reads in the DNSKEYRecord from the public key file */
private static DNSKEYRecord loadPublicKeyFile(File publicKeyFile) private static DNSKEYRecord loadPublicKeyFile(File publicKeyFile)
throws IOException throws IOException {
{
Master m = new Master(publicKeyFile.getAbsolutePath(), null, 600); Master m = new Master(publicKeyFile.getAbsolutePath(), null, 600);
Record r; Record r;
DNSKEYRecord result = null; DNSKEYRecord result = null;
while ((r = m.nextRecord()) != null) while ((r = m.nextRecord()) != null) {
{ if (r.getType() == Type.DNSKEY) {
if (r.getType() == Type.DNSKEY)
{
result = (DNSKEYRecord) r; result = (DNSKEYRecord) r;
} }
} }
@ -106,31 +76,26 @@ public class BINDKeyUtils
/** Reads in the private key verbatim from the private key file */ /** Reads in the private key verbatim from the private key file */
private static String loadPrivateKeyFile(File privateKeyFile) private static String loadPrivateKeyFile(File privateKeyFile)
throws IOException throws IOException {
{ try (BufferedReader in = new BufferedReader(new FileReader(privateKeyFile))) {
BufferedReader in = new BufferedReader(new FileReader(privateKeyFile)); StringBuilder keybuf = new StringBuilder();
StringBuffer key_buf = new StringBuffer();
String line; String line;
while ((line = in.readLine()) != null) while ((line = in.readLine()) != null) {
{ keybuf.append(line);
key_buf.append(line); keybuf.append('\n');
key_buf.append('\n'); }
return keybuf.toString().trim();
} }
in.close();
return key_buf.toString().trim();
} }
/** /**
* Given an actual path for one of the key files, return the base name * Given an actual path for one of the key files, return the base name
*/ */
private static String fixKeyFileBasePath(String basePath) private static String fixKeyFileBasePath(String basePath) {
{ if (basePath == null)
if (basePath == null) throw new IllegalArgumentException(); throw new IllegalArgumentException();
if (basePath.endsWith(".key") || basePath.endsWith(".private")) if (basePath.endsWith(".key") || basePath.endsWith(".private")) {
{
basePath = basePath.substring(0, basePath.lastIndexOf(".")); basePath = basePath.substring(0, basePath.lastIndexOf("."));
} }
@ -154,8 +119,7 @@ public class BINDKeyUtils
* if there was a problem reading the BIND9 files. * if there was a problem reading the BIND9 files.
*/ */
public static DnsKeyPair loadKeyPair(Name signer, int algorithm, int keyid, public static DnsKeyPair loadKeyPair(Name signer, int algorithm, int keyid,
File inDirectory) throws IOException File inDirectory) throws IOException {
{
String keyFileBase = getKeyFileBase(signer, algorithm, keyid); String keyFileBase = getKeyFileBase(signer, algorithm, keyid);
return loadKeyPair(keyFileBase, inDirectory); return loadKeyPair(keyFileBase, inDirectory);
@ -165,17 +129,18 @@ public class BINDKeyUtils
* Given a base path to a BIND9 key pair, load the key pair. * Given a base path to a BIND9 key pair, load the key pair.
* *
* @param keyFileBasePath * @param keyFileBasePath
* the base filename (or real filename for either the public or * the base filename (or real filename for either the
* public or
* private key) of the key. * private key) of the key.
* @param inDirectory * @param inDirectory
* the directory to look in, if the keyFileBasePath is relative. * the directory to look in, if the keyFileBasePath is
* relative.
* @return the loaded key pair. * @return the loaded key pair.
* @throws IOException * @throws IOException
* if there was a problem reading the files * if there was a problem reading the files
*/ */
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 // FIXME: should we throw the IOException when one of the files
// cannot be found, or just when both cannot be found? // cannot be found, or just when both cannot be found?
@ -198,16 +163,17 @@ public class BINDKeyUtils
* key pair * key pair
* *
* @param keyFileBasePath * @param keyFileBasePath
* the base or real path to the public part of a key pair. * the base or real path to the public part of a key
* pair.
* @param inDirectory * @param inDirectory
* the directory to look in if the path is relative (may be null). * the directory to look in if the path is relative (may
* be null).
* @return a {@link DnsKeyPair} containing just the public key information. * @return a {@link DnsKeyPair} containing just the public key information.
* @throws IOException * @throws IOException
* if there was a problem reading the public key file. * if there was a problem reading the public key file.
*/ */
public static DnsKeyPair loadKey(String keyFileBasePath, File inDirectory) public static DnsKeyPair loadKey(String keyFileBasePath, File inDirectory)
throws IOException throws IOException {
{
keyFileBasePath = fixKeyFileBasePath(keyFileBasePath); keyFileBasePath = fixKeyFileBasePath(keyFileBasePath);
File publicKeyFile = new File(inDirectory, keyFileBasePath + ".key"); File publicKeyFile = new File(inDirectory, keyFileBasePath + ".key");
@ -227,28 +193,27 @@ public class BINDKeyUtils
* @param keysetFileName * @param keysetFileName
* the name of the keyset file. * the name of the keyset file.
* @param inDirectory * @param inDirectory
* the directory to look in if the path is relative (may be null, * the directory to look in if the path is relative (may
* be null,
* defaults to the current working directory). * defaults to the current working directory).
* @return a RRset contain the KEY records and any associated SIG records. * @return a RRset contain the KEY records and any associated SIG records.
* @throws IOException * @throws IOException
* if there was a problem reading the keyset file. * if there was a problem reading the keyset file.
*/ */
public static RRset loadKeySet(String keysetFileName, File inDirectory) public static RRset loadKeySet(String keysetFileName, File inDirectory)
throws IOException throws IOException {
{
File keysetFile = new File(inDirectory, keysetFileName); File keysetFile = new File(inDirectory, keysetFileName);
Master m = new Master(keysetFile.getAbsolutePath()); try (Master m = new Master(keysetFile.getAbsolutePath())) {
Record r; Record r;
RRset keyset = new RRset(); RRset keyset = new RRset();
while ((r = m.nextRecord()) != null) while ((r = m.nextRecord()) != null) {
{
keyset.addRR(r); keyset.addRR(r);
} }
return keyset; return keyset;
} }
}
/** /**
* Calculate the key file base for this key pair. * Calculate the key file base for this key pair.
@ -257,10 +222,10 @@ public class BINDKeyUtils
* the {@link DnsKeyPair} to work from. It only needs a public key. * the {@link DnsKeyPair} to work from. It only needs a public key.
* @return the base name of the key files. * @return the base name of the key files.
*/ */
public static String keyFileBase(DnsKeyPair pair) public static String keyFileBase(DnsKeyPair pair) {
{
DNSKEYRecord keyrec = pair.getDNSKEYRecord(); DNSKEYRecord keyrec = pair.getDNSKEYRecord();
if (keyrec == null) return null; if (keyrec == null)
return null;
return getKeyFileBase(keyrec.getName(), keyrec.getAlgorithm(), return getKeyFileBase(keyrec.getName(), keyrec.getAlgorithm(),
keyrec.getFootprint()); keyrec.getFootprint());
@ -270,10 +235,10 @@ public class BINDKeyUtils
* @return a {@link java.io.File} object representing the BIND9 public key * @return a {@link java.io.File} object representing the BIND9 public key
* file. * file.
*/ */
public static File getPublicKeyFile(DnsKeyPair pair, File inDirectory) public static File getPublicKeyFile(DnsKeyPair pair, File inDirectory) {
{
String keyfilebase = keyFileBase(pair); String keyfilebase = keyFileBase(pair);
if (keyfilebase == null) return null; if (keyfilebase == null)
return null;
return new File(inDirectory, keyfilebase + ".key"); return new File(inDirectory, keyfilebase + ".key");
} }
@ -282,10 +247,10 @@ public class BINDKeyUtils
* @return a {@link java.io.File} object representing the BIND9 private key * @return a {@link java.io.File} object representing the BIND9 private key
* file * file
*/ */
public static File getPrivateKeyFile(DnsKeyPair pair, File inDirectory) public static File getPrivateKeyFile(DnsKeyPair pair, File inDirectory) {
{
String keyfilebase = keyFileBase(pair); String keyfilebase = keyFileBase(pair);
if (keyfilebase == null) return null; if (keyfilebase == null)
return null;
return new File(inDirectory, keyfilebase + ".private"); return new File(inDirectory, keyfilebase + ".private");
} }
@ -298,23 +263,16 @@ public class BINDKeyUtils
* the contents of a BIND9 key file in string form. * the contents of a BIND9 key file in string form.
* @return a {@link java.security.PrivateKey} * @return a {@link java.security.PrivateKey}
*/ */
public static PrivateKey convertPrivateKeyString(String privateKeyString) public static PrivateKey convertPrivateKeyString(String privateKeyString) {
{ if (privateKeyString == null)
if (privateKeyString == null) return null; return null;
// FIXME: should this swallow exceptions or actually propagate // FIXME: should this swallow exceptions or actually propagate
// them? // them?
try try {
{
DnsKeyConverter conv = new DnsKeyConverter(); DnsKeyConverter conv = new DnsKeyConverter();
return conv.parsePrivateKeyString(privateKeyString); return conv.parsePrivateKeyString(privateKeyString);
} } catch (IOException|NoSuchAlgorithmException e) {
catch (IOException e)
{
e.printStackTrace();
}
catch (NoSuchAlgorithmException e)
{
e.printStackTrace(); e.printStackTrace();
} }
@ -332,14 +290,10 @@ public class BINDKeyUtils
* require information from both. * require information from both.
* @return a string containing the contents of a BIND9 private key file. * @return a string containing the contents of a BIND9 private key file.
*/ */
public static String convertPrivateKey(PrivateKey priv, PublicKey pub, int alg) public static String convertPrivateKey(PrivateKey priv, PublicKey pub, int alg) {
{ if (priv != null) {
if (priv != null)
{
DnsKeyConverter keyconv = new DnsKeyConverter(); DnsKeyConverter keyconv = new DnsKeyConverter();
String priv_string = keyconv.generatePrivateKeyString(priv, pub, alg); return keyconv.generatePrivateKeyString(priv, pub, alg);
return priv_string;
} }
return null; return null;
@ -350,13 +304,14 @@ public class BINDKeyUtils
* routines need. Currently, the DNSJAVA package uses a multiline mode for its * routines need. Currently, the DNSJAVA package uses a multiline mode for its
* record formatting. The BIND9 tools require everything on a single line. * record formatting. The BIND9 tools require everything on a single line.
*/ */
private static String DNSKEYtoString(DNSKEYRecord rec) private static String DNSKEYtoString(DNSKEYRecord rec) {
{ StringBuilder buf = new StringBuilder();
StringBuffer buf = new StringBuffer();
buf.append(rec.getName()); buf.append(rec.getName());
if (rec.getTTL() > 0) {
buf.append(" "); buf.append(" ");
buf.append(rec.getTTL()); buf.append(rec.getTTL());
}
buf.append(" IN DNSKEY "); buf.append(" IN DNSKEY ");
buf.append(rec.getFlags() & 0xFFFF); buf.append(rec.getFlags() & 0xFFFF);
buf.append(" "); buf.append(" ");
@ -373,7 +328,8 @@ public class BINDKeyUtils
* This routine will write out the BIND9 dnssec-* tool compatible files. * This routine will write out the BIND9 dnssec-* tool compatible files.
* *
* @param baseFileName * @param baseFileName
* use this base file name. If null, the standard BIND9 base file * use this base file name. If null, the standard BIND9 base
* file
* name will be computed. * name will be computed.
* @param pair * @param pair
* the keypair in question. * the keypair in question.
@ -383,18 +339,17 @@ public class BINDKeyUtils
* if there is a problem writing the files. * if there is a problem writing the files.
*/ */
public static void writeKeyFiles(String baseFileName, DnsKeyPair pair, public static void writeKeyFiles(String baseFileName, DnsKeyPair pair,
File inDirectory) throws IOException File inDirectory) throws IOException {
{
DNSKEYRecord pub = pair.getDNSKEYRecord(); DNSKEYRecord pub = pair.getDNSKEYRecord();
String priv = pair.getPrivateKeyString(); String priv = pair.getPrivateKeyString();
if (priv == null) if (priv == null) {
{
priv = convertPrivateKey(pair.getPrivate(), pair.getPublic(), priv = convertPrivateKey(pair.getPrivate(), pair.getPublic(),
pair.getDNSKEYAlgorithm()); pair.getDNSKEYAlgorithm());
} }
if (pub == null || priv == null) return; if (pub == null || priv == null)
return;
// Write the public key file // Write the public key file
File pubkeyfile = new File(inDirectory, baseFileName + ".key"); File pubkeyfile = new File(inDirectory, baseFileName + ".key");
@ -420,8 +375,7 @@ public class BINDKeyUtils
* the directory to write to (may be null). * the directory to write to (may be null).
*/ */
public static void writeKeyFiles(DnsKeyPair pair, File inDirectory) public static void writeKeyFiles(DnsKeyPair pair, File inDirectory)
throws IOException throws IOException {
{
String base = keyFileBase(pair); String base = keyFileBase(pair);
writeKeyFiles(base, pair, inDirectory); writeKeyFiles(base, pair, inDirectory);
} }

View File

@ -1,6 +1,4 @@
// $Id$ // Copyright (C) 2001-2003, 2022 VeriSign, Inc.
//
// Copyright (C) 2001-2003 VeriSign, Inc.
// //
// This library is free software; you can redistribute it and/or // This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public // modify it under the terms of the GNU Lesser General Public
@ -26,34 +24,25 @@ import java.util.logging.Logger;
* useful for comparing RDATA portions of DNS records in doing DNSSEC canonical * useful for comparing RDATA portions of DNS records in doing DNSSEC canonical
* ordering. * ordering.
* *
* @author David Blacka (original) * @author David Blacka
* @author $Author$
* @version $Revision$
*/ */
public class ByteArrayComparator implements Comparator<byte[]> public class ByteArrayComparator implements Comparator<byte[]> {
{
private int mOffset = 0; private int mOffset = 0;
private boolean mDebug = false; private boolean mDebug = false;
private Logger log; private Logger log;
public ByteArrayComparator() public ByteArrayComparator() {
{
} }
public ByteArrayComparator(int offset, boolean debug) public ByteArrayComparator(int offset, boolean debug) {
{
mOffset = offset; mOffset = offset;
mDebug = debug; mDebug = debug;
} }
public int compare(byte[] b1, byte[] b2) public int compare(byte[] b1, byte[] b2) {
{ for (int i = mOffset; i < b1.length && i < b2.length; i++) {
for (int i = mOffset; i < b1.length && i < b2.length; i++) if (b1[i] != b2[i]) {
{ if (mDebug) {
if (b1[i] != b2[i])
{
if (mDebug)
{
log.info("offset " + i + " differs (this is " log.info("offset " + i + " differs (this is "
+ (i - mOffset) + " bytes in from our offset.)"); + (i - mOffset) + " bytes in from our offset.)");
} }

View File

@ -1,7 +1,5 @@
/* /*
* $Id$ * Copyright (c) 2006, 2022 Verisign. All rights reserved.
*
* Copyright (c) 2006 VeriSign. All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met: * modification, are permitted provided that the following conditions are met:
@ -30,8 +28,22 @@
package com.verisignlabs.dnssec.security; package com.verisignlabs.dnssec.security;
import java.math.BigInteger; import java.math.BigInteger;
import java.security.*; import java.security.AlgorithmParameters;
import java.security.spec.*; import java.security.InvalidAlgorithmParameterException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.Security;
import java.security.Signature;
import java.security.spec.ECFieldFp;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.EllipticCurve;
import java.security.spec.InvalidParameterSpecException;
import java.security.spec.RSAKeyGenParameterSpec;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Set; import java.util.Set;
@ -39,10 +51,11 @@ import java.util.logging.Logger;
import org.xbill.DNS.DNSSEC; import org.xbill.DNS.DNSSEC;
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
// for now, we need to import the EdDSA parameter spec classes // for now, we need to import the EdDSA parameter spec classes
// because they have no generic form in java.security.spec.* // because they have no generic form in java.security.spec.*
// sadly, this will currently fail if you don't have the lib. // sadly, this will currently fail if you don't have the lib.
import net.i2p.crypto.eddsa.spec.*; import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec;
/** /**
* This class handles translating DNS signing algorithm identifiers into various * This class handles translating DNS signing algorithm identifiers into various
@ -53,12 +66,9 @@ import net.i2p.crypto.eddsa.spec.*;
* aliasing -- that is, defining a new algorithm identifier to be equivalent to * aliasing -- that is, defining a new algorithm identifier to be equivalent to
* an existing identifier. * an existing identifier.
* *
* @author David Blacka (orig) * @author David Blacka
* @author $Author: davidb $ (latest)
* @version $Revision: 2098 $
*/ */
public class DnsKeyAlgorithm 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,
@ -71,39 +81,33 @@ public class DnsKeyAlgorithm
public static final int ECDSA = 5; public static final int ECDSA = 5;
public static final int EDDSA = 6; public static final int EDDSA = 6;
private static class AlgEntry private static class AlgEntry {
{
public int dnssecAlgorithm; public int dnssecAlgorithm;
public String sigName; public String sigName;
public int baseType; public int baseType;
public AlgEntry(int algorithm, String sigName, int baseType) public AlgEntry(int algorithm, String sigName, int baseType) {
{
this.dnssecAlgorithm = algorithm; this.dnssecAlgorithm = algorithm;
this.sigName = sigName; this.sigName = sigName;
this.baseType = baseType; this.baseType = baseType;
} }
} }
private static class ECAlgEntry extends AlgEntry private static class ECAlgEntry extends AlgEntry {
{ public ECParameterSpec ecSpec;
public ECParameterSpec ec_spec;
public ECAlgEntry(int algorithm, String sigName, int baseType, ECParameterSpec spec) public ECAlgEntry(int algorithm, String sigName, int baseType, ECParameterSpec spec) {
{
super(algorithm, sigName, baseType); super(algorithm, sigName, baseType);
this.ec_spec = spec; this.ecSpec = spec;
} }
} }
private static class EdAlgEntry extends AlgEntry private static class EdAlgEntry extends AlgEntry {
{ public EdDSAParameterSpec edSpec;
public EdDSAParameterSpec ed_spec;
public EdAlgEntry(int algorithm, String sigName, int baseType, EdDSAParameterSpec spec) public EdAlgEntry(int algorithm, String sigName, int baseType, EdDSAParameterSpec spec) {
{
super(algorithm, sigName, baseType); super(algorithm, sigName, baseType);
this.ed_spec = spec; this.edSpec = spec;
} }
} }
@ -138,38 +142,35 @@ public class DnsKeyAlgorithm
/** This is the global instance for this class. */ /** This is the global instance for this class. */
private static DnsKeyAlgorithm mInstance = null; private static DnsKeyAlgorithm mInstance = null;
public DnsKeyAlgorithm() public DnsKeyAlgorithm() {
{
// Attempt to add the bouncycastle provider. // Attempt to add the bouncycastle provider.
// This is so we can use this provider if it is available, but not require // 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. // the user to add it as one of the java.security providers.
try try {
{
Class<?> bc_provider_class = Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider"); Class<?> bc_provider_class = Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider");
Provider bc_provider = (Provider) bc_provider_class.newInstance(); // Provider bc_provider = (Provider) bc_provider_class.newInstance();
Provider bc_provider = (Provider) bc_provider_class.getDeclaredConstructor().newInstance();
Security.addProvider(bc_provider); Security.addProvider(bc_provider);
} catch (ReflectiveOperationException e) {
} }
catch (ReflectiveOperationException e) { }
// Attempt to add the EdDSA-Java provider. // Attempt to add the EdDSA-Java provider.
try try {
{
Class<?> eddsa_provider_class = Class.forName("net.i2p.crypto.eddsa.EdDSASecurityProvider"); Class<?> eddsa_provider_class = Class.forName("net.i2p.crypto.eddsa.EdDSASecurityProvider");
Provider eddsa_provider = (Provider) eddsa_provider_class.newInstance(); // Provider eddsa_provider = (Provider) eddsa_provider_class.newInstance();
Provider eddsa_provider = (Provider) eddsa_provider_class.getDeclaredConstructor().newInstance();
Security.addProvider(eddsa_provider); Security.addProvider(eddsa_provider);
} } catch (ReflectiveOperationException e) {
catch (ReflectiveOperationException e) {
log.warning("Unable to load EdDSA provider"); log.warning("Unable to load EdDSA provider");
} }
initialize(); initialize();
} }
private void initialize() private void initialize() {
{ mAlgorithmMap = new HashMap<>();
mAlgorithmMap = new HashMap<Integer, AlgEntry>(); mMnemonicToIdMap = new HashMap<>();
mMnemonicToIdMap = new HashMap<String, Integer>(); mIdToMnemonicMap = new HashMap<>();
mIdToMnemonicMap = new HashMap<Integer, String>();
// Load the standard DNSSEC algorithms. // Load the standard DNSSEC algorithms.
addAlgorithm(DNSSEC.Algorithm.RSAMD5, "MD5withRSA", RSA); addAlgorithm(DNSSEC.Algorithm.RSAMD5, "MD5withRSA", RSA);
@ -223,18 +224,17 @@ public class DnsKeyAlgorithm
addMnemonic("ED25519", 15); addMnemonic("ED25519", 15);
} }
private void addAlgorithm(int algorithm, String sigName, int baseType) private void addAlgorithm(int algorithm, String sigName, int baseType) {
{
mAlgorithmMap.put(algorithm, new AlgEntry(algorithm, sigName, baseType)); mAlgorithmMap.put(algorithm, new AlgEntry(algorithm, sigName, baseType));
} }
private void addAlgorithm(int algorithm, String sigName, int baseType, String curveName) private void addAlgorithm(int algorithm, String sigName, int baseType, String curveName) {
{ if (baseType == ECDSA) {
if (baseType == ECDSA) ECParameterSpec ecSpec = ECSpecFromAlgorithm(algorithm);
{ if (ecSpec == null)
ECParameterSpec ec_spec = ECSpecFromAlgorithm(algorithm); ecSpec = ECSpecFromName(curveName);
if (ec_spec == null) ec_spec = ECSpecFromName(curveName); if (ecSpec == null)
if (ec_spec == null) return; return;
// Check to see if we can get a Signature object for this algorithm. // Check to see if we can get a Signature object for this algorithm.
try { try {
@ -245,13 +245,12 @@ public class DnsKeyAlgorithm
// If not, do not add the algorithm. // If not, do not add the algorithm.
return; return;
} }
ECAlgEntry entry = new ECAlgEntry(algorithm, sigName, baseType, ec_spec); ECAlgEntry entry = new ECAlgEntry(algorithm, sigName, baseType, ecSpec);
mAlgorithmMap.put(algorithm, entry); mAlgorithmMap.put(algorithm, entry);
} } else if (baseType == EDDSA) {
else if (baseType == EDDSA) EdDSAParameterSpec edSpec = EdDSASpecFromName(curveName);
{ if (edSpec == null)
EdDSAParameterSpec ed_spec = EdDSASpecFromName(curveName); return;
if (ed_spec == null) return;
// Check to see if we can get a Signature object for this algorithm. // Check to see if we can get a Signature object for this algorithm.
try { try {
@ -262,35 +261,29 @@ public class DnsKeyAlgorithm
// If not, do not add the algorithm. // If not, do not add the algorithm.
return; return;
} }
EdAlgEntry entry = new EdAlgEntry(algorithm, sigName, baseType, ed_spec); EdAlgEntry entry = new EdAlgEntry(algorithm, sigName, baseType, edSpec);
mAlgorithmMap.put(algorithm, entry); mAlgorithmMap.put(algorithm, entry);
} }
} }
private void addMnemonic(String m, int alg) private void addMnemonic(String m, int alg) {
{ // Do not add mnemonics for algorithms that ended up not actually being
// Do not add mnemonics for algorithms that ended up not actually being supported. // supported.
if (!mAlgorithmMap.containsKey(alg)) return; if (!mAlgorithmMap.containsKey(alg))
return;
mMnemonicToIdMap.put(m.toUpperCase(), alg); mMnemonicToIdMap.put(m.toUpperCase(), alg);
if (!mIdToMnemonicMap.containsKey(alg)) mIdToMnemonicMap.computeIfAbsent(alg, k -> m);
{
mIdToMnemonicMap.put(alg, m);
}
} }
public void addAlias(int alias, String mnemonic, int original_algorithm) public void addAlias(int alias, String mnemonic, int original_algorithm) {
{ if (mAlgorithmMap.containsKey(alias)) {
if (mAlgorithmMap.containsKey(alias))
{
log.warning("Unable to alias algorithm " + alias + " because it already exists."); log.warning("Unable to alias algorithm " + alias + " because it already exists.");
return; return;
} }
if (!mAlgorithmMap.containsKey(original_algorithm)) if (!mAlgorithmMap.containsKey(original_algorithm)) {
{
log.warning("Unable to alias algorithm " + alias log.warning("Unable to alias algorithm " + alias
+ " to unknown algorithm identifier " + original_algorithm); + " to unknown algorithm identifier " + original_algorithm);
return; return;
@ -298,26 +291,21 @@ public class DnsKeyAlgorithm
mAlgorithmMap.put(alias, mAlgorithmMap.get(original_algorithm)); mAlgorithmMap.put(alias, mAlgorithmMap.get(original_algorithm));
if (mnemonic != null) if (mnemonic != null) {
{
addMnemonic(mnemonic, alias); addMnemonic(mnemonic, alias);
} }
} }
private AlgEntry getEntry(int alg) private AlgEntry getEntry(int alg) {
{
return mAlgorithmMap.get(alg); return mAlgorithmMap.get(alg);
} }
// For curves where we don't (or can't) get the parameters from a standard // 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 // name, we can construct the parameters here. For now, we only do this for
// the ECC-GOST curve. // the ECC-GOST curve.
private ECParameterSpec ECSpecFromAlgorithm(int algorithm) private ECParameterSpec ECSpecFromAlgorithm(int algorithm) {
{ switch (algorithm) {
switch (algorithm) case DNSSEC.Algorithm.ECC_GOST: {
{
case DNSSEC.Algorithm.ECC_GOST:
{
// From RFC 4357 Section 11.4 // From RFC 4357 Section 11.4
BigInteger p = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD97", 16); BigInteger p = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD97", 16);
BigInteger a = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD94", 16); BigInteger a = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD94", 16);
@ -335,35 +323,31 @@ public class DnsKeyAlgorithm
} }
// 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
{
AlgorithmParameters ap = AlgorithmParameters.getInstance("EC"); AlgorithmParameters ap = AlgorithmParameters.getInstance("EC");
ECGenParameterSpec ecg_spec = new ECGenParameterSpec(stdName); ECGenParameterSpec ecg_spec = new ECGenParameterSpec(stdName);
ap.init(ecg_spec); ap.init(ecg_spec);
return ap.getParameterSpec(ECParameterSpec.class); return ap.getParameterSpec(ECParameterSpec.class);
} } catch (NoSuchAlgorithmException e) {
catch (NoSuchAlgorithmException e) {
log.info("Elliptic Curve not supported by any crypto provider: " + e.getMessage()); log.info("Elliptic Curve not supported by any crypto provider: " + e.getMessage());
} } catch (InvalidParameterSpecException e) {
catch (InvalidParameterSpecException e) {
log.info("Elliptic Curve " + stdName + " not supported"); log.info("Elliptic Curve " + stdName + " not supported");
} }
return null; return null;
} }
// Fetch the curve parameters from a named EdDSA curve. // Fetch the curve parameters from a named EdDSA curve.
private EdDSAParameterSpec EdDSASpecFromName(String stdName) private EdDSAParameterSpec EdDSASpecFromName(String stdName) {
{ try {
try
{
EdDSAParameterSpec spec = EdDSANamedCurveTable.getByName(stdName); EdDSAParameterSpec spec = EdDSANamedCurveTable.getByName(stdName);
if (spec != null) return spec; if (spec != null)
return spec;
throw new InvalidParameterSpecException("Edwards Curve " + stdName + " not found."); throw new InvalidParameterSpecException("Edwards Curve " + stdName + " not found.");
} }
// catch (NoSuchAlgorithmException e) { // catch (NoSuchAlgorithmException e) {
// log.info("Edwards Curve not supported by any crypto provider: " + e.getMessage()); // log.info("Edwards Curve not supported by any crypto provider: " +
// e.getMessage());
// } // }
catch (InvalidParameterSpecException e) { catch (InvalidParameterSpecException e) {
log.info("Edwards Curve " + stdName + " not supported"); log.info("Edwards Curve " + stdName + " not supported");
@ -371,15 +355,13 @@ public class DnsKeyAlgorithm
return null; return null;
} }
public String[] supportedAlgMnemonics() public String[] supportedAlgMnemonics() {
{
Set<Integer> keyset = mAlgorithmMap.keySet(); Set<Integer> keyset = mAlgorithmMap.keySet();
Integer[] algs = keyset.toArray(new Integer[keyset.size()]); Integer[] algs = keyset.toArray(new Integer[keyset.size()]);
Arrays.sort(algs); Arrays.sort(algs);
String[] result = new String[algs.length]; String[] result = new String[algs.length];
for (int i = 0; i < algs.length; i++) for (int i = 0; i < algs.length; i++) {
{
result[i] = mIdToMnemonicMap.get(algs[i]); result[i] = mIdToMnemonicMap.get(algs[i]);
} }
@ -388,22 +370,20 @@ public class DnsKeyAlgorithm
/** /**
* Return a Signature object for the specified DNSSEC algorithm. * Return a Signature object for the specified DNSSEC algorithm.
*
* @param algorithm The DNSSEC algorithm (by number). * @param algorithm The DNSSEC algorithm (by number).
* @return a Signature object. * @return a Signature object.
*/ */
public Signature getSignature(int algorithm) public Signature getSignature(int algorithm) {
{
AlgEntry entry = getEntry(algorithm); AlgEntry entry = getEntry(algorithm);
if (entry == null) return null; if (entry == null)
return null;
Signature s = null; Signature s = null;
try try {
{
s = Signature.getInstance(entry.sigName); s = Signature.getInstance(entry.sigName);
} } catch (NoSuchAlgorithmException e) {
catch (NoSuchAlgorithmException e)
{
log.severe("Unable to get signature implementation for algorithm " + algorithm log.severe("Unable to get signature implementation for algorithm " + algorithm
+ ": " + e); + ": " + e);
} }
@ -420,17 +400,19 @@ public class DnsKeyAlgorithm
* @return The calculated JCA ECParameterSpec for that DNSSEC algorithm, or * @return The calculated JCA ECParameterSpec for that DNSSEC algorithm, or
* null if not a recognized/supported EC algorithm. * null if not a recognized/supported EC algorithm.
*/ */
public ECParameterSpec getEllipticCurveParams(int algorithm) public ECParameterSpec getEllipticCurveParams(int algorithm) {
{
AlgEntry entry = getEntry(algorithm); AlgEntry entry = getEntry(algorithm);
if (entry == null) return null; if (entry == null)
if (!(entry instanceof ECAlgEntry)) return null; return null;
if (!(entry instanceof ECAlgEntry))
return null;
ECAlgEntry ec_entry = (ECAlgEntry) entry; ECAlgEntry ec_entry = (ECAlgEntry) entry;
return ec_entry.ec_spec; return ec_entry.ecSpec;
} }
/** Given one of the EdDSA algorithms (Ed25519, Ed448) return the /**
* Given one of the EdDSA algorithms (Ed25519, Ed448) return the
* elliptic curve parameters. * elliptic curve parameters.
* *
* @param algorithm * @param algorithm
@ -438,14 +420,15 @@ public class DnsKeyAlgorithm
* @return The stored EdDSAParameterSpec for that algorithm, or * @return The stored EdDSAParameterSpec for that algorithm, or
* null if not a recognized/supported EdDSA algorithm. * null if not a recognized/supported EdDSA algorithm.
*/ */
public EdDSAParameterSpec getEdwardsCurveParams(int algorithm) public EdDSAParameterSpec getEdwardsCurveParams(int algorithm) {
{
AlgEntry entry = getEntry(algorithm); AlgEntry entry = getEntry(algorithm);
if (entry == null) return null; if (entry == null)
if (!(entry instanceof EdAlgEntry)) return null; return null;
if (!(entry instanceof EdAlgEntry))
return null;
EdAlgEntry ed_entry = (EdAlgEntry) entry; EdAlgEntry ed_entry = (EdAlgEntry) entry;
return ed_entry.ed_spec; return ed_entry.edSpec;
} }
/** /**
@ -457,10 +440,10 @@ public class DnsKeyAlgorithm
* @return -1 if the algorithm isn't recognised, the orignal algorithm number * @return -1 if the algorithm isn't recognised, the orignal algorithm number
* if it is. * if it is.
*/ */
public int originalAlgorithm(int algorithm) public int originalAlgorithm(int algorithm) {
{
AlgEntry entry = getEntry(algorithm); AlgEntry entry = getEntry(algorithm);
if (entry == null) return -1; if (entry == null)
return -1;
return entry.dnssecAlgorithm; return entry.dnssecAlgorithm;
} }
@ -468,11 +451,12 @@ public class DnsKeyAlgorithm
* Test if a given algorithm is supported. * Test if a given algorithm is supported.
* *
* @param algorithm The DNSSEC algorithm number. * @param algorithm The DNSSEC algorithm number.
* @return true if the algorithm is a recognized and supported algorithm or alias. * @return true if the algorithm is a recognized and supported algorithm or
* alias.
*/ */
public boolean supportedAlgorithm(int algorithm) public boolean supportedAlgorithm(int algorithm) {
{ if (mAlgorithmMap.containsKey(algorithm))
if (mAlgorithmMap.containsKey(algorithm)) return true; return true;
return false; return false;
} }
@ -485,10 +469,10 @@ public class DnsKeyAlgorithm
* @return -1 if the mnemonic isn't recognized or supported, the algorithm * @return -1 if the mnemonic isn't recognized or supported, the algorithm
* number if it is. * number if it is.
*/ */
public int stringToAlgorithm(String s) public int stringToAlgorithm(String s) {
{
Integer alg = mMnemonicToIdMap.get(s.toUpperCase()); Integer alg = mMnemonicToIdMap.get(s.toUpperCase());
if (alg != null) return alg.intValue(); if (alg != null)
return alg.intValue();
return -1; return -1;
} }
@ -500,51 +484,39 @@ public class DnsKeyAlgorithm
* @return The preferred mnemonic string, or null if not supported or * @return The preferred mnemonic string, or null if not supported or
* recognized. * recognized.
*/ */
public String algToString(int algorithm) public String algToString(int algorithm) {
{
return mIdToMnemonicMap.get(algorithm); return mIdToMnemonicMap.get(algorithm);
} }
public int baseType(int algorithm) public int baseType(int algorithm) {
{
AlgEntry entry = getEntry(algorithm); AlgEntry entry = getEntry(algorithm);
if (entry != null) return entry.baseType; if (entry != null)
return entry.baseType;
return UNKNOWN; return UNKNOWN;
} }
public boolean isDSA(int algorithm) public boolean isDSA(int algorithm) {
{
return (baseType(algorithm) == DSA); return (baseType(algorithm) == DSA);
} }
public KeyPair generateKeyPair(int algorithm, int keysize, boolean useLargeExp) public KeyPair generateKeyPair(int algorithm, int keysize, boolean useLargeExp)
throws NoSuchAlgorithmException throws NoSuchAlgorithmException {
{
KeyPair pair = null; KeyPair pair = null;
switch (baseType(algorithm)) switch (baseType(algorithm)) {
{ case RSA: {
case RSA: if (mRSAKeyGenerator == null) {
{
if (mRSAKeyGenerator == null)
{
mRSAKeyGenerator = KeyPairGenerator.getInstance("RSA"); mRSAKeyGenerator = KeyPairGenerator.getInstance("RSA");
} }
RSAKeyGenParameterSpec rsa_spec; RSAKeyGenParameterSpec rsa_spec;
if (useLargeExp) if (useLargeExp) {
{
rsa_spec = new RSAKeyGenParameterSpec(keysize, RSAKeyGenParameterSpec.F4); rsa_spec = new RSAKeyGenParameterSpec(keysize, RSAKeyGenParameterSpec.F4);
} } else {
else
{
rsa_spec = new RSAKeyGenParameterSpec(keysize, RSAKeyGenParameterSpec.F0); rsa_spec = new RSAKeyGenParameterSpec(keysize, RSAKeyGenParameterSpec.F0);
} }
try try {
{
mRSAKeyGenerator.initialize(rsa_spec); mRSAKeyGenerator.initialize(rsa_spec);
} } catch (InvalidAlgorithmParameterException e) {
catch (InvalidAlgorithmParameterException e)
{
// Fold the InvalidAlgorithmParameterException into our existing // Fold the InvalidAlgorithmParameterException into our existing
// thrown exception. Ugly, but requires less code change. // thrown exception. Ugly, but requires less code change.
throw new NoSuchAlgorithmException("invalid key parameter spec"); throw new NoSuchAlgorithmException("invalid key parameter spec");
@ -553,30 +525,23 @@ public class DnsKeyAlgorithm
pair = mRSAKeyGenerator.generateKeyPair(); pair = mRSAKeyGenerator.generateKeyPair();
break; break;
} }
case DSA: case DSA: {
{ if (mDSAKeyGenerator == null) {
if (mDSAKeyGenerator == null)
{
mDSAKeyGenerator = KeyPairGenerator.getInstance("DSA"); mDSAKeyGenerator = KeyPairGenerator.getInstance("DSA");
} }
mDSAKeyGenerator.initialize(keysize); mDSAKeyGenerator.initialize(keysize);
pair = mDSAKeyGenerator.generateKeyPair(); pair = mDSAKeyGenerator.generateKeyPair();
break; break;
} }
case ECC_GOST: case ECC_GOST: {
{ if (mECGOSTKeyGenerator == null) {
if (mECGOSTKeyGenerator == null)
{
mECGOSTKeyGenerator = KeyPairGenerator.getInstance("ECGOST3410"); mECGOSTKeyGenerator = KeyPairGenerator.getInstance("ECGOST3410");
} }
ECParameterSpec ec_spec = getEllipticCurveParams(algorithm); ECParameterSpec ecSpec = getEllipticCurveParams(algorithm);
try try {
{ mECGOSTKeyGenerator.initialize(ecSpec);
mECGOSTKeyGenerator.initialize(ec_spec); } catch (InvalidAlgorithmParameterException e) {
}
catch (InvalidAlgorithmParameterException e)
{
// Fold the InvalidAlgorithmParameterException into our existing // Fold the InvalidAlgorithmParameterException into our existing
// thrown exception. Ugly, but requires less code change. // thrown exception. Ugly, but requires less code change.
throw new NoSuchAlgorithmException("invalid key parameter spec"); throw new NoSuchAlgorithmException("invalid key parameter spec");
@ -584,20 +549,15 @@ public class DnsKeyAlgorithm
pair = mECGOSTKeyGenerator.generateKeyPair(); pair = mECGOSTKeyGenerator.generateKeyPair();
break; break;
} }
case ECDSA: case ECDSA: {
{ if (mECKeyGenerator == null) {
if (mECKeyGenerator == null)
{
mECKeyGenerator = KeyPairGenerator.getInstance("EC"); mECKeyGenerator = KeyPairGenerator.getInstance("EC");
} }
ECParameterSpec ec_spec = getEllipticCurveParams(algorithm); ECParameterSpec ecSpec = getEllipticCurveParams(algorithm);
try try {
{ mECKeyGenerator.initialize(ecSpec);
mECKeyGenerator.initialize(ec_spec); } catch (InvalidAlgorithmParameterException e) {
}
catch (InvalidAlgorithmParameterException e)
{
// Fold the InvalidAlgorithmParameterException into our existing // Fold the InvalidAlgorithmParameterException into our existing
// thrown exception. Ugly, but requires less code change. // thrown exception. Ugly, but requires less code change.
throw new NoSuchAlgorithmException("invalid key parameter spec"); throw new NoSuchAlgorithmException("invalid key parameter spec");
@ -605,20 +565,15 @@ public class DnsKeyAlgorithm
pair = mECKeyGenerator.generateKeyPair(); pair = mECKeyGenerator.generateKeyPair();
break; break;
} }
case EDDSA: case EDDSA: {
{ if (mEdKeyGenerator == null) {
if (mEdKeyGenerator == null)
{
mEdKeyGenerator = KeyPairGenerator.getInstance("EdDSA"); mEdKeyGenerator = KeyPairGenerator.getInstance("EdDSA");
} }
EdDSAParameterSpec ed_spec = getEdwardsCurveParams(algorithm); EdDSAParameterSpec edSpec = getEdwardsCurveParams(algorithm);
try try {
{ mEdKeyGenerator.initialize(edSpec, new SecureRandom());
mEdKeyGenerator.initialize(ed_spec, new SecureRandom()); } catch (InvalidAlgorithmParameterException e) {
}
catch (InvalidAlgorithmParameterException e)
{
// Fold the InvalidAlgorithmParameterException into our existing // Fold the InvalidAlgorithmParameterException into our existing
// thrown exception. Ugly, but requires less code change. // thrown exception. Ugly, but requires less code change.
throw new NoSuchAlgorithmException("invalid key parameter spec"); throw new NoSuchAlgorithmException("invalid key parameter spec");
@ -634,14 +589,13 @@ public class DnsKeyAlgorithm
} }
public KeyPair generateKeyPair(int algorithm, int keysize) public KeyPair generateKeyPair(int algorithm, int keysize)
throws NoSuchAlgorithmException throws NoSuchAlgorithmException {
{
return generateKeyPair(algorithm, keysize, false); return generateKeyPair(algorithm, keysize, false);
} }
public static DnsKeyAlgorithm getInstance() public static DnsKeyAlgorithm getInstance() {
{ if (mInstance == null)
if (mInstance == null) mInstance = new DnsKeyAlgorithm(); mInstance = new DnsKeyAlgorithm();
return mInstance; return mInstance;
} }
} }

View File

@ -1,6 +1,4 @@
// $Id$ // Copyright (C) 2001-2003, 2022 VeriSign, Inc.
//
// Copyright (C) 2001-2003 VeriSign, Inc.
// //
// This library is free software; you can redistribute it and/or // This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public // modify it under the terms of the GNU Lesser General Public
@ -27,8 +25,19 @@ import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.interfaces.*; import java.security.interfaces.DSAParams;
import java.security.spec.*; import java.security.interfaces.DSAPrivateKey;
import java.security.interfaces.DSAPublicKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.spec.DSAPrivateKeySpec;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPrivateKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import javax.crypto.interfaces.DHPrivateKey; import javax.crypto.interfaces.DHPrivateKey;
@ -36,27 +45,25 @@ import javax.crypto.interfaces.DHPublicKey;
import javax.crypto.spec.DHParameterSpec; import javax.crypto.spec.DHParameterSpec;
import javax.crypto.spec.DHPrivateKeySpec; import javax.crypto.spec.DHPrivateKeySpec;
// For now, just import the native EdDSA classes
import net.i2p.crypto.eddsa.EdDSAPublicKey;
import net.i2p.crypto.eddsa.EdDSAPrivateKey;
import net.i2p.crypto.eddsa.spec.*;
import org.xbill.DNS.DNSKEYRecord; import org.xbill.DNS.DNSKEYRecord;
import org.xbill.DNS.DNSSEC;
import org.xbill.DNS.DNSSEC.DNSSECException; import org.xbill.DNS.DNSSEC.DNSSECException;
import org.xbill.DNS.Name; import org.xbill.DNS.Name;
import org.xbill.DNS.utils.base64; import org.xbill.DNS.utils.base64;
import net.i2p.crypto.eddsa.EdDSAPrivateKey;
// For now, just import the native EdDSA classes
import net.i2p.crypto.eddsa.EdDSAPublicKey;
import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec;
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
/** /**
* This class handles conversions between JCA key formats and DNSSEC and BIND9 * This class handles conversions between JCA key formats and DNSSEC and BIND9
* key formats. * key formats.
* *
* @author David Blacka (original) * @author David Blacka
* @author $Author$ (latest)
* @version $Revision$
*/ */
public class DnsKeyConverter public class DnsKeyConverter {
{
private KeyFactory mRSAKeyFactory; private KeyFactory mRSAKeyFactory;
private KeyFactory mDSAKeyFactory; private KeyFactory mDSAKeyFactory;
private KeyFactory mDHKeyFactory; private KeyFactory mDHKeyFactory;
@ -64,8 +71,7 @@ public class DnsKeyConverter
private KeyFactory mEdKeyFactory; private KeyFactory mEdKeyFactory;
private DnsKeyAlgorithm mAlgorithms; private DnsKeyAlgorithm mAlgorithms;
public DnsKeyConverter() public DnsKeyConverter() {
{
mAlgorithms = DnsKeyAlgorithm.getInstance(); mAlgorithms = DnsKeyAlgorithm.getInstance();
} }
@ -75,20 +81,20 @@ public class DnsKeyConverter
* @throws NoSuchAlgorithmException * @throws NoSuchAlgorithmException
*/ */
public PublicKey parseDNSKEYRecord(DNSKEYRecord pKeyRecord) public PublicKey parseDNSKEYRecord(DNSKEYRecord pKeyRecord)
throws NoSuchAlgorithmException throws NoSuchAlgorithmException {
{ if (pKeyRecord.getKey() == null)
if (pKeyRecord.getKey() == null) return null; return null;
// Because we have arbitrarily aliased algorithms, we need to possibly // Because we have arbitrarily aliased algorithms, we need to possibly
// translate the aliased algorithm back to the actual algorithm. // translate the aliased algorithm back to the actual algorithm.
int originalAlgorithm = mAlgorithms.originalAlgorithm(pKeyRecord.getAlgorithm()); int originalAlgorithm = mAlgorithms.originalAlgorithm(pKeyRecord.getAlgorithm());
if (originalAlgorithm <= 0) throw new NoSuchAlgorithmException("DNSKEY algorithm " if (originalAlgorithm <= 0)
throw new NoSuchAlgorithmException("DNSKEY algorithm "
+ pKeyRecord.getAlgorithm() + " is unrecognized"); + pKeyRecord.getAlgorithm() + " is unrecognized");
if (pKeyRecord.getAlgorithm() != originalAlgorithm) if (pKeyRecord.getAlgorithm() != originalAlgorithm) {
{
pKeyRecord = new DNSKEYRecord(pKeyRecord.getName(), pKeyRecord.getDClass(), pKeyRecord = new DNSKEYRecord(pKeyRecord.getName(), pKeyRecord.getDClass(),
pKeyRecord.getTTL(), pKeyRecord.getFlags(), pKeyRecord.getTTL(), pKeyRecord.getFlags(),
pKeyRecord.getProtocol(), originalAlgorithm, pKeyRecord.getProtocol(), originalAlgorithm,
@ -96,8 +102,7 @@ public class DnsKeyConverter
} }
// do not rely on DNSJava's method for EdDSA for now. // do not rely on DNSJava's method for EdDSA for now.
if (mAlgorithms.baseType(originalAlgorithm) == DnsKeyAlgorithm.EDDSA) if (mAlgorithms.baseType(originalAlgorithm) == DnsKeyAlgorithm.EDDSA) {
{
try { try {
return parseEdDSADNSKEYRecord(pKeyRecord); return parseEdDSADNSKEYRecord(pKeyRecord);
} catch (InvalidKeySpecException e) { } catch (InvalidKeySpecException e) {
@ -106,26 +111,24 @@ public class DnsKeyConverter
} }
} }
try try {
{
// This uses DNSJava's DNSSEC.toPublicKey() method. // This uses DNSJava's DNSSEC.toPublicKey() method.
return pKeyRecord.getPublicKey(); return pKeyRecord.getPublicKey();
} } catch (DNSSECException e) {
catch (DNSSECException e)
{
throw new NoSuchAlgorithmException(e); throw new NoSuchAlgorithmException(e);
} }
} }
/** Since we don't (yet) have support in DNSJava for parsing the /**
newer EdDSA algorithms, here is a local version. */ * Since we don't (yet) have support in DNSJava for parsing the
* newer EdDSA algorithms, here is a local version.
*/
private PublicKey parseEdDSADNSKEYRecord(DNSKEYRecord pKeyRecord) private PublicKey parseEdDSADNSKEYRecord(DNSKEYRecord pKeyRecord)
throws IllegalArgumentException, NoSuchAlgorithmException, InvalidKeySpecException throws IllegalArgumentException, NoSuchAlgorithmException, InvalidKeySpecException {
{
byte[] seed = pKeyRecord.getKey(); byte[] seed = pKeyRecord.getKey();
EdDSAPublicKeySpec spec = new EdDSAPublicKeySpec EdDSAPublicKeySpec spec = new EdDSAPublicKeySpec(seed,
(seed, mAlgorithms.getEdwardsCurveParams(pKeyRecord.getAlgorithm())); mAlgorithms.getEdwardsCurveParams(pKeyRecord.getAlgorithm()));
KeyFactory factory = KeyFactory.getInstance("EdDSA"); KeyFactory factory = KeyFactory.getInstance("EdDSA");
return factory.generatePublic(spec); return factory.generatePublic(spec);
@ -135,27 +138,22 @@ public class DnsKeyConverter
* Given a JCA public key and the ancillary data, generate a DNSKEY record. * Given a JCA public key and the ancillary data, generate a DNSKEY record.
*/ */
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
{
if (mAlgorithms.baseType(alg) == DnsKeyAlgorithm.EDDSA) { if (mAlgorithms.baseType(alg) == DnsKeyAlgorithm.EDDSA) {
return generateEdDSADNSKEYRecord(name, dclass, ttl, flags, alg, key); return generateEdDSADNSKEYRecord(name, dclass, ttl, flags, alg, key);
} }
return new DNSKEYRecord(name, dclass, ttl, flags, DNSKEYRecord.Protocol.DNSSEC, alg, return new DNSKEYRecord(name, dclass, ttl, flags, DNSKEYRecord.Protocol.DNSSEC, alg,
key); key);
} } catch (DNSSECException e) {
catch (DNSSECException e) // FIXME: this mimics the behavior of KEYConverter.buildRecord(), which would
{ // return null if the algorithm was unknown.
// FIXME: this mimics the behavior of KEYConverter.buildRecord(), which would return null if the algorithm was unknown.
return null; return null;
} }
} }
private DNSKEYRecord generateEdDSADNSKEYRecord(Name name, int dclass, long ttl, private DNSKEYRecord generateEdDSADNSKEYRecord(Name name, int dclass, long ttl,
int flags, int alg, PublicKey key) int flags, int alg, PublicKey key) {
{
EdDSAPublicKey ed_key = (EdDSAPublicKey) key; EdDSAPublicKey ed_key = (EdDSAPublicKey) key;
byte[] key_data = ed_key.getAbyte(); byte[] key_data = ed_key.getAbyte();
return new DNSKEYRecord(name, dclass, ttl, flags, DNSKEYRecord.Protocol.DNSSEC, alg, return new DNSKEYRecord(name, dclass, ttl, flags, DNSKEYRecord.Protocol.DNSSEC, alg,
@ -166,21 +164,18 @@ public class DnsKeyConverter
/** /**
* Convert a PKCS#8 encoded private key into a PrivateKey object. * Convert a PKCS#8 encoded private key into a PrivateKey object.
*/ */
public PrivateKey convertEncodedPrivateKey(byte[] key, int algorithm) public PrivateKey convertEncodedPrivateKey(byte[] key, int algorithm) {
{
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(key); PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(key);
try try {
{ switch (mAlgorithms.baseType(algorithm)) {
switch (mAlgorithms.baseType(algorithm))
{
case DnsKeyAlgorithm.RSA: case DnsKeyAlgorithm.RSA:
return mRSAKeyFactory.generatePrivate(spec); return mRSAKeyFactory.generatePrivate(spec);
case DnsKeyAlgorithm.DSA: case DnsKeyAlgorithm.DSA:
return mDSAKeyFactory.generatePrivate(spec); return mDSAKeyFactory.generatePrivate(spec);
default:
return null;
} }
} } catch (GeneralSecurityException e) {
catch (GeneralSecurityException e)
{
e.printStackTrace(); e.printStackTrace();
} }
@ -191,14 +186,10 @@ public class DnsKeyConverter
* A simple wrapper for parsing integers; parse failures result in the * A simple wrapper for parsing integers; parse failures result in the
* supplied default. * supplied default.
*/ */
private static int parseInt(String s, int def) private static int parseInt(String s, int def) {
{ try {
try
{
return Integer.parseInt(s); return Integer.parseInt(s);
} } catch (NumberFormatException e) {
catch (NumberFormatException e)
{
return def; return def;
} }
} }
@ -207,37 +198,33 @@ public class DnsKeyConverter
* @return a JCA private key, given a BIND9-style textual encoding * @return a JCA private key, given a BIND9-style textual encoding
*/ */
public PrivateKey parsePrivateKeyString(String key) public PrivateKey parsePrivateKeyString(String key)
throws IOException, NoSuchAlgorithmException throws IOException, NoSuchAlgorithmException {
{
StringTokenizer lines = new StringTokenizer(key, "\n"); StringTokenizer lines = new StringTokenizer(key, "\n");
while (lines.hasMoreTokens()) while (lines.hasMoreTokens()) {
{
String line = lines.nextToken(); String line = lines.nextToken();
if (line == null) continue; if (line == null)
continue;
if (line.startsWith("#")) continue; if (line.startsWith("#"))
continue;
String val = value(line); String val = value(line);
if (val == null) continue; if (val == null)
continue;
if (line.startsWith("Private-key-format: ")) if (line.startsWith("Private-key-format: ")) {
{ if (!val.equals("v1.2") && !val.equals("v1.3")) {
if (!val.equals("v1.2") && !val.equals("v1.3"))
{
throw new IOException("unsupported private key format: " + val); throw new IOException("unsupported private key format: " + val);
} }
} } else if (line.startsWith("Algorithm: ")) {
else if (line.startsWith("Algorithm: "))
{
// here we assume that the value looks like # (MNEM) or just the // here we assume that the value looks like # (MNEM) or just the
// number. // number.
String[] toks = val.split("\\s", 2); String[] toks = val.split("\\s", 2);
val = toks[0]; val = toks[0];
int alg = parseInt(val, -1); int alg = parseInt(val, -1);
switch (mAlgorithms.baseType(alg)) switch (mAlgorithms.baseType(alg)) {
{
case DnsKeyAlgorithm.RSA: case DnsKeyAlgorithm.RSA:
return parsePrivateRSA(lines); return parsePrivateRSA(lines);
case DnsKeyAlgorithm.DSA: case DnsKeyAlgorithm.DSA:
@ -261,14 +248,16 @@ public class DnsKeyConverter
/** /**
* @return the value part of an "attribute:value" pair. The value is trimmed. * @return the value part of an "attribute:value" pair. The value is trimmed.
*/ */
private static String value(String av) private static String value(String av) {
{ if (av == null)
if (av == null) return null; return null;
int pos = av.indexOf(':'); int pos = av.indexOf(':');
if (pos < 0) return av; if (pos < 0)
return av;
if (pos >= av.length()) return null; if (pos >= av.length())
return null;
return av.substring(pos + 1).trim(); return av.substring(pos + 1).trim();
} }
@ -281,8 +270,7 @@ public class DnsKeyConverter
* if the RSA algorithm is not available. * if the RSA algorithm is not available.
*/ */
private PrivateKey parsePrivateRSA(StringTokenizer lines) private PrivateKey parsePrivateRSA(StringTokenizer lines)
throws NoSuchAlgorithmException throws NoSuchAlgorithmException {
{
BigInteger modulus = null; BigInteger modulus = null;
BigInteger public_exponent = null; BigInteger public_exponent = null;
BigInteger private_exponent = null; BigInteger private_exponent = null;
@ -292,71 +280,54 @@ public class DnsKeyConverter
BigInteger prime_q_exponent = null; BigInteger prime_q_exponent = null;
BigInteger coefficient = null; BigInteger coefficient = null;
while (lines.hasMoreTokens()) while (lines.hasMoreTokens()) {
{
String line = lines.nextToken(); String line = lines.nextToken();
if (line == null) continue; if (line == null)
continue;
if (line.startsWith("#")) continue; if (line.startsWith("#"))
continue;
String val = value(line); String val = value(line);
if (val == null) continue; if (val == null)
continue;
byte[] data = base64.fromString(val); byte[] data = base64.fromString(val);
if (line.startsWith("Modulus: ")) if (line.startsWith("Modulus: ")) {
{
modulus = new BigInteger(1, data); modulus = new BigInteger(1, data);
// printBigIntCompare(data, modulus); // printBigIntCompare(data, modulus);
} } else if (line.startsWith("PublicExponent: ")) {
else if (line.startsWith("PublicExponent: "))
{
public_exponent = new BigInteger(1, data); public_exponent = new BigInteger(1, data);
// printBigIntCompare(data, public_exponent); // printBigIntCompare(data, public_exponent);
} } else if (line.startsWith("PrivateExponent: ")) {
else if (line.startsWith("PrivateExponent: "))
{
private_exponent = new BigInteger(1, data); private_exponent = new BigInteger(1, data);
// printBigIntCompare(data, private_exponent); // printBigIntCompare(data, private_exponent);
} } else if (line.startsWith("Prime1: ")) {
else if (line.startsWith("Prime1: "))
{
prime_p = new BigInteger(1, data); prime_p = new BigInteger(1, data);
// printBigIntCompare(data, prime_p); // printBigIntCompare(data, prime_p);
} } else if (line.startsWith("Prime2: ")) {
else if (line.startsWith("Prime2: "))
{
prime_q = new BigInteger(1, data); prime_q = new BigInteger(1, data);
// printBigIntCompare(data, prime_q); // printBigIntCompare(data, prime_q);
} } else if (line.startsWith("Exponent1: ")) {
else if (line.startsWith("Exponent1: "))
{
prime_p_exponent = new BigInteger(1, data); prime_p_exponent = new BigInteger(1, data);
} } else if (line.startsWith("Exponent2: ")) {
else if (line.startsWith("Exponent2: "))
{
prime_q_exponent = new BigInteger(1, data); prime_q_exponent = new BigInteger(1, data);
} } else if (line.startsWith("Coefficient: ")) {
else if (line.startsWith("Coefficient: "))
{
coefficient = new BigInteger(1, data); coefficient = new BigInteger(1, data);
} }
} }
try try {
{
KeySpec spec = new RSAPrivateCrtKeySpec(modulus, public_exponent, KeySpec spec = new RSAPrivateCrtKeySpec(modulus, public_exponent,
private_exponent, prime_p, private_exponent, prime_p,
prime_q, prime_p_exponent, prime_q, prime_p_exponent,
prime_q_exponent, coefficient); prime_q_exponent, coefficient);
if (mRSAKeyFactory == null) if (mRSAKeyFactory == null) {
{
mRSAKeyFactory = KeyFactory.getInstance("RSA"); mRSAKeyFactory = KeyFactory.getInstance("RSA");
} }
return mRSAKeyFactory.generatePrivate(spec); return mRSAKeyFactory.generatePrivate(spec);
} } catch (InvalidKeySpecException e) {
catch (InvalidKeySpecException e)
{
e.printStackTrace(); e.printStackTrace();
return null; return null;
} }
@ -370,49 +341,41 @@ public class DnsKeyConverter
* if the DH algorithm is not available. * if the DH algorithm is not available.
*/ */
private PrivateKey parsePrivateDH(StringTokenizer lines) private PrivateKey parsePrivateDH(StringTokenizer lines)
throws NoSuchAlgorithmException throws NoSuchAlgorithmException {
{
BigInteger p = null; BigInteger p = null;
BigInteger x = null; BigInteger x = null;
BigInteger g = null; BigInteger g = null;
while (lines.hasMoreTokens()) while (lines.hasMoreTokens()) {
{
String line = lines.nextToken(); String line = lines.nextToken();
if (line == null) continue; if (line == null)
continue;
if (line.startsWith("#")) continue; if (line.startsWith("#"))
continue;
String val = value(line); String val = value(line);
if (val == null) continue; if (val == null)
continue;
byte[] data = base64.fromString(val); byte[] data = base64.fromString(val);
if (line.startsWith("Prime(p): ")) if (line.startsWith("Prime(p): ")) {
{
p = new BigInteger(1, data); p = new BigInteger(1, data);
} } else if (line.startsWith("Generator(g): ")) {
else if (line.startsWith("Generator(g): "))
{
g = new BigInteger(1, data); g = new BigInteger(1, data);
} } else if (line.startsWith("Private_value(x): ")) {
else if (line.startsWith("Private_value(x): "))
{
x = new BigInteger(1, data); x = new BigInteger(1, data);
} }
} }
try try {
{
KeySpec spec = new DHPrivateKeySpec(x, p, g); KeySpec spec = new DHPrivateKeySpec(x, p, g);
if (mDHKeyFactory == null) if (mDHKeyFactory == null) {
{
mDHKeyFactory = KeyFactory.getInstance("DH"); mDHKeyFactory = KeyFactory.getInstance("DH");
} }
return mDHKeyFactory.generatePrivate(spec); return mDHKeyFactory.generatePrivate(spec);
} } catch (InvalidKeySpecException e) {
catch (InvalidKeySpecException e)
{
e.printStackTrace(); e.printStackTrace();
return null; return null;
} }
@ -426,54 +389,44 @@ public class DnsKeyConverter
* if the DSA algorithm is not available. * if the DSA algorithm is not available.
*/ */
private PrivateKey parsePrivateDSA(StringTokenizer lines) private PrivateKey parsePrivateDSA(StringTokenizer lines)
throws NoSuchAlgorithmException throws NoSuchAlgorithmException {
{
BigInteger p = null; BigInteger p = null;
BigInteger q = null; BigInteger q = null;
BigInteger g = null; BigInteger g = null;
BigInteger x = null; BigInteger x = null;
while (lines.hasMoreTokens()) while (lines.hasMoreTokens()) {
{
String line = lines.nextToken(); String line = lines.nextToken();
if (line == null) continue; if (line == null)
continue;
if (line.startsWith("#")) continue; if (line.startsWith("#"))
continue;
String val = value(line); String val = value(line);
if (val == null) continue; if (val == null)
continue;
byte[] data = base64.fromString(val); byte[] data = base64.fromString(val);
if (line.startsWith("Prime(p): ")) if (line.startsWith("Prime(p): ")) {
{
p = new BigInteger(1, data); p = new BigInteger(1, data);
} } else if (line.startsWith("Subprime(q): ")) {
else if (line.startsWith("Subprime(q): "))
{
q = new BigInteger(1, data); q = new BigInteger(1, data);
} } else if (line.startsWith("Base(g): ")) {
else if (line.startsWith("Base(g): "))
{
g = new BigInteger(1, data); g = new BigInteger(1, data);
} } else if (line.startsWith("Private_value(x): ")) {
else if (line.startsWith("Private_value(x): "))
{
x = new BigInteger(1, data); x = new BigInteger(1, data);
} }
} }
try try {
{
KeySpec spec = new DSAPrivateKeySpec(x, p, q, g); KeySpec spec = new DSAPrivateKeySpec(x, p, q, g);
if (mDSAKeyFactory == null) if (mDSAKeyFactory == null) {
{
mDSAKeyFactory = KeyFactory.getInstance("DSA"); mDSAKeyFactory = KeyFactory.getInstance("DSA");
} }
return mDSAKeyFactory.generatePrivate(spec); return mDSAKeyFactory.generatePrivate(spec);
} } catch (InvalidKeySpecException e) {
catch (InvalidKeySpecException e)
{
e.printStackTrace(); e.printStackTrace();
return null; return null;
} }
@ -482,52 +435,48 @@ public class DnsKeyConverter
/** /**
* Given the remaining lines in a BIND9-style ECDSA private key, parse the key * Given the remaining lines in a BIND9-style ECDSA private key, parse the key
* info and translate it into a JCA private key object. * info and translate it into a JCA private key object.
*
* @param lines The remaining lines in a private key file (after * @param lines The remaining lines in a private key file (after
* @throws NoSuchAlgorithmException * @throws NoSuchAlgorithmException
* If elliptic curve is not available. * If elliptic curve is not available.
*/ */
private PrivateKey parsePrivateECDSA(StringTokenizer lines, int algorithm) private PrivateKey parsePrivateECDSA(StringTokenizer lines, int algorithm)
throws NoSuchAlgorithmException throws NoSuchAlgorithmException {
{
BigInteger s = null; BigInteger s = null;
while (lines.hasMoreTokens()) while (lines.hasMoreTokens()) {
{
String line = lines.nextToken(); String line = lines.nextToken();
if (line == null) continue; if (line == null)
continue;
if (line.startsWith("#")) continue; if (line.startsWith("#"))
continue;
String val = value(line); String val = value(line);
if (val == null) continue; if (val == null)
continue;
byte[] data = base64.fromString(val); byte[] data = base64.fromString(val);
if (line.startsWith("PrivateKey: ")) if (line.startsWith("PrivateKey: ")) {
{
s = new BigInteger(1, data); s = new BigInteger(1, data);
} }
} }
if (mECKeyFactory == null) if (mECKeyFactory == null) {
{
mECKeyFactory = KeyFactory.getInstance("EC"); mECKeyFactory = KeyFactory.getInstance("EC");
} }
ECParameterSpec ec_spec = mAlgorithms.getEllipticCurveParams(algorithm); ECParameterSpec ec_spec = mAlgorithms.getEllipticCurveParams(algorithm);
if (ec_spec == null) if (ec_spec == null) {
{
throw new NoSuchAlgorithmException("DNSSEC algorithm " + algorithm + throw new NoSuchAlgorithmException("DNSSEC algorithm " + algorithm +
" is not a recognized Elliptic Curve algorithm"); " is not a recognized Elliptic Curve algorithm");
} }
KeySpec spec = new ECPrivateKeySpec(s, ec_spec); KeySpec spec = new ECPrivateKeySpec(s, ec_spec);
try try {
{
return mECKeyFactory.generatePrivate(spec); return mECKeyFactory.generatePrivate(spec);
} } catch (InvalidKeySpecException e) {
catch (InvalidKeySpecException e)
{
e.printStackTrace(); e.printStackTrace();
return null; return null;
} }
@ -536,52 +485,48 @@ public class DnsKeyConverter
/** /**
* Given the remaining lines in a BIND9-style ECDSA private key, parse the key * Given the remaining lines in a BIND9-style ECDSA private key, parse the key
* info and translate it into a JCA private key object. * info and translate it into a JCA private key object.
*
* @param lines The remaining lines in a private key file (after * @param lines The remaining lines in a private key file (after
* @throws NoSuchAlgorithmException * @throws NoSuchAlgorithmException
* If elliptic curve is not available. * If elliptic curve is not available.
*/ */
private PrivateKey parsePrivateEdDSA(StringTokenizer lines, int algorithm) private PrivateKey parsePrivateEdDSA(StringTokenizer lines, int algorithm)
throws NoSuchAlgorithmException throws NoSuchAlgorithmException {
{
byte[] seed = null; byte[] seed = null;
while (lines.hasMoreTokens()) while (lines.hasMoreTokens()) {
{
String line = lines.nextToken(); String line = lines.nextToken();
if (line == null) continue; if (line == null)
continue;
if (line.startsWith("#")) continue; if (line.startsWith("#"))
continue;
String val = value(line); String val = value(line);
if (val == null) continue; if (val == null)
continue;
byte[] data = base64.fromString(val); byte[] data = base64.fromString(val);
if (line.startsWith("PrivateKey: ")) if (line.startsWith("PrivateKey: ")) {
{
seed = data; seed = data;
} }
} }
if (mEdKeyFactory == null) if (mEdKeyFactory == null) {
{
mEdKeyFactory = KeyFactory.getInstance("EdDSA"); mEdKeyFactory = KeyFactory.getInstance("EdDSA");
} }
EdDSAParameterSpec ed_spec = mAlgorithms.getEdwardsCurveParams(algorithm); EdDSAParameterSpec ed_spec = mAlgorithms.getEdwardsCurveParams(algorithm);
if (ed_spec == null) if (ed_spec == null) {
{
throw new NoSuchAlgorithmException("DNSSEC algorithm " + algorithm + throw new NoSuchAlgorithmException("DNSSEC algorithm " + algorithm +
" is not a recognized Edwards Curve algorithm"); " is not a recognized Edwards Curve algorithm");
} }
KeySpec spec = new EdDSAPrivateKeySpec(seed, ed_spec); KeySpec spec = new EdDSAPrivateKeySpec(seed, ed_spec);
try try {
{
return mEdKeyFactory.generatePrivate(spec); return mEdKeyFactory.generatePrivate(spec);
} } catch (InvalidKeySpecException e) {
catch (InvalidKeySpecException e)
{
e.printStackTrace(); e.printStackTrace();
return null; return null;
} }
@ -591,26 +536,16 @@ public class DnsKeyConverter
* Given a private key and public key, generate the BIND9 style private key * Given a private key and public key, generate the BIND9 style private key
* format. * format.
*/ */
public String generatePrivateKeyString(PrivateKey priv, PublicKey pub, int alg) public String generatePrivateKeyString(PrivateKey priv, PublicKey pub, int alg) {
{ if (priv instanceof RSAPrivateCrtKey) {
if (priv instanceof RSAPrivateCrtKey)
{
return generatePrivateRSA((RSAPrivateCrtKey) priv, alg); return generatePrivateRSA((RSAPrivateCrtKey) priv, alg);
} } else if (priv instanceof DSAPrivateKey && pub instanceof DSAPublicKey) {
else if (priv instanceof DSAPrivateKey && pub instanceof DSAPublicKey)
{
return generatePrivateDSA((DSAPrivateKey) priv, (DSAPublicKey) pub, alg); return generatePrivateDSA((DSAPrivateKey) priv, (DSAPublicKey) pub, alg);
} } else if (priv instanceof DHPrivateKey && pub instanceof DHPublicKey) {
else if (priv instanceof DHPrivateKey && pub instanceof DHPublicKey)
{
return generatePrivateDH((DHPrivateKey) priv, (DHPublicKey) pub, alg); return generatePrivateDH((DHPrivateKey) priv, (DHPublicKey) pub, alg);
} } else if (priv instanceof ECPrivateKey && pub instanceof ECPublicKey) {
else if (priv instanceof ECPrivateKey && pub instanceof ECPublicKey)
{
return generatePrivateEC((ECPrivateKey) priv, (ECPublicKey) pub, alg); return generatePrivateEC((ECPrivateKey) priv, (ECPublicKey) pub, alg);
} } else if (priv instanceof EdDSAPrivateKey && pub instanceof EdDSAPublicKey) {
else if (priv instanceof EdDSAPrivateKey && pub instanceof EdDSAPublicKey)
{
return generatePrivateED((EdDSAPrivateKey) priv, (EdDSAPublicKey) pub, alg); return generatePrivateED((EdDSAPrivateKey) priv, (EdDSAPublicKey) pub, alg);
} }
@ -620,12 +555,10 @@ public class DnsKeyConverter
/** /**
* Convert from 'unsigned' big integer to original 'signed format' in Base64 * Convert from 'unsigned' big integer to original 'signed format' in Base64
*/ */
private static String b64BigInt(BigInteger i) private static String b64BigInt(BigInteger i) {
{
byte[] orig_bytes = i.toByteArray(); byte[] orig_bytes = i.toByteArray();
if (orig_bytes[0] != 0 || orig_bytes.length == 1) if (orig_bytes[0] != 0 || orig_bytes.length == 1) {
{
return base64.toString(orig_bytes); return base64.toString(orig_bytes);
} }
@ -639,8 +572,7 @@ public class DnsKeyConverter
* Given a RSA private key (in Crt format), return the BIND9-style text * Given a RSA private key (in Crt format), return the BIND9-style text
* encoding. * encoding.
*/ */
private String generatePrivateRSA(RSAPrivateCrtKey key, int algorithm) private String generatePrivateRSA(RSAPrivateCrtKey key, int algorithm) {
{
StringWriter sw = new StringWriter(); StringWriter sw = new StringWriter();
PrintWriter out = new PrintWriter(sw); PrintWriter out = new PrintWriter(sw);
@ -669,8 +601,7 @@ public class DnsKeyConverter
/** Given a DH key pair, return the BIND9-style text encoding */ /** Given a DH key pair, return the BIND9-style text encoding */
private String generatePrivateDH(DHPrivateKey key, DHPublicKey pub, private String generatePrivateDH(DHPrivateKey key, DHPublicKey pub,
int algorithm) int algorithm) {
{
StringWriter sw = new StringWriter(); StringWriter sw = new StringWriter();
PrintWriter out = new PrintWriter(sw); PrintWriter out = new PrintWriter(sw);
@ -693,8 +624,7 @@ public class DnsKeyConverter
/** Given a DSA key pair, return the BIND9-style text encoding */ /** Given a DSA key pair, return the BIND9-style text encoding */
private String generatePrivateDSA(DSAPrivateKey key, DSAPublicKey pub, private String generatePrivateDSA(DSAPrivateKey key, DSAPublicKey pub,
int algorithm) int algorithm) {
{
StringWriter sw = new StringWriter(); StringWriter sw = new StringWriter();
PrintWriter out = new PrintWriter(sw); PrintWriter out = new PrintWriter(sw);
@ -721,8 +651,7 @@ public class DnsKeyConverter
* Given an elliptic curve key pair, and the actual algorithm (which will * Given an elliptic curve key pair, and the actual algorithm (which will
* describe the curve used), return the BIND9-style text encoding. * describe the curve used), return the BIND9-style text encoding.
*/ */
private String generatePrivateEC(ECPrivateKey priv, ECPublicKey pub, int alg) private String generatePrivateEC(ECPrivateKey priv, ECPublicKey pub, int alg) {
{
StringWriter sw = new StringWriter(); StringWriter sw = new StringWriter();
PrintWriter out = new PrintWriter(sw); PrintWriter out = new PrintWriter(sw);
@ -739,8 +668,7 @@ public class DnsKeyConverter
* Given an edwards curve key pair, and the actual algorithm (which will * Given an edwards curve key pair, and the actual algorithm (which will
* describe the curve used), return the BIND9-style text encoding. * describe the curve used), return the BIND9-style text encoding.
*/ */
private String generatePrivateED(EdDSAPrivateKey priv, EdDSAPublicKey pub, int alg) private String generatePrivateED(EdDSAPrivateKey priv, EdDSAPublicKey pub, int alg) {
{
StringWriter sw = new StringWriter(); StringWriter sw = new StringWriter();
PrintWriter out = new PrintWriter(sw); PrintWriter out = new PrintWriter(sw);

View File

@ -1,6 +1,4 @@
// $Id$ // Copyright (C) 2001-2003, 2022 VeriSign, Inc.
//
// Copyright (C) 2001-2003 VeriSign, Inc.
// //
// This library is free software; you can redistribute it and/or // This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public // modify it under the terms of the GNU Lesser General Public
@ -18,11 +16,21 @@
package com.verisignlabs.dnssec.security; package com.verisignlabs.dnssec.security;
import java.security.*; import java.security.InvalidKeyException;
import java.security.interfaces.*; import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.interfaces.DSAPrivateKey;
import java.security.interfaces.DSAPublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.xbill.DNS.*; import org.xbill.DNS.DClass;
import org.xbill.DNS.DNSKEYRecord;
import org.xbill.DNS.DNSSEC;
import org.xbill.DNS.Name;
/** /**
* This class forms the basis for representing public/private key pairs in a * This class forms the basis for representing public/private key pairs in a
@ -33,16 +41,13 @@ import org.xbill.DNS.*;
* *
* JCA == Java Cryptography Architecture. * JCA == Java Cryptography Architecture.
* *
* @author David Blacka (orig) * @author David Blacka
* @author $Author$ (latest)
* @version $Revision$
*/ */
// NOTE: this class is designed to do "lazy" evaluation of it's // NOTE: this class is designed to do "lazy" evaluation of it's
// various cached objects and format conversions, so methods should // various cached objects and format conversions, so methods should
// avoid direct access to the member variables. // avoid direct access to the member variables.
public class DnsKeyPair public class DnsKeyPair {
{
/** This is the real (base) encoding of the public key. */ /** This is the real (base) encoding of the public key. */
protected DNSKEYRecord mPublicKeyRecord; protected DNSKEYRecord mPublicKeyRecord;
@ -79,37 +84,32 @@ public class DnsKeyPair
private Logger log; private Logger log;
public DnsKeyPair() public DnsKeyPair() {
{
log = Logger.getLogger(this.getClass().toString()); log = Logger.getLogger(this.getClass().toString());
} }
public DnsKeyPair(DNSKEYRecord keyRecord, PrivateKey privateKey) public DnsKeyPair(DNSKEYRecord keyRecord, PrivateKey privateKey) {
{
this(); this();
setDNSKEYRecord(keyRecord); setDNSKEYRecord(keyRecord);
setPrivate(privateKey); setPrivate(privateKey);
} }
public DnsKeyPair(DNSKEYRecord keyRecord, String privateKeyString) public DnsKeyPair(DNSKEYRecord keyRecord, String privateKeyString) {
{
this(); this();
setDNSKEYRecord(keyRecord); setDNSKEYRecord(keyRecord);
setPrivateKeyString(privateKeyString); setPrivateKeyString(privateKeyString);
} }
public DnsKeyPair(DNSKEYRecord keyRecord) public DnsKeyPair(DNSKEYRecord keyRecord) {
{
this(); this();
setDNSKEYRecord(keyRecord); setDNSKEYRecord(keyRecord);
setPrivateKeyString(null); setPrivateKeyString(null);
} }
public DnsKeyPair(Name keyName, int algorithm, PublicKey publicKey, public DnsKeyPair(Name keyName, int algorithm, PublicKey publicKey,
PrivateKey privateKey) PrivateKey privateKey) {
{
this(); this();
DnsKeyConverter conv = new DnsKeyConverter(); DnsKeyConverter conv = new DnsKeyConverter();
@ -119,8 +119,7 @@ public class DnsKeyPair
setPrivate(privateKey); setPrivate(privateKey);
} }
public DnsKeyPair(DnsKeyPair pair) public DnsKeyPair(DnsKeyPair pair) {
{
this(); this();
setDNSKEYRecord(pair.getDNSKEYRecord()); setDNSKEYRecord(pair.getDNSKEYRecord());
@ -129,10 +128,8 @@ public class DnsKeyPair
} }
/** @return cached DnsKeyConverter object. */ /** @return cached DnsKeyConverter object. */
protected DnsKeyConverter getKeyConverter() protected DnsKeyConverter getKeyConverter() {
{ if (mKeyConverter == null) {
if (mKeyConverter == null)
{
mKeyConverter = new DnsKeyConverter(); mKeyConverter = new DnsKeyConverter();
} }
@ -140,8 +137,7 @@ public class DnsKeyPair
} }
/** @return the appropriate Signature object for this keypair. */ /** @return the appropriate Signature object for this keypair. */
protected Signature getSignature() protected Signature getSignature() {
{
DnsKeyAlgorithm algorithms = DnsKeyAlgorithm.getInstance(); DnsKeyAlgorithm algorithms = DnsKeyAlgorithm.getInstance();
return algorithms.getSignature(getDNSKEYAlgorithm()); return algorithms.getSignature(getDNSKEYAlgorithm());
} }
@ -149,17 +145,12 @@ public class DnsKeyPair
/** /**
* @return the public key, translated from the KEYRecord, if necessary. * @return the public key, translated from the KEYRecord, if necessary.
*/ */
public PublicKey getPublic() public PublicKey getPublic() {
{ if (mPublicKey == null && getDNSKEYRecord() != null) {
if (mPublicKey == null && getDNSKEYRecord() != null) try {
{
try
{
DnsKeyConverter conv = getKeyConverter(); DnsKeyConverter conv = getKeyConverter();
setPublic(conv.parseDNSKEYRecord(getDNSKEYRecord())); setPublic(conv.parseDNSKEYRecord(getDNSKEYRecord()));
} } catch (NoSuchAlgorithmException e) {
catch (NoSuchAlgorithmException e)
{
log.severe(e.toString()); log.severe(e.toString());
return null; return null;
} }
@ -171,18 +162,15 @@ public class DnsKeyPair
/** /**
* sets the public key. This method is generally not used directly. * sets the public key. This method is generally not used directly.
*/ */
protected void setPublic(PublicKey k) protected void setPublic(PublicKey k) {
{
mPublicKey = k; mPublicKey = k;
} }
/** @return the private key. */ /** @return the private key. */
public PrivateKey getPrivate() public PrivateKey getPrivate() {
{
// attempt to convert the private key string format into a JCA // attempt to convert the private key string format into a JCA
// private key. // private key.
if (mPrivateKey == null && mPrivateKeyString != null) if (mPrivateKey == null && mPrivateKeyString != null) {
{
mPrivateKey = BINDKeyUtils.convertPrivateKeyString(mPrivateKeyString); mPrivateKey = BINDKeyUtils.convertPrivateKeyString(mPrivateKeyString);
} }
@ -190,8 +178,7 @@ public class DnsKeyPair
} }
/** sets the private key */ /** sets the private key */
public void setPrivate(PrivateKey k) public void setPrivate(PrivateKey k) {
{
mPrivateKey = k; mPrivateKey = k;
} }
@ -199,10 +186,8 @@ public class DnsKeyPair
* @return the opaque private key string, null if one doesn't exist. * @return the opaque private key string, null if one doesn't exist.
* @throws NoSuchAlgorithmException * @throws NoSuchAlgorithmException
*/ */
public String getPrivateKeyString() public String getPrivateKeyString() {
{ if (mPrivateKeyString == null && mPrivateKey != null) {
if (mPrivateKeyString == null && mPrivateKey != null)
{
PublicKey pub = getPublic(); PublicKey pub = getPublic();
mPrivateKeyString = BINDKeyUtils.convertPrivateKey(mPrivateKey, pub, mPrivateKeyString = BINDKeyUtils.convertPrivateKey(mPrivateKey, pub,
getDNSKEYAlgorithm()); getDNSKEYAlgorithm());
@ -212,17 +197,16 @@ public class DnsKeyPair
} }
/** sets the opaque private key string. */ /** sets the opaque private key string. */
public void setPrivateKeyString(String p) public void setPrivateKeyString(String p) {
{
mPrivateKeyString = p; mPrivateKeyString = p;
} }
/** @return the private key in an encoded form (normally PKCS#8). */ /** @return the private key in an encoded form (normally PKCS#8). */
public byte[] getEncodedPrivate() public byte[] getEncodedPrivate() {
{
PrivateKey priv = getPrivate(); PrivateKey priv = getPrivate();
if (priv != null) return priv.getEncoded(); if (priv != null)
return null; return priv.getEncoded();
return new byte[0];
} }
/** /**
@ -230,20 +214,17 @@ public class DnsKeyPair
* that the public key already be assigned. Currently it can only handle DSA * that the public key already be assigned. Currently it can only handle DSA
* and RSA keys. * and RSA keys.
*/ */
public void setEncodedPrivate(byte[] encoded) public void setEncodedPrivate(byte[] encoded) {
{
int alg = getDNSKEYAlgorithm(); int alg = getDNSKEYAlgorithm();
if (alg >= 0) if (alg >= 0) {
{
DnsKeyConverter conv = getKeyConverter(); DnsKeyConverter conv = getKeyConverter();
setPrivate(conv.convertEncodedPrivateKey(encoded, alg)); setPrivate(conv.convertEncodedPrivateKey(encoded, alg));
} }
} }
/** @return the public DNSKEY record */ /** @return the public DNSKEY record */
public DNSKEYRecord getDNSKEYRecord() public DNSKEYRecord getDNSKEYRecord() {
{
return mPublicKeyRecord; return mPublicKeyRecord;
} }
@ -251,25 +232,17 @@ public class DnsKeyPair
* @return a Signature object initialized for signing, or null if this key * @return a Signature object initialized for signing, or null if this key
* pair does not have a valid private key. * pair does not have a valid private key.
*/ */
public Signature getSigner() public Signature getSigner() {
{ if (mSigner == null) {
if (mSigner == null)
{
mSigner = getSignature(); mSigner = getSignature();
PrivateKey priv = getPrivate(); PrivateKey priv = getPrivate();
if (mSigner != null && priv != null) if (mSigner != null && priv != null) {
{ try {
try
{
mSigner.initSign(priv); mSigner.initSign(priv);
} } catch (InvalidKeyException e) {
catch (InvalidKeyException e)
{
log.severe("Signature error: " + e); log.severe("Signature error: " + e);
} }
} } else {
else
{
// do not return an uninitialized signer. // do not return an uninitialized signer.
return null; return null;
} }
@ -283,24 +256,16 @@ public class DnsKeyPair
* pair does not have a valid public key. * pair does not have a valid public key.
* @throws NoSuchAlgorithmException * @throws NoSuchAlgorithmException
*/ */
public Signature getVerifier() public Signature getVerifier() {
{ if (mVerifier == null) {
if (mVerifier == null)
{
mVerifier = getSignature(); mVerifier = getSignature();
PublicKey pk = getPublic(); PublicKey pk = getPublic();
if (mVerifier != null && pk != null) if (mVerifier != null && pk != null) {
{ try {
try
{
mVerifier.initVerify(pk); mVerifier.initVerify(pk);
} catch (InvalidKeyException e) {
} }
catch (InvalidKeyException e) } else {
{
}
}
else
{
// do not return an uninitialized verifier // do not return an uninitialized verifier
return null; return null;
} }
@ -310,47 +275,48 @@ public class DnsKeyPair
} }
/** sets the public key record */ /** sets the public key record */
public void setDNSKEYRecord(DNSKEYRecord r) public void setDNSKEYRecord(DNSKEYRecord r) {
{
mPublicKeyRecord = r; mPublicKeyRecord = r;
// force the conversion to PublicKey: // force the conversion to PublicKey:
mPublicKey = null; mPublicKey = null;
} }
public Name getDNSKEYName() public Name getDNSKEYName() {
{
DNSKEYRecord kr = getDNSKEYRecord(); DNSKEYRecord kr = getDNSKEYRecord();
if (kr != null) return kr.getName(); if (kr != null)
return kr.getName();
return null; return null;
} }
public int getDNSKEYAlgorithm() public int getDNSKEYAlgorithm() {
{
DNSKEYRecord kr = getDNSKEYRecord(); DNSKEYRecord kr = getDNSKEYRecord();
if (kr != null) return kr.getAlgorithm(); if (kr != null)
return kr.getAlgorithm();
PublicKey pk = getPublic(); PublicKey pk = getPublic();
if (pk != null) if (pk != null) {
{
// currently, alg 5 is the default over alg 1 (RSASHA1). // currently, alg 5 is the default over alg 1 (RSASHA1).
if (pk instanceof RSAPublicKey) return DNSSEC.Algorithm.RSASHA1; if (pk instanceof RSAPublicKey)
if (pk instanceof DSAPublicKey) return DNSSEC.Algorithm.DSA; return DNSSEC.Algorithm.RSASHA1;
if (pk instanceof DSAPublicKey)
return DNSSEC.Algorithm.DSA;
} }
PrivateKey priv = getPrivate(); PrivateKey priv = getPrivate();
if (priv != null) if (priv != null) {
{ if (priv instanceof RSAPrivateKey)
if (priv instanceof RSAPrivateKey) return DNSSEC.Algorithm.RSASHA1; return DNSSEC.Algorithm.RSASHA1;
if (priv instanceof DSAPrivateKey) return DNSSEC.Algorithm.DSA; if (priv instanceof DSAPrivateKey)
return DNSSEC.Algorithm.DSA;
} }
return -1; return -1;
} }
public int getDNSKEYFootprint() public int getDNSKEYFootprint() {
{
DNSKEYRecord kr = getDNSKEYRecord(); DNSKEYRecord kr = getDNSKEYRecord();
if (kr != null) return kr.getFootprint(); if (kr != null)
return kr.getFootprint();
return -1; return -1;
} }
} }

View File

@ -1,6 +1,4 @@
// $Id$ // Copyright (C) 2001-2003, 2022 VeriSign, Inc.
//
// Copyright (C) 2001-2003 VeriSign, Inc.
// //
// This library is free software; you can redistribute it and/or // This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public // modify it under the terms of the GNU Lesser General Public
@ -24,14 +22,17 @@ import java.security.GeneralSecurityException;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.Signature; import java.security.Signature;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.xbill.DNS.*; import org.xbill.DNS.DNSKEYRecord;
import org.xbill.DNS.DNSSEC;
import org.xbill.DNS.Name;
import org.xbill.DNS.RRSIGRecord;
import org.xbill.DNS.RRset;
/** /**
* A class for performing basic DNSSEC verification. The DNSJAVA package * A class for performing basic DNSSEC verification. The DNSJAVA package
@ -39,62 +40,46 @@ import org.xbill.DNS.*;
* timing "fudge" factors and logging more specifically why an RRset did not * timing "fudge" factors and logging more specifically why an RRset did not
* validate. * validate.
* *
* @author David Blacka (original) * @author David Blacka
* @author $Author$
* @version $Revision$
*/ */
public class DnsSecVerifier public class DnsSecVerifier {
{
private class TrustedKeyStore private class TrustedKeyStore {
{
// for now, this is implemented as a hash table of lists of // for now, this is implemented as a hash table of lists of
// DnsKeyPair objects (obviously, all of them will not have // DnsKeyPair objects (obviously, all of them will not have
// private keys). // private keys).
private HashMap<String, List<DnsKeyPair>> mKeyMap; private HashMap<String, List<DnsKeyPair>> mKeyMap;
public TrustedKeyStore() public TrustedKeyStore() {
{ mKeyMap = new HashMap<>();
mKeyMap = new HashMap<String, List<DnsKeyPair>>();
} }
public void add(DnsKeyPair pair) public void add(DnsKeyPair pair) {
{
String n = pair.getDNSKEYName().toString().toLowerCase(); String n = pair.getDNSKEYName().toString().toLowerCase();
List<DnsKeyPair> l = mKeyMap.get(n); List<DnsKeyPair> l = mKeyMap.computeIfAbsent(n, k -> new ArrayList<>());
if (l == null)
{
l = new ArrayList<DnsKeyPair>();
mKeyMap.put(n, l);
}
l.add(pair); l.add(pair);
} }
public void add(DNSKEYRecord keyrec) public void add(DNSKEYRecord keyrec) {
{
DnsKeyPair pair = new DnsKeyPair(keyrec, (PrivateKey) null); DnsKeyPair pair = new DnsKeyPair(keyrec, (PrivateKey) null);
add(pair); add(pair);
} }
public void add(Name name, int algorithm, PublicKey key) public void add(Name name, int algorithm, PublicKey key) {
{
DnsKeyPair pair = new DnsKeyPair(name, algorithm, key, null); DnsKeyPair pair = new DnsKeyPair(name, algorithm, key, null);
add(pair); add(pair);
} }
public DnsKeyPair find(Name name, int algorithm, int keyid) public DnsKeyPair find(Name name, int algorithm, int keyid) {
{
String n = name.toString().toLowerCase(); String n = name.toString().toLowerCase();
List<DnsKeyPair> l = mKeyMap.get(n); List<DnsKeyPair> l = mKeyMap.get(n);
if (l == null) return null; if (l == null)
return null;
// FIXME: this algorithm assumes that name+alg+footprint is // FIXME: this algorithm assumes that name+alg+footprint is
// unique, which isn't necessarily true. // unique, which isn't necessarily true.
for (DnsKeyPair p : l) for (DnsKeyPair p : l) {
{ if (p.getDNSKEYAlgorithm() == algorithm && p.getDNSKEYFootprint() == keyid) {
if (p.getDNSKEYAlgorithm() == algorithm && p.getDNSKEYFootprint() == keyid)
{
return p; return p;
} }
} }
@ -110,119 +95,105 @@ public class DnsSecVerifier
private Logger log; private Logger log;
public DnsSecVerifier() public DnsSecVerifier() {
{
log = Logger.getLogger(this.getClass().toString()); log = Logger.getLogger(this.getClass().toString());
mKeyStore = new TrustedKeyStore(); mKeyStore = new TrustedKeyStore();
} }
public void addTrustedKey(DNSKEYRecord keyrec) public void addTrustedKey(DNSKEYRecord keyrec) {
{
mKeyStore.add(keyrec); mKeyStore.add(keyrec);
} }
public void addTrustedKey(DnsKeyPair pair) public void addTrustedKey(DnsKeyPair pair) {
{
mKeyStore.add(pair); mKeyStore.add(pair);
} }
public void addTrustedKey(Name name, int algorithm, PublicKey key) public void addTrustedKey(Name name, int algorithm, PublicKey key) {
{
mKeyStore.add(name, algorithm, key); mKeyStore.add(name, algorithm, key);
} }
public void addTrustedKey(Name name, PublicKey key) public void addTrustedKey(Name name, PublicKey key) {
{
mKeyStore.add(name, 0, key); mKeyStore.add(name, 0, key);
} }
public void setExpireFudge(int fudge) public void setExpireFudge(int fudge) {
{
mExpireFudge = fudge; mExpireFudge = fudge;
} }
public void setStartFudge(int fudge) public void setStartFudge(int fudge) {
{
mStartFudge = fudge; mStartFudge = fudge;
} }
public void setVerifyAllSigs(boolean v) public void setVerifyAllSigs(boolean v) {
{
mVerifyAllSigs = v; mVerifyAllSigs = v;
} }
public void setIgnoreTime(boolean v) public void setIgnoreTime(boolean v) {
{
mIgnoreTime = v; mIgnoreTime = v;
} }
private DnsKeyPair findKey(Name name, int algorithm, int footprint) private DnsKeyPair findKey(Name name, int algorithm, int footprint) {
{
return mKeyStore.find(name, algorithm, footprint); return mKeyStore.find(name, algorithm, footprint);
} }
private boolean validateSignature(RRset rrset, RRSIGRecord sigrec, List<String> reasons) private boolean validateSignature(RRset rrset, RRSIGRecord sigrec, List<String> reasons) {
{ if (rrset == null || sigrec == null)
if (rrset == null || sigrec == null) return false; return false;
if (!rrset.getName().equals(sigrec.getName())) if (!rrset.getName().equals(sigrec.getName())) {
{
log.fine("Signature name does not match RRset name"); log.fine("Signature name does not match RRset name");
if (reasons != null) reasons.add("Signature name does not match RRset name"); if (reasons != null)
reasons.add("Signature name does not match RRset name");
return false; return false;
} }
if (rrset.getType() != sigrec.getTypeCovered()) if (rrset.getType() != sigrec.getTypeCovered()) {
{
log.fine("Signature type does not match RRset type"); log.fine("Signature type does not match RRset type");
if (reasons != null) reasons.add("Signature type does not match RRset type"); if (reasons != null)
reasons.add("Signature type does not match RRset type");
} }
if (mIgnoreTime) return true; if (mIgnoreTime)
return true;
Date now = new Date(); Instant now = Instant.now();
Date start = sigrec.getTimeSigned(); Instant start = sigrec.getTimeSigned();
Date expire = sigrec.getExpire(); Instant expire = sigrec.getExpire();
if (mStartFudge >= 0) if (mStartFudge >= 0) {
{ if (mStartFudge > 0) {
if (mStartFudge > 0) start = start.minusSeconds(mStartFudge);
{
start = new Date(start.getTime() - ((long) mStartFudge * 1000));
} }
if (now.before(start)) if (now.isBefore(start)) {
{
log.fine("Signature is not yet valid"); log.fine("Signature is not yet valid");
if (reasons != null) reasons.add("Signature not yet valid"); if (reasons != null)
reasons.add("Signature not yet valid");
return false; return false;
} }
} }
if (mExpireFudge >= 0) if (mExpireFudge >= 0) {
{ if (mExpireFudge > 0) {
if (mExpireFudge > 0) expire = expire.plusSeconds(mExpireFudge);
{
expire = new Date(expire.getTime() + ((long) mExpireFudge * 1000));
} }
if (now.after(expire)) if (now.isAfter(expire)) {
{
log.fine("Signature has expired (now = " + now + ", sig expires = " + expire); log.fine("Signature has expired (now = " + now + ", sig expires = " + expire);
if (reasons != null) reasons.add("Signature has expired."); if (reasons != null)
reasons.add("Signature has expired.");
return false; return false;
} }
} }
if (rrset.getTTL() > sigrec.getOrigTTL()) if (rrset.getTTL() > sigrec.getOrigTTL()) {
{
log.fine("RRset's TTL is greater than the Signature's orignal TTL"); log.fine("RRset's TTL is greater than the Signature's orignal TTL");
if (reasons != null) reasons.add("RRset TTL greater than RRSIG origTTL"); if (reasons != null)
reasons.add("RRset TTL greater than RRSIG origTTL");
return false; return false;
} }
return true; return true;
} }
public boolean verifySignature(RRset rrset, RRSIGRecord sigrec) public boolean verifySignature(RRset rrset, RRSIGRecord sigrec) {
{
return verifySignature(rrset, sigrec, null); return verifySignature(rrset, sigrec, null);
} }
@ -232,23 +203,22 @@ public class DnsSecVerifier
* @return true if the signature verified, false if it did * @return true if the signature verified, false if it did
* not verify (for any reason, including not finding the DNSKEY.) * not verify (for any reason, including not finding the DNSKEY.)
*/ */
public boolean verifySignature(RRset rrset, RRSIGRecord sigrec, List<String> reasons) public boolean verifySignature(RRset rrset, RRSIGRecord sigrec, List<String> reasons) {
{
boolean result = validateSignature(rrset, sigrec, reasons); boolean result = validateSignature(rrset, sigrec, reasons);
if (!result) return result; if (!result)
return result;
DnsKeyPair keypair = findKey(sigrec.getSigner(), sigrec.getAlgorithm(), DnsKeyPair keypair = findKey(sigrec.getSigner(), sigrec.getAlgorithm(),
sigrec.getFootprint()); sigrec.getFootprint());
if (keypair == null) if (keypair == null) {
{ if (reasons != null)
if (reasons != null) reasons.add("Could not find matching trusted key"); reasons.add("Could not find matching trusted key");
log.fine("could not find matching trusted key"); log.fine("could not find matching trusted key");
return false; return false;
} }
try try {
{
byte[] data = SignUtils.generateSigData(rrset, sigrec); byte[] data = SignUtils.generateSigData(rrset, sigrec);
DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance(); DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance();
@ -258,35 +228,30 @@ public class DnsSecVerifier
byte[] sig = sigrec.getSignature(); byte[] sig = sigrec.getSignature();
if (algs.baseType(sigrec.getAlgorithm()) == DnsKeyAlgorithm.DSA) if (algs.baseType(sigrec.getAlgorithm()) == DnsKeyAlgorithm.DSA) {
{
sig = SignUtils.convertDSASignature(sig); sig = SignUtils.convertDSASignature(sig);
} }
if (sigrec.getAlgorithm() == DNSSEC.Algorithm.ECDSAP256SHA256 || if (sigrec.getAlgorithm() == DNSSEC.Algorithm.ECDSAP256SHA256 ||
sigrec.getAlgorithm() == DNSSEC.Algorithm.ECDSAP384SHA384) sigrec.getAlgorithm() == DNSSEC.Algorithm.ECDSAP384SHA384) {
{
sig = SignUtils.convertECDSASignature(sig); sig = SignUtils.convertECDSASignature(sig);
} }
if (!signer.verify(sig)) if (!signer.verify(sig)) {
{ if (reasons != null)
if (reasons != null) reasons.add("Signature failed to verify cryptographically"); reasons.add("Signature failed to verify cryptographically");
log.fine("Signature failed to verify cryptographically"); log.fine("Signature failed to verify cryptographically");
return false; return false;
} }
return true; return true;
} } catch (IOException e) {
catch (IOException e)
{
log.severe("I/O error: " + e); log.severe("I/O error: " + e);
} } catch (GeneralSecurityException e) {
catch (GeneralSecurityException e)
{
log.severe("Security error: " + e); log.severe("Security error: " + e);
} }
if (reasons != null) reasons.add("Signature failed to verify due to exception"); if (reasons != null)
reasons.add("Signature failed to verify due to exception");
log.fine("Signature failed to verify due to exception"); log.fine("Signature failed to verify due to exception");
return false; return false;
} }
@ -296,30 +261,24 @@ public class DnsSecVerifier
* *
* @return true if the set verified, false if it did not. * @return true if the set verified, false if it did not.
*/ */
public boolean verify(RRset rrset) public boolean verify(RRset rrset) {
{
boolean result = mVerifyAllSigs ? true : false; boolean result = mVerifyAllSigs ? true : false;
Iterator i = rrset.sigs(); if (rrset.sigs().isEmpty()) {
if (!i.hasNext())
{
log.fine("RRset failed to verify due to lack of signatures"); log.fine("RRset failed to verify due to lack of signatures");
return false; return false;
} }
for (RRSIGRecord sigrec : rrset.sigs()) {
while (i.hasNext())
{
RRSIGRecord sigrec = (RRSIGRecord) i.next();
boolean res = verifySignature(rrset, sigrec); boolean res = verifySignature(rrset, sigrec);
// If not requiring all signature to validate, then any successful validation is sufficient. // If not requiring all signature to validate, then any successful validation is
if (!mVerifyAllSigs && res) return res; // sufficient.
if (!mVerifyAllSigs && res)
return res;
// Otherwise, note if a signature failed to validate. // Otherwise, note if a signature failed to validate.
if (mVerifyAllSigs && !res) if (mVerifyAllSigs && !res) {
{
result = res; result = res;
} }
} }

View File

@ -1,6 +1,4 @@
// $Id$ // Copyright (C) 2001-2003, 2009, 2022 Verisign, Inc.
//
// Copyright (C) 2001-2003, 2009 VeriSign, Inc.
// //
// This library is free software; you can redistribute it and/or // This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public // modify it under the terms of the GNU Lesser General Public
@ -25,15 +23,20 @@ import java.security.KeyPair;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.Signature; import java.security.Signature;
import java.security.interfaces.DSAPublicKey; import java.security.interfaces.DSAPublicKey;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.ListIterator; import java.util.ListIterator;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.xbill.DNS.*; import org.xbill.DNS.DNSKEYRecord;
import org.xbill.DNS.DNSSEC;
import org.xbill.DNS.Name;
import org.xbill.DNS.RRSIGRecord;
import org.xbill.DNS.RRset;
import org.xbill.DNS.Record;
import org.xbill.DNS.Type;
import org.xbill.DNS.utils.hexdump; import org.xbill.DNS.utils.hexdump;
/** /**
@ -43,26 +46,21 @@ import org.xbill.DNS.utils.hexdump;
* the ability to sign an entire zone. It primarily glues together the more * the ability to sign an entire zone. It primarily glues together the more
* basic primitives found in {@link SignUtils}. * basic primitives found in {@link SignUtils}.
* *
* @author David Blacka (original) * @author David Blacka
* @author $Author$
* @version $Revision$
*/ */
public class JCEDnsSecSigner public class JCEDnsSecSigner {
{
private DnsKeyConverter mKeyConverter; private DnsKeyConverter mKeyConverter;
private boolean mVerboseSigning = false; private boolean mVerboseSigning = false;
private Logger log = Logger.getLogger(this.getClass().toString()); private Logger log = Logger.getLogger(this.getClass().toString());
public JCEDnsSecSigner() public JCEDnsSecSigner() {
{
this.mKeyConverter = null; this.mKeyConverter = null;
this.mVerboseSigning = false; this.mVerboseSigning = false;
} }
public JCEDnsSecSigner(boolean verboseSigning) public JCEDnsSecSigner(boolean verboseSigning) {
{
this.mKeyConverter = null; this.mKeyConverter = null;
this.mVerboseSigning = verboseSigning; this.mVerboseSigning = verboseSigning;
} }
@ -77,7 +75,8 @@ public class JCEDnsSecSigner
* @param dclass * @param dclass
* the KEY RR's DNS class. * the KEY RR's DNS class.
* @param algorithm * @param algorithm
* the DNSSEC algorithm (RSAMD5, RSASHA1, or DSA). * the DNSSEC algorithm (RSASHA258, RSASHA512,
* ECDSAP256, etc.)
* @param flags * @param flags
* any flags for the KEY RR. * any flags for the KEY RR.
* @param keysize * @param keysize
@ -88,16 +87,15 @@ public class JCEDnsSecSigner
*/ */
public DnsKeyPair generateKey(Name owner, long ttl, int dclass, int algorithm, public DnsKeyPair generateKey(Name owner, long ttl, int dclass, int algorithm,
int flags, int keysize, boolean useLargeExponent) int flags, int keysize, boolean useLargeExponent)
throws NoSuchAlgorithmException throws NoSuchAlgorithmException {
{
DnsKeyAlgorithm algorithms = DnsKeyAlgorithm.getInstance(); DnsKeyAlgorithm algorithms = DnsKeyAlgorithm.getInstance();
if (ttl < 0) ttl = 86400; // set to a reasonable default. if (ttl < 0)
ttl = 86400; // set to a reasonable default.
KeyPair pair = algorithms.generateKeyPair(algorithm, keysize, useLargeExponent); KeyPair pair = algorithms.generateKeyPair(algorithm, keysize, useLargeExponent);
if (mKeyConverter == null) if (mKeyConverter == null) {
{
mKeyConverter = new DnsKeyConverter(); mKeyConverter = new DnsKeyConverter();
} }
@ -125,19 +123,21 @@ public class JCEDnsSecSigner
* the expiration time for the resulting RRSIG records. * the expiration time for the resulting RRSIG records.
* @return a list of RRSIGRecord objects. * @return a list of RRSIGRecord objects.
*/ */
public List<RRSIGRecord> signRRset(RRset rrset, List<DnsKeyPair> keypairs, Date start, public List<RRSIGRecord> signRRset(RRset rrset, List<DnsKeyPair> keypairs, Instant start,
Date expire) throws IOException, Instant expire) throws IOException,
GeneralSecurityException GeneralSecurityException {
{ if (rrset == null || keypairs == null)
if (rrset == null || keypairs == null) return null; return null;
// default start to now, expire to start + 1 second. // default start to now, expire to start + 1 second.
if (start == null) start = new Date(); if (start == null)
if (expire == null) expire = new Date(start.getTime() + 1000L); start = Instant.now();
if (keypairs.size() == 0) return null; if (expire == null)
expire = start.plusSeconds(1);
if (keypairs.size() == 0)
return null;
if (mVerboseSigning) if (mVerboseSigning) {
{
log.info("Signing RRset:"); log.info("Signing RRset:");
log.info(ZoneUtils.rrsetToString(rrset, false)); log.info(ZoneUtils.rrsetToString(rrset, false));
} }
@ -148,17 +148,16 @@ public class JCEDnsSecSigner
ArrayList<RRSIGRecord> sigs = new ArrayList<RRSIGRecord>(keypairs.size()); ArrayList<RRSIGRecord> sigs = new ArrayList<RRSIGRecord>(keypairs.size());
// for each keypair, sign the RRset. // for each keypair, sign the RRset.
for (DnsKeyPair pair : keypairs) for (DnsKeyPair pair : keypairs) {
{
DNSKEYRecord keyrec = pair.getDNSKEYRecord(); DNSKEYRecord keyrec = pair.getDNSKEYRecord();
if (keyrec == null) continue; if (keyrec == null)
continue;
RRSIGRecord presig = SignUtils.generatePreRRSIG(rrset, keyrec, start, expire, RRSIGRecord presig = SignUtils.generatePreRRSIG(rrset, keyrec, start, expire,
rrset.getTTL()); rrset.getTTL());
byte[] sign_data = SignUtils.generateSigData(rrset_data, presig); byte[] sign_data = SignUtils.generateSigData(rrset_data, presig);
if (mVerboseSigning) if (mVerboseSigning) {
{
log.info("Canonical pre-signature data to sign with key " log.info("Canonical pre-signature data to sign with key "
+ keyrec.getName().toString() + "/" + keyrec.getAlgorithm() + "/" + keyrec.getName().toString() + "/" + keyrec.getAlgorithm() + "/"
+ keyrec.getFootprint() + ":"); + keyrec.getFootprint() + ":");
@ -167,8 +166,7 @@ public class JCEDnsSecSigner
Signature signer = pair.getSigner(); Signature signer = pair.getSigner();
if (signer == null) if (signer == null) {
{
// debug // debug
log.fine("missing private key that goes with:\n" + pair.getDNSKEYRecord()); log.fine("missing private key that goes with:\n" + pair.getDNSKEYRecord());
throw new GeneralSecurityException("cannot sign without a valid Signer " throw new GeneralSecurityException("cannot sign without a valid Signer "
@ -179,28 +177,24 @@ public class JCEDnsSecSigner
signer.update(sign_data); signer.update(sign_data);
byte[] sig = signer.sign(); byte[] sig = signer.sign();
if (mVerboseSigning) if (mVerboseSigning) {
{
log.info("Raw Signature:"); log.info("Raw Signature:");
log.info(hexdump.dump(null, sig)); log.info(hexdump.dump(null, sig));
} }
DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance(); DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance();
// Convert to RFC 2536 format, if necessary. // Convert to RFC 2536 format, if necessary.
if (algs.baseType(pair.getDNSKEYAlgorithm()) == DnsKeyAlgorithm.DSA) if (algs.baseType(pair.getDNSKEYAlgorithm()) == DnsKeyAlgorithm.DSA) {
{
DSAPublicKey pk = (DSAPublicKey) pair.getPublic(); DSAPublicKey pk = (DSAPublicKey) pair.getPublic();
sig = SignUtils.convertDSASignature(pk.getParams(), sig); sig = SignUtils.convertDSASignature(pk.getParams(), sig);
} }
// Convert to RFC 6605, etc format // Convert to RFC 6605, etc format
if (pair.getDNSKEYAlgorithm() == DNSSEC.Algorithm.ECDSAP256SHA256 || if (pair.getDNSKEYAlgorithm() == DNSSEC.Algorithm.ECDSAP256SHA256 ||
pair.getDNSKEYAlgorithm() == DNSSEC.Algorithm.ECDSAP384SHA384) pair.getDNSKEYAlgorithm() == DNSSEC.Algorithm.ECDSAP384SHA384) {
{
sig = SignUtils.convertECDSASignature(pair.getDNSKEYAlgorithm(), sig); sig = SignUtils.convertECDSASignature(pair.getDNSKEYAlgorithm(), sig);
} }
RRSIGRecord sigrec = SignUtils.generateRRSIG(sig, presig); RRSIGRecord sigrec = SignUtils.generateRRSIG(sig, presig);
if (mVerboseSigning) if (mVerboseSigning) {
{
log.info("RRSIG:\n" + sigrec); log.info("RRSIG:\n" + sigrec);
} }
sigs.add(sigrec); sigs.add(sigrec);
@ -220,22 +214,19 @@ public class JCEDnsSecSigner
* the RRSIG expiration time. * the RRSIG expiration time.
* @return a signed RRset. * @return a signed RRset.
*/ */
public RRset makeKeySet(List<DnsKeyPair> keypairs, Date start, Date expire) public RRset makeKeySet(List<DnsKeyPair> keypairs, Instant start, Instant expire)
throws IOException, GeneralSecurityException throws IOException, GeneralSecurityException {
{
// Generate a KEY RR set to sign. // Generate a KEY RR set to sign.
RRset keyset = new RRset(); RRset keyset = new RRset();
for (DnsKeyPair pair : keypairs) for (DnsKeyPair pair : keypairs) {
{
keyset.addRR(pair.getDNSKEYRecord()); keyset.addRR(pair.getDNSKEYRecord());
} }
List<RRSIGRecord> records = signRRset(keyset, keypairs, start, expire); List<RRSIGRecord> records = signRRset(keyset, keypairs, start, expire);
for (RRSIGRecord r : records) for (RRSIGRecord r : records) {
{
keyset.addRR(r); keyset.addRR(r);
} }
@ -260,49 +251,44 @@ public class JCEDnsSecSigner
* @param expire * @param expire
* the RRSIG expiration time. * the RRSIG expiration time.
* @param fullySignKeyset * @param fullySignKeyset
* if true, sign the zone apex keyset with both KSKs and ZSKs. * if true, sign the zone apex keyset with both KSKs and
* ZSKs.
* @param last_cut * @param last_cut
* the name of the last delegation point encountered. * the name of the last delegation point encountered.
* *
* @return the name of the new last_cut. * @return the name of the new last_cut.
*/ */
@SuppressWarnings("unchecked")
private Name addRRset(List<Record> toList, Name zonename, RRset rrset, private Name addRRset(List<Record> toList, Name zonename, RRset rrset,
List<DnsKeyPair> kskpairs, List<DnsKeyPair> zskpairs, Date start, List<DnsKeyPair> kskpairs, List<DnsKeyPair> zskpairs, Instant start,
Date expire, boolean fullySignKeyset, Name last_cut, Instant expire, boolean fullySignKeyset, Name last_cut,
Name last_dname) throws IOException, GeneralSecurityException Name last_dname) throws IOException, GeneralSecurityException {
{
// add the records themselves // add the records themselves
for (Iterator<Record> i = rrset.rrs(); i.hasNext();) rrset.rrs().forEach(record -> {
{ toList.add(record);
toList.add(i.next()); });
}
int type = SignUtils.recordSecType(zonename, rrset.getName(), rrset.getType(), int type = SignUtils.recordSecType(zonename, rrset.getName(), rrset.getType(),
last_cut, last_dname); last_cut, last_dname);
// we don't sign non-normal sets (delegations, glue, invalid). // we don't sign non-normal sets (delegations, glue, invalid).
if (type == SignUtils.RR_DELEGATION) if (type == SignUtils.RR_DELEGATION) {
{
return rrset.getName(); return rrset.getName();
} }
if (type == SignUtils.RR_GLUE || type == SignUtils.RR_INVALID) if (type == SignUtils.RR_GLUE || type == SignUtils.RR_INVALID) {
{
return last_cut; return last_cut;
} }
// check for the zone apex keyset. // check for the zone apex keyset.
if (rrset.getName().equals(zonename) && rrset.getType() == Type.DNSKEY) if (rrset.getName().equals(zonename) && rrset.getType() == Type.DNSKEY) {
{
// if we have ksks, sign the keyset with them, otherwise we will just sign // if we have ksks, sign the keyset with them, otherwise we will just sign
// them with the zsks. // them with the zsks.
if (kskpairs != null && kskpairs.size() > 0) if (kskpairs != null && kskpairs.size() > 0) {
{
List<RRSIGRecord> sigs = signRRset(rrset, kskpairs, start, expire); List<RRSIGRecord> sigs = signRRset(rrset, kskpairs, start, expire);
toList.addAll(sigs); toList.addAll(sigs);
// If we aren't going to sign with all the keys, bail out now. // If we aren't going to sign with all the keys, bail out now.
if (!fullySignKeyset) return last_cut; if (!fullySignKeyset)
return last_cut;
} }
} }
@ -328,8 +314,10 @@ public class JCEDnsSecSigner
* @param zonename * @param zonename
* The name of the zone * The name of the zone
* @param records * @param records
* The records comprising the zone. They do not have to be in any * The records comprising the zone. They do not have to
* particular order, as this method will order them as necessary. * be in any
* particular order, as this method will order them as
* necessary.
* @param kskpairs * @param kskpairs
* The key pairs designated as "key signing keys" * The key pairs designated as "key signing keys"
* @param zskpairs * @param zskpairs
@ -339,7 +327,8 @@ public class JCEDnsSecSigner
* @param expire * @param expire
* The RRSIG expiration time * The RRSIG expiration time
* @param fullySignKeyset * @param fullySignKeyset
* If true, all keys (ksk or zsk) will sign the DNSKEY RRset. If * If true, all keys (ksk or zsk) will sign the DNSKEY
* RRset. If
* false, only the ksks will sign it. * false, only the ksks will sign it.
* @param ds_digest_alg * @param ds_digest_alg
* The hash algorithm to use for generating DS records * The hash algorithm to use for generating DS records
@ -348,18 +337,22 @@ public class JCEDnsSecSigner
* The NSEC/NSEC3 generation mode: NSEC_MODE, NSEC3_MODE, * The NSEC/NSEC3 generation mode: NSEC_MODE, NSEC3_MODE,
* NSEC3_OPTOUT_MODE, etc. * NSEC3_OPTOUT_MODE, etc.
* @param includedNames * @param includedNames
* When using an Opt-In/Opt-Out mode, the names listed here will be * When using an Opt-In/Opt-Out mode, the names listed
* here will be
* included in the NSEC/NSEC3 chain regardless * included in the NSEC/NSEC3 chain regardless
* @param salt * @param salt
* When using an NSEC3 mode, use this salt. * When using an NSEC3 mode, use this salt.
* @param iterations * @param iterations
* When using an NSEC3 mode, use this number of iterations * When using an NSEC3 mode, use this number of
* iterations
* @param beConservative * @param beConservative
* If true, then only turn on the Opt-In flag when there are insecure * If true, then only turn on the Opt-In flag when there
* are insecure
* delegations in the span. Currently this only works for * delegations in the span. Currently this only works for
* NSEC_EXP_OPT_IN mode. * NSEC_EXP_OPT_IN mode.
* @param nsec3paramttl * @param nsec3paramttl
* The TTL to use for the generated NSEC3PARAM record. Negative * The TTL to use for the generated NSEC3PARAM record.
* Negative
* values will use the SOA TTL. * values will use the SOA TTL.
* @return an ordered list of {@link org.xbill.DNS.Record} objects, * @return an ordered list of {@link org.xbill.DNS.Record} objects,
* representing the signed zone. * representing the signed zone.
@ -369,12 +362,11 @@ public class JCEDnsSecSigner
*/ */
private List<Record> signZone(Name zonename, List<Record> records, private List<Record> signZone(Name zonename, List<Record> records,
List<DnsKeyPair> kskpairs, List<DnsKeyPair> zskpairs, List<DnsKeyPair> kskpairs, List<DnsKeyPair> zskpairs,
Date start, Date expire, boolean fullySignKeyset, Instant start, Instant expire, boolean fullySignKeyset,
int ds_digest_alg, int mode, List<Name> includedNames, int ds_digest_alg, int mode, List<Name> includedNames,
byte[] salt, int iterations, long nsec3paramttl, byte[] salt, int iterations, long nsec3paramttl,
boolean beConservative) throws IOException, boolean beConservative) throws IOException,
GeneralSecurityException GeneralSecurityException {
{
// Remove any existing generated DNSSEC records (NSEC, NSEC3, NSEC3PARAM, // Remove any existing generated DNSSEC records (NSEC, NSEC3, NSEC3PARAM,
// RRSIG) // RRSIG)
SignUtils.removeGeneratedRecords(zonename, records); SignUtils.removeGeneratedRecords(zonename, records);
@ -391,8 +383,7 @@ public class JCEDnsSecSigner
SignUtils.generateDSRecords(zonename, records, ds_digest_alg); SignUtils.generateDSRecords(zonename, records, ds_digest_alg);
// Generate the NSEC or NSEC3 records based on 'mode' // Generate the NSEC or NSEC3 records based on 'mode'
switch (mode) switch (mode) {
{
case NSEC_MODE: case NSEC_MODE:
SignUtils.generateNSECRecords(zonename, records); SignUtils.generateNSECRecords(zonename, records);
break; break;
@ -418,21 +409,18 @@ public class JCEDnsSecSigner
Name last_cut = null; Name last_cut = null;
Name last_dname = null; Name last_dname = null;
for (ListIterator<Record> i = records.listIterator(); i.hasNext();) for (ListIterator<Record> i = records.listIterator(); i.hasNext();) {
{
Record r = i.next(); Record r = i.next();
// First record // First record
if (rrset.size() == 0) if (rrset.size() == 0) {
{
rrset.addRR(r); rrset.addRR(r);
continue; continue;
} }
// Current record is part of the current RRset. // Current record is part of the current RRset.
if (rrset.getName().equals(r.getName()) && rrset.getDClass() == r.getDClass() if (rrset.getName().equals(r.getName()) && rrset.getDClass() == r.getDClass()
&& rrset.getType() == r.getType()) && rrset.getType() == r.getType()) {
{
rrset.addRR(r); rrset.addRR(r);
continue; continue;
} }
@ -444,7 +432,8 @@ public class JCEDnsSecSigner
// whether or not we actually end up signing the set. // whether or not we actually end up signing the set.
last_cut = addRRset(signed_records, zonename, rrset, kskpairs, zskpairs, start, last_cut = addRRset(signed_records, zonename, rrset, kskpairs, zskpairs, start,
expire, fullySignKeyset, last_cut, last_dname); expire, fullySignKeyset, last_cut, last_dname);
if (rrset.getType() == Type.DNAME) last_dname = rrset.getName(); if (rrset.getType() == Type.DNAME)
last_dname = rrset.getName();
rrset.clear(); rrset.clear();
rrset.addRR(r); rrset.addRR(r);
@ -463,31 +452,36 @@ public class JCEDnsSecSigner
* @param zonename * @param zonename
* The name of the zone. * The name of the zone.
* @param records * @param records
* The records comprising the zone. They do not have to be in any * The records comprising the zone. They do not have to
* particular order, as this method will order them as necessary. * be in any
* particular order, as this method will order them as
* necessary.
* @param kskpairs * @param kskpairs
* The key pairs that are designated as "key signing keys". * The key pairs that are designated as "key signing
* keys".
* @param zskpairs * @param zskpairs
* This key pairs that are designated as "zone signing keys". * This key pairs that are designated as "zone signing
* keys".
* @param start * @param start
* The RRSIG inception time. * The RRSIG inception time.
* @param expire * @param expire
* The RRSIG expiration time. * The RRSIG expiration time.
* @param fullySignKeyset * @param fullySignKeyset
* Sign the zone apex keyset with all available keys (instead of just * Sign the zone apex keyset with all available keys
* (instead of just
* the key signing keys). * the key signing keys).
* @param ds_digest_alg * @param ds_digest_alg
* The digest algorithm to use when generating DS records. * The digest algorithm to use when generating DS
* records.
* *
* @return an ordered list of {@link org.xbill.DNS.Record} objects, * @return an ordered list of {@link org.xbill.DNS.Record} objects,
* representing the signed zone. * representing the signed zone.
*/ */
public List<Record> signZone(Name zonename, List<Record> records, public List<Record> signZone(Name zonename, List<Record> records,
List<DnsKeyPair> kskpairs, List<DnsKeyPair> zskpairs, List<DnsKeyPair> kskpairs, List<DnsKeyPair> zskpairs,
Date start, Date expire, boolean fullySignKeyset, Instant start, Instant expire, boolean fullySignKeyset,
int ds_digest_alg) throws IOException, int ds_digest_alg) throws IOException,
GeneralSecurityException GeneralSecurityException {
{
return signZone(zonename, records, kskpairs, zskpairs, start, expire, return signZone(zonename, records, kskpairs, zskpairs, start, expire,
fullySignKeyset, ds_digest_alg, NSEC_MODE, null, null, 0, 0, false); fullySignKeyset, ds_digest_alg, NSEC_MODE, null, null, 0, 0, false);
} }
@ -500,32 +494,43 @@ public class JCEDnsSecSigner
* @param zonename * @param zonename
* The name of the zone being signed. * The name of the zone being signed.
* @param records * @param records
* The records comprising the zone. They do not have to be in any * The records comprising the zone. They do not have to
* particular order, as this method will order them as necessary. * be in any
* particular order, as this method will order them as
* necessary.
* @param kskpairs * @param kskpairs
* The key pairs that are designated as "key signing keys". * The key pairs that are designated as "key signing
* keys".
* @param zskpairs * @param zskpairs
* This key pairs that are designated as "zone signing keys". * This key pairs that are designated as "zone signing
* keys".
* @param start * @param start
* The RRSIG inception time. * The RRSIG inception time.
* @param expire * @param expire
* The RRSIG expiration time. * The RRSIG expiration time.
* @param fullySignKeyset * @param fullySignKeyset
* If true then the DNSKEY RRset will be signed by all available * If true then the DNSKEY RRset will be signed by all
* available
* keys, if false, only the key signing keys. * keys, if false, only the key signing keys.
* @param useOptOut * @param useOptOut
* If true, insecure delegations will be omitted from the NSEC3 * If true, insecure delegations will be omitted from the
* chain, and all NSEC3 records will have the Opt-Out flag set. * NSEC3
* chain, and all NSEC3 records will have the Opt-Out
* flag set.
* @param includedNames * @param includedNames
* A list of names to include in the NSEC3 chain regardless. * A list of names to include in the NSEC3 chain
* regardless.
* @param salt * @param salt
* The salt to use for the NSEC3 hashing. null means no salt. * The salt to use for the NSEC3 hashing. null means no
* salt.
* @param iterations * @param iterations
* The number of iterations to use for the NSEC3 hashing. * The number of iterations to use for the NSEC3 hashing.
* @param ds_digest_alg * @param ds_digest_alg
* The digest algorithm to use when generating DS records. * The digest algorithm to use when generating DS
* records.
* @param nsec3paramttl * @param nsec3paramttl
* The TTL to use for the generated NSEC3PARAM record. Negative * The TTL to use for the generated NSEC3PARAM record.
* Negative
* values will use the SOA TTL. * values will use the SOA TTL.
* @return an ordered list of {@link org.xbill.DNS.Record} objects, * @return an ordered list of {@link org.xbill.DNS.Record} objects,
* representing the signed zone. * representing the signed zone.
@ -535,20 +540,16 @@ public class JCEDnsSecSigner
*/ */
public List<Record> signZoneNSEC3(Name zonename, List<Record> records, public List<Record> signZoneNSEC3(Name zonename, List<Record> records,
List<DnsKeyPair> kskpairs, List<DnsKeyPair> zskpairs, List<DnsKeyPair> kskpairs, List<DnsKeyPair> zskpairs,
Date start, Date expire, boolean fullySignKeyset, Instant start, Instant expire, boolean fullySignKeyset,
boolean useOptOut, List<Name> includedNames, boolean useOptOut, List<Name> includedNames,
byte[] salt, int iterations, int ds_digest_alg, byte[] salt, int iterations, int ds_digest_alg,
long nsec3paramttl) throws IOException, long nsec3paramttl) throws IOException,
GeneralSecurityException GeneralSecurityException {
{ if (useOptOut) {
if (useOptOut)
{
return signZone(zonename, records, kskpairs, zskpairs, start, expire, return signZone(zonename, records, kskpairs, zskpairs, start, expire,
fullySignKeyset, ds_digest_alg, NSEC3_OPTOUT_MODE, includedNames, fullySignKeyset, ds_digest_alg, NSEC3_OPTOUT_MODE, includedNames,
salt, iterations, nsec3paramttl, false); salt, iterations, nsec3paramttl, false);
} } else {
else
{
return signZone(zonename, records, kskpairs, zskpairs, start, expire, return signZone(zonename, records, kskpairs, zskpairs, start, expire,
fullySignKeyset, ds_digest_alg, NSEC3_MODE, null, salt, iterations, fullySignKeyset, ds_digest_alg, NSEC3_MODE, null, salt, iterations,
nsec3paramttl, false); nsec3paramttl, false);
@ -562,37 +563,44 @@ public class JCEDnsSecSigner
* @param zonename * @param zonename
* the name of the zone. * the name of the zone.
* @param records * @param records
* the records comprising the zone. They do not have to be in any * the records comprising the zone. They do not have
* particular order, as this method will order them as necessary. * to be in any
* particular order, as this method will order them
* as necessary.
* @param kskpairs * @param kskpairs
* the key pairs that are designated as "key signing keys". * the key pairs that are designated as "key signing
* keys".
* @param zskpairs * @param zskpairs
* this key pairs that are designated as "zone signing keys". * this key pairs that are designated as "zone
* signing keys".
* @param start * @param start
* the RRSIG inception time. * the RRSIG inception time.
* @param expire * @param expire
* the RRSIG expiration time. * the RRSIG expiration time.
* @param useConservativeOptIn * @param useConservativeOptIn
* if true, Opt-In NSEC records will only be generated if there are * if true, Opt-In NSEC records will only be
* generated if there are
* insecure, unsigned delegations in the span. * insecure, unsigned delegations in the span.
* @param fullySignKeyset * @param fullySignKeyset
* sign the zone apex keyset with all available keys. * sign the zone apex keyset with all available
* keys.
* @param ds_digest_alg * @param ds_digest_alg
* The digest algorithm to use when generating DS records. * The digest algorithm to use when generating DS
* records.
* @param NSECIncludeNames * @param NSECIncludeNames
* names that are to be included in the NSEC chain regardless. This * names that are to be included in the NSEC chain
* regardless. This
* may be null. * may be null.
* @return an ordered list of {@link org.xbill.DNS.Record} objects, * @return an ordered list of {@link org.xbill.DNS.Record} objects,
* representing the signed zone. * representing the signed zone.
*/ */
public List<Record> signZoneOptIn(Name zonename, List<Record> records, public List<Record> signZoneOptIn(Name zonename, List<Record> records,
List<DnsKeyPair> kskpairs, List<DnsKeyPair> zskpairs, List<DnsKeyPair> kskpairs, List<DnsKeyPair> zskpairs,
Date start, Date expire, Instant start, Instant expire,
boolean useConservativeOptIn, boolean useConservativeOptIn,
boolean fullySignKeyset, List<Name> NSECIncludeNames, boolean fullySignKeyset, List<Name> NSECIncludeNames,
int ds_digest_alg) throws IOException, int ds_digest_alg) throws IOException,
GeneralSecurityException GeneralSecurityException {
{
return signZone(zonename, records, kskpairs, zskpairs, start, expire, return signZone(zonename, records, kskpairs, zskpairs, start, expire,
fullySignKeyset, ds_digest_alg, NSEC_EXP_OPT_IN, NSECIncludeNames, fullySignKeyset, ds_digest_alg, NSEC_EXP_OPT_IN, NSECIncludeNames,

View File

@ -1,7 +1,5 @@
/* /*
* $Id$ * Copyright (c) 2005, 2022 Verisign. All rights reserved.
*
* Copyright (c) 2005 VeriSign. All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met: * modification, are permitted provided that the following conditions are met:
@ -29,7 +27,10 @@
package com.verisignlabs.dnssec.security; package com.verisignlabs.dnssec.security;
import org.xbill.DNS.*; import org.xbill.DNS.DClass;
import org.xbill.DNS.NSEC3Record;
import org.xbill.DNS.Name;
import org.xbill.DNS.TextParseException;
import org.xbill.DNS.utils.base16; import org.xbill.DNS.utils.base16;
import org.xbill.DNS.utils.base32; import org.xbill.DNS.utils.base32;
@ -38,12 +39,9 @@ import org.xbill.DNS.utils.base32;
* used as an intermediate stage (in zone signing) between determining the list * used as an intermediate stage (in zone signing) between determining the list
* of NSEC3 records and forming them into a viable chain. * of NSEC3 records and forming them into a viable chain.
* *
* @author David Blacka (original) * @author David Blacka
* @author $Author: davidb $
* @version $Revision: 183 $
*/ */
public class ProtoNSEC3 public class ProtoNSEC3 {
{
private Name originalOwner; private Name originalOwner;
private int hashAlg; private int hashAlg;
private byte flags; private byte flags;
@ -64,8 +62,7 @@ public class ProtoNSEC3
*/ */
public ProtoNSEC3(byte[] owner, Name originalOwner, Name zone, int dclass, long ttl, public ProtoNSEC3(byte[] owner, Name originalOwner, Name zone, int dclass, long ttl,
int hashAlg, byte flags, int iterations, byte[] salt, byte[] next, int hashAlg, byte flags, int iterations, byte[] salt, byte[] next,
TypeMap typemap) TypeMap typemap) {
{
this.zone = zone; this.zone = zone;
this.owner = owner; this.owner = owner;
this.dclass = dclass; this.dclass = dclass;
@ -81,28 +78,22 @@ public class ProtoNSEC3
public ProtoNSEC3(byte[] owner, Name originalOwner, Name zone, int dclass, long ttl, public ProtoNSEC3(byte[] owner, Name originalOwner, Name zone, int dclass, long ttl,
int hashAlg, byte flags, int iterations, byte[] salt, byte[] next, int hashAlg, byte flags, int iterations, byte[] salt, byte[] next,
int[] types) int[] types) {
{
this(owner, originalOwner, zone, dclass, ttl, hashAlg, flags, iterations, salt, next, this(owner, originalOwner, zone, dclass, ttl, hashAlg, flags, iterations, salt, next,
TypeMap.fromTypes(types)); TypeMap.fromTypes(types));
} }
private String hashToString(byte[] hash) private String hashToString(byte[] hash) {
{ if (hash == null)
if (hash == null) return null; return null;
return b32.toString(hash); return b32.toString(hash);
} }
public Name getName() public Name getName() {
{ if (name == null) {
if (name == null) try {
{
try
{
name = new Name(hashToString(owner), zone); name = new Name(hashToString(owner), zone);
} } catch (TextParseException e) {
catch (TextParseException e)
{
// This isn't going to happen. // This isn't going to happen.
} }
} }
@ -110,122 +101,104 @@ public class ProtoNSEC3
return name; return name;
} }
public byte[] getNext() public Name getOriginalOwnerName() {
{ return this.originalOwner;
}
public byte[] getNext() {
return next; return next;
} }
public void setNext(byte[] next) public void setNext(byte[] next) {
{
this.next = next; this.next = next;
} }
public byte getFlags() public byte getFlags() {
{
return flags; return flags;
} }
public boolean getOptOutFlag() public boolean getOptOutFlag() {
{
return (flags & NSEC3Record.Flags.OPT_OUT) != 0; return (flags & NSEC3Record.Flags.OPT_OUT) != 0;
} }
public void setOptOutFlag(boolean optOutFlag) public void setOptOutFlag(boolean optOutFlag) {
{
if (optOutFlag) if (optOutFlag)
this.flags |= NSEC3Record.Flags.OPT_OUT; this.flags |= NSEC3Record.Flags.OPT_OUT;
else else
this.flags &= ~NSEC3Record.Flags.OPT_OUT; this.flags &= ~NSEC3Record.Flags.OPT_OUT;
} }
public long getTTL() public long getTTL() {
{
return ttl; return ttl;
} }
public void setTTL(long ttl) public void setTTL(long ttl) {
{
this.ttl = ttl; this.ttl = ttl;
} }
public TypeMap getTypemap() public TypeMap getTypemap() {
{
return typemap; return typemap;
} }
public int[] getTypes() public int[] getTypes() {
{
return typemap.getTypes(); return typemap.getTypes();
} }
public void setTypemap(TypeMap typemap) public void setTypemap(TypeMap typemap) {
{
this.typemap = typemap; this.typemap = typemap;
} }
public int getDClass() public int getDClass() {
{
return dclass; return dclass;
} }
public int getHashAlgorithm() public int getHashAlgorithm() {
{
return hashAlg; return hashAlg;
} }
public int getIterations() public int getIterations() {
{
return iterations; return iterations;
} }
public byte[] getOwner() public byte[] getOwner() {
{
return owner; return owner;
} }
public byte[] getSalt() public byte[] getSalt() {
{
return salt; return salt;
} }
public Name getZone() public Name getZone() {
{
return zone; return zone;
} }
public NSEC3Record getNSEC3Record() public NSEC3Record getNSEC3Record() {
{ return new NSEC3Record(getName(), dclass, ttl, hashAlg, flags, iterations, salt, next, getTypes());
String comment = (originalOwner == null) ? "(unknown original ownername)"
: originalOwner.toString();
return new NSEC3Record(getName(), dclass, ttl, hashAlg, flags, iterations, salt,
next, getTypes(), comment);
} }
public void mergeTypes(TypeMap new_types) public void mergeTypes(TypeMap newTypes) {
{ int[] nt = newTypes.getTypes();
int[] nt = new_types.getTypes(); for (int i = 0; i < nt.length; i++) {
for (int i = 0; i < nt.length; i++) if (!typemap.get(nt[i]))
{ typemap.set(nt[i]);
if (!typemap.get(nt[i])) typemap.set(nt[i]);
} }
} }
public int compareTo(ProtoNSEC3 o) public int compareTo(ProtoNSEC3 o) {
{ if (o == null)
if (o == null) return 1; return 1;
byte[] o_owner = o.getOwner(); byte[] origOwner = o.getOwner();
int len = owner.length < o_owner.length ? o_owner.length : owner.length; int len = owner.length < origOwner.length ? origOwner.length : owner.length;
for (int i = 0; i < len; i++) for (int i = 0; i < len; i++) {
{ int d = ((owner[i] & 0xFF) - (origOwner[i] & 0xFF));
int d = ((owner[i] & 0xFF) - (o_owner[i] & 0xFF)); if (d != 0)
if (d != 0) return d; return d;
} }
return owner.length - o_owner.length; return owner.length - origOwner.length;
} }
public String toString() public String toString() {
{ StringBuilder sb = new StringBuilder();
StringBuffer sb = new StringBuffer();
sb.append(getName()); sb.append(getName());
sb.append(' '); sb.append(' ');
sb.append(ttl); sb.append(ttl);
@ -248,10 +221,8 @@ public class ProtoNSEC3
return sb.toString(); return sb.toString();
} }
public static class Comparator implements java.util.Comparator<ProtoNSEC3> public static class Comparator implements java.util.Comparator<ProtoNSEC3> {
{ public int compare(ProtoNSEC3 a, ProtoNSEC3 b) {
public int compare(ProtoNSEC3 a, ProtoNSEC3 b)
{
return a.compareTo(b); return a.compareTo(b);
} }

View File

@ -1,6 +1,4 @@
// $Id$ // Copyright (C) 2000-2003 Network Solutions, Inc., 2022 Verisign, Inc.
//
// Copyright (C) 2000-2003 Network Solutions, Inc.
// //
// This library is free software; you can redistribute it and/or // This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public // modify it under the terms of the GNU Lesser General Public
@ -30,79 +28,87 @@ import org.xbill.DNS.Type;
* objects. It imposes a canonical order consistent with DNSSEC. It does not put * objects. It imposes a canonical order consistent with DNSSEC. It does not put
* records within a RRset into canonical order: see {@link ByteArrayComparator}. * records within a RRset into canonical order: see {@link ByteArrayComparator}.
* *
* @author David Blacka (original) * @author David Blacka
* @author $Author$
* @version $Revision$
*/ */
public class RecordComparator implements Comparator<Record> public class RecordComparator implements Comparator<Record> {
{ public RecordComparator() {
public RecordComparator()
{
} }
/** /**
* In general, types are compared numerically. However, SOA, NS, and DNAME are ordered * In general, types are compared numerically. However, SOA, NS, and DNAME are
* ordered
* before the rest. * before the rest.
*/ */
private int compareTypes(int a, int b) private int compareTypes(int a, int b) {
{ if (a == b)
if (a == b) return 0; return 0;
if (a == Type.SOA) return -1; if (a == Type.SOA)
if (b == Type.SOA) return 1; return -1;
if (b == Type.SOA)
return 1;
if (a == Type.NS) return -1; if (a == Type.NS)
if (b == Type.NS) return 1; return -1;
if (b == Type.NS)
return 1;
if (a == Type.DNAME) return -1; if (a == Type.DNAME)
if (b == Type.DNAME) return 1; return -1;
if (b == Type.DNAME)
return 1;
if (a < b) return -1; if (a < b)
return -1;
return 1; return 1;
} }
private int compareRDATA(Record a, Record b) private int compareRDATA(Record a, Record b) {
{
byte[] a_rdata = a.rdataToWireCanonical(); byte[] a_rdata = a.rdataToWireCanonical();
byte[] b_rdata = b.rdataToWireCanonical(); byte[] b_rdata = b.rdataToWireCanonical();
for (int i = 0; i < a_rdata.length && i < b_rdata.length; i++) for (int i = 0; i < a_rdata.length && i < b_rdata.length; i++) {
{
int n = (a_rdata[i] & 0xFF) - (b_rdata[i] & 0xFF); int n = (a_rdata[i] & 0xFF) - (b_rdata[i] & 0xFF);
if (n != 0) return n; if (n != 0)
return n;
} }
return (a_rdata.length - b_rdata.length); return (a_rdata.length - b_rdata.length);
} }
public int compare(Record a, Record b) public int compare(Record a, Record b) {
{ if (a == null && b == null)
if (a == null && b == null) return 0; return 0;
if (a == null) return 1; if (a == null)
if (b == null) return -1; return 1;
if (b == null)
return -1;
int res = a.getName().compareTo(b.getName()); int res = a.getName().compareTo(b.getName());
if (res != 0) return res; if (res != 0)
return res;
int a_type = a.getType(); int a_type = a.getType();
int b_type = b.getType(); int b_type = b.getType();
int sig_type = 0; int sig_type = 0;
if (a_type == Type.RRSIG) if (a_type == Type.RRSIG) {
{
a_type = ((RRSIGRecord) a).getTypeCovered(); a_type = ((RRSIGRecord) a).getTypeCovered();
if (b_type != Type.RRSIG) sig_type = 1; if (b_type != Type.RRSIG)
sig_type = 1;
} }
if (b_type == Type.RRSIG) if (b_type == Type.RRSIG) {
{
b_type = ((RRSIGRecord) b).getTypeCovered(); b_type = ((RRSIGRecord) b).getTypeCovered();
if (a.getType() != Type.RRSIG) sig_type = -1; if (a.getType() != Type.RRSIG)
sig_type = -1;
} }
res = compareTypes(a_type, b_type); res = compareTypes(a_type, b_type);
if (res != 0) return res; if (res != 0)
return res;
if (sig_type != 0) return sig_type; if (sig_type != 0)
return sig_type;
return compareRDATA(a, b); return compareRDATA(a, b);
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,19 @@
// $Id$ // Copyright (C) 2004, 2022 Verisign, Inc.
// //
// Copyright (C) 2004 Verisign, Inc. // This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
// USA
package com.verisignlabs.dnssec.security; package com.verisignlabs.dnssec.security;
import java.util.Arrays; import java.util.Arrays;
@ -18,45 +30,39 @@ import org.xbill.DNS.Type;
* DNSjava: {@link org.xbill.DNS.TypeBitmap}. * DNSjava: {@link org.xbill.DNS.TypeBitmap}.
*/ */
public class TypeMap public class TypeMap {
{
private static final Integer[] integerArray = new Integer[0]; private static final Integer[] integerArray = new Integer[0];
private static final byte[] emptyBitmap = new byte[0]; private static final byte[] emptyBitmap = new byte[0];
private Set<Integer> typeSet; private Set<Integer> typeSet;
public TypeMap() public TypeMap() {
{
this.typeSet = new HashSet<Integer>(); this.typeSet = new HashSet<Integer>();
} }
/** Add the given type to the typemap. */ /** Add the given type to the typemap. */
public void set(int type) public void set(int type) {
{
typeSet.add(type); typeSet.add(type);
} }
/** Remove the given type from the type map. */ /** Remove the given type from the type map. */
public void clear(int type) public void clear(int type) {
{
typeSet.remove(type); typeSet.remove(type);
} }
/** @return true if the given type is present in the type map. */ /** @return true if the given type is present in the type map. */
public boolean get(int type) public boolean get(int type) {
{
return typeSet.contains(type); return typeSet.contains(type);
} }
/** /**
* Given an array of DNS type code, construct a TypeMap object. * Given an array of DNS type code, construct a TypeMap object.
*/ */
public static TypeMap fromTypes(int[] types) public static TypeMap fromTypes(int[] types) {
{
TypeMap m = new TypeMap(); TypeMap m = new TypeMap();
if (types == null) return m; if (types == null)
for (int i = 0; i < types.length; i++) return m;
{ for (int i = 0; i < types.length; i++) {
m.set(types[i]); m.set(types[i]);
} }
@ -67,25 +73,20 @@ public class TypeMap
* Given an array of bytes representing a wire-format type map, construct the * Given an array of bytes representing a wire-format type map, construct the
* TypeMap object. * TypeMap object.
*/ */
public static TypeMap fromBytes(byte[] map) public static TypeMap fromBytes(byte[] map) {
{
int m = 0; int m = 0;
TypeMap typemap = new TypeMap(); TypeMap typemap = new TypeMap();
int page; int page;
int byte_length; int byte_length;
while (m < map.length) while (m < map.length) {
{
page = map[m++]; page = map[m++];
byte_length = map[m++]; byte_length = map[m++];
for (int i = 0; i < byte_length; i++) for (int i = 0; i < byte_length; i++) {
{ for (int j = 0; j < 8; j++) {
for (int j = 0; j < 8; j++) if ((map[m + i] & (1 << (7 - j))) != 0) {
{
if ((map[m + i] & (1 << (7 - j))) != 0)
{
typemap.set((page << 8) + (i * 8) + j); typemap.set((page << 8) + (i * 8) + j);
} }
} }
@ -99,12 +100,10 @@ public class TypeMap
/** /**
* Given list of type mnemonics, construct a TypeMap object. * Given list of type mnemonics, construct a TypeMap object.
*/ */
public static TypeMap fromString(String types) public static TypeMap fromString(String types) {
{
TypeMap typemap = new TypeMap(); TypeMap typemap = new TypeMap();
for (String type : types.split("\\s+")) for (String type : types.split("\\s+")) {
{
typemap.set(Type.value(type)); typemap.set(Type.value(type));
} }
@ -112,24 +111,22 @@ public class TypeMap
} }
/** @return the normal string representation of the typemap. */ /** @return the normal string representation of the typemap. */
public String toString() public String toString() {
{
int[] types = getTypes(); int[] types = getTypes();
Arrays.sort(types); Arrays.sort(types);
StringBuffer sb = new StringBuffer(); StringBuffer sb = new StringBuffer();
for (int i = 0; i < types.length; i++) for (int i = 0; i < types.length; i++) {
{ if (i > 0)
if (i > 0) sb.append(" "); sb.append(" ");
sb.append(Type.string(types[i])); sb.append(Type.string(types[i]));
} }
return sb.toString(); return sb.toString();
} }
protected static void mapToWire(DNSOutput out, int[] types, int base, int start, int end) protected static void mapToWire(DNSOutput out, int[] types, int base, int start, int end) {
{
// calculate the length of this map by looking at the largest // calculate the length of this map by looking at the largest
// typecode in this section. // typecode in this section.
int max_type = types[end - 1] & 0xFF; int max_type = types[end - 1] & 0xFF;
@ -144,22 +141,20 @@ public class TypeMap
byte[] map = new byte[map_length]; byte[] map = new byte[map_length];
// for each type in our sub-array, set its corresponding bit in the map. // for each type in our sub-array, set its corresponding bit in the map.
for (int i = start; i < end; i++) for (int i = start; i < end; i++) {
{
map[(types[i] & 0xFF) / 8] |= (1 << (7 - types[i] % 8)); map[(types[i] & 0xFF) / 8] |= (1 << (7 - types[i] % 8));
} }
// write out the resulting binary bitmap. // write out the resulting binary bitmap.
for (int i = 0; i < map.length; i++) for (int i = 0; i < map.length; i++) {
{
out.writeU8(map[i]); out.writeU8(map[i]);
} }
} }
public byte[] toWire() public byte[] toWire() {
{
int[] types = getTypes(); int[] types = getTypes();
if (types.length == 0) return emptyBitmap; if (types.length == 0)
return emptyBitmap;
Arrays.sort(types); Arrays.sort(types);
@ -168,12 +163,11 @@ public class TypeMap
DNSOutput out = new DNSOutput(); DNSOutput out = new DNSOutput();
for (int i = 0; i < types.length; i++) for (int i = 0; i < types.length; i++) {
{
int base = (types[i] >> 8) & 0xFF; int base = (types[i] >> 8) & 0xFF;
if (base == mapbase) continue; if (base == mapbase)
if (mapstart >= 0) continue;
{ if (mapstart >= 0) {
mapToWire(out, types, mapbase, mapstart, i); mapToWire(out, types, mapbase, mapstart, i);
} }
mapbase = base; mapbase = base;
@ -184,26 +178,22 @@ public class TypeMap
return out.toByteArray(); return out.toByteArray();
} }
public int[] getTypes() public int[] getTypes() {
{
Integer[] a = (Integer[]) typeSet.toArray(integerArray); Integer[] a = (Integer[]) typeSet.toArray(integerArray);
int[] res = new int[a.length]; int[] res = new int[a.length];
for (int i = 0; i < res.length; i++) for (int i = 0; i < res.length; i++) {
{
res[i] = a[i].intValue(); res[i] = a[i].intValue();
} }
return res; return res;
} }
public static int[] fromWireToTypes(byte[] wire_fmt) public static int[] fromWireToTypes(byte[] wire_fmt) {
{
return TypeMap.fromBytes(wire_fmt).getTypes(); return TypeMap.fromBytes(wire_fmt).getTypes();
} }
public static byte[] fromTypesToWire(int[] types) public static byte[] fromTypesToWire(int[] types) {
{
return TypeMap.fromTypes(types).toWire(); return TypeMap.fromTypes(types).toWire();
} }

View File

@ -1,6 +1,4 @@
// $Id$ // Copyright (C) 2003, 2022 Verisign, Inc.
//
// Copyright (C) 2003 VeriSign, Inc.
// //
// This library is free software; you can redistribute it and/or // This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public // modify it under the terms of the GNU Lesser General Public
@ -24,11 +22,11 @@ import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator;
import java.util.List; import java.util.List;
import org.xbill.DNS.Master; import org.xbill.DNS.Master;
import org.xbill.DNS.Name; import org.xbill.DNS.Name;
import org.xbill.DNS.RRSIGRecord;
import org.xbill.DNS.RRset; import org.xbill.DNS.RRset;
import org.xbill.DNS.Record; import org.xbill.DNS.Record;
import org.xbill.DNS.Type; import org.xbill.DNS.Type;
@ -37,44 +35,42 @@ import org.xbill.DNS.Type;
* This class contains a bunch of utility methods that are generally useful in * This class contains a bunch of utility methods that are generally useful in
* manipulating zones. * manipulating zones.
* *
* @author David Blacka (original) * @author David Blacka
* @author $Author$
* @version $Revision$
*/ */
public class ZoneUtils public class ZoneUtils {
{
/** /**
* Load a zone file. * Load a zone file.
* *
* @param zonefile * @param zonefile
* the filename/path of the zonefile to read. * the filename/path of the zonefile to read.
* @param origin * @param origin
* the origin to use for the zonefile (may be null if the origin is * the origin to use for the zonefile (may be null if the origin
* is
* specified in the zone file itself). * specified in the zone file itself).
* @return a {@link java.util.List} of {@link org.xbill.DNS.Record} objects. * @return a {@link java.util.List} of {@link org.xbill.DNS.Record} objects.
* @throws IOException * @throws IOException
* if something goes wrong reading the zone file. * if something goes wrong reading the zone file.
*/ */
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<Record>(); ArrayList<Record> records = new ArrayList<Record>();
Master m; Master m;
if (zonefile.equals("-")) try {
{ if (zonefile.equals("-")) {
m = new Master(System.in); m = new Master(System.in);
} } else {
else
{
m = new Master(zonefile, origin); m = 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;
} }
@ -83,26 +79,23 @@ public class ZoneUtils
* Write the records out into a zone file. * Write the records out into a zone file.
* *
* @param records * @param records
* a {@link java.util.List} of {@link org.xbill.DNS.Record} objects * a {@link java.util.List} of {@link org.xbill.DNS.Record}
* objects
* forming a zone. * forming a zone.
* @param zonefile * @param zonefile
* the file to write to. If null or equal to "-", System.out is used. * the file to write to. If null or equal to "-", System.out is
* used.
*/ */
public static void writeZoneFile(List<Record> records, String zonefile) throws IOException public static void writeZoneFile(List<Record> records, String zonefile) throws IOException {
{
PrintWriter out = null; PrintWriter out = null;
if (zonefile == null || zonefile.equals("-")) if (zonefile == null || zonefile.equals("-")) {
{
out = new PrintWriter(System.out); out = new PrintWriter(System.out);
} } else {
else
{
out = new PrintWriter(new BufferedWriter(new FileWriter(zonefile))); out = new PrintWriter(new BufferedWriter(new FileWriter(zonefile)));
} }
for (Record r : records) for (Record r : records) {
{
out.println(r); out.println(r);
} }
@ -116,51 +109,38 @@ public class ZoneUtils
* a list of {@link org.xbill.DNS.Record} objects. * a list of {@link org.xbill.DNS.Record} objects.
* @return the zone name, if found. null if one couldn't be found. * @return the zone name, if found. null if one couldn't be found.
*/ */
public static Name findZoneName(List<Record> records) public static Name findZoneName(List<Record> records) {
{ for (Record r : records) {
for (Record r : records) if (r.getType() == Type.SOA) {
{ return r.getName();
int type = r.getType(); }
if (type == Type.SOA) return r.getName();
} }
return null; return null;
} }
public static List<Record> findRRs(List<Record> records, Name name, int type) public static List<Record> findRRs(List<Record> records, Name name, int type) {
{
List<Record> res = new ArrayList<Record>(); List<Record> res = new ArrayList<Record>();
for (Record r : records) for (Record r : records) {
{ if (r.getName().equals(name) && r.getType() == type) {
if (r.getName().equals(name) && r.getType() == type)
{
res.add(r); res.add(r);
} }
} }
return res; return res;
} }
/** This is an alternate way to format an RRset into a string */ /** This is an alternate way to format an RRset into a string */
@SuppressWarnings("unchecked") public static String rrsetToString(RRset rrset, boolean includeSigs) {
public static String rrsetToString(RRset rrset, boolean includeSigs)
{
StringBuilder out = new StringBuilder(); StringBuilder out = new StringBuilder();
for (Iterator<Record> i = rrset.rrs(false); i.hasNext();) for (Record r : rrset.rrs(false)) {
{
Record r = i.next();
out.append(r.toString()); out.append(r.toString());
out.append("\n"); out.append("\n");
} }
if (includeSigs) if (includeSigs) {
{ for (RRSIGRecord s : rrset.sigs()) {
for (Iterator<Record> i = rrset.sigs(); i.hasNext();) out.append(s.toString());
{
Record r = i.next();
out.append(r.toString());
out.append("\n"); out.append("\n");
} }
} }

View File

@ -1,6 +1,4 @@
// $Id: DnsSecVerifier.java 172 2009-08-23 19:13:42Z davidb $ // Copyright (C) 2010, 2022 Verisign, Inc.
//
// Copyright (C) 2010 Verisign, Inc.
// //
// This library is free software; you can redistribute it and/or // This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public // modify it under the terms of the GNU Lesser General Public
@ -49,12 +47,9 @@ import org.xbill.DNS.utils.base32;
* verifying signatures, this class will also detect invalid NSEC and NSEC3 * verifying signatures, this class will also detect invalid NSEC and NSEC3
* chains. * chains.
* *
* @author David Blacka (original) * @author David Blacka
* @author $Author: davidb $
* @version $Revision: 172 $
*/ */
public class ZoneVerifier public class ZoneVerifier {
{
private SortedMap<Name, Set<Integer>> mNodeMap; private SortedMap<Name, Set<Integer>> mNodeMap;
private HashMap<String, RRset> mRRsetMap; private HashMap<String, RRset> mRRsetMap;
@ -72,39 +67,33 @@ public class ZoneVerifier
private Logger log = Logger.getLogger("ZoneVerifier"); private Logger log = Logger.getLogger("ZoneVerifier");
// The various types of signed zones. // The various types of signed zones.
enum DNSSECType enum DNSSECType {
{
UNSIGNED, NSEC, NSEC3, NSEC3_OPTOUT; UNSIGNED, NSEC, NSEC3, NSEC3_OPTOUT;
} }
// The types of nodes (a node consists of all RRs with the same name). // The types of nodes (a node consists of all RRs with the same name).
enum NodeType enum NodeType {
{
NORMAL, DELEGATION, GLUE; NORMAL, DELEGATION, GLUE;
} }
/** /**
* This is a subclass of {@link org.xbill.DNS.RRset} that adds a "mark". * This is a subclass of {@link org.xbill.DNS.RRset} that adds a "mark".
*/ */
private class MarkRRset extends RRset private class MarkRRset extends RRset {
{
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private boolean mIsMarked = false; private boolean mIsMarked = false;
boolean getMark() boolean getMark() {
{
return mIsMarked; return mIsMarked;
} }
void setMark(boolean value) void setMark(boolean value) {
{
mIsMarked = value; mIsMarked = value;
} }
} }
public ZoneVerifier() public ZoneVerifier() {
{
mVerifier = new DnsSecVerifier(); mVerifier = new DnsSecVerifier();
mBase32 = new base32(base32.Alphabet.BASE32HEX, false, true); mBase32 = new base32(base32.Alphabet.BASE32HEX, false, true);
mBAcmp = new ByteArrayComparator(); mBAcmp = new ByteArrayComparator();
@ -112,94 +101,78 @@ public class ZoneVerifier
} }
/** @return the DnsSecVerifier object used to verify individual RRsets. */ /** @return the DnsSecVerifier object used to verify individual RRsets. */
public DnsSecVerifier getVerifier() public DnsSecVerifier getVerifier() {
{
return mVerifier; return mVerifier;
} }
public void setIgnoreDuplicateRRs(boolean value) public void setIgnoreDuplicateRRs(boolean value) {
{
mIgnoreDuplicateRRs = value; mIgnoreDuplicateRRs = value;
} }
private static String key(Name n, int type) private static String key(Name n, int type) {
{
return n.toString() + ':' + type; return n.toString() + ':' + type;
} }
@SuppressWarnings("rawtypes") private boolean addRRtoRRset(RRset rrset, Record rr) {
private boolean addRRtoRRset(RRset rrset, Record rr) if (mIgnoreDuplicateRRs) {
{
if (mIgnoreDuplicateRRs)
{
rrset.addRR(rr); rrset.addRR(rr);
return true; return true;
} }
Iterator i = (rr instanceof RRSIGRecord) ? rrset.sigs() : rrset.rrs(); if (rr instanceof RRSIGRecord) {
for ( ; i.hasNext(); ) for (RRSIGRecord sigrec : rrset.sigs()) {
{ if (rr.equals(sigrec)) {
Record record = (Record) i.next(); return false;
if (rr.equals(record)) return false;
} }
}
} else {
for (Record rec : rrset.rrs()) {
if (rr.equals(rec)) {
return false;
}
}
}
rrset.addRR(rr); rrset.addRR(rr);
return true; return true;
} }
/** /**
* Add a record to the various maps. * Add a record to the various maps.
*
* @return TODO * @return TODO
*/ */
private boolean addRR(Record r) private boolean addRR(Record r) {
{ Name n = r.getName();
Name r_name = r.getName(); int t = r.getType();
int r_type = r.getType(); if (t == Type.RRSIG)
if (r_type == Type.RRSIG) r_type = ((RRSIGRecord) r).getTypeCovered(); t = ((RRSIGRecord) r).getTypeCovered();
// Add NSEC and NSEC3 RRs to their respective maps // Add NSEC and NSEC3 RRs to their respective maps
if (r_type == Type.NSEC) if (t == Type.NSEC) {
{ if (mNSECMap == null) {
if (mNSECMap == null) mNSECMap = new TreeMap<Name, MarkRRset>(); mNSECMap = new TreeMap<>();
MarkRRset rrset = mNSECMap.get(r_name);
if (rrset == null)
{
rrset = new MarkRRset();
mNSECMap.put(r_name, rrset);
} }
MarkRRset rrset = mNSECMap.computeIfAbsent(n, k -> new MarkRRset());
return addRRtoRRset(rrset, r); return addRRtoRRset(rrset, r);
} }
if (r_type == Type.NSEC3) if (t == Type.NSEC3) {
{ if (mNSEC3Map == null) {
if (mNSEC3Map == null) mNSEC3Map = new TreeMap<Name, MarkRRset>(); mNSEC3Map = new TreeMap<>();
MarkRRset rrset = mNSEC3Map.get(r_name);
if (rrset == null)
{
rrset = new MarkRRset();
mNSEC3Map.put(r_name, rrset);
} }
MarkRRset rrset = mNSECMap.computeIfAbsent(n, k -> new MarkRRset());
return addRRtoRRset(rrset, r); return addRRtoRRset(rrset, r);
} }
// Add the name and type to the node map // Add the name and type to the node map
Set<Integer> typeset = mNodeMap.get(r_name); Set<Integer> typeset = mNodeMap.computeIfAbsent(n, k -> new HashSet<>());
if (typeset == null)
{
typeset = new HashSet<Integer>();
mNodeMap.put(r_name, typeset);
}
typeset.add(r.getType()); // add the original type typeset.add(r.getType()); // add the original type
// Add the record to the RRset map // Add the record to the RRset map
String k = key(r_name, r_type); String k = key(n, t);
RRset rrset = mRRsetMap.get(k); RRset rrset = mRRsetMap.computeIfAbsent(k, k2 -> new RRset());
if (rrset == null)
{
rrset = new RRset();
mRRsetMap.put(k, rrset);
}
return addRRtoRRset(rrset, r); return addRRtoRRset(rrset, r);
} }
@ -207,14 +180,12 @@ public class ZoneVerifier
* Given a record, determine the DNSSEC signing type. If the record doesn't * Given a record, determine the DNSSEC signing type. If the record doesn't
* determine that, DNSSECType.UNSIGNED is returned * determine that, DNSSECType.UNSIGNED is returned
*/ */
private DNSSECType determineDNSSECType(Record r) private DNSSECType determineDNSSECType(Record r) {
{ if (r.getType() == Type.NSEC)
if (r.getType() == Type.NSEC) return DNSSECType.NSEC; return DNSSECType.NSEC;
if (r.getType() == Type.NSEC3) if (r.getType() == Type.NSEC3) {
{
NSEC3Record nsec3 = (NSEC3Record) r; NSEC3Record nsec3 = (NSEC3Record) r;
if ((nsec3.getFlags() & NSEC3Record.Flags.OPT_OUT) == NSEC3Record.Flags.OPT_OUT) if ((nsec3.getFlags() & NSEC3Record.Flags.OPT_OUT) == NSEC3Record.Flags.OPT_OUT) {
{
return DNSSECType.NSEC3_OPTOUT; return DNSSECType.NSEC3_OPTOUT;
} }
return DNSSECType.NSEC3; return DNSSECType.NSEC3;
@ -230,39 +201,39 @@ public class ZoneVerifier
* @param records * @param records
* @return TODO * @return TODO
*/ */
private int calculateNodes(List<Record> records) private int calculateNodes(List<Record> records) {
{ mNodeMap = new TreeMap<>();
mNodeMap = new TreeMap<Name, Set<Integer>>(); mRRsetMap = new HashMap<>();
mRRsetMap = new HashMap<String, RRset>();
// The zone is unsigned until we get a clue otherwise. // The zone is unsigned until we get a clue otherwise.
mDNSSECType = DNSSECType.UNSIGNED; mDNSSECType = DNSSECType.UNSIGNED;
int errors = 0; int errors = 0;
for (Record r : records) for (Record r : records) {
{ Name n = r.getName();
Name r_name = r.getName(); int t = r.getType();
int r_type = r.getType();
// Add the record to the various maps. // Add the record to the various maps.
boolean res = addRR(r); boolean res = addRR(r);
if (!res) if (!res) {
{
log.warning("Record '" + r + "' detected as a duplicate"); log.warning("Record '" + r + "' detected as a duplicate");
errors++; errors++;
} }
// Learn some things about the zone as we do this pass. // Learn some things about the zone as we do this pass.
if (r_type == Type.SOA) mZoneName = r_name; if (t == Type.SOA)
if (r_type == Type.NSEC3PARAM) mNSEC3params = (NSEC3PARAMRecord) r; mZoneName = n;
if (r_type == Type.DNSKEY) { if (t == Type.NSEC3PARAM)
mNSEC3params = (NSEC3PARAMRecord) r;
if (t == Type.DNSKEY) {
DNSKEYRecord dnskey = (DNSKEYRecord) r; DNSKEYRecord dnskey = (DNSKEYRecord) r;
mVerifier.addTrustedKey(dnskey); mVerifier.addTrustedKey(dnskey);
log.info("Adding trusted key: " + dnskey + " ; keytag = " log.info("Adding trusted key: " + dnskey + " ; keytag = "
+ dnskey.getFootprint()); + dnskey.getFootprint());
} }
if (mDNSSECType == DNSSECType.UNSIGNED) mDNSSECType = determineDNSSECType(r); if (mDNSSECType == DNSSECType.UNSIGNED)
mDNSSECType = determineDNSSECType(r);
} }
return errors; return errors;
@ -272,37 +243,40 @@ public class ZoneVerifier
* Given a name, typeset, and name of the last zone cut, determine the node * Given a name, typeset, and name of the last zone cut, determine the node
* type. * type.
*/ */
private NodeType determineNodeType(Name n, Set<Integer> typeset, Name last_cut) private NodeType determineNodeType(Name n, Set<Integer> typeset, Name last_cut) {
{
// All RRs at the zone apex are normal // All RRs at the zone apex are normal
if (n.equals(mZoneName)) return NodeType.NORMAL; if (n.equals(mZoneName))
return NodeType.NORMAL;
// If the node is not below the zone itself, we will treat it as glue (it is really junk). // If the node is not below the zone itself, we will treat it as glue (it is
if (!n.subdomain(mZoneName)) // really junk).
{ if (!n.subdomain(mZoneName)) {
return NodeType.GLUE; return NodeType.GLUE;
} }
// If the node is below a zone cut (either a delegation or DNAME), it is // If the node is below a zone cut (either a delegation or DNAME), it is
// glue. // glue.
if (last_cut != null && n.subdomain(last_cut) && !n.equals(last_cut)) if (last_cut != null && n.subdomain(last_cut) && !n.equals(last_cut)) {
{
return NodeType.GLUE; return NodeType.GLUE;
} }
// If the node has a NS record it is a delegation. // If the node has a NS record it is a delegation.
if (typeset.contains(new Integer(Type.NS))) return NodeType.DELEGATION; if (typeset.contains(Integer.valueOf(Type.NS)))
return NodeType.DELEGATION;
return NodeType.NORMAL; return NodeType.NORMAL;
} }
private Set<Integer> cleanupDelegationTypeset(Set<Integer> typeset) private Set<Integer> cleanupDelegationTypeset(Set<Integer> typeset) {
{ Set<Integer> t = new HashSet<>();
Set<Integer> t = new HashSet<Integer>(); if (typeset.contains(Type.NS))
if (typeset.contains(Type.NS)) t.add(Type.NS); t.add(Type.NS);
if (typeset.contains(Type.DS)) t.add(Type.DS); if (typeset.contains(Type.DS))
if (typeset.contains(Type.RRSIG)) t.add(Type.RRSIG); t.add(Type.DS);
if (typeset.contains(Type.RRSIG))
t.add(Type.RRSIG);
if (!typeset.equals(t)) return t; if (!typeset.equals(t))
return t;
return typeset; return typeset;
} }
@ -310,13 +284,11 @@ public class ZoneVerifier
* For each node, determine which RRsets should be signed, verify those, and * For each node, determine which RRsets should be signed, verify those, and
* determine which nodes get NSEC or NSEC3 RRs and verify those. * determine which nodes get NSEC or NSEC3 RRs and verify those.
*/ */
private int processNodes() throws NoSuchAlgorithmException, TextParseException private int processNodes() throws NoSuchAlgorithmException, TextParseException {
{
int errors = 0; int errors = 0;
Name last_cut = null; Name last_cut = null;
for (Map.Entry<Name, Set<Integer>> entry : mNodeMap.entrySet()) for (Map.Entry<Name, Set<Integer>> entry : mNodeMap.entrySet()) {
{
Name n = entry.getKey(); Name n = entry.getKey();
Set<Integer> typeset = entry.getValue(); Set<Integer> typeset = entry.getValue();
@ -324,21 +296,22 @@ public class ZoneVerifier
log.finest("Node " + n + " is type " + ntype); log.finest("Node " + n + " is type " + ntype);
// we can ignore glue/invalid RRs. // we can ignore glue/invalid RRs.
if (ntype == NodeType.GLUE) continue; if (ntype == NodeType.GLUE)
continue;
// record the last zone cut if this node is a zone cut. // record the last zone cut if this node is a zone cut.
if (ntype == NodeType.DELEGATION || typeset.contains(Type.DNAME)) if (ntype == NodeType.DELEGATION || typeset.contains(Type.DNAME)) {
{
last_cut = n; last_cut = n;
} }
// check all of the RRsets that should be signed // check all of the RRsets that should be signed
for (int type : typeset) for (int type : typeset) {
{ if (type == Type.RRSIG)
if (type == Type.RRSIG) continue; continue;
// at delegation points, only DS RRs are signed (and NSEC, but those are // at delegation points, only DS RRs are signed (and NSEC, but those are
// checked separately) // checked separately)
if (ntype == NodeType.DELEGATION && type != Type.DS) continue; if (ntype == NodeType.DELEGATION && type != Type.DS)
continue;
// otherwise, verify the RRset. // otherwise, verify the RRset.
String k = key(n, type); String k = key(n, type);
RRset rrset = mRRsetMap.get(k); RRset rrset = mRRsetMap.get(k);
@ -348,13 +321,11 @@ public class ZoneVerifier
// cleanup the typesets of delegation nodes. // cleanup the typesets of delegation nodes.
// the only types that should be there are NS, DS and RRSIG. // the only types that should be there are NS, DS and RRSIG.
if (ntype == NodeType.DELEGATION) if (ntype == NodeType.DELEGATION) {
{
typeset = cleanupDelegationTypeset(typeset); typeset = cleanupDelegationTypeset(typeset);
} }
switch (mDNSSECType) switch (mDNSSECType) {
{
case NSEC: case NSEC:
// all nodes with NSEC records have NSEC and RRSIG types // all nodes with NSEC records have NSEC and RRSIG types
typeset.add(Type.NSEC); typeset.add(Type.NSEC);
@ -366,8 +337,7 @@ public class ZoneVerifier
break; break;
case NSEC3_OPTOUT: case NSEC3_OPTOUT:
if (ntype == NodeType.NORMAL if (ntype == NodeType.NORMAL
|| (ntype == NodeType.DELEGATION && typeset.contains(Type.DS))) || (ntype == NodeType.DELEGATION && typeset.contains(Type.DS))) {
{
errors += processNSEC3(n, typeset, ntype); errors += processNSEC3(n, typeset, ntype);
} }
break; break;
@ -378,99 +348,87 @@ public class ZoneVerifier
return errors; return errors;
} }
private static String reasonListToString(List<String> reasons) private static String reasonListToString(List<String> reasons) {
{ if (reasons == null)
if (reasons == null) return ""; return "";
StringBuffer out = new StringBuffer(); StringBuilder out = new StringBuilder();
for (Iterator<String> i = reasons.iterator(); i.hasNext();) for (Iterator<String> i = reasons.iterator(); i.hasNext();) {
{
out.append("Reason: "); out.append("Reason: ");
out.append(i.next()); out.append(i.next());
if (i.hasNext()) out.append("\n"); if (i.hasNext())
out.append("\n");
} }
return out.toString(); return out.toString();
} }
@SuppressWarnings("unchecked") private int processRRset(RRset rrset) {
private int processRRset(RRset rrset) List<String> reasons = new ArrayList<>();
{
List<String> reasons = new ArrayList<String>();
boolean result = false; boolean result = false;
for (Iterator<Record> i = rrset.sigs(); i.hasNext();) for (RRSIGRecord sigrec : rrset.sigs()) {
{
RRSIGRecord sigrec = (RRSIGRecord) i.next();
boolean res = mVerifier.verifySignature(rrset, sigrec, reasons); boolean res = mVerifier.verifySignature(rrset, sigrec, reasons);
if (!res) if (!res) {
{
log.warning("Signature failed to verify RRset:\n rr: " log.warning("Signature failed to verify RRset:\n rr: "
+ ZoneUtils.rrsetToString(rrset, false) + "\n sig: " + sigrec + "\n" + ZoneUtils.rrsetToString(rrset, false) + "\n sig: " + sigrec + "\n"
+ reasonListToString(reasons)); + reasonListToString(reasons));
} }
if (res) result = res; if (res)
result = res;
} }
String rrsetname = rrset.getName() + "/" + Type.string(rrset.getType()); String rrsetname = rrset.getName() + "/" + Type.string(rrset.getType());
if (result) if (result) {
{
log.fine("RRset " + rrsetname + " verified."); log.fine("RRset " + rrsetname + " verified.");
} } else {
else
{
log.warning("RRset " + rrsetname + " did not verify."); log.warning("RRset " + rrsetname + " did not verify.");
} }
return result ? 0 : 1; return result ? 0 : 1;
} }
private String typesToString(int[] types) private String typesToString(int[] types) {
{
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
Arrays.sort(types); Arrays.sort(types);
for (int i = 0; i < types.length; ++i) for (int i = 0; i < types.length; ++i) {
{ if (i != 0)
if (i != 0) sb.append(' '); sb.append(' ');
sb.append(Type.string(types[i])); sb.append(Type.string(types[i]));
} }
return sb.toString(); return sb.toString();
} }
private String typesetToString(Set<Integer> typeset) private String typesetToString(Set<Integer> typeset) {
{ if (typeset == null)
if (typeset == null) return ""; return "";
int[] types = new int[typeset.size()]; int[] types = new int[typeset.size()];
int i = 0; int i = 0;
for (int type : typeset) for (int type : typeset) {
{
types[i++] = type; types[i++] = type;
} }
return typesToString(types); return typesToString(types);
} }
private boolean checkTypeMap(Set<Integer> typeset, int[] types) private boolean checkTypeMap(Set<Integer> typeset, int[] types) {
{
// a null typeset means that we are expecting the typemap of an ENT, which // a null typeset means that we are expecting the typemap of an ENT, which
// should be empty. // should be empty.
if (typeset == null) return types.length == 0; if (typeset == null)
return types.length == 0;
Set<Integer> compareTypeset = new HashSet<Integer>(); Set<Integer> compareTypeset = new HashSet<>();
for (int i = 0; i < types.length; ++i) for (int i = 0; i < types.length; ++i) {
{
compareTypeset.add(types[i]); compareTypeset.add(types[i]);
} }
return typeset.equals(compareTypeset); return typeset.equals(compareTypeset);
} }
private int processNSEC(Name n, Set<Integer> typeset) private int processNSEC(Name n, Set<Integer> typeset) {
{
MarkRRset rrset = mNSECMap.get(n); MarkRRset rrset = mNSECMap.get(n);
if (n == null) if (n == null) {
{
log.warning("Missing NSEC for " + n); log.warning("Missing NSEC for " + n);
return 1; return 1;
} }
@ -482,8 +440,7 @@ public class ZoneVerifier
NSECRecord nsec = (NSECRecord) rrset.first(); NSECRecord nsec = (NSECRecord) rrset.first();
// check typemap // check typemap
if (!checkTypeMap(typeset, nsec.getTypes())) if (!checkTypeMap(typeset, nsec.getTypes())) {
{
log.warning("Typemap for NSEC RR " + n log.warning("Typemap for NSEC RR " + n
+ " did not match what was expected. Expected '" + typesetToString(typeset) + " did not match what was expected. Expected '" + typesetToString(typeset)
+ "', got '" + typesToString(nsec.getTypes())); + "', got '" + typesToString(nsec.getTypes()));
@ -496,32 +453,27 @@ public class ZoneVerifier
return errors; return errors;
} }
private boolean shouldCheckENTs(Name n, Set<Integer> typeset, NodeType ntype) private boolean shouldCheckENTs(Name n, Set<Integer> typeset, NodeType ntype) {
{
// if we are just one (or zero) labels longer than the zonename, the node // if we are just one (or zero) labels longer than the zonename, the node
// can't create a ENT // can't create a ENT
if (n.labels() <= mZoneName.labels() + 1) return false; if (n.labels() <= mZoneName.labels() + 1)
return false;
// we probably won't ever get called for a GLUE node // we probably won't ever get called for a GLUE node
if (ntype == NodeType.GLUE) return false; if (ntype == NodeType.GLUE)
return false;
// if we aren't doing opt-out, then all possible ENTs must be checked. // if we aren't doing opt-out, then all possible ENTs must be checked.
if (mDNSSECType == DNSSECType.NSEC3) return true; if (mDNSSECType == DNSSECType.NSEC3)
return true;
// if we are opt-out, and the node is an insecure delegation, don't check // if we are opt-out, and the node is an insecure delegation, don't check
// ENTs. // ENTs.
if (ntype == NodeType.DELEGATION && !typeset.contains(Type.DS)) return !(ntype == NodeType.DELEGATION && !typeset.contains(Type.DS));
{
return false;
}
// otherwise, check ENTs.
return true;
} }
private int processNSEC3(Name n, Set<Integer> typeset, NodeType ntype) private int processNSEC3(Name n, Set<Integer> typeset, NodeType ntype)
throws NoSuchAlgorithmException, TextParseException throws NoSuchAlgorithmException, TextParseException {
{
// calculate the NSEC3 RR name // calculate the NSEC3 RR name
byte[] hash = mNSEC3params.hashName(n); byte[] hash = mNSEC3params.hashName(n);
@ -529,8 +481,7 @@ public class ZoneVerifier
Name hashname = new Name(hashstr, mZoneName); Name hashname = new Name(hashstr, mZoneName);
MarkRRset rrset = mNSEC3Map.get(hashname); MarkRRset rrset = mNSEC3Map.get(hashname);
if (rrset == null) if (rrset == null) {
{
log.warning("Missing NSEC3 for " + hashname + " corresponding to " + n); log.warning("Missing NSEC3 for " + hashname + " corresponding to " + n);
return 1; return 1;
} }
@ -542,8 +493,7 @@ public class ZoneVerifier
NSEC3Record nsec3 = (NSEC3Record) rrset.first(); NSEC3Record nsec3 = (NSEC3Record) rrset.first();
// check typemap // check typemap
if (!checkTypeMap(typeset, nsec3.getTypes())) if (!checkTypeMap(typeset, nsec3.getTypes())) {
{
log.warning("Typemap for NSEC3 RR " + hashname + " for " + n log.warning("Typemap for NSEC3 RR " + hashname + " for " + n
+ " did not match what was expected. Expected '" + typesetToString(typeset) + " did not match what was expected. Expected '" + typesetToString(typeset)
+ "', got '" + typesToString(nsec3.getTypes()) + "'"); + "', got '" + typesToString(nsec3.getTypes()) + "'");
@ -555,11 +505,9 @@ public class ZoneVerifier
// check NSEC3 RRs for empty non-terminals. // check NSEC3 RRs for empty non-terminals.
// this is recursive. // this is recursive.
if (shouldCheckENTs(n, typeset, ntype)) if (shouldCheckENTs(n, typeset, ntype)) {
{
Name ent = new Name(n, 1); Name ent = new Name(n, 1);
if (mNodeMap.get(ent) == null) if (mNodeMap.get(ent) == null) {
{
errors += processNSEC3(ent, null, NodeType.NORMAL); errors += processNSEC3(ent, null, NodeType.NORMAL);
} }
} }
@ -567,25 +515,19 @@ public class ZoneVerifier
return errors; return errors;
} }
private int processNSECChain() private int processNSECChain() {
{
int errors = 0; int errors = 0;
NSECRecord lastNSEC = null; NSECRecord lastNSEC = null;
for (Iterator<Map.Entry<Name, MarkRRset>> i = mNSECMap.entrySet().iterator(); i.hasNext();) for (Iterator<Map.Entry<Name, MarkRRset>> i = mNSECMap.entrySet().iterator(); i.hasNext();) {
{
// check the internal ordering of the previous NSEC record. This avoids // check the internal ordering of the previous NSEC record. This avoids
// looking at the last one, // looking at the last one,
// which is different. // which is different.
if (lastNSEC != null) if (lastNSEC != null && lastNSEC.getName().compareTo(lastNSEC.getNext()) >= 0) {
{
if (lastNSEC.getName().compareTo(lastNSEC.getNext()) >= 0)
{
log.warning("NSEC for " + lastNSEC.getName() log.warning("NSEC for " + lastNSEC.getName()
+ " has next name >= owner but is not the last NSEC in the chain."); + " has next name >= owner but is not the last NSEC in the chain.");
errors++; errors++;
} }
}
Map.Entry<Name, MarkRRset> entry = i.next(); Map.Entry<Name, MarkRRset> entry = i.next();
Name n = entry.getKey(); Name n = entry.getKey();
@ -593,8 +535,7 @@ public class ZoneVerifier
// check to see if the NSEC is marked. If not, it was not correlated to a // check to see if the NSEC is marked. If not, it was not correlated to a
// signed node. // signed node.
if (!rrset.getMark()) if (!rrset.getMark()) {
{
log.warning("NSEC RR for " + n + " appears to be extra."); log.warning("NSEC RR for " + n + " appears to be extra.");
errors++; errors++;
} }
@ -604,47 +545,39 @@ public class ZoneVerifier
// This is just a sanity check. If this isn't true, we are constructing // This is just a sanity check. If this isn't true, we are constructing
// the // the
// nsec map incorrectly. // nsec map incorrectly.
if (!n.equals(nsec.getName())) if (!n.equals(nsec.getName())) {
{
log.warning("The NSEC in the map for name " + n + " has name " + nsec.getName()); log.warning("The NSEC in the map for name " + n + " has name " + nsec.getName());
errors++; errors++;
} }
// If this is the first row, ensure that the owner name equals the zone // If this is the first row, ensure that the owner name equals the zone
// name // name
if (lastNSEC == null && !n.equals(mZoneName)) if (lastNSEC == null && !n.equals(mZoneName)) {
{
log.warning("The first NSEC in the chain does not match the zone name: name = " log.warning("The first NSEC in the chain does not match the zone name: name = "
+ n + " zonename = " + mZoneName); + n + " zonename = " + mZoneName);
errors++; errors++;
} }
// Check that the prior NSEC's next name equals this rows owner name. // Check that the prior NSEC's next name equals this rows owner name.
if (lastNSEC != null) if (lastNSEC != null && !lastNSEC.getNext().equals(nsec.getName())) {
{
if (!lastNSEC.getNext().equals(nsec.getName()))
{
log.warning("NSEC for " + lastNSEC.getName() log.warning("NSEC for " + lastNSEC.getName()
+ " does not point to the next NSEC in the chain: " + n); + " does not point to the next NSEC in the chain: " + n);
errors++; errors++;
} }
}
lastNSEC = nsec; lastNSEC = nsec;
} }
// check the internal ordering of the last NSEC in the chain // check the internal ordering of the last NSEC in the chain
// the ownername should be >= next name. // the ownername should be >= next name.
if (lastNSEC.getName().compareTo(lastNSEC.getNext()) < 0) if (lastNSEC.getName().compareTo(lastNSEC.getNext()) < 0) {
{
log.warning("The last NSEC RR in the chain did not have an owner >= next: owner = " log.warning("The last NSEC RR in the chain did not have an owner >= next: owner = "
+ lastNSEC.getName() + " next = " + lastNSEC.getNext()); + lastNSEC.getName() + " next = " + lastNSEC.getNext());
errors++; errors++;
} }
// check to make sure it links to the first NSEC in the chain // check to make sure it links to the first NSEC in the chain
if (!lastNSEC.getNext().equals(mZoneName)) if (!lastNSEC.getNext().equals(mZoneName)) {
{
log.warning("The last NSEC RR in the chain did not link to the first NSEC"); log.warning("The last NSEC RR in the chain did not link to the first NSEC");
errors++; errors++;
} }
@ -652,8 +585,7 @@ public class ZoneVerifier
return errors; return errors;
} }
private int compareNSEC3Hashes(Name owner, byte[] hash) private int compareNSEC3Hashes(Name owner, byte[] hash) {
{
// we will compare the binary images // we will compare the binary images
String ownerhashstr = owner.getLabelString(0); String ownerhashstr = owner.getLabelString(0);
byte[] ownerhash = mBase32.fromString(ownerhashstr); byte[] ownerhash = mBase32.fromString(ownerhashstr);
@ -661,26 +593,20 @@ public class ZoneVerifier
return mBAcmp.compare(ownerhash, hash); return mBAcmp.compare(ownerhash, hash);
} }
private int processNSEC3Chain() private int processNSEC3Chain() {
{
int errors = 0; int errors = 0;
NSEC3Record lastNSEC3 = null; NSEC3Record lastNSEC3 = null;
NSEC3Record firstNSEC3 = null; NSEC3Record firstNSEC3 = null;
for (Iterator<Map.Entry<Name, MarkRRset>> i = mNSEC3Map.entrySet().iterator(); i.hasNext();) for (Iterator<Map.Entry<Name, MarkRRset>> i = mNSEC3Map.entrySet().iterator(); i.hasNext();) {
{
// check the internal ordering of the previous NSEC3 record. This avoids // check the internal ordering of the previous NSEC3 record. This avoids
// looking at the last one, // looking at the last one,
// which is different. // which is different.
if (lastNSEC3 != null) if (lastNSEC3 != null && compareNSEC3Hashes(lastNSEC3.getName(), lastNSEC3.getNext()) >= 0) {
{
if (compareNSEC3Hashes(lastNSEC3.getName(), lastNSEC3.getNext()) >= 0)
{
log.warning("NSEC3 for " + lastNSEC3.getName() log.warning("NSEC3 for " + lastNSEC3.getName()
+ " has next name >= owner but is not the last NSEC3 in the chain."); + " has next name >= owner but is not the last NSEC3 in the chain.");
errors++; errors++;
} }
}
Map.Entry<Name, MarkRRset> entry = i.next(); Map.Entry<Name, MarkRRset> entry = i.next();
Name n = entry.getKey(); Name n = entry.getKey();
@ -688,8 +614,7 @@ public class ZoneVerifier
// check to see if the NSEC is marked. If not, it was not correlated to a // check to see if the NSEC is marked. If not, it was not correlated to a
// signed node. // signed node.
if (!rrset.getMark()) if (!rrset.getMark()) {
{
log.warning("NSEC3 RR for " + n + " appears to be extra."); log.warning("NSEC3 RR for " + n + " appears to be extra.");
errors++; errors++;
} }
@ -699,23 +624,19 @@ public class ZoneVerifier
// This is just a sanity check. If this isn't true, we are constructing // This is just a sanity check. If this isn't true, we are constructing
// the // the
// nsec3 map incorrectly. // nsec3 map incorrectly.
if (!n.equals(nsec3.getName())) if (!n.equals(nsec3.getName())) {
{
log.severe("The NSEC3 in the map for name " + n + " has name " + nsec3.getName()); log.severe("The NSEC3 in the map for name " + n + " has name " + nsec3.getName());
errors++; errors++;
} }
// note the first NSEC3 in the chain. // note the first NSEC3 in the chain.
if (lastNSEC3 == null) if (lastNSEC3 == null) {
{
firstNSEC3 = nsec3; firstNSEC3 = nsec3;
} } else
else
// Check that the prior NSEC3's next hashed name equals this row's hashed // Check that the prior NSEC3's next hashed name equals this row's hashed
// owner name. // owner name.
{ {
if (compareNSEC3Hashes(nsec3.getName(), lastNSEC3.getNext()) != 0) if (compareNSEC3Hashes(nsec3.getName(), lastNSEC3.getNext()) != 0) {
{
String nextstr = mBase32.toString(lastNSEC3.getNext()); String nextstr = mBase32.toString(lastNSEC3.getNext());
log.warning("NSEC3 for " + lastNSEC3.getName() log.warning("NSEC3 for " + lastNSEC3.getName()
+ " does not point to the next NSEC3 in the chain: " + nsec3.getName() + " does not point to the next NSEC3 in the chain: " + nsec3.getName()
@ -729,8 +650,7 @@ public class ZoneVerifier
// check the internal ordering of the last NSEC in the chain // check the internal ordering of the last NSEC in the chain
// the ownername should be >= next name. // the ownername should be >= next name.
if (compareNSEC3Hashes(lastNSEC3.getName(), lastNSEC3.getNext()) < 0) if (compareNSEC3Hashes(lastNSEC3.getName(), lastNSEC3.getNext()) < 0) {
{
String nextstr = mBase32.toString(lastNSEC3.getNext()); String nextstr = mBase32.toString(lastNSEC3.getNext());
log.warning("The last NSEC3 RR in the chain did not have an owner >= next: owner = " log.warning("The last NSEC3 RR in the chain did not have an owner >= next: owner = "
+ lastNSEC3.getName() + " next = " + nextstr); + lastNSEC3.getName() + " next = " + nextstr);
@ -738,8 +658,7 @@ public class ZoneVerifier
} }
// check to make sure it links to the first NSEC in the chain // check to make sure it links to the first NSEC in the chain
if (compareNSEC3Hashes(firstNSEC3.getName(), lastNSEC3.getNext()) != 0) if (compareNSEC3Hashes(firstNSEC3.getName(), lastNSEC3.getNext()) != 0) {
{
log.warning("The last NSEC3 RR in the chain did not link to the first NSEC3"); log.warning("The last NSEC3 RR in the chain did not link to the first NSEC3");
errors++; errors++;
} }
@ -747,29 +666,22 @@ public class ZoneVerifier
return errors; return errors;
} }
public int verifyZone(List<Record> records) throws NoSuchAlgorithmException, TextParseException public int verifyZone(List<Record> records) throws NoSuchAlgorithmException, TextParseException {
{
int errors = 0; int errors = 0;
errors += calculateNodes(records); errors += calculateNodes(records);
errors += processNodes(); errors += processNodes();
if (mDNSSECType == DNSSECType.NSEC) if (mDNSSECType == DNSSECType.NSEC) {
{
errors += processNSECChain(); errors += processNSECChain();
} } else if (mDNSSECType == DNSSECType.NSEC3 || mDNSSECType == DNSSECType.NSEC3_OPTOUT) {
else if (mDNSSECType == DNSSECType.NSEC3 || mDNSSECType == DNSSECType.NSEC3_OPTOUT)
{
errors += processNSEC3Chain(); errors += processNSEC3Chain();
} }
if (errors > 0) if (errors > 0) {
{
log.info("Zone " + mZoneName + " failed verification with " + errors + " errors"); log.info("Zone " + mZoneName + " failed verification with " + errors + " errors");
} } else {
else
{
log.info("Zone " + mZoneName + " verified with 0 errors"); log.info("Zone " + mZoneName + " verified with 0 errors");
} }