Move all signZone() method variants into JCEDnsSecSigner, make the SignZone class use them.

git-svn-id: https://svn.verisignlabs.com/jdnssec/tools/trunk@112 4cbd57fe-54e5-0310-bd9a-f30fe5ea5e6e
This commit is contained in:
David Blacka 2009-02-02 04:45:49 +00:00
parent 5170a087c9
commit e5270de8ee
2 changed files with 444 additions and 532 deletions

View File

@ -1,6 +1,6 @@
// $Id$ // $Id$
// //
// Copyright (C) 2001-2003 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,21 +25,42 @@ import java.io.FileFilter;
import java.io.FileReader; import java.io.FileReader;
import java.io.IOException; import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.security.GeneralSecurityException;
import java.text.ParseException; import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.*; import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.TimeZone;
import java.util.logging.Handler; import java.util.logging.Handler;
import java.util.logging.Logger;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.cli.*; import org.apache.commons.cli.AlreadySelectedException;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options; import org.apache.commons.cli.Options;
import org.apache.commons.cli.PosixParser;
import org.xbill.DNS.*; import org.apache.commons.cli.UnrecognizedOptionException;
import org.xbill.DNS.DNSKEYRecord;
import org.xbill.DNS.DNSSEC;
import org.xbill.DNS.DSRecord;
import org.xbill.DNS.Name;
import org.xbill.DNS.RRset;
import org.xbill.DNS.Record;
import org.xbill.DNS.TextParseException;
import org.xbill.DNS.utils.base16; import org.xbill.DNS.utils.base16;
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.DnsSecVerifier;
import com.verisignlabs.dnssec.security.JCEDnsSecSigner;
import com.verisignlabs.dnssec.security.SignUtils;
import com.verisignlabs.dnssec.security.ZoneUtils;
/** /**
* This class forms the command line implementation of a DNSSEC zone signer. * This class forms the command line implementation of a DNSSEC zone signer.
@ -93,66 +114,80 @@ public class SignZone
// boolean options // boolean options
opts.addOption("h", "help", false, "Print this message."); opts.addOption("h", "help", false, "Print this message.");
opts.addOption("a", "verify", false, "verify generated signatures>"); opts.addOption("a", "verify", false, "verify generated signatures>");
opts.addOption("F", opts.addOption("F", "fully-sign-keyset", false,
"fully-sign-keyset",
false,
"sign the zone apex keyset with all available keys."); "sign the zone apex keyset with all available keys.");
// Argument options // Argument options
opts.addOption(OptionBuilder.hasOptionalArg().withLongOpt("verbose") opts.addOption(OptionBuilder.hasOptionalArg()
.withArgName("level").withDescription("verbosity level.") .withLongOpt("verbose")
.withArgName("level")
.withDescription("verbosity level.")
.create('v')); .create('v'));
opts.addOption(OptionBuilder.hasArg().withArgName("dir") opts.addOption(OptionBuilder.hasArg()
.withArgName("dir")
.withLongOpt("keyset-directory") .withLongOpt("keyset-directory")
.withDescription("directory to find keyset files (default '.').") .withDescription("directory to find keyset files (default '.').")
.create('d')); .create('d'));
opts.addOption(OptionBuilder.hasArg().withArgName("dir") opts.addOption(OptionBuilder.hasArg()
.withArgName("dir")
.withLongOpt("key-directory") .withLongOpt("key-directory")
.withDescription("directory to find key files (default '.').") .withDescription("directory to find key files (default '.').")
.create('D')); .create('D'));
opts.addOption(OptionBuilder.hasArg().withArgName("time/offset") opts.addOption(OptionBuilder.hasArg()
.withArgName("time/offset")
.withLongOpt("start-time") .withLongOpt("start-time")
.withDescription("signature starting time " .withDescription("signature starting time (default is now - 1 hour)")
+ "(default is now - 1 hour)").create('s')); .create('s'));
opts.addOption(OptionBuilder.hasArg().withArgName("time/offset") opts.addOption(OptionBuilder.hasArg()
.withArgName("time/offset")
.withLongOpt("expire-time") .withLongOpt("expire-time")
.withDescription("signature expiration time " .withDescription(
+ "(default is start-time + 30 days).").create('e')); "signature expiration time (default is start-time + 30 days).")
opts.addOption(OptionBuilder.hasArg().withArgName("outfile") .create('e'));
.withDescription("file the signed zone is written to " opts.addOption(OptionBuilder.hasArg()
+ "(default is <origin>.signed).").create('f')); .withArgName("outfile")
opts.addOption(OptionBuilder.hasArg().withArgName("KSK file") .withDescription(
"file the signed zone is written to (default is <origin>.signed).")
.create('f'));
opts.addOption(OptionBuilder.hasArg()
.withArgName("KSK file")
.withLongOpt("ksk-file") .withLongOpt("ksk-file")
.withDescription("this key is a key signing key (may repeat).") .withDescription("this key is a key signing key (may repeat).")
.create('k')); .create('k'));
opts.addOption(OptionBuilder.hasArg().withArgName("file") opts.addOption(OptionBuilder.hasArg()
.withArgName("file")
.withLongOpt("include-file") .withLongOpt("include-file")
.withDescription("include names in this " .withDescription("include names in this file in the NSEC/NSEC3 chain.")
+ "file in the NSEC/NSEC3 chain.").create('I')); .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", opts.addOption("O", "use-opt-out", false,
"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(OptionBuilder.hasArg().withLongOpt("salt") opts.addOption(OptionBuilder.hasArg()
.withArgName("hex value").withDescription("supply a salt value.") .withLongOpt("salt")
.withArgName("hex value")
.withDescription("supply a salt value.")
.create('S')); .create('S'));
opts.addOption(OptionBuilder.hasArg().withLongOpt("random-salt") opts.addOption(OptionBuilder.hasArg()
.withArgName("length").withDescription("generate a random salt.") .withLongOpt("random-salt")
.withArgName("length")
.withDescription("generate a random salt.")
.create('R')); .create('R'));
opts.addOption(OptionBuilder.hasArg().withLongOpt("iterations") opts.addOption(OptionBuilder.hasArg()
.withLongOpt("iterations")
.withArgName("value") .withArgName("value")
.withDescription("use this value for the iterations in NSEC3.") .withDescription("use this value for the iterations in NSEC3.")
.create()); .create());
opts.addOption(OptionBuilder.hasArg() opts.addOption(OptionBuilder.hasArg()
.withArgName("alias:original:mnemonic").withLongOpt("alg-alias") .withArgName("alias:original:mnemonic")
.withLongOpt("alg-alias")
.withDescription("Define an alias for an algorithm (may repeat).") .withDescription("Define an alias for an algorithm (may repeat).")
.create('A')); .create('A'));
opts.addOption(OptionBuilder.hasArg().withArgName("id") opts.addOption(OptionBuilder.hasArg()
.withArgName("id")
.withLongOpt("ds-digest") .withLongOpt("ds-digest")
.withDescription("Digest algorithm to use for generated DSs") .withDescription("Digest algorithm to use for generated DSs")
.create()); .create());
@ -177,17 +212,17 @@ public class SignZone
switch (value) switch (value)
{ {
case 0 : case 0:
rootLogger.setLevel(Level.OFF); rootLogger.setLevel(Level.OFF);
break; break;
case 4 : case 4:
default : default:
rootLogger.setLevel(Level.INFO); rootLogger.setLevel(Level.INFO);
break; break;
case 5 : case 5:
rootLogger.setLevel(Level.FINE); rootLogger.setLevel(Level.FINE);
break; break;
case 6 : case 6:
rootLogger.setLevel(Level.ALL); rootLogger.setLevel(Level.ALL);
break; break;
} }
@ -352,11 +387,7 @@ public class SignZone
// print our own usage statement: // print our own usage statement:
out.println("usage: signZone.sh [..options..] " out.println("usage: signZone.sh [..options..] "
+ "zone_file [key_file ...] "); + "zone_file [key_file ...] ");
f.printHelp(out, f.printHelp(out, 75, "signZone.sh", null, opts,
75,
"signZone.sh",
null,
opts,
HelpFormatter.DEFAULT_LEFT_PAD, HelpFormatter.DEFAULT_LEFT_PAD,
HelpFormatter.DEFAULT_DESC_PAD, HelpFormatter.DEFAULT_DESC_PAD,
"\ntime/offset = YYYYMMDDHHmmss|+offset|\"now\"+offset\n"); "\ntime/offset = YYYYMMDDHHmmss|+offset|\"now\"+offset\n");
@ -369,8 +400,10 @@ public class SignZone
/** /**
* This is just a convenience method for parsing integers from strings. * This is just a convenience method for parsing integers from strings.
* *
* @param s the string to parse. * @param s
* @param def the default value, if the string doesn't parse. * the string to parse.
* @param def
* the default value, if the string doesn't parse.
* @return the parsed integer, or the default. * @return the parsed integer, or the default.
*/ */
private static int parseInt(String s, int def) private static int parseInt(String s, int def)
@ -389,9 +422,12 @@ public class SignZone
/** /**
* Verify the generated signatures. * Verify the generated signatures.
* *
* @param zonename the origin name of the zone. * @param zonename
* @param records a list of {@link org.xbill.DNS.Record}s. * the origin name of the zone.
* @param keypairs a list of keypairs used the sign the zone. * @param records
* a list of {@link org.xbill.DNS.Record}s.
* @param keypairs
* a list of keypairs used the sign the zone.
* @return true if all of the signatures validated. * @return true if all of the signatures validated.
*/ */
private static boolean verifyZoneSigs(Name zonename, List records, private static boolean verifyZoneSigs(Name zonename, List records,
@ -433,11 +469,14 @@ public class SignZone
/** /**
* Load the key pairs from the key files. * Load the key pairs from the key files.
* *
* @param keyfiles a string array containing the base names or paths of the * @param keyfiles
* keys to be loaded. * a string array containing the base names or paths of the keys to
* @param start_index the starting index of keyfiles string array to use. * be loaded.
* This allows us to use the straight command line argument array. * @param start_index
* @param inDirectory the directory to look in (may be null). * the starting index of keyfiles string array to use. This allows us
* to use the straight command line argument array.
* @param inDirectory
* the directory to look in (may be null).
* @return a list of keypair objects. * @return a list of keypair objects.
*/ */
private static List getKeys(String[] keyfiles, int start_index, private static List getKeys(String[] keyfiles, int start_index,
@ -477,13 +516,13 @@ public class SignZone
/** /**
* Load keysets (which contain delegation point security info). * Load keysets (which contain delegation point security info).
* *
* @param inDirectory the directory to look for the keyset files (may be * @param inDirectory
* null, in which case it defaults to looking in the current * the directory to look for the keyset files (may be null, in which
* working directory). * case it defaults to looking in the current working directory).
* @param zonename the name of the zone we are signing, so we can ignore * @param zonename
* keysets that do not belong in the zone. * the name of the zone we are signing, so we can ignore keysets that
* @return a list of {@link org.xbill.DNS.Record}s found in the keyset * do not belong in the zone.
* files. * @return a list of {@link org.xbill.DNS.Record}s found in the keyset files.
*/ */
private static List getKeysets(File inDirectory, Name zonename) private static List getKeysets(File inDirectory, Name zonename)
throws IOException throws IOException
@ -521,8 +560,8 @@ public class SignZone
/** /**
* Load a list of DNS names from a file. * Load a list of DNS names from a file.
* *
* @param nameListFile the path of a file containing a bare list of DNS * @param nameListFile
* names. * the path of a file containing a bare list of DNS names.
* @return a list of {@link org.xbill.DNS.Name} objects. * @return a list of {@link org.xbill.DNS.Name} objects.
*/ */
private static List getNameList(File nameListFile) throws IOException private static List getNameList(File nameListFile) throws IOException
@ -557,8 +596,10 @@ public class SignZone
/** /**
* Calculate a date/time from a command line time/offset duration string. * Calculate a date/time from a command line time/offset duration string.
* *
* @param start the start time to calculate offsets from. * @param start
* @param duration the time/offset string to parse. * the start time to calculate offsets from.
* @param duration
* the time/offset string to parse.
* @return the calculated time. * @return the calculated time.
*/ */
private static Date convertDuration(Date start, String duration) private static Date convertDuration(Date start, String duration)
@ -587,9 +628,11 @@ public class SignZone
/** /**
* 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 the zone origin. * @param zonename
* @param keypairs a list of {@link DnsKeyPair} objects that will be used to * the zone origin.
* sign the zone. * @param keypairs
* a list of {@link DnsKeyPair} objects that will be used to sign the
* zone.
* @return true if the keypairs valid. * @return true if the keypairs valid.
*/ */
private static boolean keyPairsValidForZone(Name zonename, List keypairs) private static boolean keyPairsValidForZone(Name zonename, List keypairs)
@ -600,306 +643,12 @@ public class SignZone
{ {
DnsKeyPair kp = (DnsKeyPair) i.next(); DnsKeyPair kp = (DnsKeyPair) i.next();
Name keyname = kp.getDNSKEYRecord().getName(); Name keyname = kp.getDNSKEYRecord().getName();
if (!keyname.equals(zonename)) if (!keyname.equals(zonename)) { return false; }
{
return false;
}
} }
return true; return true;
} }
/**
* Conditionally sign an RRset and add it to the toList.
*
* @param toList the list to which we are adding the processed RRsets.
* @param zonename the zone apex name.
* @param rrset the rrset under consideration.
* @param keysigningkeypairs the List of KSKs..
* @param zonekeypairs the List of zone keys.
* @param start the RRSIG inception time.
* @param expire the RRSIG expiration time.
* @param fullySignKeyset if true, sign the zone apex keyset with both KSKs
* and ZSKs.
* @param last_cut the name of the last delegation point encountered.
* @return the name of the new last_cut.
*/
private static Name addRRset(JCEDnsSecSigner signer, List toList,
Name zonename, RRset rrset, List keysigningkeypairs, List zonekeypairs,
Date start, Date expire, boolean fullySignKeyset, Name last_cut)
throws IOException, GeneralSecurityException
{
// add the records themselves
for (Iterator i = rrset.rrs(); i.hasNext();)
{
toList.add(i.next());
}
int type = SignUtils.recordSecType(zonename, rrset.getName(), rrset
.getType(), last_cut);
// we don't sign non-normal sets (delegations, glue, invalid).
// we also don't sign the zone key set unless we've been asked.
if (type == SignUtils.RR_DELEGATION)
{
return rrset.getName();
}
if (type == SignUtils.RR_GLUE || type == SignUtils.RR_INVALID)
{
return last_cut;
}
// check for the zone apex keyset.
if (rrset.getName().equals(zonename) && rrset.getType() == Type.DNSKEY)
{
// if we have key signing keys, sign the keyset with them,
// otherwise we will just sign them with the zonesigning keys.
if (keysigningkeypairs != null && keysigningkeypairs.size() > 0)
{
List sigs = signer
.signRRset(rrset, keysigningkeypairs, start, expire);
toList.addAll(sigs);
// If we aren't going to sign with all the keys, bail out now.
if (!fullySignKeyset) return last_cut;
}
}
// otherwise, we are OK to sign this set.
List sigs = signer.signRRset(rrset, zonekeypairs, start, expire);
toList.addAll(sigs);
return last_cut;
}
/**
* Given a zone, sign it.
*
* @param signer A signer (utility) object to use to actually sign stuff.
* @param zonename The name of the zone.
* @param records The records comprising the zone. They do not have to be in
* any particular order, as this method will order them as
* necessary.
* @param keysigningkeypairs The key pairs that are designated as "key
* signing keys".
* @param zonekeypair This key pairs that are designated as "zone signing
* keys".
* @param start The RRSIG inception time.
* @param expire The RRSIG expiration time.
* @param fullySignKeyset Sign the zone apex keyset with all available keys.
* @param digest_id The digest identifier to use when generating DS records.
*
* @return an ordered list of {@link org.xbill.DNS.Record} objects,
* representing the signed zone.
*/
private static List signZone(JCEDnsSecSigner signer, Name zonename,
List records, List keysigningkeypairs, List zonekeypairs, Date start,
Date expire, boolean fullySignKeyset, int digest_id)
throws IOException, GeneralSecurityException
{
// Remove any existing DNSSEC records (NSEC, NSEC3, RRSIG)
SignUtils.removeGeneratedRecords(zonename, records);
// Sort the zone
Collections.sort(records, new RecordComparator());
// Remove any duplicate records.
SignUtils.removeDuplicateRecords(records);
// Generate DS records
SignUtils.generateDSRecords(zonename, records, digest_id);
// Generate the NSEC records
SignUtils.generateNSECRecords(zonename, records);
// Assemble into RRsets and sign.
RRset rrset = new RRset();
ArrayList signed_records = new ArrayList();
Name last_cut = null;
for (ListIterator i = records.listIterator(); i.hasNext();)
{
Record r = (Record) i.next();
// First record
if (rrset.size() == 0)
{
rrset.addRR(r);
continue;
}
// Current record is part of the current RRset.
if (rrset.getName().equals(r.getName())
&& rrset.getDClass() == r.getDClass()
&& rrset.getType() == r.getType())
{
rrset.addRR(r);
continue;
}
// Otherwise, we have completed the RRset
// Sign the records
// add the RRset to the list of signed_records, regardless of
// whether or not we actually end up signing the set.
last_cut = addRRset(signer,
signed_records,
zonename,
rrset,
keysigningkeypairs,
zonekeypairs,
start,
expire,
fullySignKeyset,
last_cut);
rrset.clear();
rrset.addRR(r);
}
// add the last RR set
addRRset(signer,
signed_records,
zonename,
rrset,
keysigningkeypairs,
zonekeypairs,
start,
expire,
fullySignKeyset,
last_cut);
return signed_records;
}
/**
* Given a zone sign it using NSEC3 records.
*
* @param signer A signer (utility) object used to actually sign stuff.
* @param zonename The name of the zone being signed.
* @param records The records comprising the zone. They do not have to be in
* any particular order, as this method will order them as
* necessary.
* @param keysigningkeypairs The key pairs that are designated as "key
* signing keys".
* @param zonekeypairs This key pairs that are designated as "zone signing
* keys".
* @param start The RRSIG inception time.
* @param expire The RRSIG expiration time.
* @param fullySignKeyset If true then the DNSKEY RRset will be signed by
* all available keys, if false, only the key signing keys.
* @param useOptOut If true, insecure delegations will be omitted from the
* NSEC3 chain, and all NSEC3 records will have the Opt-Out flag
* set.
* @param includedNames A list of names to include in the NSEC3 chain
* regardless.
* @param salt The salt to use for the NSEC3 hashing. null means no salt.
* @param iterations The number of iterations to use for the NSEC3 hashing.
* @param ds_digest_id The digest algorithm to use when generating DS
* records.
*
* @return an ordered list of {@link org.xbill.DNS.Record} objects,
* representing the signed zone.
*
* @throws IOException
* @throws GeneralSecurityException
*/
private static List signZoneNSEC3(JCEDnsSecSigner signer, Name zonename,
List records, List keysigningkeypairs, List zonekeypairs, Date start,
Date expire, boolean fullySignKeyset, boolean useOptOut,
List includedNames, byte[] salt, int iterations, int ds_digest_id)
throws IOException, GeneralSecurityException
{
// Remove any existing DNSSEC records (NSEC, NSEC3, NSEC3PARAM, RRSIG)
SignUtils.removeGeneratedRecords(zonename, records);
// Sort the zone
Collections.sort(records, new RecordComparator());
// Remove duplicate records
SignUtils.removeDuplicateRecords(records);
// Generate DS records
SignUtils.generateDSRecords(zonename, records, ds_digest_id);
// Generate NSEC3 records
if (useOptOut)
{
SignUtils.generateOptOutNSEC3Records(zonename,
records,
includedNames,
salt,
iterations);
}
else
{
SignUtils.generateNSEC3Records(zonename, records, salt, iterations);
}
// Re-sort so we can assemble into rrsets.
Collections.sort(records, new RecordComparator());
// Assemble into RRsets and sign.
RRset rrset = new RRset();
ArrayList signed_records = new ArrayList();
Name last_cut = null;
for (ListIterator i = records.listIterator(); i.hasNext();)
{
Record r = (Record) i.next();
// First record
if (rrset.size() == 0)
{
rrset.addRR(r);
continue;
}
// Current record is part of the current RRset.
if (rrset.getName().equals(r.getName())
&& rrset.getDClass() == r.getDClass()
&& rrset.getType() == r.getType())
{
rrset.addRR(r);
continue;
}
// Otherwise, we have completed the RRset
// Sign the records
// add the RRset to the list of signed_records, regardless of
// whether or not we actually end up signing the set.
last_cut = addRRset(signer,
signed_records,
zonename,
rrset,
keysigningkeypairs,
zonekeypairs,
start,
expire,
fullySignKeyset,
last_cut);
rrset.clear();
rrset.addRR(r);
}
// add the last RR set
addRRset(signer,
signed_records,
zonename,
rrset,
keysigningkeypairs,
zonekeypairs,
start,
expire,
fullySignKeyset,
last_cut);
return signed_records;
}
public static void execute(CLIState state) throws Exception public static void execute(CLIState state) throws Exception
{ {
// Load the key pairs. // Load the key pairs.
@ -1014,31 +763,19 @@ public class SignZone
if (state.useNsec3) if (state.useNsec3)
{ {
signed_records = signZoneNSEC3(signer, signed_records = signer.signZoneNSEC3(zonename, records, kskpairs,
zonename, keypairs, state.start,
records,
kskpairs,
keypairs,
state.start,
state.expire, state.expire,
state.fullySignKeyset, state.fullySignKeyset,
state.useOptOut, state.useOptOut,
state.includeNames, state.includeNames, state.salt,
state.salt, state.iterations, state.digest_id);
state.iterations,
state.digest_id);
} }
else else
{ {
signed_records = signZone(signer, signed_records = signer.signZone(zonename, records, kskpairs, keypairs,
zonename, state.start, state.expire,
records, state.fullySignKeyset, state.digest_id);
kskpairs,
keypairs,
state.start,
state.expire,
state.fullySignKeyset,
state.digest_id);
} }
// write out the signed zone // write out the signed zone
@ -1080,8 +817,7 @@ public class SignZone
} }
catch (UnrecognizedOptionException e) catch (UnrecognizedOptionException e)
{ {
System.err.println("error: unknown option encountered: " System.err.println("error: unknown option encountered: " + e.getMessage());
+ e.getMessage());
state.usage(); state.usage();
} }
catch (AlreadySelectedException e) catch (AlreadySelectedException e)

View File

@ -1,6 +1,6 @@
// $Id$ // $Id$
// //
// Copyright (C) 2001-2003 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
@ -43,13 +43,14 @@ import org.xbill.DNS.Type;
* This class contains routines for signing DNS zones. * This class contains routines for signing DNS zones.
* *
* In particular, it contains both an ability to sign an individual RRset and * In particular, it contains both an ability to sign an individual RRset and
* the ability to sign and 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 (original)
* @author $Author$ * @author $Author$
* @version $Revision$ * @version $Revision$
*/ */
public class JCEDnsSecSigner public class JCEDnsSecSigner
{ {
private DnsKeyConverter mKeyConverter; private DnsKeyConverter mKeyConverter;
@ -57,34 +58,41 @@ public class JCEDnsSecSigner
/** /**
* Cryptographically generate a new DNSSEC key. * Cryptographically generate a new DNSSEC key.
* *
* @param owner the KEY RR's owner name. * @param owner
* @param ttl the KEY RR's TTL. * the KEY RR's owner name.
* @param dclass the KEY RR's DNS class. * @param ttl
* @param algorithm the DNSSEC algorithm (RSAMD5, RSASHA1, or DSA). * the KEY RR's TTL.
* @param flags any flags for the KEY RR. * @param dclass
* @param keysize the size of the key to generate. * the KEY RR's DNS class.
* @param useLargeExponent if generating an RSA key, use the large exponent. * @param algorithm
* the DNSSEC algorithm (RSAMD5, RSASHA1, or DSA).
* @param flags
* any flags for the KEY RR.
* @param keysize
* the size of the key to generate.
* @param useLargeExponent
* if generating an RSA key, use the large exponent.
* @return a DnsKeyPair with the public and private keys populated. * @return a DnsKeyPair with the public and private keys populated.
*/ */
public DnsKeyPair generateKey(Name owner, long ttl, int dclass, public DnsKeyPair generateKey(Name owner, long ttl, int dclass,
int algorithm, int flags, int keysize, boolean useLargeExponent) int algorithm, 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();
} }
DNSKEYRecord keyrec = mKeyConverter.generateDNSKEYRecord(owner, DNSKEYRecord keyrec = mKeyConverter.generateDNSKEYRecord(owner, dclass,
dclass, ttl, flags,
ttl,
flags,
algorithm, algorithm,
pair.getPublic()); pair.getPublic());
@ -99,10 +107,14 @@ public class JCEDnsSecSigner
/** /**
* Sign an RRset. * Sign an RRset.
* *
* @param rrset the RRset to sign -- any existing signatures are ignored. * @param rrset
* @param keypars a list of DnsKeyPair objects containing private keys. * the RRset to sign -- any existing signatures are ignored.
* @param start the inception time for the resulting RRSIG records. * @param keypars
* @param expire the expiration time for the resulting RRSIG records. * a list of DnsKeyPair objects containing private keys.
* @param start
* the inception time for the resulting RRSIG records.
* @param expire
* the expiration time for the resulting RRSIG records.
* @return a list of RRSIGRecord objects. * @return a list of RRSIGRecord objects.
*/ */
public List signRRset(RRset rrset, List keypairs, Date start, Date expire) public List signRRset(RRset rrset, List keypairs, Date start, Date expire)
@ -115,23 +127,20 @@ public class JCEDnsSecSigner
if (expire == null) expire = new Date(start.getTime() + 1000L); if (expire == null) expire = new Date(start.getTime() + 1000L);
if (keypairs.size() == 0) return null; if (keypairs.size() == 0) return null;
// first, pre-calculate the rrset bytes. // first, pre-calculate the RRset bytes.
byte[] rrset_data = SignUtils.generateCanonicalRRsetData(rrset); byte[] rrset_data = SignUtils.generateCanonicalRRsetData(rrset);
ArrayList sigs = new ArrayList(keypairs.size()); ArrayList sigs = new ArrayList(keypairs.size());
// for each keypair, sign the rrset. // for each keypair, sign the RRset.
for (Iterator i = keypairs.iterator(); i.hasNext();) for (Iterator i = keypairs.iterator(); i.hasNext();)
{ {
DnsKeyPair pair = (DnsKeyPair) i.next(); DnsKeyPair pair = (DnsKeyPair) i.next();
DNSKEYRecord keyrec = pair.getDNSKEYRecord(); DNSKEYRecord keyrec = pair.getDNSKEYRecord();
if (keyrec == null) continue; if (keyrec == null) continue;
RRSIGRecord presig = SignUtils.generatePreRRSIG(rrset, RRSIGRecord presig = SignUtils.generatePreRRSIG(rrset, keyrec, start,
keyrec, expire, rrset.getTTL());
start,
expire,
rrset.getTTL());
byte[] sign_data = SignUtils.generateSigData(rrset_data, presig); byte[] sign_data = SignUtils.generateSigData(rrset_data, presig);
Signature signer = pair.getSigner(); Signature signer = pair.getSigner();
@ -142,8 +151,7 @@ public class JCEDnsSecSigner
System.out.println("missing private key that goes with:\n" System.out.println("missing private key that goes with:\n"
+ pair.getDNSKEYRecord()); + pair.getDNSKEYRecord());
throw new GeneralSecurityException( throw new GeneralSecurityException(
"cannot sign without a valid Signer " "cannot sign without a valid Signer (probably missing private key)");
+ "(probably missing private key)");
} }
// sign the data. // sign the data.
@ -154,8 +162,9 @@ public class JCEDnsSecSigner
// 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)
{ {
sig = SignUtils.convertDSASignature(((DSAPublicKey) pair.getPublic()) sig = SignUtils.convertDSASignature(
.getParams(), sig); ((DSAPublicKey) pair.getPublic()).getParams(),
sig);
} }
RRSIGRecord sigrec = SignUtils.generateRRSIG(sig, presig); RRSIGRecord sigrec = SignUtils.generateRRSIG(sig, presig);
sigs.add(sigrec); sigs.add(sigrec);
@ -165,11 +174,14 @@ public class JCEDnsSecSigner
} }
/** /**
* Create a completely self-signed KEY RRset. * Create a completely self-signed DNSKEY RRset.
* *
* @param keypairs the public & private keypairs to use in the keyset. * @param keypairs
* @param start the RRSIG inception time. * the public & private keypairs to use in the keyset.
* @param expire the RRSIG expiration time. * @param start
* the RRSIG inception time.
* @param expire
* the RRSIG expiration time.
* @return a signed RRset. * @return a signed RRset.
*/ */
public RRset makeKeySet(List keypairs, Date start, Date expire) public RRset makeKeySet(List keypairs, Date start, Date expire)
@ -198,20 +210,29 @@ public class JCEDnsSecSigner
/** /**
* Conditionally sign an RRset and add it to the toList. * Conditionally sign an RRset and add it to the toList.
* *
* @param toList the list to which we are adding the processed RRsets. * @param toList
* @param zonename the zone apex name. * the list to which we are adding the processed RRsets.
* @param rrset the rrset under consideration. * @param zonename
* @param keysigningkeypairs the List of KSKs.. * the zone apex name.
* @param zonekeypairs the List of zone keys. * @param rrset
* @param start the RRSIG inception time. * the RRset under consideration.
* @param expire the RRSIG expiration time. * @param kskpairs
* @param fullySignKeyset if true, sign the zone apex keyset with both KSKs * the List of KSKs..
* and ZSKs. * @param zskpairs
* @param last_cut the name of the last delegation point encountered. * the List of zone keys.
* @param start
* the RRSIG inception time.
* @param expire
* the RRSIG expiration time.
* @param fullySignKeyset
* if true, sign the zone apex keyset with both KSKs and ZSKs.
* @param last_cut
* the name of the last delegation point encountered.
*
* @return the name of the new last_cut. * @return the name of the new last_cut.
*/ */
private Name addRRset(List toList, Name zonename, RRset rrset, private Name addRRset(List toList, Name zonename, RRset rrset, List kskpairs,
List keysigningkeypairs, List zonekeypairs, Date start, Date expire, List zskpairs, Date start, Date expire,
boolean fullySignKeyset, Name last_cut) boolean fullySignKeyset, Name last_cut)
throws IOException, GeneralSecurityException throws IOException, GeneralSecurityException
{ {
@ -221,28 +242,21 @@ public class JCEDnsSecSigner
toList.add(i.next()); toList.add(i.next());
} }
int type = SignUtils.recordSecType(zonename, rrset.getName(), rrset int type = SignUtils.recordSecType(zonename, rrset.getName(),
.getType(), last_cut); rrset.getType(), last_cut);
// we don't sign non-normal sets (delegations, glue, invalid). // we don't sign non-normal sets (delegations, glue, invalid).
// we also don't sign the zone key set unless we've been asked. if (type == SignUtils.RR_DELEGATION) { return rrset.getName(); }
if (type == SignUtils.RR_DELEGATION) if (type == SignUtils.RR_GLUE || type == SignUtils.RR_INVALID) { return last_cut; }
{
return rrset.getName();
}
if (type == SignUtils.RR_GLUE || type == SignUtils.RR_INVALID)
{
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 key signing keys, sign the keyset with them, // if we have ksks, sign the keyset with them, otherwise we will just sign
// otherwise we will just sign them with the zonesigning keys. // them with the zsks.
if (keysigningkeypairs != null && keysigningkeypairs.size() > 0) if (kskpairs != null && kskpairs.size() > 0)
{ {
List sigs = signRRset(rrset, keysigningkeypairs, start, expire); List 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.
@ -251,70 +265,108 @@ public class JCEDnsSecSigner
} }
// otherwise, we are OK to sign this set. // otherwise, we are OK to sign this set.
List sigs = signRRset(rrset, zonekeypairs, start, expire); List sigs = signRRset(rrset, zskpairs, start, expire);
toList.addAll(sigs); toList.addAll(sigs);
return last_cut; return last_cut;
} }
// Various NSEC/NSEC3 generation modes
private static final int NSEC_MODE = 0;
private static final int NSEC3_MODE = 1;
private static final int NSEC3_OPTOUT_MODE = 2;
private static final int NSEC_EXP_OPT_IN = 3;
/** /**
* Given a zone, sign it. * Master zone signing method. This method handles all of the different zone
* signing variants (NSEC with or without Opt-In, NSEC3 with or without
* Opt-Out, etc.) External users of this class are expected to use the
* appropriate public signZone* methods instead of this.
*
* @param zonename
* The name of the zone
* @param records
* The records comprising the zone. They do not have to be in any
* particular order, as this method will order them as necessary.
* @param kskpairs
* The key pairs designated as "key signing keys"
* @param zskpairs
* The key pairs designated as "zone signing keys"
* @param start
* The RRSIG inception time
* @param expire
* The RRSIG expiration time
* @param fullySignKeyset
* If true, all keys (ksk or zsk) will sign the DNSKEY RRset. If
* false, only the ksks will sign it.
* @param ds_digest_alg
* The hash algorithm to use for generating DS records
* (DSRecord.SHA1_DIGEST_ID, e.g.)
* @param mode
* The NSEC/NSEC3 generation mode: NSEC_MODE, NSEC3_MODE,
* NSEC3_OPTOUT_MODE, etc.
* @param includedNames
* When using an Opt-In/Opt-Out mode, the names listed here will be
* included in the NSEC/NSEC3 chain regardless
* @param salt
* When using an NSEC3 mode, use this salt.
* @param iterations
* When using an NSEC3 mode, use this number of iterations
* @param beConservative
* If true, then only turn on the Opt-In flag when there are insecure
* delegations in the span. Currently this only works for
* NSEC_EXP_OPT_IN mode.
* *
* @param zonename the name of the zone.
* @param records the records comprising the zone. They do not have to be in
* any particular order, as this method will order them as
* necessary.
* @param keysigningkeypairs the key pairs that are designated as "key
* signing keys".
* @param start the RRSIG inception time.
* @param expire the RRSIG expiration time.
* @param useOptIn generate Opt-In style NXT records. It will consider any
* insecure delegation to be unsigned. To override this, include
* the name of the insecure delegation in the NXTIncludeNames list.
* @param useConservativeOptIn if true, Opt-In NXT records will only be
* generated if there are insecure, unsigned delegations in the
* span. Not effect if useOptIn is false.
* @param fullySignKeyset sign the zone apex keyset with all available keys.
* @param ds_digest_id TODO
* @param zonekeypair this key pairs that are designated as "zone signing
* keys".
* @param NXTIncludeNames names that are to be included in the NXT chain
* regardless. This may be null and is only used if useOptIn is
* true.
* @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.
*
* @throws IOException
* @throws GeneralSecurityException
*/ */
public List signZone(Name zonename, List records, List keysigningkeypairs, private List signZone(Name zonename, List records, List kskpairs,
List zonekeypairs, Date start, Date expire, boolean useOptIn, List zskpairs, Date start, Date expire,
boolean useConservativeOptIn, boolean fullySignKeyset, boolean fullySignKeyset, int ds_digest_alg, int mode,
List NSECIncludeNames, int ds_digest_id) List includedNames, byte[] salt, int iterations,
boolean beConservative)
throws IOException, GeneralSecurityException throws IOException, GeneralSecurityException
{ {
// Remove any existing generated DNSSEC records (NSEC, NSEC3, NSEC3PARAM,
// Remove any existing DNSSEC records (NSEC, RRSIG) // RRSIG)
SignUtils.removeGeneratedRecords(zonename, records); SignUtils.removeGeneratedRecords(zonename, records);
// Sort the zone
Collections.sort(records, new RecordComparator());
// Remove any duplicate records. RecordComparator rc = new RecordComparator();
// Sort the zone
Collections.sort(records, rc);
// Remove duplicate records
SignUtils.removeDuplicateRecords(records); SignUtils.removeDuplicateRecords(records);
// Generate DS records // Generate DS records. This replaces any non-zone-apex DNSKEY RRs with DS
SignUtils.generateDSRecords(zonename, records, ds_digest_id); // RRs.
SignUtils.generateDSRecords(zonename, records, ds_digest_alg);
// Generate NXT records // Generate the NSEC or NSEC3 records based on 'mode'
if (useOptIn) switch (mode)
{
SignUtils.generateOptInNSECRecords(zonename,
records,
NSECIncludeNames,
useConservativeOptIn);
}
else
{ {
case NSEC_MODE:
SignUtils.generateNSECRecords(zonename, records); SignUtils.generateNSECRecords(zonename, records);
break;
case NSEC3_MODE:
SignUtils.generateNSEC3Records(zonename, records, salt, iterations);
break;
case NSEC3_OPTOUT_MODE:
SignUtils.generateOptOutNSEC3Records(zonename, records, includedNames,
salt, iterations);
break;
case NSEC_EXP_OPT_IN:
SignUtils.generateOptInNSECRecords(zonename, records, includedNames,
beConservative);
break;
} }
// Re-sort so we can assemble into rrsets.
Collections.sort(records, rc);
// Assemble into RRsets and sign. // Assemble into RRsets and sign.
RRset rrset = new RRset(); RRset rrset = new RRset();
ArrayList signed_records = new ArrayList(); ArrayList signed_records = new ArrayList();
@ -325,7 +377,7 @@ public class JCEDnsSecSigner
Record r = (Record) i.next(); Record r = (Record) i.next();
// First record // First record
if (rrset.getName() == null) if (rrset.size() == 0)
{ {
rrset.addRR(r); rrset.addRR(r);
continue; continue;
@ -345,31 +397,155 @@ public class JCEDnsSecSigner
// add the RRset to the list of signed_records, regardless of // add the RRset to the list of signed_records, regardless of
// 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, last_cut = addRRset(signed_records, zonename, rrset, kskpairs, zskpairs,
zonename, start, expire, fullySignKeyset, last_cut);
rrset,
keysigningkeypairs,
zonekeypairs,
start,
expire,
fullySignKeyset,
last_cut);
rrset.clear(); rrset.clear();
rrset.addRR(r); rrset.addRR(r);
} }
// add the last RR set // add the last RR set
addRRset(signed_records, addRRset(signed_records, zonename, rrset, kskpairs, zskpairs, start,
zonename, expire, fullySignKeyset, last_cut);
rrset,
keysigningkeypairs,
zonekeypairs,
start,
expire,
fullySignKeyset,
last_cut);
return signed_records; return signed_records;
} }
/**
* Given a zone, sign it using standard NSEC records.
*
* @param zonename
* The name of the zone.
* @param records
* The records comprising the zone. They do not have to be in any
* particular order, as this method will order them as necessary.
* @param kskpairs
* The key pairs that are designated as "key signing keys".
* @param zskpairs
* This key pairs that are designated as "zone signing keys".
* @param start
* The RRSIG inception time.
* @param expire
* The RRSIG expiration time.
* @param fullySignKeyset
* Sign the zone apex keyset with all available keys (instead of just
* the key signing keys).
* @param ds_digest_alg
* The digest algorithm to use when generating DS records.
*
* @return an ordered list of {@link org.xbill.DNS.Record} objects,
* representing the signed zone.
*/
public List signZone(Name zonename, List records, List kskpairs,
List zskpairs, Date start, Date expire,
boolean fullySignKeyset, int ds_digest_alg)
throws IOException, GeneralSecurityException
{
return signZone(zonename, records, kskpairs, zskpairs, start, expire,
fullySignKeyset, ds_digest_alg, NSEC_MODE, null, null, 0,
false);
}
/**
* Given a zone, sign it using NSEC3 records.
*
* @param signer
* A signer (utility) object used to actually sign stuff.
* @param zonename
* The name of the zone being signed.
* @param records
* The records comprising the zone. They do not have to be in any
* particular order, as this method will order them as necessary.
* @param kskpairs
* The key pairs that are designated as "key signing keys".
* @param zskpairs
* This key pairs that are designated as "zone signing keys".
* @param start
* The RRSIG inception time.
* @param expire
* The RRSIG expiration time.
* @param fullySignKeyset
* If true then the DNSKEY RRset will be signed by all available
* keys, if false, only the key signing keys.
* @param useOptOut
* If true, insecure delegations will be omitted from the NSEC3
* chain, and all NSEC3 records will have the Opt-Out flag set.
* @param includedNames
* A list of names to include in the NSEC3 chain regardless.
* @param salt
* The salt to use for the NSEC3 hashing. null means no salt.
* @param iterations
* The number of iterations to use for the NSEC3 hashing.
* @param ds_digest_alg
* The digest algorithm to use when generating DS records.
*
* @return an ordered list of {@link org.xbill.DNS.Record} objects,
* representing the signed zone.
*
* @throws IOException
* @throws GeneralSecurityException
*/
public List signZoneNSEC3(Name zonename, List records, List kskpairs,
List zskpairs, Date start, Date expire,
boolean fullySignKeyset, boolean useOptOut,
List includedNames, byte[] salt, int iterations,
int ds_digest_alg)
throws IOException, GeneralSecurityException
{
if (useOptOut)
{
return signZone(zonename, records, kskpairs, zskpairs, start, expire,
fullySignKeyset, ds_digest_alg, NSEC3_OPTOUT_MODE,
includedNames, salt, iterations, false);
}
else
{
return signZone(zonename, records, kskpairs, zskpairs, start, expire,
fullySignKeyset, ds_digest_alg, NSEC3_MODE, null, salt,
iterations, false);
}
}
/**
* Given a zone, sign it using experimental Opt-In NSEC records (see RFC
* 4956).
*
* @param zonename
* the name of the zone.
* @param records
* the records comprising the zone. They do not have to be in any
* particular order, as this method will order them as necessary.
* @param kskpairs
* the key pairs that are designated as "key signing keys".
* @param zskpairs
* this key pairs that are designated as "zone signing keys".
* @param start
* the RRSIG inception time.
* @param expire
* the RRSIG expiration time.
* @param useConservativeOptIn
* if true, Opt-In NSEC records will only be generated if there are
* insecure, unsigned delegations in the span.
* @param fullySignKeyset
* sign the zone apex keyset with all available keys.
* @param ds_digest_alg
* The digest algorithm to use when generating DS records.
* @param NSECIncludeNames
* names that are to be included in the NSEC chain regardless. This
* may be null.
* @return an ordered list of {@link org.xbill.DNS.Record} objects,
* representing the signed zone.
*/
public List signZoneOptIn(Name zonename, List records, List kskpairs,
List zskpairs, Date start, Date expire,
boolean useConservativeOptIn,
boolean fullySignKeyset, List NSECIncludeNames,
int ds_digest_alg)
throws IOException, GeneralSecurityException
{
return signZone(zonename, records, kskpairs, zskpairs, start, expire,
fullySignKeyset, ds_digest_alg, NSEC_EXP_OPT_IN,
NSECIncludeNames, null, 0, useConservativeOptIn);
}
} }