// Copyright (c) 1999-2010 Brian Wellington (bwelling@xbill.org)
package org.xbill.DNS;
import java.io.*;
import java.math.*;
import java.security.*;
import java.security.interfaces.*;
import java.security.spec.*;
import java.util.*;
/**
* Constants and methods relating to DNSSEC.
*
* DNSSEC provides authentication for DNS information.
* @see RRSIGRecord
* @see DNSKEYRecord
* @see RRset
*
* @author Brian Wellington
*/
public class DNSSEC {
public static class Algorithm {
private Algorithm() {}
/** RSA/MD5 public key (deprecated) */
public static final int RSAMD5 = 1;
/** Diffie Hellman key */
public static final int DH = 2;
/** DSA public key */
public static final int DSA = 3;
/** RSA/SHA1 public key */
public static final int RSASHA1 = 5;
/** DSA/SHA1, NSEC3-aware public key */
public static final int DSA_NSEC3_SHA1 = 6;
/** RSA/SHA1, NSEC3-aware public key */
public static final int RSA_NSEC3_SHA1 = 7;
/** RSA/SHA256 public key */
public static final int RSASHA256 = 8;
/** RSA/SHA512 public key */
public static final int RSASHA512 = 10;
/** GOST R 34.10-2001.
* This requires an external cryptography provider,
* such as BouncyCastle.
*/
public static final int ECC_GOST = 12;
/** ECDSA Curve P-256 with SHA-256 public key **/
public static final int ECDSAP256SHA256 = 13;
/** ECDSA Curve P-384 with SHA-384 public key **/
public static final int ECDSAP384SHA384 = 14;
/** Indirect keys; the actual key is elsewhere. */
public static final int INDIRECT = 252;
/** Private algorithm, specified by domain name */
public static final int PRIVATEDNS = 253;
/** Private algorithm, specified by OID */
public static final int PRIVATEOID = 254;
private static Mnemonic algs = new Mnemonic("DNSSEC algorithm",
Mnemonic.CASE_UPPER);
static {
algs.setMaximum(0xFF);
algs.setNumericAllowed(true);
algs.add(RSAMD5, "RSAMD5");
algs.add(DH, "DH");
algs.add(DSA, "DSA");
algs.add(RSASHA1, "RSASHA1");
algs.add(DSA_NSEC3_SHA1, "DSA-NSEC3-SHA1");
algs.add(RSA_NSEC3_SHA1, "RSA-NSEC3-SHA1");
algs.add(RSASHA256, "RSASHA256");
algs.add(RSASHA512, "RSASHA512");
algs.add(ECC_GOST, "ECC-GOST");
algs.add(ECDSAP256SHA256, "ECDSAP256SHA256");
algs.add(ECDSAP384SHA384, "ECDSAP384SHA384");
algs.add(INDIRECT, "INDIRECT");
algs.add(PRIVATEDNS, "PRIVATEDNS");
algs.add(PRIVATEOID, "PRIVATEOID");
}
/**
* Converts an algorithm into its textual representation
*/
public static String
string(int alg) {
return algs.getText(alg);
}
/**
* Converts a textual representation of an algorithm into its numeric
* code. Integers in the range 0..255 are also accepted.
* @param s The textual representation of the algorithm
* @return The algorithm code, or -1 on error.
*/
public static int
value(String s) {
return algs.getValue(s);
}
}
private
DNSSEC() { }
private static void
digestSIG(DNSOutput out, SIGBase sig) {
out.writeU16(sig.getTypeCovered());
out.writeU8(sig.getAlgorithm());
out.writeU8(sig.getLabels());
out.writeU32(sig.getOrigTTL());
out.writeU32(sig.getExpire().getTime() / 1000);
out.writeU32(sig.getTimeSigned().getTime() / 1000);
out.writeU16(sig.getFootprint());
sig.getSigner().toWireCanonical(out);
}
/**
* Creates a byte array containing the concatenation of the fields of the
* SIG record and the RRsets to be signed/verified. This does not perform
* a cryptographic digest.
* @param rrsig The RRSIG record used to sign/verify the rrset.
* @param rrset The data to be signed/verified.
* @return The data to be cryptographically signed or verified.
*/
public static byte []
digestRRset(RRSIGRecord rrsig, RRset rrset) {
DNSOutput out = new DNSOutput();
digestSIG(out, rrsig);
int size = rrset.size();
Record [] records = new Record[size];
Iterator it = rrset.rrs();
Name name = rrset.getName();
Name wild = null;
int sigLabels = rrsig.getLabels() + 1; // Add the root label back.
if (name.labels() > sigLabels)
wild = name.wild(name.labels() - sigLabels);
while (it.hasNext())
records[--size] = (Record) it.next();
Arrays.sort(records);
DNSOutput header = new DNSOutput();
if (wild != null)
wild.toWireCanonical(header);
else
name.toWireCanonical(header);
header.writeU16(rrset.getType());
header.writeU16(rrset.getDClass());
header.writeU32(rrsig.getOrigTTL());
for (int i = 0; i < records.length; i++) {
out.writeByteArray(header.toByteArray());
int lengthPosition = out.current();
out.writeU16(0);
out.writeByteArray(records[i].rdataToWireCanonical());
int rrlength = out.current() - lengthPosition - 2;
out.save();
out.jump(lengthPosition);
out.writeU16(rrlength);
out.restore();
}
return out.toByteArray();
}
/**
* Creates a byte array containing the concatenation of the fields of the
* SIG(0) record and the message to be signed. This does not perform
* a cryptographic digest.
* @param sig The SIG record used to sign the rrset.
* @param msg The message to be signed.
* @param previous If this is a response, the signature from the query.
* @return The data to be cryptographically signed.
*/
public static byte []
digestMessage(SIGRecord sig, Message msg, byte [] previous) {
DNSOutput out = new DNSOutput();
digestSIG(out, sig);
if (previous != null)
out.writeByteArray(previous);
msg.toWire(out);
return out.toByteArray();
}
/**
* A DNSSEC exception.
*/
public static class DNSSECException extends Exception {
DNSSECException(String s) {
super(s);
}
}
/**
* An algorithm is unsupported by this DNSSEC implementation.
*/
public static class UnsupportedAlgorithmException extends DNSSECException {
UnsupportedAlgorithmException(int alg) {
super("Unsupported algorithm: " + alg);
}
}
/**
* The cryptographic data in a DNSSEC key is malformed.
*/
public static class MalformedKeyException extends DNSSECException {
MalformedKeyException(KEYBase rec) {
super("Invalid key data: " + rec.rdataToString());
}
}
/**
* A DNSSEC verification failed because fields in the DNSKEY and RRSIG records
* do not match.
*/
public static class KeyMismatchException extends DNSSECException {
private KEYBase key;
private SIGBase sig;
KeyMismatchException(KEYBase key, SIGBase sig) {
super("key " +
key.getName() + "/" +
Algorithm.string(key.getAlgorithm()) + "/" +
key.getFootprint() + " " +
"does not match signature " +
sig.getSigner() + "/" +
Algorithm.string(sig.getAlgorithm()) + "/" +
sig.getFootprint());
}
}
/**
* A DNSSEC verification failed because the signature has expired.
*/
public static class SignatureExpiredException extends DNSSECException {
private Date when, now;
SignatureExpiredException(Date when, Date now) {
super("signature expired");
this.when = when;
this.now = now;
}
/**
* @return When the signature expired
*/
public Date
getExpiration() {
return when;
}
/**
* @return When the verification was attempted
*/
public Date
getVerifyTime() {
return now;
}
}
/**
* A DNSSEC verification failed because the signature has not yet become valid.
*/
public static class SignatureNotYetValidException extends DNSSECException {
private Date when, now;
SignatureNotYetValidException(Date when, Date now) {
super("signature is not yet valid");
this.when = when;
this.now = now;
}
/**
* @return When the signature will become valid
*/
public Date
getExpiration() {
return when;
}
/**
* @return When the verification was attempted
*/
public Date
getVerifyTime() {
return now;
}
}
/**
* A DNSSEC verification failed because the cryptographic signature
* verification failed.
*/
public static class SignatureVerificationException extends DNSSECException {
SignatureVerificationException() {
super("signature verification failed");
}
}
/**
* The key data provided is inconsistent.
*/
public static class IncompatibleKeyException extends IllegalArgumentException {
IncompatibleKeyException() {