Add ability for jdnssec-signzone to find the necessary keys by either looking in the zone to find DNSKEY RRs, or by looking on disk for key files matching the zonename.

git-svn-id: https://svn.verisignlabs.com/jdnssec/tools/trunk@122 4cbd57fe-54e5-0310-bd9a-f30fe5ea5e6e
This commit is contained in:
David Blacka 2009-02-05 05:04:30 +00:00
parent 49dfddb432
commit 8b61f84308
2 changed files with 216 additions and 100 deletions

View File

@ -52,6 +52,7 @@ 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;
import org.xbill.DNS.TextParseException; import org.xbill.DNS.TextParseException;
import org.xbill.DNS.Type;
import org.xbill.DNS.utils.base16; import org.xbill.DNS.utils.base16;
import com.verisignlabs.dnssec.security.BINDKeyUtils; import com.verisignlabs.dnssec.security.BINDKeyUtils;
@ -117,80 +118,88 @@ public class SignZone
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.");
OptionBuilder.hasOptionalArg();
OptionBuilder.withLongOpt("verbose");
OptionBuilder.withArgName("level");
OptionBuilder.withDescription("verbosity level.");
// Argument options // Argument options
opts.addOption(OptionBuilder.hasOptionalArg() opts.addOption(OptionBuilder.create('v'));
.withLongOpt("verbose")
.withArgName("level") OptionBuilder.hasArg();
.withDescription("verbosity level.") OptionBuilder.withArgName("dir");
.create('v')); OptionBuilder.withLongOpt("keyset-directory");
opts.addOption(OptionBuilder.hasArg() OptionBuilder.withDescription("directory to find keyset files (default '.').");
.withArgName("dir") opts.addOption(OptionBuilder.create('d'));
.withLongOpt("keyset-directory")
.withDescription("directory to find keyset files (default '.').") OptionBuilder.hasArg();
.create('d')); OptionBuilder.withArgName("dir");
opts.addOption(OptionBuilder.hasArg() OptionBuilder.withLongOpt("key-directory");
.withArgName("dir") OptionBuilder.withDescription("directory to find key files (default '.').");
.withLongOpt("key-directory") opts.addOption(OptionBuilder.create('D'));
.withDescription("directory to find key files (default '.').")
.create('D')); OptionBuilder.hasArg();
opts.addOption(OptionBuilder.hasArg() OptionBuilder.withArgName("time/offset");
.withArgName("time/offset") OptionBuilder.withLongOpt("start-time");
.withLongOpt("start-time") OptionBuilder.withDescription("signature starting time (default is now - 1 hour)");
.withDescription("signature starting time (default is now - 1 hour)") opts.addOption(OptionBuilder.create('s'));
.create('s'));
opts.addOption(OptionBuilder.hasArg() OptionBuilder.hasArg();
.withArgName("time/offset") OptionBuilder.withArgName("time/offset");
.withLongOpt("expire-time") OptionBuilder.withLongOpt("expire-time");
.withDescription( OptionBuilder.withDescription("signature expiration time (default is start-time + 30 days).");
"signature expiration time (default is start-time + 30 days).") opts.addOption(OptionBuilder.create('e'));
.create('e'));
opts.addOption(OptionBuilder.hasArg() OptionBuilder.hasArg();
.withArgName("outfile") OptionBuilder.withArgName("outfile");
.withDescription( OptionBuilder.withDescription("file the signed zone is written to (default is <origin>.signed).");
"file the signed zone is written to (default is <origin>.signed).") opts.addOption(OptionBuilder.create('f'));
.create('f'));
opts.addOption(OptionBuilder.hasArg() OptionBuilder.hasArg();
.withArgName("KSK file") OptionBuilder.withArgName("KSK file");
.withLongOpt("ksk-file") OptionBuilder.withLongOpt("ksk-file");
.withDescription("this key is a key signing key (may repeat).") OptionBuilder.withDescription("this key is a key signing key (may repeat).");
.create('k')); opts.addOption(OptionBuilder.create('k'));
opts.addOption(OptionBuilder.hasArg()
.withArgName("file") OptionBuilder.hasArg();
.withLongOpt("include-file") OptionBuilder.withArgName("file");
.withDescription("include names in this file in the NSEC/NSEC3 chain.") OptionBuilder.withLongOpt("include-file");
.create('I')); 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(OptionBuilder.hasArg() OptionBuilder.hasArg();
.withLongOpt("salt") OptionBuilder.withLongOpt("salt");
.withArgName("hex value") OptionBuilder.withArgName("hex value");
.withDescription("supply a salt value.") OptionBuilder.withDescription("supply a salt value.");
.create('S')); opts.addOption(OptionBuilder.create('S'));
opts.addOption(OptionBuilder.hasArg()
.withLongOpt("random-salt")
.withArgName("length")
.withDescription("generate a random salt.")
.create('R'));
opts.addOption(OptionBuilder.hasArg()
.withLongOpt("iterations")
.withArgName("value")
.withDescription("use this value for the iterations in NSEC3.")
.create());
opts.addOption(OptionBuilder.hasArg() OptionBuilder.hasArg();
.withArgName("alias:original:mnemonic") OptionBuilder.withLongOpt("random-salt");
.withLongOpt("alg-alias") OptionBuilder.withArgName("length");
.withDescription("Define an alias for an algorithm (may repeat).") OptionBuilder.withDescription("generate a random salt.");
.create('A')); opts.addOption(OptionBuilder.create('R'));
opts.addOption(OptionBuilder.hasArg()
.withArgName("id") OptionBuilder.hasArg();
.withLongOpt("ds-digest") OptionBuilder.withLongOpt("iterations");
.withDescription("Digest algorithm to use for generated DSs") OptionBuilder.withArgName("value");
.create()); OptionBuilder.withDescription("use this value for the iterations in NSEC3.");
opts.addOption(OptionBuilder.create());
OptionBuilder.hasArg();
OptionBuilder.withArgName("alias:original:mnemonic");
OptionBuilder.withLongOpt("alg-alias");
OptionBuilder.withDescription("Define an alias for an algorithm (may repeat).");
opts.addOption(OptionBuilder.create('A'));
OptionBuilder.hasArg();
OptionBuilder.withArgName("id");
OptionBuilder.withLongOpt("ds-digest");
OptionBuilder.withDescription("Digest algorithm to use for generated DSs");
opts.addOption(OptionBuilder.create());
} }
public void parseCommandLine(String[] args) public void parseCommandLine(String[] args)
@ -385,7 +394,7 @@ public class SignZone
PrintWriter out = new PrintWriter(System.err); PrintWriter out = new PrintWriter(System.err);
// print our own usage statement: // print our own usage statement:
out.println("usage: signZone.sh [..options..] " out.println("usage: jdnssec-signzone [..options..] "
+ "zone_file [key_file ...] "); + "zone_file [key_file ...] ");
f.printHelp(out, 75, "signZone.sh", null, opts, f.printHelp(out, 75, "signZone.sh", null, opts,
HelpFormatter.DEFAULT_LEFT_PAD, HelpFormatter.DEFAULT_LEFT_PAD,
@ -498,6 +507,69 @@ public class SignZone
return keys; return keys;
} }
private static List getKeys(List dnskeyrrs, File inDirectory)
throws IOException
{
List res = new ArrayList();
for (Iterator i = dnskeyrrs.iterator(); i.hasNext();)
{
// Construct a public-key-only DnsKeyPair just so we can calculate the
// base name.
DnsKeyPair pub = new DnsKeyPair((DNSKEYRecord) i.next());
DnsKeyPair pair = BINDKeyUtils.loadKeyPair(BINDKeyUtils.keyFileBase(pub),
inDirectory);
if (pair != null)
{
res.add(pair);
}
}
if (res.size() > 0) return res;
return null;
}
private static class KeyFileFilter implements FileFilter
{
private String prefix;
public KeyFileFilter(Name origin)
{
prefix = "K" + origin.toString();
}
public boolean accept(File pathname)
{
if (!pathname.isFile()) return false;
String name = pathname.getName();
if (name.startsWith(prefix) && name.endsWith(".private")) return true;
return false;
}
}
private static List findZoneKeys(File inDirectory, Name zonename)
throws IOException
{
if (inDirectory == null)
{
inDirectory = new File(".");
}
// get the list of "K<zone>.*.private files.
FileFilter filter = new KeyFileFilter(zonename);
File[] files = inDirectory.listFiles(filter);
// read in all of the records
ArrayList keys = new ArrayList();
for (int i = 0; i < files.length; i++)
{
DnsKeyPair p = BINDKeyUtils.loadKeyPair(files[i].getName(), inDirectory);
keys.add(p);
}
if (keys.size() > 0) return keys;
return null;
}
/** /**
* This is an implementation of a file filter used for finding BIND 9-style * This is an implementation of a file filter used for finding BIND 9-style
* keyset-* files. * keyset-* files.
@ -651,15 +723,42 @@ public class SignZone
public static void execute(CLIState state) throws Exception public static void execute(CLIState state) throws Exception
{ {
// Load the key pairs. // Read in the zone
List records = ZoneUtils.readZoneFile(state.zonefile, null);
if (records == null || records.size() == 0)
{
System.err.println("error: empty zone file");
state.usage();
}
// FIXME: should we do what BIND 9.3.x snapshots do and look at // calculate the zone name.
// zone apex DNSKEY RRs, and from that be able to load all of the Name zonename = ZoneUtils.findZoneName(records);
// keys? if (zonename == null)
{
System.err.println("error: invalid zone file - no SOA");
state.usage();
}
// Load the key pairs.
List keypairs = getKeys(state.keyFiles, 0, state.keyDirectory); List keypairs = getKeys(state.keyFiles, 0, state.keyDirectory);
List kskpairs = getKeys(state.kskFiles, 0, state.keyDirectory); List kskpairs = getKeys(state.kskFiles, 0, state.keyDirectory);
// If we didn't get any keys on the command line, look at the zone apex for
// any public keys.
if (keypairs == null && kskpairs == null)
{
List dnskeys = ZoneUtils.findRRs(records, zonename, Type.DNSKEY);
keypairs = getKeys(dnskeys, state.keyDirectory);
}
// If we *still* don't have any key pairs, look for keys the key directory
// that match
if (keypairs == null && kskpairs == null)
{
keypairs = findZoneKeys(state.keyDirectory, zonename);
}
// If we don't have any KSKs, but we do have more than one zone // If we don't have any KSKs, but we do have more than one zone
// 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
@ -694,22 +793,6 @@ public class SignZone
state.usage(); state.usage();
} }
// Read in the zone
List records = ZoneUtils.readZoneFile(state.zonefile, null);
if (records == null || records.size() == 0)
{
System.err.println("error: empty zone file");
state.usage();
}
// calculate the zone name.
Name zonename = ZoneUtils.findZoneName(records);
if (zonename == null)
{
System.err.println("error: invalid zone file - no SOA");
state.usage();
}
// default the output file, if not set. // default the output file, if not set.
if (state.outputfile == null) if (state.outputfile == null)
{ {

View File

@ -138,4 +138,37 @@ public class ZoneUtils
return null; return null;
} }
public static List findRRs(List records, Name name, int type)
{
List res = new ArrayList();
for (Iterator i = records.iterator(); i.hasNext();)
{
Object o = i.next();
if (o instanceof Record)
{
Record r = (Record) o;
if (r.getName().equals(name) && r.getType() == type)
{
res.add(r);
}
}
else if (o instanceof RRset)
{
RRset r = (RRset) o;
if (r.getName().equals(name) && r.getType() == type)
{
for (Iterator j = r.rrs(); j.hasNext();)
{
res.add(j.next());
}
}
}
}
return res;
}
} }