430 lines
13 KiB
Java
430 lines
13 KiB
Java
// $Id$
|
|
//
|
|
// 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.BufferedReader;
|
|
import java.io.File;
|
|
import java.io.FileReader;
|
|
import java.io.FileWriter;
|
|
import java.io.IOException;
|
|
import java.io.PrintWriter;
|
|
import java.security.NoSuchAlgorithmException;
|
|
import java.security.PrivateKey;
|
|
import java.security.PublicKey;
|
|
import java.text.NumberFormat;
|
|
|
|
import org.xbill.DNS.DNSKEYRecord;
|
|
import org.xbill.DNS.Master;
|
|
import org.xbill.DNS.Name;
|
|
import org.xbill.DNS.RRset;
|
|
import org.xbill.DNS.Record;
|
|
import org.xbill.DNS.Type;
|
|
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$
|
|
* @version $Revision$
|
|
*/
|
|
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 DNSKEYRecord 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');
|
|
}
|
|
in.close();
|
|
|
|
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)
|
|
{
|
|
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(" ");
|
|
buf.append(rec.getTTL());
|
|
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);
|
|
}
|
|
|
|
}
|