jdnssec-tools/src/main/java/com/verisignlabs/dnssec/security/BINDKeyUtils.java

384 lines
12 KiB
Java

// Copyright (C) 2001-2003, 2022 VeriSign, Inc.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
// USA
package com.verisignlabs.dnssec.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 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
*/
public class BINDKeyUtils {
private BINDKeyUtils() { }
/**
* 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) {
keyid &= 0xFFFF;
return String.format("K%1$s+%2$03d+%3$05d", signer, algorithm, keyid);
}
/** 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 {
try (BufferedReader in = new BufferedReader(new FileReader(privateKeyFile))) {
StringBuilder keybuf = new StringBuilder();
String line;
while ((line = in.readLine()) != null) {
keybuf.append(line);
keybuf.append('\n');
}
return keybuf.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);
try (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|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();
return keyconv.generatePrivateKeyString(priv, pub, alg);
}
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) {
StringBuilder buf = new StringBuilder();
buf.append(rec.getName());
if (rec.getTTL() > 0) {
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);
}
}