aboutsummaryrefslogtreecommitdiff
path: root/src/org/xbill/DNS/TSIG.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/org/xbill/DNS/TSIG.java')
-rw-r--r--src/org/xbill/DNS/TSIG.java592
1 files changed, 592 insertions, 0 deletions
diff --git a/src/org/xbill/DNS/TSIG.java b/src/org/xbill/DNS/TSIG.java
new file mode 100644
index 0000000..d9e6972
--- /dev/null
+++ b/src/org/xbill/DNS/TSIG.java
@@ -0,0 +1,592 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.util.*;
+import org.xbill.DNS.utils.*;
+
+/**
+ * Transaction signature handling. This class generates and verifies
+ * TSIG records on messages, which provide transaction security.
+ * @see TSIGRecord
+ *
+ * @author Brian Wellington
+ */
+
+public class TSIG {
+
+private static final String HMAC_MD5_STR = "HMAC-MD5.SIG-ALG.REG.INT.";
+private static final String HMAC_SHA1_STR = "hmac-sha1.";
+private static final String HMAC_SHA224_STR = "hmac-sha224.";
+private static final String HMAC_SHA256_STR = "hmac-sha256.";
+private static final String HMAC_SHA384_STR = "hmac-sha384.";
+private static final String HMAC_SHA512_STR = "hmac-sha512.";
+
+/** The domain name representing the HMAC-MD5 algorithm. */
+public static final Name HMAC_MD5 = Name.fromConstantString(HMAC_MD5_STR);
+
+/** The domain name representing the HMAC-MD5 algorithm (deprecated). */
+public static final Name HMAC = HMAC_MD5;
+
+/** The domain name representing the HMAC-SHA1 algorithm. */
+public static final Name HMAC_SHA1 = Name.fromConstantString(HMAC_SHA1_STR);
+
+/**
+ * The domain name representing the HMAC-SHA224 algorithm.
+ * Note that SHA224 is not supported by Java out-of-the-box, this requires use
+ * of a third party provider like BouncyCastle.org.
+ */
+public static final Name HMAC_SHA224 = Name.fromConstantString(HMAC_SHA224_STR);
+
+/** The domain name representing the HMAC-SHA256 algorithm. */
+public static final Name HMAC_SHA256 = Name.fromConstantString(HMAC_SHA256_STR);
+
+/** The domain name representing the HMAC-SHA384 algorithm. */
+public static final Name HMAC_SHA384 = Name.fromConstantString(HMAC_SHA384_STR);
+
+/** The domain name representing the HMAC-SHA512 algorithm. */
+public static final Name HMAC_SHA512 = Name.fromConstantString(HMAC_SHA512_STR);
+
+/**
+ * The default fudge value for outgoing packets. Can be overriden by the
+ * tsigfudge option.
+ */
+public static final short FUDGE = 300;
+
+private Name name, alg;
+private String digest;
+private int digestBlockLength;
+private byte [] key;
+
+private void
+getDigest() {
+ if (alg.equals(HMAC_MD5)) {
+ digest = "md5";
+ digestBlockLength = 64;
+ } else if (alg.equals(HMAC_SHA1)) {
+ digest = "sha-1";
+ digestBlockLength = 64;
+ } else if (alg.equals(HMAC_SHA224)) {
+ digest = "sha-224";
+ digestBlockLength = 64;
+ } else if (alg.equals(HMAC_SHA256)) {
+ digest = "sha-256";
+ digestBlockLength = 64;
+ } else if (alg.equals(HMAC_SHA512)) {
+ digest = "sha-512";
+ digestBlockLength = 128;
+ } else if (alg.equals(HMAC_SHA384)) {
+ digest = "sha-384";
+ digestBlockLength = 128;
+ } else
+ throw new IllegalArgumentException("Invalid algorithm");
+}
+
+/**
+ * Creates a new TSIG key, which can be used to sign or verify a message.
+ * @param algorithm The algorithm of the shared key.
+ * @param name The name of the shared key.
+ * @param key The shared key's data.
+ */
+public
+TSIG(Name algorithm, Name name, byte [] key) {
+ this.name = name;
+ this.alg = algorithm;
+ this.key = key;
+ getDigest();
+}
+
+/**
+ * Creates a new TSIG key with the hmac-md5 algorithm, which can be used to
+ * sign or verify a message.
+ * @param name The name of the shared key.
+ * @param key The shared key's data.
+ */
+public
+TSIG(Name name, byte [] key) {
+ this(HMAC_MD5, name, key);
+}
+
+/**
+ * Creates a new TSIG object, which can be used to sign or verify a message.
+ * @param name The name of the shared key.
+ * @param key The shared key's data represented as a base64 encoded string.
+ * @throws IllegalArgumentException The key name is an invalid name
+ * @throws IllegalArgumentException The key data is improperly encoded
+ */
+public
+TSIG(Name algorithm, String name, String key) {
+ this.key = base64.fromString(key);
+ if (this.key == null)
+ throw new IllegalArgumentException("Invalid TSIG key string");
+ try {
+ this.name = Name.fromString(name, Name.root);
+ }
+ catch (TextParseException e) {
+ throw new IllegalArgumentException("Invalid TSIG key name");
+ }
+ this.alg = algorithm;
+ getDigest();
+}
+
+/**
+ * Creates a new TSIG object, which can be used to sign or verify a message.
+ * @param name The name of the shared key.
+ * @param algorithm The algorithm of the shared key. The legal values are
+ * "hmac-md5", "hmac-sha1", "hmac-sha224", "hmac-sha256", "hmac-sha384", and
+ * "hmac-sha512".
+ * @param key The shared key's data represented as a base64 encoded string.
+ * @throws IllegalArgumentException The key name is an invalid name
+ * @throws IllegalArgumentException The key data is improperly encoded
+ */
+public
+TSIG(String algorithm, String name, String key) {
+ this(HMAC_MD5, name, key);
+ if (algorithm.equalsIgnoreCase("hmac-md5"))
+ this.alg = HMAC_MD5;
+ else if (algorithm.equalsIgnoreCase("hmac-sha1"))
+ this.alg = HMAC_SHA1;
+ else if (algorithm.equalsIgnoreCase("hmac-sha224"))
+ this.alg = HMAC_SHA224;
+ else if (algorithm.equalsIgnoreCase("hmac-sha256"))
+ this.alg = HMAC_SHA256;
+ else if (algorithm.equalsIgnoreCase("hmac-sha384"))
+ this.alg = HMAC_SHA384;
+ else if (algorithm.equalsIgnoreCase("hmac-sha512"))
+ this.alg = HMAC_SHA512;
+ else
+ throw new IllegalArgumentException("Invalid TSIG algorithm");
+ getDigest();
+}
+
+/**
+ * Creates a new TSIG object with the hmac-md5 algorithm, which can be used to
+ * sign or verify a message.
+ * @param name The name of the shared key
+ * @param key The shared key's data, represented as a base64 encoded string.
+ * @throws IllegalArgumentException The key name is an invalid name
+ * @throws IllegalArgumentException The key data is improperly encoded
+ */
+public
+TSIG(String name, String key) {
+ this(HMAC_MD5, name, key);
+}
+
+/**
+ * Creates a new TSIG object, which can be used to sign or verify a message.
+ * @param str The TSIG key, in the form name:secret, name/secret,
+ * alg:name:secret, or alg/name/secret. If an algorithm is specified, it must
+ * be "hmac-md5", "hmac-sha1", or "hmac-sha256".
+ * @throws IllegalArgumentException The string does not contain both a name
+ * and secret.
+ * @throws IllegalArgumentException The key name is an invalid name
+ * @throws IllegalArgumentException The key data is improperly encoded
+ */
+static public TSIG
+fromString(String str) {
+ String [] parts = str.split("[:/]", 3);
+ if (parts.length < 2)
+ throw new IllegalArgumentException("Invalid TSIG key " +
+ "specification");
+ if (parts.length == 3) {
+ try {
+ return new TSIG(parts[0], parts[1], parts[2]);
+ } catch (IllegalArgumentException e) {
+ parts = str.split("[:/]", 2);
+ }
+ }
+ return new TSIG(HMAC_MD5, parts[0], parts[1]);
+}
+
+/**
+ * Generates a TSIG record with a specific error for a message that has
+ * been rendered.
+ * @param m The message
+ * @param b The rendered message
+ * @param error The error
+ * @param old If this message is a response, the TSIG from the request
+ * @return The TSIG record to be added to the message
+ */
+public TSIGRecord
+generate(Message m, byte [] b, int error, TSIGRecord old) {
+ Date timeSigned;
+ if (error != Rcode.BADTIME)
+ timeSigned = new Date();
+ else
+ timeSigned = old.getTimeSigned();
+ int fudge;
+ HMAC hmac = null;
+ if (error == Rcode.NOERROR || error == Rcode.BADTIME)
+ hmac = new HMAC(digest, digestBlockLength, key);
+
+ fudge = Options.intValue("tsigfudge");
+ if (fudge < 0 || fudge > 0x7FFF)
+ fudge = FUDGE;
+
+ if (old != null) {
+ DNSOutput out = new DNSOutput();
+ out.writeU16(old.getSignature().length);
+ if (hmac != null) {
+ hmac.update(out.toByteArray());
+ hmac.update(old.getSignature());
+ }
+ }
+
+ /* Digest the message */
+ if (hmac != null)
+ hmac.update(b);
+
+ DNSOutput out = new DNSOutput();
+ name.toWireCanonical(out);
+ out.writeU16(DClass.ANY); /* class */
+ out.writeU32(0); /* ttl */
+ alg.toWireCanonical(out);
+ long time = timeSigned.getTime() / 1000;
+ int timeHigh = (int) (time >> 32);
+ long timeLow = (time & 0xFFFFFFFFL);
+ out.writeU16(timeHigh);
+ out.writeU32(timeLow);
+ out.writeU16(fudge);
+
+ out.writeU16(error);
+ out.writeU16(0); /* No other data */
+
+ if (hmac != null)
+ hmac.update(out.toByteArray());
+
+ byte [] signature;
+ if (hmac != null)
+ signature = hmac.sign();
+ else
+ signature = new byte[0];
+
+ byte [] other = null;
+ if (error == Rcode.BADTIME) {
+ out = new DNSOutput();
+ time = new Date().getTime() / 1000;
+ timeHigh = (int) (time >> 32);
+ timeLow = (time & 0xFFFFFFFFL);
+ out.writeU16(timeHigh);
+ out.writeU32(timeLow);
+ other = out.toByteArray();
+ }
+
+ return (new TSIGRecord(name, DClass.ANY, 0, alg, timeSigned, fudge,
+ signature, m.getHeader().getID(), error, other));
+}
+
+/**
+ * Generates a TSIG record with a specific error for a message and adds it
+ * to the message.
+ * @param m The message
+ * @param error The error
+ * @param old If this message is a response, the TSIG from the request
+ */
+public void
+apply(Message m, int error, TSIGRecord old) {
+ Record r = generate(m, m.toWire(), error, old);
+ m.addRecord(r, Section.ADDITIONAL);
+ m.tsigState = Message.TSIG_SIGNED;
+}
+
+/**
+ * Generates a TSIG record for a message and adds it to the message
+ * @param m The message
+ * @param old If this message is a response, the TSIG from the request
+ */
+public void
+apply(Message m, TSIGRecord old) {
+ apply(m, Rcode.NOERROR, old);
+}
+
+/**
+ * Generates a TSIG record for a message and adds it to the message
+ * @param m The message
+ * @param old If this message is a response, the TSIG from the request
+ */
+public void
+applyStream(Message m, TSIGRecord old, boolean first) {
+ if (first) {
+ apply(m, old);
+ return;
+ }
+ Date timeSigned = new Date();
+ int fudge;
+ HMAC hmac = new HMAC(digest, digestBlockLength, key);
+
+ fudge = Options.intValue("tsigfudge");
+ if (fudge < 0 || fudge > 0x7FFF)
+ fudge = FUDGE;
+
+ DNSOutput out = new DNSOutput();
+ out.writeU16(old.getSignature().length);
+ hmac.update(out.toByteArray());
+ hmac.update(old.getSignature());
+
+ /* Digest the message */
+ hmac.update(m.toWire());
+
+ out = new DNSOutput();
+ long time = timeSigned.getTime() / 1000;
+ int timeHigh = (int) (time >> 32);
+ long timeLow = (time & 0xFFFFFFFFL);
+ out.writeU16(timeHigh);
+ out.writeU32(timeLow);
+ out.writeU16(fudge);
+
+ hmac.update(out.toByteArray());
+
+ byte [] signature = hmac.sign();
+ byte [] other = null;
+
+ Record r = new TSIGRecord(name, DClass.ANY, 0, alg, timeSigned, fudge,
+ signature, m.getHeader().getID(),
+ Rcode.NOERROR, other);
+ m.addRecord(r, Section.ADDITIONAL);
+ m.tsigState = Message.TSIG_SIGNED;
+}
+
+/**
+ * Verifies a TSIG record on an incoming message. Since this is only called
+ * in the context where a TSIG is expected to be present, it is an error
+ * if one is not present. After calling this routine, Message.isVerified() may
+ * be called on this message.
+ * @param m The message
+ * @param b An array containing the message in unparsed form. This is
+ * necessary since TSIG signs the message in wire format, and we can't
+ * recreate the exact wire format (with the same name compression).
+ * @param length The length of the message in the array.
+ * @param old If this message is a response, the TSIG from the request
+ * @return The result of the verification (as an Rcode)
+ * @see Rcode
+ */
+public byte
+verify(Message m, byte [] b, int length, TSIGRecord old) {
+ m.tsigState = Message.TSIG_FAILED;
+ TSIGRecord tsig = m.getTSIG();
+ HMAC hmac = new HMAC(digest, digestBlockLength, key);
+ if (tsig == null)
+ return Rcode.FORMERR;
+
+ if (!tsig.getName().equals(name) || !tsig.getAlgorithm().equals(alg)) {
+ if (Options.check("verbose"))
+ System.err.println("BADKEY failure");
+ return Rcode.BADKEY;
+ }
+ long now = System.currentTimeMillis();
+ long then = tsig.getTimeSigned().getTime();
+ long fudge = tsig.getFudge();
+ if (Math.abs(now - then) > fudge * 1000) {
+ if (Options.check("verbose"))
+ System.err.println("BADTIME failure");
+ return Rcode.BADTIME;
+ }
+
+ if (old != null && tsig.getError() != Rcode.BADKEY &&
+ tsig.getError() != Rcode.BADSIG)
+ {
+ DNSOutput out = new DNSOutput();
+ out.writeU16(old.getSignature().length);
+ hmac.update(out.toByteArray());
+ hmac.update(old.getSignature());
+ }
+ m.getHeader().decCount(Section.ADDITIONAL);
+ byte [] header = m.getHeader().toWire();
+ m.getHeader().incCount(Section.ADDITIONAL);
+ hmac.update(header);
+
+ int len = m.tsigstart - header.length;
+ hmac.update(b, header.length, len);
+
+ DNSOutput out = new DNSOutput();
+ tsig.getName().toWireCanonical(out);
+ out.writeU16(tsig.dclass);
+ out.writeU32(tsig.ttl);
+ tsig.getAlgorithm().toWireCanonical(out);
+ long time = tsig.getTimeSigned().getTime() / 1000;
+ int timeHigh = (int) (time >> 32);
+ long timeLow = (time & 0xFFFFFFFFL);
+ out.writeU16(timeHigh);
+ out.writeU32(timeLow);
+ out.writeU16(tsig.getFudge());
+ out.writeU16(tsig.getError());
+ if (tsig.getOther() != null) {
+ out.writeU16(tsig.getOther().length);
+ out.writeByteArray(tsig.getOther());
+ } else {
+ out.writeU16(0);
+ }
+
+ hmac.update(out.toByteArray());
+
+ byte [] signature = tsig.getSignature();
+ int digestLength = hmac.digestLength();
+ int minDigestLength = digest.equals("md5") ? 10 : digestLength / 2;
+
+ if (signature.length > digestLength) {
+ if (Options.check("verbose"))
+ System.err.println("BADSIG: signature too long");
+ return Rcode.BADSIG;
+ } else if (signature.length < minDigestLength) {
+ if (Options.check("verbose"))
+ System.err.println("BADSIG: signature too short");
+ return Rcode.BADSIG;
+ } else if (!hmac.verify(signature, true)) {
+ if (Options.check("verbose"))
+ System.err.println("BADSIG: signature verification");
+ return Rcode.BADSIG;
+ }
+
+ m.tsigState = Message.TSIG_VERIFIED;
+ return Rcode.NOERROR;
+}
+
+/**
+ * Verifies a TSIG record on an incoming message. Since this is only called
+ * in the context where a TSIG is expected to be present, it is an error
+ * if one is not present. After calling this routine, Message.isVerified() may
+ * be called on this message.
+ * @param m The message
+ * @param b The message in unparsed form. This is necessary since TSIG
+ * signs the message in wire format, and we can't recreate the exact wire
+ * format (with the same name compression).
+ * @param old If this message is a response, the TSIG from the request
+ * @return The result of the verification (as an Rcode)
+ * @see Rcode
+ */
+public int
+verify(Message m, byte [] b, TSIGRecord old) {
+ return verify(m, b, b.length, old);
+}
+
+/**
+ * Returns the maximum length of a TSIG record generated by this key.
+ * @see TSIGRecord
+ */
+public int
+recordLength() {
+ return (name.length() + 10 +
+ alg.length() +
+ 8 + // time signed, fudge
+ 18 + // 2 byte MAC length, 16 byte MAC
+ 4 + // original id, error
+ 8); // 2 byte error length, 6 byte max error field.
+}
+
+public static class StreamVerifier {
+ /**
+ * A helper class for verifying multiple message responses.
+ */
+
+ private TSIG key;
+ private HMAC verifier;
+ private int nresponses;
+ private int lastsigned;
+ private TSIGRecord lastTSIG;
+
+ /** Creates an object to verify a multiple message response */
+ public
+ StreamVerifier(TSIG tsig, TSIGRecord old) {
+ key = tsig;
+ verifier = new HMAC(key.digest, key.digestBlockLength, key.key);
+ nresponses = 0;
+ lastTSIG = old;
+ }
+
+ /**
+ * Verifies a TSIG record on an incoming message that is part of a
+ * multiple message response.
+ * TSIG records must be present on the first and last messages, and
+ * at least every 100 records in between.
+ * After calling this routine, Message.isVerified() may be called on
+ * this message.
+ * @param m The message
+ * @param b The message in unparsed form
+ * @return The result of the verification (as an Rcode)
+ * @see Rcode
+ */
+ public int
+ verify(Message m, byte [] b) {
+ TSIGRecord tsig = m.getTSIG();
+
+ nresponses++;
+
+ if (nresponses == 1) {
+ int result = key.verify(m, b, lastTSIG);
+ if (result == Rcode.NOERROR) {
+ byte [] signature = tsig.getSignature();
+ DNSOutput out = new DNSOutput();
+ out.writeU16(signature.length);
+ verifier.update(out.toByteArray());
+ verifier.update(signature);
+ }
+ lastTSIG = tsig;
+ return result;
+ }
+
+ if (tsig != null)
+ m.getHeader().decCount(Section.ADDITIONAL);
+ byte [] header = m.getHeader().toWire();
+ if (tsig != null)
+ m.getHeader().incCount(Section.ADDITIONAL);
+ verifier.update(header);
+
+ int len;
+ if (tsig == null)
+ len = b.length - header.length;
+ else
+ len = m.tsigstart - header.length;
+ verifier.update(b, header.length, len);
+
+ if (tsig != null) {
+ lastsigned = nresponses;
+ lastTSIG = tsig;
+ }
+ else {
+ boolean required = (nresponses - lastsigned >= 100);
+ if (required) {
+ m.tsigState = Message.TSIG_FAILED;
+ return Rcode.FORMERR;
+ } else {
+ m.tsigState = Message.TSIG_INTERMEDIATE;
+ return Rcode.NOERROR;
+ }
+ }
+
+ if (!tsig.getName().equals(key.name) ||
+ !tsig.getAlgorithm().equals(key.alg))
+ {
+ if (Options.check("verbose"))
+ System.err.println("BADKEY failure");
+ m.tsigState = Message.TSIG_FAILED;
+ return Rcode.BADKEY;
+ }
+
+ DNSOutput out = new DNSOutput();
+ long time = tsig.getTimeSigned().getTime() / 1000;
+ int timeHigh = (int) (time >> 32);
+ long timeLow = (time & 0xFFFFFFFFL);
+ out.writeU16(timeHigh);
+ out.writeU32(timeLow);
+ out.writeU16(tsig.getFudge());
+ verifier.update(out.toByteArray());
+
+ if (verifier.verify(tsig.getSignature()) == false) {
+ if (Options.check("verbose"))
+ System.err.println("BADSIG failure");
+ m.tsigState = Message.TSIG_FAILED;
+ return Rcode.BADSIG;
+ }
+
+ verifier.clear();
+ out = new DNSOutput();
+ out.writeU16(tsig.getSignature().length);
+ verifier.update(out.toByteArray());
+ verifier.update(tsig.getSignature());
+
+ m.tsigState = Message.TSIG_VERIFIED;
+ return Rcode.NOERROR;
+ }
+}
+
+}