initial import

git-svn-id: https://svn.verisignlabs.com/jdnssec/tools/trunk@13 4cbd57fe-54e5-0310-bd9a-f30fe5ea5e6e
This commit is contained in:
David Blacka
2005-08-13 23:18:03 +00:00
commit eb93599f0b
56 changed files with 5962 additions and 0 deletions

View File

@@ -0,0 +1,352 @@
// $Id: KeyGen.java,v 1.2 2004/01/16 17:56:17 davidb Exp $
//
// Copyright (C) 2001-2003 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;
import java.util.*;
import java.io.*;
import java.text.SimpleDateFormat;
import java.text.ParseException;
import java.security.GeneralSecurityException;
import org.xbill.DNS.*;
import com.verisignlabs.dnssec.security.*;
import org.apache.commons.cli.*;
import org.apache.commons.cli.Options;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/** This class forms the command line implementation of a DNSSEC key
* generator
*
* @author David Blacka (original)
* @author $Author: davidb $
* @version $Revision: 1.2 $
*/
public class KeyGen
{
private static Log log;
/** This is a small inner class used to hold all of the command line
* option state. */
private static class CLIState
{
public int algorithm = 5;
public int keylength = 1024;
public String outputfile = null;
public File keydir = null;
public boolean zoneKey = true;
public boolean kskFlag = false;
public String owner = null;
public long ttl = 86400;
public CLIState() { }
public void parseCommandLine(Options opts, String[] args)
throws org.apache.commons.cli.ParseException, ParseException,
IOException
{
CommandLineParser cli_parser = new PosixParser();
CommandLine cli = cli_parser.parse(opts, args);
String optstr = null;
if (cli.hasOption('h')) usage(opts);
if (cli.hasOption('v'))
{
int value = parseInt(cli.getOptionValue('v'), 5);
switch (value)
{
case 0:
System.setProperty("org.apache.commons.logging.simplelog.defaultlog",
"fatal");
break;
case 5:
default:
System.setProperty("org.apache.commons.logging.simplelog.defaultlog",
"debug");
break;
case 6:
System.setProperty("org.apache.commons.logging.simplelog.defaultlog",
"trace");
break;
}
}
if (cli.hasOption('k')) kskFlag = true;
outputfile = cli.getOptionValue('f');
if ((optstr = cli.getOptionValue('d')) != null)
{
keydir = new File(optstr);
}
if ((optstr = cli.getOptionValue('n')) != null)
{
if (! optstr.equalsIgnoreCase("ZONE"))
{
zoneKey = false;
}
}
if ((optstr = cli.getOptionValue('a')) != null)
{
algorithm = parseAlg(optstr);
}
if ((optstr = cli.getOptionValue('b')) != null)
{
keylength = parseInt(optstr, 1024);
}
if ((optstr = cli.getOptionValue("ttl")) != null)
{
ttl = parseInt(optstr, 86400);
}
String[] cl_args = cli.getArgs();
if (cl_args.length < 1)
{
System.err.println("error: missing key owner name");
usage(opts);
}
owner = cl_args[0];
}
}
/** This is just a convenience method for parsing integers from
* strings.
*
* @param s the string to parse.
* @param def the default value, if the string doesn't parse.
* @return the parsed integer, or the default.
*/
private static int parseInt(String s, int def)
{
try
{
int v = Integer.parseInt(s);
return v;
}
catch (NumberFormatException e)
{
return def;
}
}
private static int parseAlg(String s)
{
int alg = parseInt(s, -1);
if (alg > 0) return alg;
s = s.toUpperCase();
if (s.equals("RSA"))
{
return DNSSEC.RSASHA1;
}
else if (s.equals("RSAMD5"))
{
return DNSSEC.RSA;
}
else if (s.equals("DH"))
{
return DNSSEC.DH;
}
else if (s.equals("DSA"))
{
return DNSSEC.DSA;
}
else if (s.equals("RSASHA1"))
{
return DNSSEC.RSASHA1;
}
// default
return DNSSEC.RSASHA1;
}
/** Set up the command line options.
*
* @return a set of command line options.
*/
private static Options setupCLI()
{
Options options = new Options();
// boolean options
options.addOption("h", "help", false, "Print this message.");
options.addOption("k", "kskflag", false,
"Key is a key-signing-key (sets the SEP flag).");
// Argument options
options.addOption(OptionBuilder.hasArg()
.withLongOpt("nametype")
.withArgName("type")
.withDescription("ZONE | OTHER (default ZONE)")
.create('n'));
options.addOption(OptionBuilder.hasOptionalArg()
.withLongOpt("verbose")
.withArgName("level")
.withDescription("verbosity level -- 0 is silence, " +
"5 is debug information, " +
"6 is trace information.\n"+
"default is level 5.")
.create('v'));
options.addOption(OptionBuilder.hasArg()
.withArgName("algorithm")
.withDescription("RSA | RSASHA1 | RSAMD5 | DH | DSA, " +
"RSASHA1 is default.")
.create('a'));
options.addOption(OptionBuilder.hasArg()
.withArgName("size")
.withDescription
("key size, in bits. (default = 1024)\n" +
"RSA|RSASHA1|RSAMD5: [512..4096]\n" +
"DSA: [512..1024]\n" +
"DH: [128..4096]")
.create('b'));
options.addOption(OptionBuilder.hasArg()
.withArgName("file")
.withLongOpt("output-file")
.withDescription
("base filename for the public/private key files")
.create('f'));
options.addOption(OptionBuilder.hasArg()
.withLongOpt("keydir")
.withArgName("dir")
.withDescription
("place generated key files in this directory")
.create('d'));
return options;
}
/** Print out the usage and help statements, then quit. */
private static void usage(Options opts)
{
HelpFormatter f = new HelpFormatter();
PrintWriter out = new PrintWriter(System.err);
// print our own usage statement:
f.printHelp(out, 75, "keyGen.sh [..options..] name", null, opts,
HelpFormatter.DEFAULT_LEFT_PAD,
HelpFormatter.DEFAULT_DESC_PAD, null);
out.flush();
System.exit(64);
}
public static void execute(CLIState state, Options opts)
throws Exception
{
JCEDnsSecSigner signer = new JCEDnsSecSigner();
// Minor hack to make the owner name absolute.
if (! state.owner.endsWith("."))
{
state.owner = state.owner + ".";
}
Name owner_name = Name.fromString(state.owner);
// Calculate our flags
int flags = 0;
if (state.zoneKey) flags |= DNSKEYRecord.OWNER_ZONE;
if (state.kskFlag) flags |= DNSKEYRecord.FLAG_SEP;
log.debug("create key pair with (name = " + owner_name + ", ttl = " +
state.ttl + ", alg = " + state.algorithm + ", flags = " +
flags + ", length = " + state.keylength + ")");
DnsKeyPair pair = signer.generateKey(owner_name,
state.ttl,
DClass.IN,
state.algorithm,
flags,
state.keylength);
if (state.outputfile != null)
{
BINDKeyUtils.writeKeyFiles(state.outputfile, pair, state.keydir);
}
else
{
BINDKeyUtils.writeKeyFiles(pair, state.keydir);
}
}
public static void main(String[] args)
{
// set up logging.
// For now, we force the commons logging to use the built-in
// SimpleLog.
System.setProperty("org.apache.commons.logging.Log",
"org.apache.commons.logging.impl.SimpleLog");
// set up the command line options
Options opts = setupCLI();
CLIState state = new CLIState();
try
{
state.parseCommandLine(opts, args);
}
catch (UnrecognizedOptionException e)
{
System.err.println("error: unknown option encountered: " +
e.getMessage());
usage(opts);
}
catch (AlreadySelectedException e)
{
System.err.println("error: mutually exclusive options have " +
"been selected:\n " + e.getMessage());
usage(opts);
}
catch (Exception e)
{
System.err.println("error: unknown command line parsing exception:");
e.printStackTrace();
usage(opts);
}
log = LogFactory.getLog(KeyGen.class);
try
{
execute(state, opts);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,719 @@
// $Id: SignZone.java,v 1.4 2004/01/16 17:57:47 davidb Exp $
//
// Copyright (C) 2001-2003 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;
import java.util.*;
import java.io.*;
import java.text.SimpleDateFormat;
import java.text.ParseException;
import java.security.GeneralSecurityException;
import org.xbill.DNS.*;
import com.verisignlabs.dnssec.security.*;
import org.apache.commons.cli.*;
import org.apache.commons.cli.Options;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/** This class forms the command line implementation of a DNSSEC zone
* signer.
*
* @author David Blacka (original)
* @author $Author: davidb $
* @version $Revision: 1.4 $
*/
public class SignZone
{
private static Log log;
/** This is a small inner class used to hold all of the command line
* option state. */
private static class CLIState
{
private File keyDirectory = null;
public File keysetDirectory = null;
public String[] kskFiles = null;
public String[] keyFiles = null;
public String zonefile = null;
public Date start = null;
public Date expire = null;
public String outputfile = null;
public boolean verifySigs = false;
public boolean selfSignKeys = true;
public boolean useOptIn = false;
public boolean optInConserve = false;
public boolean fullySignKeyset = false;
public List includeNames = null;
public CLIState() { }
public void parseCommandLine(Options opts, String[] args)
throws org.apache.commons.cli.ParseException, ParseException,
IOException
{
CommandLineParser cli_parser = new PosixParser();
CommandLine cli = cli_parser.parse(opts, args);
String optstr = null;
if (cli.hasOption('h')) usage(opts);
if (cli.hasOption('v'))
{
int value = parseInt(cli.getOptionValue('v'), 5);
switch (value)
{
case 0:
System.setProperty("org.apache.commons.logging.simplelog.defaultlog",
"fatal");
break;
case 5:
default:
System.setProperty("org.apache.commons.logging.simplelog.defaultlog",
"debug");
break;
case 6:
System.setProperty("org.apache.commons.logging.simplelog.defaultlog",
"trace");
break;
}
}
if (cli.hasOption('a')) verifySigs = true;
if (cli.hasOption('O')) useOptIn = true;
if (cli.hasOption('C'))
{
useOptIn = true;
optInConserve = true;
}
if (cli.hasOption('F')) fullySignKeyset = true;
if ((optstr = cli.getOptionValue('d')) != null)
{
keysetDirectory = new File(optstr);
if (! keysetDirectory.isDirectory())
{
System.err.println("error: " + optstr + " is not a directory");
usage(opts);
}
}
if ((optstr = cli.getOptionValue('D')) != null)
{
keyDirectory = new File(optstr);
if (! keyDirectory.isDirectory())
{
System.err.println("error: " + optstr + " is not a directory");
usage(opts);
}
}
if ((optstr = cli.getOptionValue('s')) != null)
{
start = convertDuration(null, optstr);
}
else
{
// default is now - 1 hour.
start = new Date(System.currentTimeMillis() - (3600 * 1000));
}
if ((optstr = cli.getOptionValue('e')) != null)
{
expire = convertDuration(start, optstr);
}
else
{
expire = convertDuration(start, "+2592000"); // 30 days
}
outputfile = cli.getOptionValue('f');
kskFiles = cli.getOptionValues('k');
if ((optstr = cli.getOptionValue('I')) != null)
{
File includeNamesFile = new File(optstr);
includeNames = getNameList(includeNamesFile);
}
String[] files = cli.getArgs();
if (files.length < 2)
{
System.err.println("error: missing zone file and/or key files");
usage(opts);
}
zonefile = files[0];
keyFiles = new String[files.length - 1];
System.arraycopy(files, 1, keyFiles, 0, files.length - 1);
}
}
/** This is just a convenience method for parsing integers from
* strings.
*
* @param s the string to parse.
* @param def the default value, if the string doesn't parse.
* @return the parsed integer, or the default.
*/
private static int parseInt(String s, int def)
{
try
{
int v = Integer.parseInt(s);
return v;
}
catch (NumberFormatException e)
{
return def;
}
}
/** Verify the generated signatures.
*
* @param zonename the origin name of 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.
*/
private static boolean verifyZoneSigs(Name zonename, List records,
List keypairs)
{
boolean secure = true;
DnsSecVerifier verifier = new DnsSecVerifier();
for (Iterator i = keypairs.iterator(); i.hasNext(); )
{
verifier.addTrustedKey((DnsKeyPair) i.next());
}
verifier.setVerifyAllSigs(true);
List rrsets = SignUtils.assembleIntoRRsets(records);
for (Iterator i = rrsets.iterator(); i.hasNext(); )
{
RRset rrset = (RRset) i.next();
// skip unsigned rrsets.
if (!rrset.sigs().hasNext()) continue;
byte result = verifier.verify(rrset, null);
if (result != DNSSEC.Secure)
{
log.debug("Signatures did not verify for RRset: (" + result + "): " +
rrset);
secure = false;
}
}
return secure;
}
/** Load the key pairs from the key files.
*
* @param keyfiles a string array containing the base names or
* paths of the keys to be loaded.
* @param start_index 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.
*/
private static List getKeys(String[] keyfiles, int start_index,
File inDirectory)
throws IOException
{
if (keyfiles == null) return null;
int len = keyfiles.length - start_index;
if (len <= 0) return null;
ArrayList keys = new ArrayList(len);
for (int i = start_index; i < keyfiles.length; i++)
{
DnsKeyPair k = BINDKeyUtils.loadKeyPair(keyfiles[i], inDirectory);
if (k != null) keys.add(k);
}
return keys;
}
/** Load a single key from a given keyfile.
*
* @param keyfile the keyfile.
* @param inDirectory the default directory to look in (may be
* null).
* @return a list containing one or zero keypair objects.
*/
private static List getKeys(File keyfile, File inDirectory)
throws IOException
{
if (keyfile == null) return null;
DnsKeyPair k = BINDKeyUtils.loadKeyPair(keyfile.getPath(),
inDirectory);
if (k != null)
{
ArrayList keys = new ArrayList(1);
keys.add(k);
return keys;
}
return null;
}
/** This is an implementation of a file filter used for finding BIND
* 9-style keyset-* files. */
private static class KeysetFileFilter implements FileFilter
{
public boolean accept(File pathname)
{
if (! pathname.isFile()) return false;
String name = pathname.getName();
if (name.startsWith("keyset-")) return true;
return false;
}
}
/** Load keysets (which contain delegation point security info).
*
* @param inDirectory the directory to look for the keyset files
* (may be null, in which case it defaults to looking in the
* current working directory).
* @param zonename the name of the zone we are signing, so we can
* ignore keysets that do not belong in the zone.
* @return a list of {@link org.xbill.DNS.Record}s found in the
* keyset files.
*/
private static List getKeysets(File inDirectory, Name zonename)
throws IOException
{
if (inDirectory == null)
{
// FIXME: dunno how cross-platform this is
inDirectory = new File(".");
}
// get the list of "keyset-" files.
FileFilter filter = new KeysetFileFilter();
File[] files = inDirectory.listFiles(filter);
// read in all of the records
ArrayList keysetRecords = new ArrayList();
for (int i = 0; i < files.length; i++)
{
List l = ZoneUtils.readZoneFile(files[i].getAbsolutePath(), zonename);
keysetRecords.addAll(l);
}
// discard records that do not belong to the zone in question.
for (Iterator i = keysetRecords.iterator(); i.hasNext(); )
{
Record r = (Record) i.next();
if (!r.getName().subdomain(zonename))
{
i.remove();
}
}
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 getNameList(File nameListFile)
throws IOException
{
BufferedReader br = new BufferedReader(new FileReader(nameListFile));
List 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)
{
log.error("DNS Name parsing error", e);
}
}
if (res.size() == 0) return null;
return res;
}
/** Calculate a date/time from a command line time/offset duration string.
*
* @param start the start time to calculate offsets from.
* @param duration the time/offset string to parse.
* @return the calculated time.
*/
private static Date convertDuration(Date start, String duration)
throws ParseException
{
if (start == null) start = new Date();
if (duration.startsWith("now"))
{
start = new Date();
if (duration.indexOf("+") < 0) return start;
duration = duration.substring(3);
}
if (duration.startsWith("+"))
{
long offset = (long) parseInt(duration.substring(1), 0) * 1000;
return new Date(start.getTime() + offset);
}
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyyMMddHHmmss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT"));
return dateFormatter.parse(duration);
}
/** Determine if the given keypairs can be used to sign the zone.
* @param zonename the zone origin.
* @param keypairs a list of {@link DnsKeyPair} objects that will
* be used to sign the zone.
* @return true if the keypairs valid.
*/
private static boolean keyPairsValidForZone(Name zonename, List keypairs)
{
if (keypairs == null) return true; // technically true, I guess.
for (Iterator i = keypairs.iterator(); i.hasNext(); )
{
DnsKeyPair kp = (DnsKeyPair) i.next();
Name keyname = kp.getDNSKEYRecord().getName();
if (!keyname.equals(zonename))
{
return false;
}
}
return true;
}
/** Set up the command line options.
*
* @return a set of command line options.
*/
private static Options setupCLI()
{
Options options = new Options();
// boolean options
options.addOption("h", "help", false, "Print this message.");
options.addOption("a", false, "verify generated signatures>");
options.addOption("F", "fully-sign-keyset", false,
"sign the zone apex keyset with all " +
"available keys, instead of just key-signing-keys.");
// Opt-In generation switches
OptionGroup opt_in_opts = new OptionGroup();
opt_in_opts.addOption(new Option
("O", "generate a fully Opt-In zone."));
opt_in_opts.addOption(new Option
("C", "generate a conservative Opt-In zone."));
options.addOptionGroup(opt_in_opts);
// Argument options
options.addOption(OptionBuilder.hasOptionalArg()
.withArgName("level")
.withDescription("verbosity level -- 0 is silence, " +
"5 is debug information, " +
"6 is trace information. " +
"No argument means 5.")
.create('v'));
options.addOption(OptionBuilder.hasArg()
.withArgName("dir")
.withLongOpt("keyset-directory")
.withDescription
("directory to find keyset files (default '.').")
.create('d'));
options.addOption(OptionBuilder.hasArg()
.withArgName("dir")
.withLongOpt("key-directory")
.withDescription
("directory to find key files (default '.').")
.create('D'));
options.addOption(OptionBuilder.hasArg()
.withArgName("time/offset")
.withLongOpt("start-time")
.withDescription
("signature starting time (default is now - 1 hour)")
.create('s'));
options.addOption(OptionBuilder.hasArg()
.withArgName("time/offset")
.withLongOpt("expire-time")
.withDescription
("signature expiration time (default is " +
"start-time + 30 days")
.create('e'));
options.addOption(OptionBuilder.hasArg()
.withArgName("outfile")
.withDescription("file the signed zone is written " +
"to (default is <origin>.signed).")
.create('f'));
options.addOption(OptionBuilder.hasArgs()
.withArgName("KSK file")
.withLongOpt("ksk-file")
.withDescription("this key is a key signing key " +
"(may repeat)")
.create('k'));
options.addOption(OptionBuilder.hasArg()
.withArgName("file")
.withLongOpt("include-file")
.withDescription("include names in this file " +
"in the NSEC chain")
.create('I'));
return options;
}
/** Print out the usage and help statements, then quit. */
private static void usage(Options opts)
{
HelpFormatter f = new HelpFormatter();
PrintWriter out = new PrintWriter(System.err);
// print our own usage statement:
out.println("usage: signZone.sh [..options..] zone_file [key_file ...] ");
f.printHelp(out, 75, "signZone.sh", null, opts,
HelpFormatter.DEFAULT_LEFT_PAD, HelpFormatter.DEFAULT_DESC_PAD,
"\ntime/offset = YYYYMMDDHHmmss|+offset|\"now\"+offset\n");
out.flush();
System.exit(64);
}
public static void execute(CLIState state, Options opts)
throws Exception
{
// Load the key pairs.
// FIXME: should we do what BIND 9.3.x snapshots do and look at
// zone apex DNSKEY RRs, and from that be able to load all of the
// keys?
List keypairs = getKeys(state.keyFiles, 0, state.keyDirectory);
List kskpairs = getKeys(state.kskFiles, 0, state.keyDirectory);
// If we don't have any KSKs, but we do have more than one zone
// signing key (presumably), presume that the zone signing keys
// are just not differentiated and try to figure out which keys
// are actually ksks by looking at the SEP flag.
if ( (kskpairs == null || kskpairs.size() == 0) &&
keypairs != null && keypairs.size() > 1)
{
for (Iterator i = keypairs.iterator(); i.hasNext(); )
{
DnsKeyPair pair = (DnsKeyPair) i.next();
DNSKEYRecord kr = pair.getDNSKEYRecord();
if ((kr.getFlags() & DNSKEYRecord.FLAG_SEP) != 0)
{
if (kskpairs == null) kskpairs = new ArrayList();
kskpairs.add(pair);
i.remove();
}
}
}
// Read in the zone
List records = ZoneUtils.readZoneFile(state.zonefile, null);
if (records == null || records.size() == 0)
{
System.err.println("error: empty zone file");
usage(opts);
}
// calculate the zone name.
Name zonename = ZoneUtils.findZoneName(records);
if (zonename == null)
{
System.err.println("error: invalid zone file - no SOA");
usage(opts);
}
// default the output file, if not set.
if (state.outputfile == null)
{
if (zonename.isAbsolute())
{
state.outputfile = zonename + "signed";
}
else
{
state.outputfile = zonename + ".signed";
}
}
// Verify that the keys can be in the zone.
List kpairs = keypairs;
if (!keyPairsValidForZone(zonename, keypairs) ||
!keyPairsValidForZone(zonename, kskpairs))
{
usage(opts);
}
// We force the signing keys to be in the zone by just appending
// them to the zone here. Currently JCEDnsSecSigner.signZone
// removes duplicate records.
if (kskpairs != null)
{
for (Iterator i = kskpairs.iterator(); i.hasNext(); )
{
records.add( ((DnsKeyPair) i.next()).getDNSKEYRecord() );
}
}
if (keypairs != null)
{
for (Iterator i = keypairs.iterator(); i.hasNext(); )
{
records.add( ((DnsKeyPair) i.next()).getDNSKEYRecord() );
}
}
// read in the keysets, if any.
List keysetrecs = getKeysets(state.keysetDirectory, zonename);
if (keysetrecs != null)
{
records.addAll(keysetrecs);
}
JCEDnsSecSigner signer = new JCEDnsSecSigner();
// Sign the zone.
List signed_records = signer.signZone(zonename,
records,
kskpairs,
keypairs,
state.start,
state.expire,
state.useOptIn,
state.optInConserve,
state.fullySignKeyset,
state.includeNames);
// write out the signed zone
// force multiline mode for now
org.xbill.DNS.Options.set("multiline");
ZoneUtils.writeZoneFile(signed_records, state.outputfile);
if (state.verifySigs)
{
// FIXME: ugh.
if (kskpairs != null)
{
keypairs.addAll(kskpairs);
}
log.debug("verifying generated signatures");
boolean res = verifyZoneSigs(zonename, signed_records, keypairs);
if (res)
{
System.out.println("Generated signatures verified");
// log.info("Generated signatures verified");
}
else
{
System.out.println("Generated signatures did not verify.");
// log.warn("Generated signatures did not verify.");
}
}
}
public static void main(String[] args)
{
// set up logging.
// For now, we force the commons logging to use the built-in
// SimpleLog.
System.setProperty("org.apache.commons.logging.Log",
"org.apache.commons.logging.impl.SimpleLog");
// set up the command line options
Options opts = setupCLI();
CLIState state = new CLIState();
try
{
state.parseCommandLine(opts, args);
}
catch (UnrecognizedOptionException e)
{
System.err.println("error: unknown option encountered: " +
e.getMessage());
usage(opts);
}
catch (AlreadySelectedException e)
{
System.err.println("error: mutually exclusive options have " +
"been selected:\n " + e.getMessage());
usage(opts);
}
catch (Exception e)
{
System.err.println("error: unknown command line parsing exception:");
e.printStackTrace();
usage(opts);
}
log = LogFactory.getLog(SignZone.class);
try
{
execute(state, opts);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,331 @@
// $Id: VerifyZone.java,v 1.1 2004/01/16 17:57:59 davidb Exp $
//
// Copyright (C) 2001-2003 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;
import java.util.*;
import java.io.*;
import java.text.SimpleDateFormat;
import java.text.ParseException;
import java.security.GeneralSecurityException;
import org.xbill.DNS.*;
import com.verisignlabs.dnssec.security.*;
import org.apache.commons.cli.*;
import org.apache.commons.cli.Options;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/** This class forms the command line implementation of a DNSSEC zone
* validator.
* @author David Blacka (original)
* @author $Author: davidb $
* @version $Revision: 1.1 $
*/
public class VerifyZone
{
private static Log log;
/** This is a small inner class used to hold all of the command line
* option state. */
private static class CLIState
{
public boolean strict = false;
public File keydir = null;
public String zonefile = null;
public String[] keyfiles = null;
public CLIState() { }
public void parseCommandLine(Options opts, String[] args)
throws org.apache.commons.cli.ParseException, ParseException,
IOException
{
CommandLineParser cli_parser = new PosixParser();
CommandLine cli = cli_parser.parse(opts, args);
String optstr = null;
if (cli.hasOption('h')) usage(opts);
if (cli.hasOption('v'))
{
int value = parseInt(cli.getOptionValue('v'), 5);
switch (value)
{
case 0:
System.setProperty("org.apache.commons.logging.simplelog.defaultlog",
"fatal");
break;
case 5:
default:
System.setProperty("org.apache.commons.logging.simplelog.defaultlog",
"debug");
break;
case 6:
System.setProperty("org.apache.commons.logging.simplelog.defaultlog",
"trace");
break;
}
}
if (cli.hasOption('s')) strict = true;
if ((optstr = cli.getOptionValue('d')) != null)
{
keydir = new File(optstr);
}
String[] cl_args = cli.getArgs();
if (cl_args.length < 1)
{
System.err.println("error: missing zone file");
usage(opts);
}
zonefile = cl_args[0];
if (cl_args.length < 2)
{
System.err.println("error: at least one trusted key is required");
usage(opts);
}
keyfiles = new String[cl_args.length - 1];
System.arraycopy(cl_args, 1, keyfiles, 0, keyfiles.length);
}
}
/** This is just a convenience method for parsing integers from
* strings.
*
* @param s the string to parse.
* @param def the default value, if the string doesn't parse.
* @return the parsed integer, or the default.
*/
private static int parseInt(String s, int def)
{
try
{
int v = Integer.parseInt(s);
return v;
}
catch (NumberFormatException e)
{
return def;
}
}
/** Set up the command line options.
*
* @return a set of command line options.
*/
private static Options setupCLI()
{
Options options = new Options();
// boolean options
options.addOption("h", "help", false, "Print this message.");
options.addOption("s", "strict", false,
"Zone will only be considered valid if all " +
"signatures could be cryptographically verified");
// Argument options
options.addOption(OptionBuilder.hasArg()
.withLongOpt("keydir")
.withArgName("dir")
.withDescription("directory to find trusted key files")
.create('d'));
options.addOption(OptionBuilder.hasOptionalArg()
.withLongOpt("verbose")
.withArgName("level")
.withDescription("verbosity level -- 0 is silence, " +
"5 is debug information, " +
"6 is trace information.\n" +
"default is level 5.")
.create('v'));
return options;
}
/** Print out the usage and help statements, then quit. */
private static void usage(Options opts)
{
HelpFormatter f = new HelpFormatter();
PrintWriter out = new PrintWriter(System.err);
// print our own usage statement:
f.printHelp(out, 75,
"verifyZone.sh [..options..] zonefile " +
"keyfile [keyfile...]", null, opts,
HelpFormatter.DEFAULT_LEFT_PAD,
HelpFormatter.DEFAULT_DESC_PAD, null);
out.flush();
System.exit(64);
}
private static byte verifyZoneSignatures(List records, List keypairs)
{
// Zone is secure until proven otherwise.
byte result = DNSSEC.Secure;
DnsSecVerifier verifier = new DnsSecVerifier();
for (Iterator i = keypairs.iterator(); i.hasNext(); )
{
verifier.addTrustedKey((DnsKeyPair) i.next());
}
List rrsets = SignUtils.assembleIntoRRsets(records);
for (Iterator i = rrsets.iterator(); i.hasNext(); )
{
RRset rrset = (RRset) i.next();
// We verify each signature separately so that we can report
// which exact signature failed.
for (Iterator j = rrset.sigs(); j.hasNext(); )
{
Object o = j.next();
if (! (o instanceof RRSIGRecord))
{
log.debug("found " + o + " where expecting a RRSIG");
continue;
}
RRSIGRecord sigrec = (RRSIGRecord) o;
byte res = verifier.verifySignature(rrset, sigrec, null);
if (res != DNSSEC.Secure)
{
log.info("Signature failed to verify RRset: " + rrset + "\nsig: " + sigrec);
}
if (res < result) result = res;
}
}
return result;
}
private static List getTrustedKeys(String[] keyfiles, File inDirectory)
throws IOException
{
if (keyfiles == null) return null;
List keys = new ArrayList(keyfiles.length);
for (int i = 0; i < keyfiles.length; i++)
{
DnsKeyPair pair = BINDKeyUtils.loadKeyPair(keyfiles[i], inDirectory);
if (pair != null) keys.add(pair);
}
return keys;
}
public static void execute(CLIState state, Options opts)
throws Exception
{
List keypairs = getTrustedKeys(state.keyfiles, state.keydir);
List records = ZoneUtils.readZoneFile(state.zonefile, null);
Collections.sort(records, new RecordComparator());
log.debug("verifying signatures...");
byte result = verifyZoneSignatures(records, keypairs);
log.debug("completed verification process.");
switch (result)
{
case DNSSEC.Failed:
System.out.println("zone did not verify.");
System.exit(1);
break;
case DNSSEC.Insecure:
if (state.strict)
{
System.out.println("zone did not verify.");
System.exit(1);
}
case DNSSEC.Secure:
System.out.println("zone verified.");
break;
}
System.exit(0);
}
public static void main(String[] args)
{
// set up logging.
// For now, we force the commons logging to use the built-in
// SimpleLog.
System.setProperty("org.apache.commons.logging.Log",
"org.apache.commons.logging.impl.SimpleLog");
// set up the command line options
Options opts = setupCLI();
CLIState state = new CLIState();
try
{
state.parseCommandLine(opts, args);
}
catch (UnrecognizedOptionException e)
{
System.err.println("error: unknown option encountered: " +
e.getMessage());
usage(opts);
}
catch (AlreadySelectedException e)
{
System.err.println("error: mutually exclusive options have " +
"been selected:\n " + e.getMessage());
usage(opts);
}
catch (Exception e)
{
System.err.println("error: unknown command line parsing exception:");
e.printStackTrace();
usage(opts);
}
log = LogFactory.getLog(VerifyZone.class);
try
{
execute(state, opts);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,33 @@
<BODY BGCOLOR="#FFFFFF">
java-dnssec-tools is a collection of Java-based command line tools for
managing DNSSEC zones and keys.
<p>
<center>
<table width="90%" border=1 cellpadding=10 cellspacing=0>
<tr><td class="TableRowColor">
Copyright &copy; 2003 Verisign, Inc. by
<a href="mailto:davidb@verisignlabs.com">David Blacka</a><P>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.<P>
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
Library General Public License for more details.<P>
You should have received a copy of the
<a href="http://www.gnu.org/copyleft/lgpl.html">GNU Library General Public License</a>
along with this library; if not, write to the
Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.
</td></tr>
</table><P>
</center>
</BODY>

View File

@@ -0,0 +1,398 @@
// $Id: BINDKeyUtils.java,v 1.5 2004/02/25 20:46:14 davidb Exp $
//
// Copyright (C) 2001-2003 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;
import java.io.*;
import java.security.*;
import java.text.NumberFormat;
import org.xbill.DNS.*;
import org.xbill.DNS.utils.base64;
/** This class contains a series of static methods used for
* manipulating BIND 9.x.x-style DNSSEC key files.
*
* In this class, the "base" key path or name is the file name
* without the trailing ".key" or ".private".
*
* @author David Blacka (original)
* @author $Author: davidb $
* @version $Revision: 1.5 $
*/
public class BINDKeyUtils
{
// formatters used to generated the BIND key file names
private static NumberFormat mAlgNumberFormatter;
private static NumberFormat mKeyIdNumberFormatter;
/** Calculate the BIND9 key file base name (i.e., without the ".key"
* or ".private" extensions) */
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;
String fn = "K" + signer + "+" +
mAlgNumberFormatter.format(algorithm) +
"+" +
mKeyIdNumberFormatter.format(keyid);
return fn;
}
/** Reads in the KEYRecord from the public key file */
private static DNSKEYRecord loadPublicKeyFile(File publicKeyFile)
throws IOException
{
Master m = new Master(publicKeyFile.getAbsolutePath(), null, 600);
Record r;
DNSKEYRecord result = null;
while ( (r = m.nextRecord()) != null )
{
if (r.getType() == Type.DNSKEY)
{
result = (DNSKEYRecord) r;
}
}
return result;
}
/** Reads in the private key verbatim from the private key file */
private static String loadPrivateKeyFile(File privateKeyFile)
throws IOException
{
BufferedReader in = new BufferedReader(new FileReader(privateKeyFile));
StringBuffer key_buf = new StringBuffer();
String line;
while ( (line = in.readLine()) != null)
{
key_buf.append(line);
key_buf.append('\n');
}
return key_buf.toString().trim();
}
/** Given an actual path for one of the key files, return the base
* name */
private static String fixKeyFileBasePath(String basePath)
{
if (basePath == null) throw new IllegalArgumentException();
if (basePath.endsWith(".key") ||
basePath.endsWith(".private"))
{
basePath = basePath.substring
(0, basePath.lastIndexOf("."));
}
return basePath;
}
/** Given the information necessary to construct the path to a BIND9
* generated key pair, load the key pair.
*
* @param signer the DNS name of the key.
* @param algorithm the DNSSEC algorithm of the key.
* @param keyid the DNSSEC key footprint.
* @param inDirectory the directory to look for the files (may be
* null).
* @return the loaded key pair.
* @throws IOException if there was a problem reading the BIND9
* files. */
public static DnsKeyPair loadKeyPair(Name signer, int algorithm,
int keyid, File inDirectory)
throws IOException
{
String keyFileBase = getKeyFileBase(signer, algorithm, keyid);
return loadKeyPair(keyFileBase, inDirectory);
}
/** Given a base path to a BIND9 key pair, load the key pair.
*
* @param keyFileBasePath the base filename (or real filename for
* either the public or private key) of the key.
* @param inDirectory the directory to look in, if the
* keyFileBasePath is relative.
* @return the loaded key pair.
* @throws IOException if there was a problem reading the files */
public static DnsKeyPair loadKeyPair(String keyFileBasePath,
File inDirectory)
throws IOException
{
keyFileBasePath = fixKeyFileBasePath(keyFileBasePath);
// FIXME: should we throw the IOException when one of the files
// cannot be found, or just when both cannot be found?
File publicKeyFile = new File(inDirectory, keyFileBasePath + ".key");
File privateKeyFile = new File(inDirectory, keyFileBasePath + ".private");
DnsKeyPair kp = new DnsKeyPair();
DNSKEYRecord kr = loadPublicKeyFile(publicKeyFile);
kp.setDNSKEYRecord(kr);
String pk = loadPrivateKeyFile(privateKeyFile);
kp.setPrivateKeyString(pk);
return kp;
}
/** Given a base path to a BIND9 key pair, load the public part
* (only) of the key pair
*
* @param keyFileBasePath the base or real path to the public part
* of a key pair.
* @param inDirectory the directory to look in if the path is
* relative (may be null).
* @return a {@link DnsKeyPair} containing just the public key
* information.
* @throws IOException if there was a problem reading the public
* key file.
*/
public static DnsKeyPair loadKey(String keyFileBasePath, File inDirectory)
throws IOException
{
keyFileBasePath = fixKeyFileBasePath(keyFileBasePath);
File publicKeyFile = new File(inDirectory, keyFileBasePath + ".key");
DnsKeyPair kp = new DnsKeyPair();
DNSKEYRecord kr = loadPublicKeyFile(publicKeyFile);
kp.setDNSKEYRecord(kr);
return kp;
}
/** Load a BIND keyset file. The BIND 9 dnssec tools typically call
* these files "keyset-[signer]." where [signer] is the DNS owner
* name of the key. The keyset may be signed, but doesn't have to
* be.
*
* @param keysetFileName the name of the keyset file.
* @param inDirectory the directory to look in if the path is
* relative (may be null, defaults to the current working
* directory).
* @return a RRset contain the KEY records and any associated SIG records.
* @throws IOException if there was a problem reading the keyset
* file.
*/
public static RRset loadKeySet(String keysetFileName, File inDirectory)
throws IOException
{
File keysetFile = new File(inDirectory, keysetFileName);
Master m = new Master(keysetFile.getAbsolutePath());
Record r;
RRset keyset = new RRset();
while ( (r = m.nextRecord()) != null )
{
keyset.addRR(r);
}
return keyset;
}
/** Calculate the key file base for this key pair.
*
* @param pair the {@link DnsKeyPair} to work from. It only needs a public
* key.
* @return the base name of the key files.
*/
public static String keyFileBase(DnsKeyPair pair)
{
DNSKEYRecord keyrec = pair.getDNSKEYRecord();
if (keyrec == null) return null;
return getKeyFileBase(keyrec.getName(),
keyrec.getAlgorithm(),
keyrec.getFootprint());
}
/** @return a {@link java.io.File} object representing the BIND9
* public key file. */
public static File getPublicKeyFile(DnsKeyPair pair, File inDirectory)
{
String keyfilebase = keyFileBase(pair);
if (keyfilebase == null) return null;
return new File(inDirectory, keyfilebase + ".key");
}
/** @return a {@link java.io.File} object representing the BIND9
* private key file */
public static File getPrivateKeyFile(DnsKeyPair pair, File inDirectory)
{
String keyfilebase = keyFileBase(pair);
if (keyfilebase == null) return null;
return new File(inDirectory, keyfilebase + ".private");
}
/** Given a the contents of a BIND9 private key file, convert it
* into a native {@link java.security.PrivateKey} object.
* @param privateKeyString the contents of a BIND9 key file in
* string form.
* @return a {@link java.security.PrivateKey}
*/
public static PrivateKey convertPrivateKeyString(String privateKeyString)
{
if (privateKeyString == null) return null;
// FIXME: should this swallow exceptions or actually propagate
// them?
try
{
DnsKeyConverter conv = new DnsKeyConverter();
return conv.parsePrivateKeyString(privateKeyString);
}
catch (IOException e)
{
e.printStackTrace();
}
catch (NoSuchAlgorithmException e)
{
e.printStackTrace();
}
return null;
}
/** Given a native private key, convert it into a BIND9 private key
* file format.
* @param priv the private key to convert.
* @param pub the private key's corresponding public key. Some
* algorithms require information from both.
* @return a string containing the contents of a BIND9 private key
* file.
*/
public static String convertPrivateKey(PrivateKey priv, PublicKey pub,
int alg)
{
if (priv != null)
{
// debug
// System.out.println("converting from privatekey to bind9 string");
DnsKeyConverter keyconv = new DnsKeyConverter();
String priv_string
= keyconv.generatePrivateKeyString(priv, pub, alg);
return priv_string;
}
return null;
}
/** Convert the KEY record to the exact string format that the
* dnssec-* routines need. Currently, the DNSJAVA package uses a
* multiline mode for its record formatting. The BIND9 tools
* require everything on a single line. */
private static String DNSKEYtoString(DNSKEYRecord rec)
{
StringBuffer buf = new StringBuffer();
buf.append(rec.getName());
buf.append(" IN DNSKEY ");
buf.append(rec.getFlags() & 0xFFFF);
buf.append(" ");
buf.append(rec.getProtocol());
buf.append(" ");
buf.append(rec.getAlgorithm());
buf.append(" ");
buf.append(base64.toString(rec.getKey()));
return buf.toString();
}
/** This routine will write out the BIND9 dnssec-* tool compatible
* files.
*
* @param baseFileName use this base file name. If null, the
* standard BIND9 base file name will be computed.
* @param pair the keypair in question.
* @param inDirectory the directory to write to (may be null).
* @throws IOException if there is a problem writing the files.
*/
public static void writeKeyFiles(String baseFileName, DnsKeyPair pair,
File inDirectory)
throws IOException
{
DNSKEYRecord pub = pair.getDNSKEYRecord();
String priv = pair.getPrivateKeyString();
if (priv == null)
{
priv = convertPrivateKey(pair.getPrivate(), pair.getPublic(),
pair.getDNSKEYAlgorithm());
}
if (pub == null || priv == null) return;
// Write the public key file
File pubkeyfile = new File(inDirectory, baseFileName + ".key");
PrintWriter out
= new PrintWriter(new FileWriter(pubkeyfile));
out.println(DNSKEYtoString(pub));
out.close();
// Write the private key file
File privkeyfile = new File(inDirectory, baseFileName + ".private");
out = new PrintWriter(new FileWriter(privkeyfile));
out.print(priv);
out.close();
}
/** This routine will write out the BIND9 dnssec-* tool compatible
* files to the standard file names.
*
* @param pair the key pair in question.
* @param inDirectory the directory to write to (may be null).
*/
public static void writeKeyFiles(DnsKeyPair pair, File inDirectory)
throws IOException
{
String base = keyFileBase(pair);
writeKeyFiles(base, pair, inDirectory);
}
}

View File

@@ -0,0 +1,66 @@
// $Id: ByteArrayComparator.java,v 1.2 2004/02/25 20:46:14 davidb Exp $
//
// Copyright (C) 2001-2003 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;
import java.util.*;
/** This class implements a basic comparitor for byte arrays. It is
* primarily useful for comparing RDATA portions of DNS records in
* doing DNSSEC canonical ordering.
*
* @author David Blacka (original)
* @author $Author: davidb $
* @version $Revision: 1.2 $
*/
public class ByteArrayComparator implements Comparator
{
private int mOffset = 0;
private boolean mDebug = false;
public ByteArrayComparator()
{}
public ByteArrayComparator(int offset, boolean debug)
{
mOffset = offset;
mDebug = debug;
}
public int compare(Object o1, Object o2) throws ClassCastException
{
byte[] b1 = (byte[]) o1;
byte[] b2 = (byte[]) o2;
for (int i = mOffset; i < b1.length && i < b2.length; i++)
{
if (b1[i] != b2[i])
{
if (mDebug)
{
System.out.println("offset " + i + " differs (this is " +
(i - mOffset) +" bytes in from our offset.)");
}
return (b1[i] & 0xFF) - (b2[i] & 0xFF);
}
}
return b1.length - b2.length;
}
}

View File

@@ -0,0 +1,471 @@
// $Id: DnsKeyConverter.java,v 1.4 2004/02/23 15:06:15 davidb Exp $
//
// Copyright (C) 2001-2003 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;
import java.io.*;
import java.util.StringTokenizer;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.*;
import javax.crypto.spec.DHPrivateKeySpec;
import javax.crypto.spec.DHParameterSpec;
import javax.crypto.interfaces.DHPrivateKey;
import javax.crypto.interfaces.DHPublicKey;
import java.security.interfaces.*;
import org.xbill.DNS.*;
import org.xbill.DNS.security.KEYConverter;
import org.xbill.DNS.utils.base64;
/** This class handles conversions between JCA key formats and DNSSEC
* and BIND9 key formats.
*
* @author David Blacka (original)
* @author $Author: davidb $ (latest)
* @version $Revision: 1.4 $
*/
public class DnsKeyConverter
{
private KeyFactory mRSAKeyFactory;
private KeyFactory mDSAKeyFactory;
private KeyFactory mDHKeyFactory;
public DnsKeyConverter()
{
}
/** Given a DNS KEY record, return the JCA public key */
public PublicKey parseDNSKEYRecord(DNSKEYRecord pKeyRecord)
{
if (pKeyRecord.getKey() == null) return null;
return KEYConverter.parseRecord(pKeyRecord);
}
/** Given a JCA public key and the ancillary data, generate a DNSKEY
* record. */
public DNSKEYRecord generateDNSKEYRecord(Name name,
int dclass,
long ttl,
int flags,
int alg,
PublicKey key)
{
// FIXME: currenty org.xbill.DNS.security.KEYConverter will only
// convert to KEYRecords, and even then, assume that an RSA
// PublicKey means alg 1.
KEYRecord kr = KEYConverter.buildRecord(name, dclass, ttl, flags,
KEYRecord.PROTOCOL_DNSSEC, key);
return new DNSKEYRecord(name, dclass, ttl, flags,
DNSKEYRecord.Protocol.DNSSEC, alg,
kr.getKey());
}
// Private Key Specific Parsing routines
/** Convert a PKCS#8 encoded private key into a PrivateKey
* object. */
public PrivateKey convertEncodedPrivateKey(byte[] key, int algorithm)
{
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(key);
try
{
switch (algorithm)
{
case DNSSEC.RSAMD5:
case DNSSEC.RSASHA1:
return mRSAKeyFactory.generatePrivate(spec);
case DNSSEC.DSA:
return mDSAKeyFactory.generatePrivate(spec);
}
}
catch (GeneralSecurityException e)
{
}
return null;
}
/** @return a JCA private key, given a BIND9-style textual
* encoding */
public PrivateKey parsePrivateKeyString(String key)
throws IOException, NoSuchAlgorithmException
{
StringTokenizer lines = new StringTokenizer(key, "\n");
while (lines.hasMoreTokens())
{
String line = lines.nextToken();
if (line == null) continue;
if (line.startsWith("#")) continue;
String val = value(line);
if (val == null) continue;
if (line.startsWith("Private-key-format: "))
{
if (! val.equals("v1.2"))
{
throw new IOException("unsupported private key format: " + val);
}
}
else if (line.startsWith("Algorithm: "))
{
if (val.startsWith("1 ")) return parsePrivateRSA(lines);
if (val.startsWith("5 ")) return parsePrivateRSA(lines);
if (val.startsWith("2 ")) return parsePrivateDH(lines);
if (val.startsWith("3 ")) return parsePrivateDSA(lines);
throw new IOException("unsupported private key algorithm: " + val);
}
}
return null;
}
/** @return the value part of an "attribute:value" pair. The value
* is trimmed. */
private String value(String av)
{
if (av == null) return null;
int pos = av.indexOf(':');
if (pos < 0) return av;
if (pos >= av.length()) return null;
return av.substring(pos + 1).trim();
}
/** prints the bytes of an original byte array and the BigInteger
* copy */
private void printBigIntCompare(byte[] orig, BigInteger copy)
{
byte[] copy_bytes = copy.toByteArray();
for (int i = 0; i < 10 && i < orig.length; i++) {
System.err.print((int) orig[i] & 0xFF);
System.err.print(" ");
}
System.err.println();
for (int i = 0; i < 10 && i < copy_bytes.length; i++) {
System.err.print((int) copy_bytes[i] & 0xFF);
System.err.print(" ");
}
System.err.println();
}
/** Given the rest of the RSA BIND9 string format private key, parse
* and translate into a JCA private key
*
* @throws NoSuchAlgorithmException if the RSA algorithm is not
* available. */
private PrivateKey parsePrivateRSA(StringTokenizer lines)
throws NoSuchAlgorithmException
{
BigInteger modulus = null;
BigInteger public_exponent = null;
BigInteger private_exponent = null;
BigInteger prime_p = null;
BigInteger prime_q = null;
BigInteger prime_p_exponent = null;
BigInteger prime_q_exponent = null;
BigInteger coefficient = null;
while (lines.hasMoreTokens())
{
String line = lines.nextToken();
if (line == null) continue;
if (line.startsWith("#")) continue;
String val = value(line);
if (val == null) continue;
byte[] data = base64.fromString(val);
if (line.startsWith("Modulus: ")) {
modulus = new BigInteger(1, data);
// printBigIntCompare(data, modulus);
} else if (line.startsWith("PublicExponent: ")) {
public_exponent = new BigInteger(1, data);
// printBigIntCompare(data, public_exponent);
} else if (line.startsWith("PrivateExponent: ")) {
private_exponent = new BigInteger(1, data);
// printBigIntCompare(data, private_exponent);
} else if (line.startsWith("Prime1: ")) {
prime_p = new BigInteger(1, data);
// printBigIntCompare(data, prime_p);
} else if (line.startsWith("Prime2: ")) {
prime_q = new BigInteger(1, data);
// printBigIntCompare(data, prime_q);
} else if (line.startsWith("Exponent1: ")) {
prime_p_exponent = new BigInteger(1, data);
} else if (line.startsWith("Exponent2: ")) {
prime_q_exponent = new BigInteger(1, data);
} else if (line.startsWith("Coefficient: ")) {
coefficient = new BigInteger(1, data);
}
}
try
{
KeySpec spec = new RSAPrivateCrtKeySpec(modulus, public_exponent,
private_exponent, prime_p,
prime_q, prime_p_exponent,
prime_q_exponent, coefficient);
if (mRSAKeyFactory == null)
{
mRSAKeyFactory = KeyFactory.getInstance("RSA");
}
return mRSAKeyFactory.generatePrivate(spec);
}
catch (InvalidKeySpecException e)
{
e.printStackTrace();
return null;
}
}
/** Given the remaining lines in a BIND9 style DH private key, parse
* the key info and translate it into a JCA private key.
*
* @throws NoSuchAlgorithmException if the DH algorithm is not
* available. */
private PrivateKey parsePrivateDH(StringTokenizer lines)
throws NoSuchAlgorithmException
{
BigInteger p = null;
BigInteger x = null;
BigInteger g = null;
while (lines.hasMoreTokens())
{
String line = lines.nextToken();
if (line == null) continue;
if (line.startsWith("#")) continue;
String val = value(line);
if (val == null) continue;
byte[] data = base64.fromString(val);
if (line.startsWith("Prime(p): ")) {
p = new BigInteger(1, data);
} else if (line.startsWith("Generator(g): ")) {
g = new BigInteger(1, data);
} else if (line.startsWith("Private_value(x): ")) {
x = new BigInteger(1, data);
}
}
try
{
KeySpec spec = new DHPrivateKeySpec(x, p, g);
if (mDHKeyFactory == null)
{
mDHKeyFactory = KeyFactory.getInstance("DH");
}
return mDHKeyFactory.generatePrivate(spec);
}
catch (InvalidKeySpecException e)
{
e.printStackTrace();
return null;
}
}
/** Given the remaining lines in a BIND9 style DSA private key,
* parse the key info and translate it into a JCA private key.
*
* @throws NoSuchAlgorithmException if the DSA algorithm is not
* available. */
private PrivateKey parsePrivateDSA(StringTokenizer lines)
throws NoSuchAlgorithmException
{
BigInteger p = null;
BigInteger q = null;
BigInteger g = null;
BigInteger x = null;
while (lines.hasMoreTokens())
{
String line = lines.nextToken();
if (line == null) continue;
if (line.startsWith("#")) continue;
String val = value(line);
if (val == null) continue;
byte[] data = base64.fromString(val);
if (line.startsWith("Prime(p): ")) {
p = new BigInteger(1, data);
} else if (line.startsWith("Subprime(q): ")) {
q = new BigInteger(1, data);
} else if (line.startsWith("Base(g): ")) {
g = new BigInteger(1, data);
} else if (line.startsWith("Private_value(x): ")) {
x = new BigInteger(1, data);
}
}
try
{
KeySpec spec = new DSAPrivateKeySpec(x, p, q, g);
if (mDSAKeyFactory == null)
{
mDSAKeyFactory = KeyFactory.getInstance("DSA");
}
return mDSAKeyFactory.generatePrivate(spec);
}
catch (InvalidKeySpecException e)
{
e.printStackTrace();
return null;
}
}
/** Given a private key and public key, generate the BIND9 style
* private key format. */
public String generatePrivateKeyString(PrivateKey priv, PublicKey pub,
int alg)
{
if (priv instanceof RSAPrivateCrtKey)
{
return generatePrivateRSA((RSAPrivateCrtKey) priv, alg);
}
else if (priv instanceof DSAPrivateKey &&
pub instanceof DSAPublicKey)
{
return generatePrivateDSA((DSAPrivateKey) priv, (DSAPublicKey) pub);
}
else if (priv instanceof DHPrivateKey &&
pub instanceof DHPublicKey)
{
return generatePrivateDH((DHPrivateKey) priv, (DHPublicKey) pub);
}
return null;
}
/** Convert from 'unsigned' big integer to original 'signed format'
* in Base64 */
private String b64BigInt(BigInteger i)
{
byte[] orig_bytes = i.toByteArray();
if (orig_bytes[0] != 0 || orig_bytes.length == 1)
{
return base64.toString(orig_bytes);
}
byte[] signed_bytes = new byte[orig_bytes.length - 1];
System.arraycopy(orig_bytes, 1, signed_bytes, 0, signed_bytes.length);
return base64.toString(signed_bytes);
}
/** Given a RSA private key (in Crt format), return the BIND9-style
* text encoding. */
private String generatePrivateRSA(RSAPrivateCrtKey key, int algorithm)
{
StringWriter sw = new StringWriter();
PrintWriter out = new PrintWriter(sw);
out.println("Private-key-format: v1.2");
if (algorithm == DNSSEC.RSAMD5)
{
out.println("Algorithm: 1 (RSAMD5)");
}
else
{
out.println("Algorithm: 5 (RSASHA1)");
}
out.print("Modulus: ");
out.println(b64BigInt(key.getModulus()));
out.print("PublicExponent: ");
out.println(b64BigInt(key.getPublicExponent()));
out.print("PrivateExponent: ");
out.println(b64BigInt(key.getPrivateExponent()));
out.print("Prime1: ");
out.println(b64BigInt(key.getPrimeP()));
out.print("Prime2: ");
out.println(b64BigInt(key.getPrimeQ()));
out.print("Exponent1: ");
out.println(b64BigInt(key.getPrimeExponentP()));
out.print("Exponent2: ");
out.println(b64BigInt(key.getPrimeExponentQ()));
out.print("Coefficient: ");
out.println(b64BigInt(key.getCrtCoefficient()));
return sw.toString();
}
/** Given a DH key pair, return the BIND9-style text encoding */
private String generatePrivateDH(DHPrivateKey key, DHPublicKey pub)
{
StringWriter sw = new StringWriter();
PrintWriter out = new PrintWriter(sw);
DHParameterSpec p = key.getParams();
out.println("Private-key-format: v1.2");
out.println("Algorithm: 2 (DH)");
out.print("Prime(p): ");
out.println(b64BigInt(p.getP()));
out.print("Generator(g): ");
out.println(b64BigInt(p.getG()));
out.print("Private_value(x): ");
out.println(b64BigInt(key.getX()));
out.print("Public_value(y): ");
out.println(b64BigInt(pub.getY()));
return sw.toString();
}
/** Given a DSA key pair, return the BIND9-style text encoding */
private String generatePrivateDSA(DSAPrivateKey key, DSAPublicKey pub)
{
StringWriter sw = new StringWriter();
PrintWriter out = new PrintWriter(sw);
DSAParams p = key.getParams();
out.println("Private-key-format: v1.2");
out.println("Algorithm: 3 (DSA)");
out.print("Prime(p): ");
out.println(b64BigInt(p.getP()));
out.print("Subprime(q): ");
out.println(b64BigInt(p.getQ()));
out.print("Base(g): ");
out.println(b64BigInt(p.getG()));
out.print("Private_value(x): ");
out.println(b64BigInt(key.getX()));
out.print("Public_value(y): ");
out.println(b64BigInt(pub.getY()));
return sw.toString();
}
}

View File

@@ -0,0 +1,356 @@
// $Id: DnsKeyPair.java,v 1.3 2004/01/16 21:07:09 davidb Exp $
//
// Copyright (C) 2001-2003 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;
import java.security.*;
import java.security.interfaces.*;
import org.xbill.DNS.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/** This class forms the basis for representing public/private key
* pairs in a DNSSEC context. It is possible to get a JCA public and
* private key from this object, as well as a KEYRecord encoding of
* the public key. This class is implemented as a UNION of all the
* functionality needed for handing native java, BIND, and possibly
* other underlying KEY engines.
*
* JCA == Java Cryptography Architecture.
*
* @author David Blacka (orig)
* @author $Author: davidb $ (latest)
* @version $Revision: 1.3 $
*/
// FIXME: this class is not a generic DnsKeyPair at all. It is really
// a JCEDnsKeyPair. We probably need to reexamine the class design here.
// NOTE: this class is designed to do "lazy" evaluation of it's
// various cached objects and format conversions, so methods should
// avoid direct access to the member variables.
public class DnsKeyPair
{
/** This is the real (base) encoding of the public key. */
protected DNSKEYRecord mPublicKeyRecord;
/** This is a precalcuated cache of the KEYRecord converted into a
* JCA public key. */
private PublicKey mPublicKey;
/** The private key in Base64 encoded format. This version is
* presumed to be opaque, so no attempts will be made to convert it
* to a JCA private key. */
protected String mPrivateKeyString;
/** The private key in JCA format. This is the base encoding for
* instances were JCA private keys are used. */
protected PrivateKey mPrivateKey;
/** The local key converter. */
protected DnsKeyConverter mKeyConverter;
/** a cached Signature used for signing (initialized with the
* private key) */
protected Signature mSigner;
/** a caches Signature used for verifying (intialized with the
* public key) */
protected Signature mVerifier;
private Log log;
public DnsKeyPair()
{
log = LogFactory.getLog(this.getClass());
}
public DnsKeyPair(DNSKEYRecord keyRecord, PrivateKey privateKey)
{
this();
setDNSKEYRecord(keyRecord);
setPrivate(privateKey);
}
public DnsKeyPair(DNSKEYRecord keyRecord, String privateKeyString)
{
this();
setDNSKEYRecord(keyRecord);
setPrivateKeyString(privateKeyString);
}
public DnsKeyPair(Name keyName, int algorithm, PublicKey publicKey,
PrivateKey privateKey)
{
this();
DnsKeyConverter conv = new DnsKeyConverter();
DNSKEYRecord keyrec = conv.generateDNSKEYRecord(keyName, DClass.IN, 0, 0,
algorithm, publicKey);
setDNSKEYRecord(keyrec);
setPrivate(privateKey);
}
public DnsKeyPair(DnsKeyPair pair)
{
this();
setDNSKEYRecord(pair.getDNSKEYRecord());
setPrivate(pair.getPrivate());
setPrivateKeyString(pair.getPrivateKeyString());
}
/** @return cached DnsKeyConverter object. */
protected DnsKeyConverter getKeyConverter()
{
if (mKeyConverter == null)
{
mKeyConverter = new DnsKeyConverter();
}
return mKeyConverter;
}
/** @return the appropriate Signature object for this keypair. */
protected Signature getSignature()
{
Signature s = null;
// First try and deduce the algorithm from the KEYRecord (which
// will be specific), then try and deduce it from the private key.
// We should have one or the other.
try
{
switch (getDNSKEYAlgorithm())
{
case DNSSEC.RSAMD5:
s = Signature.getInstance("MD5withRSA");
break;
case DNSSEC.DSA:
s = Signature.getInstance("SHA1withDSA");
break;
case DNSSEC.RSASHA1:
s = Signature.getInstance("SHA1withRSA");
break;
case -1:
s = null;
break;
}
}
catch (NoSuchAlgorithmException e)
{
log.error("error getting Signature object", e);
}
return s;
}
/** @return the public key, translated from the KEYRecord, if
* necessary. */
public PublicKey getPublic()
{
if (mPublicKey == null && getDNSKEYRecord() != null)
{
DnsKeyConverter conv = getKeyConverter();
setPublic(conv.parseDNSKEYRecord(getDNSKEYRecord()));
}
return mPublicKey;
}
/** sets the public key. This method is generally not used
* directly. */
protected void setPublic(PublicKey k)
{
mPublicKey = k;
}
/** @return the private key. */
public PrivateKey getPrivate()
{
// attempt to convert the private key string format into a JCA
// private key.
if (mPrivateKey == null && mPrivateKeyString != null)
{
mPrivateKey = BINDKeyUtils.convertPrivateKeyString(mPrivateKeyString);
}
return mPrivateKey;
}
/** sets the private key */
public void setPrivate(PrivateKey k)
{
mPrivateKey = k;
}
/** @return the opaque private key string, null if one doesn't
* exist. */
public String getPrivateKeyString()
{
if (mPrivateKeyString == null && mPrivateKey != null)
{
PublicKey pub = getPublic();
mPrivateKeyString = BINDKeyUtils.convertPrivateKey(mPrivateKey, pub,
getDNSKEYAlgorithm());
}
return mPrivateKeyString;
}
/** sets the opaque private key string. */
public void setPrivateKeyString(String p)
{
mPrivateKeyString = p;
}
/** @return the private key in an encoded form (normally PKCS#8). */
public byte[] getEncodedPrivate()
{
PrivateKey priv = getPrivate();
if (priv != null) return priv.getEncoded();
return null;
}
/** Sets the private key from the encoded form (PKCS#8). This
* routine requires that the public key already be assigned.
* Currently it can only handle DSA and RSA keys. */
public void setEncodedPrivate(byte[] encoded)
{
int alg = getDNSKEYAlgorithm();
if (alg >= 0)
{
DnsKeyConverter conv = getKeyConverter();
setPrivate(conv.convertEncodedPrivateKey(encoded, alg));
}
}
/** @return the public DNSKEY record */
public DNSKEYRecord getDNSKEYRecord()
{
return mPublicKeyRecord;
}
/** @return a Signature object initialized for signing, or null if
* this key pair does not have a valid private key. */
public Signature getSigner()
{
if (mSigner == null)
{
mSigner = getSignature();
PrivateKey priv = getPrivate();
if (mSigner != null && priv != null)
{
try
{
mSigner.initSign(priv);
}
catch (InvalidKeyException e)
{
log.error("Signature error", e);
}
}
else
{
// do not return an unitialized signer.
return null;
}
}
return mSigner;
}
/** @return a Signature object initialized for verifying, or null if
* this key pair does not have a valid public key. */
public Signature getVerifier()
{
if (mVerifier == null)
{
mVerifier = getSignature();
PublicKey pk = getPublic();
if (mVerifier != null && pk != null)
{
try
{
mVerifier.initVerify(pk);
}
catch (InvalidKeyException e) {}
}
else
{
// do not return an unitialized verifier
return null;
}
}
return mVerifier;
}
/** sets the public key record */
public void setDNSKEYRecord(DNSKEYRecord r)
{
mPublicKeyRecord = r;
// force the conversion to PublicKey:
mPublicKey = null;
}
public Name getDNSKEYName()
{
DNSKEYRecord kr = getDNSKEYRecord();
if (kr != null) return kr.getName();
return null;
}
public int getDNSKEYAlgorithm()
{
DNSKEYRecord kr = getDNSKEYRecord();
if (kr != null) return kr.getAlgorithm();
PublicKey pk = getPublic();
if (pk != null)
{
// currently, alg 5 is the default over alg 1 (RSASHA1).
if (pk instanceof RSAPublicKey) return DNSSEC.RSASHA1;
if (pk instanceof DSAPublicKey) return DNSSEC.DSA;
}
PrivateKey priv = getPrivate();
if (priv != null)
{
if (priv instanceof RSAPrivateKey) return DNSSEC.RSASHA1;
if (priv instanceof DSAPrivateKey) return DNSSEC.DSA;
}
return -1;
}
public int getDNSKEYFootprint()
{
DNSKEYRecord kr = getDNSKEYRecord();
if (kr != null) return kr.getFootprint();
return -1;
}
}

View File

@@ -0,0 +1,321 @@
// $Id: DnsSecVerifier.java,v 1.5 2004/02/25 20:46:14 davidb Exp $
//
// Copyright (C) 2001-2003 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;
import java.util.*;
import java.io.*;
import java.security.*;
import org.xbill.DNS.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/** A class for performing basic DNSSEC verification. The DNSJAVA
* package contains a similar class. This differs (for the moment,
* anyway) by allowing timing "fudge" factors and logging more
* specifically why an RRset did not validate.
*
* @author David Blacka (original)
* @author $Author: davidb $
* @version $Revision: 1.5 $
*/
public class DnsSecVerifier implements Verifier
{
private class TrustedKeyStore
{
// for now, this is implemented as a hashtable of lists of
// DnsKeyPair objects (obviously, all of them will not have
// private keys).
private HashMap mKeyMap;
public TrustedKeyStore()
{
mKeyMap = new HashMap();
}
public void add(DnsKeyPair pair)
{
String n = pair.getDNSKEYName().toString().toLowerCase();
List l = (List) mKeyMap.get(n);
if (l == null)
{
l = new ArrayList();
mKeyMap.put(n, l);
}
l.add(pair);
}
public void add(DNSKEYRecord keyrec)
{
DnsKeyPair pair = new DnsKeyPair(keyrec, (PrivateKey) null);
add(pair);
}
public void add(Name name, int algorithm, PublicKey key)
{
DnsKeyPair pair = new DnsKeyPair(name, algorithm, key, null);
add(pair);
}
public DnsKeyPair find(Name name, int algorithm, int keyid)
{
String n = name.toString().toLowerCase();
List l = (List) mKeyMap.get(n);
if (l == null) return null;
// FIXME: this algorithm assumes that name+alg+footprint is
// unique, which isn't necessarily true.
for (Iterator i = l.iterator(); i.hasNext(); )
{
DnsKeyPair p = (DnsKeyPair) i.next();
if (p.getDNSKEYAlgorithm() == algorithm &&
p.getDNSKEYFootprint() == keyid)
{
return p;
}
}
return null;
}
}
private TrustedKeyStore mKeyStore;
private int mStartFudge = 0;
private int mExpireFudge = 0;
private boolean mVerifyAllSigs = false;
private Log log;
public DnsSecVerifier()
{
log = LogFactory.getLog(this.getClass());
mKeyStore = new TrustedKeyStore();
}
public void addTrustedKey(DNSKEYRecord keyrec)
{
mKeyStore.add(keyrec);
}
public void addTrustedKey(DnsKeyPair pair)
{
mKeyStore.add(pair);
}
public void addTrustedKey(Name name, int algorithm, PublicKey key)
{
mKeyStore.add(name, algorithm, key);
}
public void addTrustedKey(Name name, PublicKey key)
{
mKeyStore.add(name, 0, key);
}
public void setExpireFudge(int fudge)
{
mExpireFudge = fudge;
}
public void setStartFudge(int fudge)
{
mStartFudge = fudge;
}
public void setVerifyAllSigs(boolean v)
{
mVerifyAllSigs = v;
}
private DnsKeyPair findCachedKey(Cache cache, Name name, int algorithm,
int footprint)
{
RRset[] keysets = cache.findAnyRecords(name, Type.KEY);
if (keysets == null) return null;
// look for the particular key
// FIXME: this assumes that name+alg+footprint is unique.
for (Iterator i = keysets[0].rrs(); i.hasNext(); )
{
Object o = i.next();
if (! (o instanceof DNSKEYRecord)) continue;
DNSKEYRecord keyrec = (DNSKEYRecord) o;
if (keyrec.getAlgorithm() == algorithm &&
keyrec.getFootprint() == footprint)
{
return new DnsKeyPair(keyrec, (PrivateKey) null);
}
}
return null;
}
private DnsKeyPair findKey(Cache cache, Name name, int algorithm,
int footprint)
{
DnsKeyPair pair = mKeyStore.find(name, algorithm, footprint);
if (pair == null && cache != null)
{
pair = findCachedKey(cache, name, algorithm, footprint);
}
return pair;
}
private byte validateSignature(RRset rrset, RRSIGRecord sigrec)
{
if (rrset == null || sigrec == null) return DNSSEC.Failed;
if (! rrset.getName().equals(sigrec.getName()))
{
log.info("Signature name does not match RRset name");
return DNSSEC.Failed;
}
if (rrset.getType() != sigrec.getTypeCovered())
{
log.info("Signature type does not match RRset type");
}
Date now = new Date();
Date start = sigrec.getTimeSigned();
Date expire = sigrec.getExpire();
if (mStartFudge >= 0)
{
if (mStartFudge > 0)
{
start = new Date(start.getTime() - ((long) mStartFudge * 1000));
}
if (now.before(start))
{
log.info("Signature is not yet valid");
return DNSSEC.Failed;
}
}
if (mExpireFudge >= 0)
{
if (mExpireFudge > 0)
{
expire = new Date(expire.getTime() + ((long) mExpireFudge * 1000));
}
if (now.after(expire))
{
log.info("Signature has expired (now = " + now +
", sig expires = " + expire);
return DNSSEC.Failed;
}
}
return DNSSEC.Secure;
}
/** Verify an RRset against a particular signature.
*
* @return DNSSEC.Secure if the signature verfied, DNSSEC.Failed if
* it did not verify (for any reason), and DNSSEC.Insecure if
* verification could not be completed (usually because the public
* key was not available). */
public byte verifySignature(RRset rrset, RRSIGRecord sigrec, Cache cache)
{
byte result = validateSignature(rrset, sigrec);
if (result != DNSSEC.Secure) return result;
DnsKeyPair keypair = findKey(cache, sigrec.getSigner(),
sigrec.getAlgorithm(),
sigrec.getFootprint());
if (keypair == null)
{
log.info("could not find appropriate key");
return DNSSEC.Insecure;
}
try
{
byte[] data = SignUtils.generateSigData(rrset, sigrec);
Signature signer = keypair.getVerifier();
signer.update(data);
byte[] sig = sigrec.getSignature();
if (sigrec.getAlgorithm() == DNSSEC.DSA)
{
sig = SignUtils.convertDSASignature(sig);
}
if (!signer.verify(sig))
{
log.info("Signature failed to verify cryptographically");
return DNSSEC.Failed;
}
return DNSSEC.Secure;
}
catch (IOException e)
{
log.error("I/O error", e);
}
catch (GeneralSecurityException e)
{
log.error("Security error", e);
}
log.debug("Signature failed to verify due to exception");
return DNSSEC.Insecure;
}
/** Verifies an RRset. This routine does not modify the RRset.
*
* @return DNSSEC.Secure if the set verified, DNSSEC.Failed if it
* did not, and DNSSEC.Insecure if verification could not
* complete. */
public int verify(RRset rrset, Cache cache)
{
int result = mVerifyAllSigs ? DNSSEC.Secure: DNSSEC.Insecure;
Iterator i = rrset.sigs();
if ( ! i.hasNext())
{
log.info("RRset failed to verify due to lack of signatures");
return DNSSEC.Insecure;
}
while (i.hasNext())
{
RRSIGRecord sigrec = (RRSIGRecord) i.next();
byte res = verifySignature(rrset, sigrec, cache);
if (!mVerifyAllSigs && res == DNSSEC.Secure) return res;
if (!mVerifyAllSigs && res < result) result = res;
if (mVerifyAllSigs && res != DNSSEC.Secure && res < result)
{
result = res;
}
}
return result;
}
}

View File

@@ -0,0 +1,373 @@
// $Id: JCEDnsSecSigner.java,v 1.3 2004/02/25 20:46:14 davidb Exp $
//
// Copyright (C) 2001-2003 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;
import java.util.*;
import java.io.*;
import java.security.*;
import java.security.interfaces.*;
import org.xbill.DNS.*;
/** This class contains routines for signing DNS zones.
*
* 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 basic primitives found in {@link SignUtils}.
*
* @author David Blacka (original)
* @author $Author: davidb $
* @version $Revision: 1.3 $
*/
public class JCEDnsSecSigner
{
private DnsKeyConverter mKeyConverter;
private KeyPairGenerator mRSAKeyGenerator;
private KeyPairGenerator mDSAKeyGenerator;
/** Cryptographically generate a new DNSSEC key.
*
* @param owner the KEY RR's owner name.
* @param ttl the KEY RR's TTL.
* @param dclass the KEY RR's DNS class.
* @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.
* @return a DnsKeyPair with the public and private keys populated.
*/
public DnsKeyPair generateKey(Name owner,
long ttl,
int dclass,
int algorithm,
int flags,
int keysize)
throws IOException, NoSuchAlgorithmException
{
KeyPair pair;
if (ttl < 0) ttl = 86400; // set to a reasonable default.
switch (algorithm)
{
case DNSSEC.RSAMD5:
case DNSSEC.RSASHA1:
if (mRSAKeyGenerator == null)
{
mRSAKeyGenerator = KeyPairGenerator.getInstance("RSA");
}
mRSAKeyGenerator.initialize(keysize);
pair = mRSAKeyGenerator.generateKeyPair();
break;
case DNSSEC.DSA:
if (mDSAKeyGenerator == null)
{
mDSAKeyGenerator = KeyPairGenerator.getInstance("DSA");
}
mDSAKeyGenerator.initialize(keysize);
pair = mDSAKeyGenerator.generateKeyPair();
break;
default:
throw new NoSuchAlgorithmException("Alg " + algorithm);
}
if (mKeyConverter == null)
{
mKeyConverter = new DnsKeyConverter();
}
DNSKEYRecord keyrec = mKeyConverter.generateDNSKEYRecord(owner,
dclass,
ttl,
flags,
algorithm,
pair.getPublic());
DnsKeyPair dnspair = new DnsKeyPair();
dnspair.setDNSKEYRecord(keyrec);
dnspair.setPublic(pair.getPublic()); // keep from conv. the keyrec back.
dnspair.setPrivate(pair.getPrivate());
return dnspair;
}
/** Sign an RRset.
*
* @param rrset the RRset to sign -- any existing signatures are ignored.
* @param keypars 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.
*/
public List signRRset(RRset rrset, List keypairs, Date start, Date expire)
throws IOException, GeneralSecurityException
{
if (rrset == null || keypairs == null) return null;
// default start to now, expire to start + 1 second.
if (start == null) start = new Date();
if (expire == null) expire = new Date(start.getTime() + 1000L);
if (keypairs.size() == 0) return null;
// first, pre-calculate the rrset bytes.
byte[] rrset_data = SignUtils.generateCanonicalRRsetData(rrset);
ArrayList sigs = new ArrayList(keypairs.size());
// for each keypair, sign the rrset.
for (Iterator i = keypairs.iterator(); i.hasNext(); )
{
DnsKeyPair pair = (DnsKeyPair) i.next();
DNSKEYRecord keyrec = pair.getDNSKEYRecord();
if (keyrec == null) continue;
RRSIGRecord presig = SignUtils.generatePreRRSIG(rrset, keyrec, start,
expire, rrset.getTTL());
byte[] sign_data = SignUtils.generateSigData(rrset_data, presig);
Signature signer = pair.getSigner();
if (signer == null)
{
// debug
System.out.println("missing private key that goes with:\n" +
pair.getDNSKEYRecord());
throw new GeneralSecurityException
("cannot sign without a valid Signer " +
"(probably missing private key)");
}
// sign the data.
signer.update(sign_data);
byte[] sig = signer.sign();
// Convert to RFC 2536 format, if necessary.
if (pair.getDNSKEYAlgorithm() == DNSSEC.DSA)
{
sig = SignUtils.convertDSASignature
(((DSAPublicKey)pair.getPublic()).getParams(), sig);
}
RRSIGRecord sigrec = SignUtils.generateRRSIG(sig, presig);
sigs.add(sigrec);
}
return sigs;
}
/** Create a completely self-signed KEY RRset.
*
* @param keypairs the public & private keypairs to use in the keyset.
* @param start the RRSIG inception time.
* @param expire the RRSIG expiration time.
* @return a signed RRset.
*/
public RRset makeKeySet(List keypairs, Date start, Date expire)
throws IOException, GeneralSecurityException
{
// Generate a KEY RR set to sign.
RRset keyset = new RRset();
for (Iterator i = keypairs.iterator(); i.hasNext(); )
{
DnsKeyPair pair = (DnsKeyPair) i.next();
keyset.addRR(pair.getDNSKEYRecord());
}
List records = signRRset(keyset, keypairs, start, expire);
for (Iterator i = records.iterator(); i.hasNext(); )
{
keyset.addRR((Record) i.next());
}
return keyset;
}
/** 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 Name addRRset(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 = 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 = signRRset(rrset, zonekeypairs, start, expire);
toList.addAll(sigs);
return last_cut;
}
/** Given a zone, sign it.
*
* @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 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 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,
* representing the signed zone.
*/
public List signZone(Name zonename,
List records,
List keysigningkeypairs,
List zonekeypairs,
Date start,
Date expire,
boolean useOptIn,
boolean useConservativeOptIn,
boolean fullySignKeyset,
List NSECIncludeNames)
throws IOException, GeneralSecurityException
{
// Remove any existing DNSSEC records (NSEC, 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);
// Generate NXT records
if (useOptIn)
{
SignUtils.generateOptInNSECRecords(zonename, records,
NSECIncludeNames,
useConservativeOptIn);
}
else
{
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();
Name r_name = r.getName();
// First record
if (rrset.getName() == null)
{
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(signed_records, zonename, rrset, keysigningkeypairs,
zonekeypairs, start, expire, fullySignKeyset,
last_cut);
rrset.clear();
rrset.addRR(r);
}
// add the last RR set
addRRset(signed_records, zonename, rrset, keysigningkeypairs, zonekeypairs,
start, expire, fullySignKeyset, last_cut);
return signed_records;
}
}

View File

@@ -0,0 +1,91 @@
// $Id: RecordComparator.java,v 1.2 2004/01/16 17:54:48 davidb Exp $
//
// Copyright (C) 2000-2003 Network Solutions, 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;
import java.util.*;
import org.xbill.DNS.*;
/** This class implements a comparison operator for {@link
* org.xbill.DNS.Record} objects. It imposes a canonical order
* consistent with DNSSEC. It does not put records within a RRset
* into canonical order: see {@link ByteArrayComparator}.
*
* @author David Blacka (original)
* @author $Author: davidb $
* @version $Revision: 1.2 $ */
public class RecordComparator implements Comparator
{
public RecordComparator()
{}
/** In general, types are compared numerically. However, SOA and NS
* are ordered before the rest. */
private int compareTypes(int a, int b)
{
if (a == b) return 0;
if (a == Type.SOA) return -1;
if (b == Type.SOA) return 1;
if (a == Type.NS) return -1;
if (b == Type.NS) return 1;
if (a < b) return -1;
return 1;
}
public int compare(Object o1, Object o2)
throws ClassCastException
{
Record a = (Record) o1;
Record b = (Record) o2;
if (a == null && b == null) return 0;
if (a == null) return 1;
if (b == null) return -1;
int res = a.getName().compareTo(b.getName());
if (res != 0) return res;
int a_type = a.getType();
int b_type = b.getType();
int sig_type = 0;
if (a_type == Type.RRSIG)
{
a_type = ((RRSIGRecord) a).getTypeCovered();
sig_type = 1;
}
if (b_type == Type.RRSIG)
{
b_type = ((RRSIGRecord) b).getTypeCovered();
sig_type = -1;
}
res = compareTypes(a_type, b_type);
if (res != 0) return res;
if (sig_type != 0) return sig_type;
return 0;
}
}

View File

@@ -0,0 +1,927 @@
// $Id: SignUtils.java,v 1.7 2004/03/23 17:53:57 davidb Exp $
//
// Copyright (C) 2001-2003 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;
import java.util.*;
import java.io.*;
import java.security.MessageDigest;
import java.security.interfaces.DSAParams;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import org.xbill.DNS.*;
import org.xbill.DNS.utils.base64; // debug only
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/** This class contains a bunch of utility methods that are generally
* useful in signing zones.
*
* @author David Blacka (original)
* @author $Author: davidb $
* @version $Revision: 1.7 $
*/
public class SignUtils
{
private static final int DSA_SIGNATURE_LENGTH = 20;
private static final int ASN1_INT = 0x02;
private static final int ASN1_SEQ = 0x30;
public static final int RR_NORMAL = 0;
public static final int RR_DELEGATION = 1;
public static final int RR_GLUE = 2;
public static final int RR_INVALID = 3;
private static Log log;
static {
log = LogFactory.getLog(SignUtils.class);
}
public static void setLog(Log v)
{
log = v;
}
/** Generate from some basic information a prototype SIG RR
* containing everything but the actual signature itself.
*
* @param rrset the RRset being signed.
* @param key the public KEY RR counterpart to the key being used
* to sign the RRset
* @param start the SIG inception time.
* @param expire the SIG expiration time.
* @param sig_ttl the TTL of the resulting SIG record.
* @return a prototype signature based on the RRset and key
* information. */
public static RRSIGRecord generatePreRRSIG(RRset rrset, DNSKEYRecord key,
Date start, Date expire,
long sig_ttl)
{
return new RRSIGRecord(rrset.getName(),
rrset.getDClass(),
sig_ttl,
rrset.getType(),
key.getAlgorithm(),
(int) rrset.getTTL(),
expire,
start,
key.getFootprint(),
key.getName(),
null);
}
/** Generate from some basic information a prototype SIG RR
* containing everything but the actual signature itself.
*
* @param rec the DNS record being signed (forming an entire RRset).
* @param key the public KEY RR counterpart to the key signing the
* record.
* @param start the SIG inception time.
* @param expire the SIG expiration time.
* @param sig_ttl the TTL of the result SIG record.
* @return a prototype signature based on the Record and key
* information. */
public static RRSIGRecord generatePreRRSIG(Record rec, DNSKEYRecord key,
Date start, Date expire,
long sig_ttl)
{
return new RRSIGRecord(rec.getName(),
rec.getDClass(),
sig_ttl,
rec.getType(),
key.getAlgorithm(),
rec.getTTL(),
expire,
start,
key.getFootprint(),
key.getName(),
null);
}
/** Generate the binary image of the prototype SIG RR.
*
* @param presig the SIG RR prototype.
* @return the RDATA portion of the prototype SIG record. This
* forms the first part of the data to be signed. */
private static byte[] generatePreSigRdata(RRSIGRecord presig)
throws IOException
{
// Generate the binary image;
DNSOutput image = new DNSOutput();
// precalc some things
int start_time = (int) (presig.getTimeSigned().getTime() / 1000);
int expire_time = (int) (presig.getExpire().getTime() / 1000);
Name signer = presig.getSigner();
// first write out the partial SIG record (this is the SIG RDATA
// minus the actual signature.
image.writeU16(presig.getTypeCovered());
image.writeU8(presig.getAlgorithm());
image.writeU8(presig.getLabels());
image.writeU32((int) presig.getOrigTTL());
image.writeU32(expire_time);
image.writeU32(start_time);
image.writeU16(presig.getFootprint());
image.writeByteArray(signer.toWireCanonical());
return image.toByteArray();
}
/** Calculate the canonical wire line format of the RRset.
*
* @param rrset the RRset to convert.
* @return the canonical wire line format of the rrset. This is
* the second part of data to be signed.*/
public static byte[] generateCanonicalRRsetData(RRset rrset)
throws IOException
{
DNSOutput image = new DNSOutput();
// now convert load the wire format records in the RRset into a
// list of byte arrays.
ArrayList canonical_rrs = new ArrayList();
for (Iterator i = rrset.rrs(); i.hasNext(); )
{
Record r = (Record) i.next();
byte[] wire_fmt = r.toWireCanonical();
canonical_rrs.add(wire_fmt);
}
// put the records into the correct ordering.
// Caculate the offset where the RDATA begins (we have to skip
// past the length byte)
// FIXME: currently, draft-ietf-dnsext-dnssec-records-06 has us
// sorting by length first, then bytes. This can be accomplished
// by not skipping past the RDLENGTH field, I think.
// FIXME update: I pointed this out as an error, and subsequent
// versions should correct this, setting the standard back to
// bytes, then length.
int offset = rrset.getName().toWireCanonical().length + 10;
ByteArrayComparator bac = new ByteArrayComparator(offset, false);
Collections.sort(canonical_rrs, bac);
for (Iterator i = canonical_rrs.iterator(); i.hasNext(); )
{
byte[] wire_fmt_rec = (byte[]) i.next();
image.writeByteArray(wire_fmt_rec);
}
return image.toByteArray();
}
/** Given an RRset and the prototype signature, generate the
* canonical data that is to be signed.
*
* @param rrset the RRset to be signed.
* @param presig a prototype SIG RR created using the same RRset.
* @return a block of data ready to be signed.
*/
public static byte[] generateSigData(RRset rrset, RRSIGRecord presig)
throws IOException
{
byte[] rrset_data = generateCanonicalRRsetData(rrset);
return generateSigData(rrset_data, presig);
}
/** Given an RRset and the prototype signature, generate the
* canonical data that is to be signed.
*
* @param rrset_data the RRset converted into canonical wire line
* format (as per the canonicalization rules in RFC 2535).
* @param presig the prototype signature based on the same RRset
* represented in <code>rrset_data</code>.
* @return a block of data ready to be signed.
*/
public static byte[] generateSigData(byte[] rrset_data, RRSIGRecord presig)
throws IOException
{
byte[] sig_rdata = generatePreSigRdata(presig);
ByteArrayOutputStream image
= new ByteArrayOutputStream(sig_rdata.length + rrset_data.length);
image.write(sig_rdata);
image.write(rrset_data);
return image.toByteArray();
}
/** Given the acutal signature an the prototype signature, combine
* them and return the fully formed SIGRecord.
*
* @param signature the cryptographic signature, in DNSSEC format.
* @param presig the prototype SIG RR to add the signature to.
* @return the fully formed SIG RR.
*/
public static RRSIGRecord generateRRSIG(byte[] signature, RRSIGRecord presig)
{
return new RRSIGRecord(presig.getName(),
presig.getDClass(),
presig.getTTL(),
presig.getTypeCovered(),
presig.getAlgorithm(),
presig.getOrigTTL(),
presig.getExpire(),
presig.getTimeSigned(),
presig.getFootprint(),
presig.getSigner(),
signature);
}
/** Converts from a RFC 2536 formatted DSA signature to a JCE
* (ASN.1) formatted signature.
*
* <p>ASN.1 format = ASN1_SEQ . seq_length . ASN1_INT . Rlength . R
* . ANS1_INT . Slength . S</p>
*
* The integers R and S may have a leading null byte to force the
* integer positive.
*
* @param signature the RFC 2536 formatted DSA signature.
* @return The ASN.1 formatted DSA signature.
* @throws SignatureException if there was something wrong with the
* RFC 2536 formatted signature.
**/
public static byte[] convertDSASignature(byte[] signature)
throws SignatureException
{
if (signature.length != 41)
throw new SignatureException("RFC 2536 signature not expected length.");
byte r_pad = 0;
byte s_pad = 0;
// handle initial null byte padding.
if (signature[1] < 0) r_pad++;
if (signature[21] < 0) s_pad++;
// ASN.1 length = R length + S length + (2 + 2 + 2), where each 2
// is for a ASN.1 type-length byte pair of which there are three
// (SEQ, INT, INT).
byte sig_length = (byte) (40 + r_pad + s_pad + 6);
byte sig[] = new byte[sig_length];
byte pos = 0;
sig[pos++] = ASN1_SEQ;
sig[pos++] = (byte) (sig_length - 2); // all but the SEQ type+length.
sig[pos++] = ASN1_INT;
sig[pos++] = (byte) (20 + r_pad);
// copy the value of R, leaving a null byte if necessary
if (r_pad == 1) sig[pos++] = 0;
System.arraycopy(signature, 1, sig, pos, 20);
pos += 20;
sig[pos++] = ASN1_INT;
sig[pos++] = (byte) (20 + s_pad);
// copy the value of S, leaving a null byte if necessary
if (s_pad == 1) sig[pos++] = 0;
System.arraycopy(signature, 21, sig, pos, 20);
return sig;
}
/** Converts from a JCE (ASN.1) formatted DSA signature to a RFC
* 2536 compliant signature.
*
* <p>rfc2536 format = T . R . S</p>
*
* where T is a number between 0 and 8, which is based on the DSA
* key length, and R & S are formatted to be exactly 20 bytes each
* (no leading null bytes).
*
* @param params the DSA parameters associated with the DSA key
* used to generate the signature.
* @param signature the ASN.1 formatted DSA signature.
* @return a RFC 2536 formatted DSA signature.
* @throws SignatureException if something is wrong with the ASN.1
* format.
*/
public static byte[] convertDSASignature(DSAParams params, byte[] signature)
throws SignatureException
{
if (signature[0] != ASN1_SEQ || signature[2] != ASN1_INT)
{
throw new SignatureException
("Invalid ASN.1 signature format: expected SEQ, INT");
}
byte r_pad = (byte) (signature[3] - 20);
if (signature[24 + r_pad] != ASN1_INT) {
throw new SignatureException
("Invalid ASN.1 signature format: expected SEQ, INT, INT");
}
log.trace("(start) ASN.1 DSA Sig:\n" + base64.toString(signature));
byte s_pad = (byte) (signature[25 + r_pad] - 20);
byte[] sig = new byte[41]; // all rfc2536 signatures are 41 bytes.
// Calculate T:
sig[0] = (byte) ((params.getP().bitLength() - 512) / 64);
// copy R value
if (r_pad >= 0)
{
System.arraycopy(signature, 4 + r_pad, sig, 1, 20);
}
else
{
// R is shorter than 20 bytes, so right justify the number
// (r_pad is negative here, remember?).
Arrays.fill(sig, 1 , 1 - r_pad, (byte) 0);
System.arraycopy(signature, 4, sig, 1 - r_pad, 20 + r_pad);
}
// copy S value
if (s_pad >= 0)
{
System.arraycopy(signature, 26 + r_pad + s_pad, sig, 21, 20);
}
else
{
// S is shorter than 20 bytes, so right justify the number
// (s_pad is negative here).
Arrays.fill(sig, 21, 21 - s_pad, (byte) 0);
System.arraycopy(signature, 26 + r_pad, sig, 21 - s_pad, 20 + s_pad);
}
if (r_pad < 0 || s_pad < 0)
{
log.trace("(finish ***) RFC 2536 DSA Sig:\n" + base64.toString(sig));
}
else
{
log.trace("(finish) RFC 2536 DSA Sig:\n" + base64.toString(sig));
}
return sig;
}
/** This is a convenience routine to help us classify records/RRsets.
*
* It charaterizes a record/RRset as one of the following classes:<br/>
* <dl>
* <dt>NORMAL</dt><dd>This record/set is properly within the zone
* an subject to all NXT and SIG processing.</dd>
* <dt>DELEGATION</dt><dd>This is a zone delegation point (or
* cut). It is used in NXT processing but is not signed.</dd>
* <dt>GLUE</dt><dd>This is a glue record and therefore not
* properly within the zone. It is not included in NXT or SIG
* processing. Normally glue records are A records, but this
* routine calls anything that is below a zone delegation
* glue.</dd>
* <dt>INVALID</dt><dd>This record doesn't even belong in the
* zone.</dd>
* </dl><br/>
* This method must be called successively on records in the
* canonical name ordering, and the caller must maintain the
* last_cut parameter.
* @param zonename the name of the zone that is being processed.
* @param name the name of the record/set under consideration.
* @param type the type of the record/set under consideration.
* @param last_cut the name of the last DELEGATION record/set that
* was encountered while iterating over the zone in canonical
* order.
*/
public static int recordSecType(Name zonename, Name name, int type,
Name last_cut)
{
// records not even in the zone itself are invalid.
if (!name.subdomain(zonename)) return RR_INVALID;
// records that are at the zonename node are definitely normal.
if (name.equals(zonename)) return RR_NORMAL;
// since we are not at zone level, any NS records are delegations
if (type == Type.NS) return RR_DELEGATION;
if (last_cut != null)
{
// if we are at the same level as a delegation point, but not an
// NS record, then we either a DS record or glue.
if (name.equals(last_cut))
{
if (type == Type.DS || type == Type.NXT ||
type == Type.NSEC) return RR_NORMAL;
// actually, this is probably INVALID, but it could be glue.
return RR_GLUE;
}
// below the delegation, we are glue
if (name.subdomain(last_cut)) return RR_GLUE;
}
return RR_NORMAL;
}
/** Given a canonical ordered list of records from a single zone,
* order the raw records into a list of RRsets.
*
* @param records a list of {@link org.xbill.DNS.Record} objects,
* in DNSSEC canonical order.
* @return a List of {@link org.xbill.DNS.RRset} objects.
*/
public static List assembleIntoRRsets(List records)
{
RRset rrset = new RRset();
ArrayList rrsets = new ArrayList();
for (Iterator i = records.iterator(); i.hasNext(); )
{
Object o = i.next();
if (! (o instanceof Record))
{
log.warn("assembleIntoRRsets: a non-record object was " +
"encountered and skipped: " + o + " (" + o.getClass() + ")");
continue;
}
Record r = (Record) o;
Name r_name = r.getName();
// First record
if (rrset.getName() == null)
{
rrset.addRR(r);
continue;
}
// Current record is part of the current RRset.
if (rrset.getName().equals(r.getName()) &&
rrset.getDClass() == r.getDClass() &&
((r.getType() == Type.RRSIG &&
rrset.getType() == ((RRSIGRecord)r).getTypeCovered()) ||
rrset.getType() == r.getType()))
{
rrset.addRR(r);
continue;
}
// otherwise, we have completed the RRset
rrsets.add(rrset);
// set up for the next set.
rrset = new RRset();
rrset.addRR(r);
}
// add the last rrset.
rrsets.add(rrset);
return rrsets;
}
/** A little private class to hold information about a given
* node. */
private static class NodeInfo
{
public Name name;
public int type;
public long ttl;
public int dclass;
public Set typemap;
public boolean isSecureNode; // opt-in support.
public boolean hasOptInSpan; // opt-in support.
public int nsecIndex;
public NodeInfo(Record r)
{
this.name = r.getName();
this.type = r.getType();
this.ttl = r.getTTL();
this.dclass = r.getDClass();
this.typemap = new HashSet();
this.isSecureNode = false;
this.hasOptInSpan = false;
addType(type);
}
public void addType(int type)
{
this.typemap.add(new Integer(type));
// Opt-In support.
if (type != Type.NS && type != Type.NSEC && type != Type.RRSIG)
{
isSecureNode = true;
}
}
public String toString()
{
StringBuffer sb = new StringBuffer(name.toString());
if (isSecureNode) sb.append("(S)");
if (hasOptInSpan) sb.append("(O)");
return sb.toString();
}
public int[] getTypes()
{
Object[] a = typemap.toArray();
int[] res = new int[a.length];
for (int i = 0; i < a.length; i++)
{
res[i] = ((Integer) a[i]).intValue();
}
return res;
}
}
/** Given a canonical (by name) ordered list of records in a zone,
* generate the NXT records in place.
*
* Note that the list that the records are stored in must support
* the listIterator.add() operation.
*
* @param zonename the name of the zone (used to distinguish
* between zone apex NS RRsets and delegations).
* @param records a list of {@link org.xbill.DNS.Record} objects in
* DNSSEC canonical order.
*/
public static void generateNSECRecords(Name zonename, List records)
{
// This works by iterating over a known sorted list of records.
NodeInfo last_node = null;
NodeInfo current_node = null;
Name last_cut = null;
int backup;
for (ListIterator i = records.listIterator(); i.hasNext(); )
{
Record r = (Record) i.next();
Name r_name = r.getName();
int r_type = r.getType();
int r_sectype = recordSecType(zonename, r_name, r_type, last_cut);
// skip irrelevant records
if (r_sectype == RR_INVALID || r_sectype == RR_GLUE) continue;
// note our last delegation point so we can recognize glue.
if (r_sectype == RR_DELEGATION) last_cut = r_name;
// first node -- initialize
if (current_node == null)
{
current_node = new NodeInfo(r);
current_node.addType(Type.RRSIG);
current_node.addType(Type.NSEC);
continue;
}
// record name hasn't changed, so we are still on the same node.
if (r_name.equals(current_node.name))
{
current_node.addType(r_type);
continue;
}
if (last_node != null)
{
NSECRecord nsec = new NSECRecord(last_node.name, last_node.dclass,
last_node.ttl, current_node.name,
last_node.getTypes());
// Note: we have to add this through the iterator, otherwise
// the next access via the iterator will generate a
// ConcurrencyModificationException.
backup = i.nextIndex() - last_node.nsecIndex;
for (int j = 0; j < backup; j++) i.previous();
i.add(nsec);
for (int j = 0; j < backup; j++) i.next();
log.trace("Generated: " + nsec);
}
last_node = current_node;
current_node.nsecIndex = i.previousIndex();
current_node = new NodeInfo(r);
current_node.addType(Type.RRSIG);
current_node.addType(Type.NSEC);
}
// Generate next to last NSEC
if (last_node != null)
{
NSECRecord nsec = new NSECRecord(last_node.name, last_node.dclass,
last_node.ttl, current_node.name,
last_node.getTypes());
records.add(last_node.nsecIndex - 1, nsec);
log.trace("Generated: " + nsec);
}
// Generate last NSEC
NSECRecord nsec = new NSECRecord(current_node.name, current_node.dclass,
current_node.ttl, zonename,
current_node.getTypes());
records.add(nsec);
log.trace("Generated: " + nsec);
}
/** Given a canonical (by name) ordered list of records in a zone,
* generate the NXT records in place.
*
* Note thatthe list that the records are stored in must support
* the <code>listIterator.add</code> operation.
*
* @param zonename the name of the zone apex, used to distinguish
* between authoritative and delegation NS RRsets.
* @param records a list of {@link org.xbill.DNS.Record}s in DNSSEC
* canonical order.
* @param includeNames a list of names that should be in the NXT
* chain regardless. This may be null.
* @param beConservative if true, then Opt-In NXTs will only be
* generated where there is actually a span of insecure
* delegations.
*/
public static void generateOptInNSECRecords(Name zonename,
List records,
List includeNames,
boolean beConservative)
{
// This works by iterating over a known sorted list of records.
NodeInfo last_node = null;
NodeInfo current_node = null;
Name last_cut = null;
int backup;
HashSet includeSet = null;
if (includeNames != null)
{
includeSet = new HashSet(includeNames);
}
for (ListIterator i = records.listIterator(); i.hasNext(); )
{
Record r = (Record) i.next();
Name r_name = r.getName();
int r_type = r.getType();
int r_sectype = recordSecType(zonename, r_name, r_type, last_cut);
// skip irrelevant records
if (r_sectype == RR_INVALID || r_sectype == RR_GLUE) continue;
// note our last delegation point so we can recognize glue.
if (r_sectype == RR_DELEGATION) last_cut = r_name;
// first node -- initialize
if (current_node == null)
{
current_node = new NodeInfo(r);
current_node.addType(Type.RRSIG);
continue;
}
// record name hasn't changed, so we are still on the same node.
if (r_name.equals(current_node.name))
{
current_node.addType(r_type);
continue;
}
// If the name is in the set of included names, mark it as
// secure.
if (includeSet != null && includeSet.contains(current_node.name))
{
current_node.isSecureNode = true;
}
if (last_node != null && current_node.isSecureNode)
{
// generate a NSEC record.
if (beConservative && !last_node.hasOptInSpan)
{
last_node.addType(Type.NSEC);
}
NSECRecord nsec = new NSECRecord(last_node.name, last_node.dclass,
last_node.ttl, current_node.name,
last_node.getTypes());
// Note: we have to add this through the iterator, otherwise
// the next access via the iterator will generate a
// ConcurrencyModificationException.
backup = i.nextIndex() - last_node.nsecIndex;
for (int j = 0; j < backup; j++) i.previous();
i.add(nsec);
for (int j = 0; j < backup; j++) i.next();
log.trace("Generated: " + nsec);
}
if (current_node.isSecureNode)
{
last_node = current_node;
}
else if (last_node != null)
{
// last_node does not change -- last_node is essentially the
// last *secure* node, and current_node is not secure.
// However, we need to note the passing of the insecure node.
last_node.hasOptInSpan = true;
}
current_node.nsecIndex = i.previousIndex();
current_node = new NodeInfo(r);
current_node.addType(Type.RRSIG);
}
// Generate next to last NSEC
if (last_node != null && current_node.isSecureNode)
{
// generate a NSEC record.
if (beConservative && !last_node.hasOptInSpan)
{
last_node.addType(Type.NSEC);
}
NSECRecord nsec = new NSECRecord(last_node.name, last_node.dclass,
last_node.ttl, current_node.name,
last_node.getTypes());
records.add(last_node.nsecIndex - 1, nsec);
log.trace("Generated: " + nsec);
}
// Generate last NSEC
NSECRecord nsec;
if (current_node.isSecureNode)
{
if (beConservative)
{
current_node.addType(Type.NSEC);
}
nsec = new NSECRecord(current_node.name, current_node.dclass,
current_node.ttl, zonename,
current_node.getTypes());
// we can just tack this on the end as we are working on the
// last node.
records.add(nsec);
}
else
{
nsec = new NSECRecord(last_node.name, last_node.dclass, last_node.ttl,
zonename, last_node.getTypes());
// We need to tack this on after the last secure node, not the
// end of the whole list.
records.add(last_node.nsecIndex, nsec);
}
log.trace("Generated: " + nsec);
}
/** Given a zone with DNSKEY records at delegation points, convert
* those KEY records into their corresponding DS records in place.
*
* @param zonename the name of the zone, used to reliably
* distinguish the zone apex from other records.
* @param records a list of {@link org.xbill.DNS.Record} objects.
*/
public static void generateDSRecords(Name zonename, List records)
throws IOException
{
for (ListIterator i = records.listIterator(); i.hasNext(); )
{
Record r = (Record) i.next();
if (r == null) continue; // this should never be true.
Name r_name = r.getName();
if (r_name == null) continue; // this should never be true.
// Convert non-zone level KEY records into DS records.
if (r.getType() == Type.DNSKEY && !r_name.equals(zonename))
{
DSRecord ds = calculateDSRecord((DNSKEYRecord) r, r.getTTL());
i.set(ds);
}
}
}
/** Given a zone, remove all records that are generated.
*
* @param zonename the name of the zone.
* @param records a list of {@link org.xbill.DNS.Record} objects.
*/
public static void removeGeneratedRecords(Name zonename, List records)
{
for (Iterator i = records.iterator(); i.hasNext(); )
{
Record r = (Record) i.next();
if (r.getType() == Type.RRSIG ||
r.getType() == Type.NSEC)
{
i.remove();
}
}
}
/** Remove duplicate records from a list of records. This routine
* presumes the list of records is in a canonical sorted order, at
* least on name and RR type.
*
* @param records a list of {@link org.xbill.DNS.Record} object, in
* sorted order.
*/
public static void removeDuplicateRecords(List records)
{
Record lastrec = null;
for (Iterator i = records.iterator(); i.hasNext(); )
{
Record r = (Record) i.next();
if (lastrec == null)
{
lastrec = r;
continue;
}
if (lastrec.equals(r))
{
i.remove();
continue;
}
lastrec = r;
}
}
/** Given a DNSKEY record, generate the DS record from it.
*
* @param keyrec the KEY record in question.
* @param ttl the desired TTL for the generated DS record. If
* zero, or negative, the original KEY RR's TTL will be used.
* @return the corresponding {@link org.xbill.DNS.DSRecord}
*/
public static DSRecord calculateDSRecord(DNSKEYRecord keyrec, long ttl)
throws IOException
{
if (keyrec == null) return null;
if (ttl <= 0) ttl = keyrec.getTTL();
DNSOutput os = new DNSOutput();
os.writeByteArray(keyrec.getName().toWireCanonical());
os.writeByteArray(keyrec.rdataToWireCanonical());
try
{
MessageDigest md = MessageDigest.getInstance("SHA");
byte[] digest = md.digest(os.toByteArray());
return new DSRecord(keyrec.getName(), keyrec.getDClass(), ttl,
keyrec.getFootprint(), keyrec.getAlgorithm(),
DSRecord.SHA1_DIGEST_ID, digest);
}
catch (NoSuchAlgorithmException e)
{
log.error("", e);
return null;
}
}
}

View File

@@ -0,0 +1,182 @@
// $Id: TypeMap.java,v 1.5 2004/03/23 17:53:57 davidb Exp $
//
// Copyright (C) 2004 Verisign, Inc.
package com.verisignlabs.dnssec.security;
import java.util.*;
import org.xbill.DNS.Type;
import org.xbill.DNS.DNSOutput;
/** This class represents the multiple type maps of the NSEC
* record. Currently it is just used to convert the wire format type
* map to the int array that org.xbill.DNS.NSECRecord uses. */
public class TypeMap
{
private static final Integer[] integerArray = new Integer[0];
private Set typeSet;
public TypeMap()
{
this.typeSet = new HashSet();
}
/** Add the given type to the typemap. */
public void set(int type)
{
typeSet.add(new Integer(type));
}
/** Remove the given type from the type map. */
public void clear(int type)
{
typeSet.remove(new Integer(type));
}
/** @return true if the given type is present in the type map. */
public boolean get(int type)
{
return typeSet.contains(new Integer(type));
}
public static TypeMap fromTypes(int[] types)
{
TypeMap m = new TypeMap();
for (int i = 0; i < types.length; i++)
{
m.set(types[i]);
}
return m;
}
/** Given an array of bytes representing a wire-format type map,
* construct the TypeMap object. */
public static TypeMap fromBytes(byte[] map)
{
int m = 0;
TypeMap typemap = new TypeMap();
int map_number;
int byte_length;
while (m < map.length)
{
map_number = map[m++];
byte_length = map[m++];
for (int i = 0; i < byte_length; i++)
{
for (int j = 0; j < 8; j++)
{
if ( (map[m + i] & (1 << (7 - j))) != 0 )
{
typemap.set(map_number * 8 + j);
}
}
}
m += byte_length;
}
return typemap;
}
/** @return the normal string representation of the typemap. */
public String toString()
{
int[] types = getTypes();
Arrays.sort(types);
StringBuffer sb = new StringBuffer();
for (int i = 0; i < types.length; i++)
{
sb.append(" ");
sb.append(Type.string(types[i]));
}
return sb.toString();
}
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
// typecode in this section.
int max_type = types[end - 1] & 0xFF;
int map_length = (max_type / 8) + 1;
// write the map "header" -- the base and the length of the map.
out.writeU8(base & 0xFF);
out.writeU8(map_length & 0xFF);
// allocate a temporary scratch space for caculating the actual
// bitmap.
byte[] map = new byte[map_length];
// for each type in our sub-array, set its corresponding bit in the map.
for (int i = start; i < end; i++)
{
map[ (types[i] & 0xFF) / 8 ] |= ( 1 << (7 - types[i] % 8) );
}
// write out the resulting binary bitmap.
for (int i = 0; i < map.length; i++)
{
out.writeU8(map[i]);
}
}
public byte[] toWire()
{
int[] types = getTypes();
Arrays.sort(types);
int mapbase = -1;
int mapstart = -1;
DNSOutput out = new DNSOutput();
for (int i = 0; i < types.length; i++)
{
int base = types[i] >> 8;
if (base == mapbase) continue;
if (mapstart >= 0)
{
mapToWire(out, types, mapbase, mapstart, i);
}
mapbase = base;
mapstart = i;
}
mapToWire(out, types, mapbase, mapstart, types.length);
return out.toByteArray();
}
public int[] getTypes()
{
Integer[] a = (Integer[]) typeSet.toArray(integerArray);
int[] res = new int[a.length];
for (int i = 0; i < res.length; i++) {
res[i] = a[i].intValue();
}
return res;
}
public static int[] fromWireToTypes(byte[] wire_fmt)
{
return TypeMap.fromBytes(wire_fmt).getTypes();
}
public static byte[] fromTypesToWire(int[] types)
{
return TypeMap.fromTypes(types).toWire();
}
}

View File

@@ -0,0 +1,138 @@
// $Id: ZoneUtils.java,v 1.3 2004/01/15 17:32:18 davidb Exp $
//
// Copyright (C) 2003 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;
import java.util.*;
import java.io.*;
import org.xbill.DNS.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/** This class contains a bunch of utility methods that are generally
* useful in manipulating zones.
*
* @author David Blacka (original)
* @author $Author: davidb $
* @version $Revision: 1.3 $
*/
public class ZoneUtils
{
private static Log log;
static {
log = LogFactory.getLog(ZoneUtils.class);
}
/** Load a zone file.
*
* @param zonefile the filename/path of the zonefile to read.
* @param origin the origin to use for the zonefile (may be null if
* the origin is specified in the zone file itself).
* @return a {@link java.util.List} of {@link org.xbill.DNS.Record}
* objects.
* @throws IOException if something goes wrong reading the zone
* file.
*/
public static List readZoneFile(String zonefile, Name origin)
throws IOException
{
ArrayList records = new ArrayList();
Master m = new Master(zonefile, origin);
Record r = null;
while ( (r = m.nextRecord()) != null )
{
records.add(r);
}
return records;
}
/** Write the records out into a zone file.
*
* @param records a {@link java.util.List} of {@link
* org.xbill.DNS.Record} objects forming a zone.
* @param zonefile the file to write to. If null or equal to "-",
* System.out is used.
*/
public static void writeZoneFile(List records, String zonefile)
throws IOException
{
PrintWriter out = null;
if (zonefile == null || zonefile.equals("-"))
{
out = new PrintWriter(System.out);
}
else
{
out = new PrintWriter(new BufferedWriter(new FileWriter(zonefile)));
}
for (Iterator i = records.iterator(); i.hasNext(); )
{
out.println(i.next());
}
out.close();
}
/** Given just the list of records, determine the zone name
* (origin).
*
* @param records a list of {@link org.xbill.DNS.Record} or {@link
* org.xbill.DNS.RRset} objects.
* @return the zone name, if found. null if one couldn't be found.q
*/
public static Name findZoneName(List records)
{
for (Iterator i = records.iterator(); i.hasNext(); )
{
int type = 0;
Name n = null;
Object o = i.next();
if (o instanceof Record)
{
Record r = (Record) o;
type = r.getType();
n = r.getName();
}
else if (o instanceof RRset)
{
RRset r = (RRset) o;
type = r.getType();
n = r.getName();
}
if (type == Type.SOA) return n;
}
return null;
}
}

View File

@@ -0,0 +1,35 @@
<BODY BGCOLOR="#FFFFFF">
This package contains a number of utility classes that can be used to
implement DNSSEC tool (key generation, zone signing, etc.)
functionality.
<p>
<center>
<table width="90%" border=1 cellpadding=10 cellspacing=0>
<tr><td class="TableRowColor">
Copyright &copy; 2003 Verisign, Inc. by
<a href="mailto:davidb@verisignlabs.com">David Blacka</a><P>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.<P>
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
Library General Public License for more details.<P>
You should have received a copy of the
<a href="http://www.gnu.org/copyleft/lgpl.html">GNU Library General Public License</a>
along with this library; if not, write to the
Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.
</td></tr>
</table><P>
</center>
</BODY>