Home | History | Annotate | Download | only in DNS
      1 // Copyright (c) 1999-2004 Brian Wellington (bwelling (at) xbill.org)
      2 
      3 package org.xbill.DNS;
      4 
      5 import java.util.*;
      6 import org.xbill.DNS.utils.*;
      7 
      8 /**
      9  * Transaction signature handling.  This class generates and verifies
     10  * TSIG records on messages, which provide transaction security.
     11  * @see TSIGRecord
     12  *
     13  * @author Brian Wellington
     14  */
     15 
     16 public class TSIG {
     17 
     18 private static final String HMAC_MD5_STR = "HMAC-MD5.SIG-ALG.REG.INT.";
     19 private static final String HMAC_SHA1_STR = "hmac-sha1.";
     20 private static final String HMAC_SHA224_STR = "hmac-sha224.";
     21 private static final String HMAC_SHA256_STR = "hmac-sha256.";
     22 private static final String HMAC_SHA384_STR = "hmac-sha384.";
     23 private static final String HMAC_SHA512_STR = "hmac-sha512.";
     24 
     25 /** The domain name representing the HMAC-MD5 algorithm. */
     26 public static final Name HMAC_MD5 = Name.fromConstantString(HMAC_MD5_STR);
     27 
     28 /** The domain name representing the HMAC-MD5 algorithm (deprecated). */
     29 public static final Name HMAC = HMAC_MD5;
     30 
     31 /** The domain name representing the HMAC-SHA1 algorithm. */
     32 public static final Name HMAC_SHA1 = Name.fromConstantString(HMAC_SHA1_STR);
     33 
     34 /**
     35  * The domain name representing the HMAC-SHA224 algorithm.
     36  * Note that SHA224 is not supported by Java out-of-the-box, this requires use
     37  * of a third party provider like BouncyCastle.org.
     38  */
     39 public static final Name HMAC_SHA224 = Name.fromConstantString(HMAC_SHA224_STR);
     40 
     41 /** The domain name representing the HMAC-SHA256 algorithm. */
     42 public static final Name HMAC_SHA256 = Name.fromConstantString(HMAC_SHA256_STR);
     43 
     44 /** The domain name representing the HMAC-SHA384 algorithm. */
     45 public static final Name HMAC_SHA384 = Name.fromConstantString(HMAC_SHA384_STR);
     46 
     47 /** The domain name representing the HMAC-SHA512 algorithm. */
     48 public static final Name HMAC_SHA512 = Name.fromConstantString(HMAC_SHA512_STR);
     49 
     50 /**
     51  * The default fudge value for outgoing packets.  Can be overriden by the
     52  * tsigfudge option.
     53  */
     54 public static final short FUDGE		= 300;
     55 
     56 private Name name, alg;
     57 private String digest;
     58 private int digestBlockLength;
     59 private byte [] key;
     60 
     61 private void
     62 getDigest() {
     63 	if (alg.equals(HMAC_MD5)) {
     64 		digest = "md5";
     65 		digestBlockLength = 64;
     66 	} else if (alg.equals(HMAC_SHA1)) {
     67 		digest = "sha-1";
     68 		digestBlockLength = 64;
     69 	} else if (alg.equals(HMAC_SHA224)) {
     70 		digest = "sha-224";
     71 		digestBlockLength = 64;
     72 	} else if (alg.equals(HMAC_SHA256)) {
     73 		digest = "sha-256";
     74 		digestBlockLength = 64;
     75 	} else if (alg.equals(HMAC_SHA512)) {
     76 		digest = "sha-512";
     77 		digestBlockLength = 128;
     78 	} else if (alg.equals(HMAC_SHA384)) {
     79 		digest = "sha-384";
     80 		digestBlockLength = 128;
     81 	} else
     82 		throw new IllegalArgumentException("Invalid algorithm");
     83 }
     84 
     85 /**
     86  * Creates a new TSIG key, which can be used to sign or verify a message.
     87  * @param algorithm The algorithm of the shared key.
     88  * @param name The name of the shared key.
     89  * @param key The shared key's data.
     90  */
     91 public
     92 TSIG(Name algorithm, Name name, byte [] key) {
     93 	this.name = name;
     94 	this.alg = algorithm;
     95 	this.key = key;
     96 	getDigest();
     97 }
     98 
     99 /**
    100  * Creates a new TSIG key with the hmac-md5 algorithm, which can be used to
    101  * sign or verify a message.
    102  * @param name The name of the shared key.
    103  * @param key The shared key's data.
    104  */
    105 public
    106 TSIG(Name name, byte [] key) {
    107 	this(HMAC_MD5, name, key);
    108 }
    109 
    110 /**
    111  * Creates a new TSIG object, which can be used to sign or verify a message.
    112  * @param name The name of the shared key.
    113  * @param key The shared key's data represented as a base64 encoded string.
    114  * @throws IllegalArgumentException The key name is an invalid name
    115  * @throws IllegalArgumentException The key data is improperly encoded
    116  */
    117 public
    118 TSIG(Name algorithm, String name, String key) {
    119 	this.key = base64.fromString(key);
    120 	if (this.key == null)
    121 		throw new IllegalArgumentException("Invalid TSIG key string");
    122 	try {
    123 		this.name = Name.fromString(name, Name.root);
    124 	}
    125 	catch (TextParseException e) {
    126 		throw new IllegalArgumentException("Invalid TSIG key name");
    127 	}
    128 	this.alg = algorithm;
    129 	getDigest();
    130 }
    131 
    132 /**
    133  * Creates a new TSIG object, which can be used to sign or verify a message.
    134  * @param name The name of the shared key.
    135  * @param algorithm The algorithm of the shared key.  The legal values are
    136  * "hmac-md5", "hmac-sha1", "hmac-sha224", "hmac-sha256", "hmac-sha384", and
    137  * "hmac-sha512".
    138  * @param key The shared key's data represented as a base64 encoded string.
    139  * @throws IllegalArgumentException The key name is an invalid name
    140  * @throws IllegalArgumentException The key data is improperly encoded
    141  */
    142 public
    143 TSIG(String algorithm, String name, String key) {
    144 	this(HMAC_MD5, name, key);
    145 	if (algorithm.equalsIgnoreCase("hmac-md5"))
    146 		this.alg = HMAC_MD5;
    147 	else if (algorithm.equalsIgnoreCase("hmac-sha1"))
    148 		this.alg = HMAC_SHA1;
    149 	else if (algorithm.equalsIgnoreCase("hmac-sha224"))
    150 		this.alg = HMAC_SHA224;
    151 	else if (algorithm.equalsIgnoreCase("hmac-sha256"))
    152 		this.alg = HMAC_SHA256;
    153 	else if (algorithm.equalsIgnoreCase("hmac-sha384"))
    154 		this.alg = HMAC_SHA384;
    155 	else if (algorithm.equalsIgnoreCase("hmac-sha512"))
    156 		this.alg = HMAC_SHA512;
    157 	else
    158 		throw new IllegalArgumentException("Invalid TSIG algorithm");
    159 	getDigest();
    160 }
    161 
    162 /**
    163  * Creates a new TSIG object with the hmac-md5 algorithm, which can be used to
    164  * sign or verify a message.
    165  * @param name The name of the shared key
    166  * @param key The shared key's data, represented as a base64 encoded string.
    167  * @throws IllegalArgumentException The key name is an invalid name
    168  * @throws IllegalArgumentException The key data is improperly encoded
    169  */
    170 public
    171 TSIG(String name, String key) {
    172 	this(HMAC_MD5, name, key);
    173 }
    174 
    175 /**
    176  * Creates a new TSIG object, which can be used to sign or verify a message.
    177  * @param str The TSIG key, in the form name:secret, name/secret,
    178  * alg:name:secret, or alg/name/secret.  If an algorithm is specified, it must
    179  * be "hmac-md5", "hmac-sha1", or "hmac-sha256".
    180  * @throws IllegalArgumentException The string does not contain both a name
    181  * and secret.
    182  * @throws IllegalArgumentException The key name is an invalid name
    183  * @throws IllegalArgumentException The key data is improperly encoded
    184  */
    185 static public TSIG
    186 fromString(String str) {
    187 	String [] parts = str.split("[:/]", 3);
    188 	if (parts.length < 2)
    189 		throw new IllegalArgumentException("Invalid TSIG key " +
    190 						   "specification");
    191 	if (parts.length == 3) {
    192 		try {
    193 			return new TSIG(parts[0], parts[1], parts[2]);
    194 		} catch (IllegalArgumentException e) {
    195 			parts = str.split("[:/]", 2);
    196 		}
    197 	}
    198 	return new TSIG(HMAC_MD5, parts[0], parts[1]);
    199 }
    200 
    201 /**
    202  * Generates a TSIG record with a specific error for a message that has
    203  * been rendered.
    204  * @param m The message
    205  * @param b The rendered message
    206  * @param error The error
    207  * @param old If this message is a response, the TSIG from the request
    208  * @return The TSIG record to be added to the message
    209  */
    210 public TSIGRecord
    211 generate(Message m, byte [] b, int error, TSIGRecord old) {
    212 	Date timeSigned;
    213 	if (error != Rcode.BADTIME)
    214 		timeSigned = new Date();
    215 	else
    216 		timeSigned = old.getTimeSigned();
    217 	int fudge;
    218 	HMAC hmac = null;
    219 	if (error == Rcode.NOERROR || error == Rcode.BADTIME)
    220 		hmac = new HMAC(digest, digestBlockLength, key);
    221 
    222 	fudge = Options.intValue("tsigfudge");
    223 	if (fudge < 0 || fudge > 0x7FFF)
    224 		fudge = FUDGE;
    225 
    226 	if (old != null) {
    227 		DNSOutput out = new DNSOutput();
    228 		out.writeU16(old.getSignature().length);
    229 		if (hmac != null) {
    230 			hmac.update(out.toByteArray());
    231 			hmac.update(old.getSignature());
    232 		}
    233 	}
    234 
    235 	/* Digest the message */
    236 	if (hmac != null)
    237 		hmac.update(b);
    238 
    239 	DNSOutput out = new DNSOutput();
    240 	name.toWireCanonical(out);
    241 	out.writeU16(DClass.ANY);	/* class */
    242 	out.writeU32(0);		/* ttl */
    243 	alg.toWireCanonical(out);
    244 	long time = timeSigned.getTime() / 1000;
    245 	int timeHigh = (int) (time >> 32);
    246 	long timeLow = (time & 0xFFFFFFFFL);
    247 	out.writeU16(timeHigh);
    248 	out.writeU32(timeLow);
    249 	out.writeU16(fudge);
    250 
    251 	out.writeU16(error);
    252 	out.writeU16(0); /* No other data */
    253 
    254 	if (hmac != null)
    255 		hmac.update(out.toByteArray());
    256 
    257 	byte [] signature;
    258 	if (hmac != null)
    259 		signature = hmac.sign();
    260 	else
    261 		signature = new byte[0];
    262 
    263 	byte [] other = null;
    264 	if (error == Rcode.BADTIME) {
    265 		out = new DNSOutput();
    266 		time = new Date().getTime() / 1000;
    267 		timeHigh = (int) (time >> 32);
    268 		timeLow = (time & 0xFFFFFFFFL);
    269 		out.writeU16(timeHigh);
    270 		out.writeU32(timeLow);
    271 		other = out.toByteArray();
    272 	}
    273 
    274 	return (new TSIGRecord(name, DClass.ANY, 0, alg, timeSigned, fudge,
    275 			       signature, m.getHeader().getID(), error, other));
    276 }
    277 
    278 /**
    279  * Generates a TSIG record with a specific error for a message and adds it
    280  * to the message.
    281  * @param m The message
    282  * @param error The error
    283  * @param old If this message is a response, the TSIG from the request
    284  */
    285 public void
    286 apply(Message m, int error, TSIGRecord old) {
    287 	Record r = generate(m, m.toWire(), error, old);
    288 	m.addRecord(r, Section.ADDITIONAL);
    289 	m.tsigState = Message.TSIG_SIGNED;
    290 }
    291 
    292 /**
    293  * Generates a TSIG record for a message and adds it to the message
    294  * @param m The message
    295  * @param old If this message is a response, the TSIG from the request
    296  */
    297 public void
    298 apply(Message m, TSIGRecord old) {
    299 	apply(m, Rcode.NOERROR, old);
    300 }
    301 
    302 /**
    303  * Generates a TSIG record for a message and adds it to the message
    304  * @param m The message
    305  * @param old If this message is a response, the TSIG from the request
    306  */
    307 public void
    308 applyStream(Message m, TSIGRecord old, boolean first) {
    309 	if (first) {
    310 		apply(m, old);
    311 		return;
    312 	}
    313 	Date timeSigned = new Date();
    314 	int fudge;
    315 	HMAC hmac = new HMAC(digest, digestBlockLength, key);
    316 
    317 	fudge = Options.intValue("tsigfudge");
    318 	if (fudge < 0 || fudge > 0x7FFF)
    319 		fudge = FUDGE;
    320 
    321 	DNSOutput out = new DNSOutput();
    322 	out.writeU16(old.getSignature().length);
    323 	hmac.update(out.toByteArray());
    324 	hmac.update(old.getSignature());
    325 
    326 	/* Digest the message */
    327 	hmac.update(m.toWire());
    328 
    329 	out = new DNSOutput();
    330 	long time = timeSigned.getTime() / 1000;
    331 	int timeHigh = (int) (time >> 32);
    332 	long timeLow = (time & 0xFFFFFFFFL);
    333 	out.writeU16(timeHigh);
    334 	out.writeU32(timeLow);
    335 	out.writeU16(fudge);
    336 
    337 	hmac.update(out.toByteArray());
    338 
    339 	byte [] signature = hmac.sign();
    340 	byte [] other = null;
    341 
    342 	Record r = new TSIGRecord(name, DClass.ANY, 0, alg, timeSigned, fudge,
    343 				  signature, m.getHeader().getID(),
    344 				  Rcode.NOERROR, other);
    345 	m.addRecord(r, Section.ADDITIONAL);
    346 	m.tsigState = Message.TSIG_SIGNED;
    347 }
    348 
    349 /**
    350  * Verifies a TSIG record on an incoming message.  Since this is only called
    351  * in the context where a TSIG is expected to be present, it is an error
    352  * if one is not present.  After calling this routine, Message.isVerified() may
    353  * be called on this message.
    354  * @param m The message
    355  * @param b An array containing the message in unparsed form.  This is
    356  * necessary since TSIG signs the message in wire format, and we can't
    357  * recreate the exact wire format (with the same name compression).
    358  * @param length The length of the message in the array.
    359  * @param old If this message is a response, the TSIG from the request
    360  * @return The result of the verification (as an Rcode)
    361  * @see Rcode
    362  */
    363 public byte
    364 verify(Message m, byte [] b, int length, TSIGRecord old) {
    365 	m.tsigState = Message.TSIG_FAILED;
    366 	TSIGRecord tsig = m.getTSIG();
    367 	HMAC hmac = new HMAC(digest, digestBlockLength, key);
    368 	if (tsig == null)
    369 		return Rcode.FORMERR;
    370 
    371 	if (!tsig.getName().equals(name) || !tsig.getAlgorithm().equals(alg)) {
    372 		if (Options.check("verbose"))
    373 			System.err.println("BADKEY failure");
    374 		return Rcode.BADKEY;
    375 	}
    376 	long now = System.currentTimeMillis();
    377 	long then = tsig.getTimeSigned().getTime();
    378 	long fudge = tsig.getFudge();
    379 	if (Math.abs(now - then) > fudge * 1000) {
    380 		if (Options.check("verbose"))
    381 			System.err.println("BADTIME failure");
    382 		return Rcode.BADTIME;
    383 	}
    384 
    385 	if (old != null && tsig.getError() != Rcode.BADKEY &&
    386 	    tsig.getError() != Rcode.BADSIG)
    387 	{
    388 		DNSOutput out = new DNSOutput();
    389 		out.writeU16(old.getSignature().length);
    390 		hmac.update(out.toByteArray());
    391 		hmac.update(old.getSignature());
    392 	}
    393 	m.getHeader().decCount(Section.ADDITIONAL);
    394 	byte [] header = m.getHeader().toWire();
    395 	m.getHeader().incCount(Section.ADDITIONAL);
    396 	hmac.update(header);
    397 
    398 	int len = m.tsigstart - header.length;
    399 	hmac.update(b, header.length, len);
    400 
    401 	DNSOutput out = new DNSOutput();
    402 	tsig.getName().toWireCanonical(out);
    403 	out.writeU16(tsig.dclass);
    404 	out.writeU32(tsig.ttl);
    405 	tsig.getAlgorithm().toWireCanonical(out);
    406 	long time = tsig.getTimeSigned().getTime() / 1000;
    407 	int timeHigh = (int) (time >> 32);
    408 	long timeLow = (time & 0xFFFFFFFFL);
    409 	out.writeU16(timeHigh);
    410 	out.writeU32(timeLow);
    411 	out.writeU16(tsig.getFudge());
    412 	out.writeU16(tsig.getError());
    413 	if (tsig.getOther() != null) {
    414 		out.writeU16(tsig.getOther().length);
    415 		out.writeByteArray(tsig.getOther());
    416 	} else {
    417 		out.writeU16(0);
    418 	}
    419 
    420 	hmac.update(out.toByteArray());
    421 
    422 	byte [] signature = tsig.getSignature();
    423 	int digestLength = hmac.digestLength();
    424 	int minDigestLength = digest.equals("md5") ? 10 : digestLength / 2;
    425 
    426 	if (signature.length > digestLength) {
    427 		if (Options.check("verbose"))
    428 			System.err.println("BADSIG: signature too long");
    429 		return Rcode.BADSIG;
    430 	} else if (signature.length < minDigestLength) {
    431 		if (Options.check("verbose"))
    432 			System.err.println("BADSIG: signature too short");
    433 		return Rcode.BADSIG;
    434 	} else if (!hmac.verify(signature, true)) {
    435 		if (Options.check("verbose"))
    436 			System.err.println("BADSIG: signature verification");
    437 		return Rcode.BADSIG;
    438 	}
    439 
    440 	m.tsigState = Message.TSIG_VERIFIED;
    441 	return Rcode.NOERROR;
    442 }
    443 
    444 /**
    445  * Verifies a TSIG record on an incoming message.  Since this is only called
    446  * in the context where a TSIG is expected to be present, it is an error
    447  * if one is not present.  After calling this routine, Message.isVerified() may
    448  * be called on this message.
    449  * @param m The message
    450  * @param b The message in unparsed form.  This is necessary since TSIG
    451  * signs the message in wire format, and we can't recreate the exact wire
    452  * format (with the same name compression).
    453  * @param old If this message is a response, the TSIG from the request
    454  * @return The result of the verification (as an Rcode)
    455  * @see Rcode
    456  */
    457 public int
    458 verify(Message m, byte [] b, TSIGRecord old) {
    459 	return verify(m, b, b.length, old);
    460 }
    461 
    462 /**
    463  * Returns the maximum length of a TSIG record generated by this key.
    464  * @see TSIGRecord
    465  */
    466 public int
    467 recordLength() {
    468 	return (name.length() + 10 +
    469 		alg.length() +
    470 		8 +	// time signed, fudge
    471 		18 +	// 2 byte MAC length, 16 byte MAC
    472 		4 +	// original id, error
    473 		8);	// 2 byte error length, 6 byte max error field.
    474 }
    475 
    476 public static class StreamVerifier {
    477 	/**
    478 	 * A helper class for verifying multiple message responses.
    479 	 */
    480 
    481 	private TSIG key;
    482 	private HMAC verifier;
    483 	private int nresponses;
    484 	private int lastsigned;
    485 	private TSIGRecord lastTSIG;
    486 
    487 	/** Creates an object to verify a multiple message response */
    488 	public
    489 	StreamVerifier(TSIG tsig, TSIGRecord old) {
    490 		key = tsig;
    491 		verifier = new HMAC(key.digest, key.digestBlockLength, key.key);
    492 		nresponses = 0;
    493 		lastTSIG = old;
    494 	}
    495 
    496 	/**
    497 	 * Verifies a TSIG record on an incoming message that is part of a
    498 	 * multiple message response.
    499 	 * TSIG records must be present on the first and last messages, and
    500 	 * at least every 100 records in between.
    501 	 * After calling this routine, Message.isVerified() may be called on
    502 	 * this message.
    503 	 * @param m The message
    504 	 * @param b The message in unparsed form
    505 	 * @return The result of the verification (as an Rcode)
    506 	 * @see Rcode
    507 	 */
    508 	public int
    509 	verify(Message m, byte [] b) {
    510 		TSIGRecord tsig = m.getTSIG();
    511 
    512 		nresponses++;
    513 
    514 		if (nresponses == 1) {
    515 			int result = key.verify(m, b, lastTSIG);
    516 			if (result == Rcode.NOERROR) {
    517 				byte [] signature = tsig.getSignature();
    518 				DNSOutput out = new DNSOutput();
    519 				out.writeU16(signature.length);
    520 				verifier.update(out.toByteArray());
    521 				verifier.update(signature);
    522 			}
    523 			lastTSIG = tsig;
    524 			return result;
    525 		}
    526 
    527 		if (tsig != null)
    528 			m.getHeader().decCount(Section.ADDITIONAL);
    529 		byte [] header = m.getHeader().toWire();
    530 		if (tsig != null)
    531 			m.getHeader().incCount(Section.ADDITIONAL);
    532 		verifier.update(header);
    533 
    534 		int len;
    535 		if (tsig == null)
    536 			len = b.length - header.length;
    537 		else
    538 			len = m.tsigstart - header.length;
    539 		verifier.update(b, header.length, len);
    540 
    541 		if (tsig != null) {
    542 			lastsigned = nresponses;
    543 			lastTSIG = tsig;
    544 		}
    545 		else {
    546 			boolean required = (nresponses - lastsigned >= 100);
    547 			if (required) {
    548 				m.tsigState = Message.TSIG_FAILED;
    549 				return Rcode.FORMERR;
    550 			} else {
    551 				m.tsigState = Message.TSIG_INTERMEDIATE;
    552 				return Rcode.NOERROR;
    553 			}
    554 		}
    555 
    556 		if (!tsig.getName().equals(key.name) ||
    557 		    !tsig.getAlgorithm().equals(key.alg))
    558 		{
    559 			if (Options.check("verbose"))
    560 				System.err.println("BADKEY failure");
    561 			m.tsigState = Message.TSIG_FAILED;
    562 			return Rcode.BADKEY;
    563 		}
    564 
    565 		DNSOutput out = new DNSOutput();
    566 		long time = tsig.getTimeSigned().getTime() / 1000;
    567 		int timeHigh = (int) (time >> 32);
    568 		long timeLow = (time & 0xFFFFFFFFL);
    569 		out.writeU16(timeHigh);
    570 		out.writeU32(timeLow);
    571 		out.writeU16(tsig.getFudge());
    572 		verifier.update(out.toByteArray());
    573 
    574 		if (verifier.verify(tsig.getSignature()) == false) {
    575 			if (Options.check("verbose"))
    576 				System.err.println("BADSIG failure");
    577 			m.tsigState = Message.TSIG_FAILED;
    578 			return Rcode.BADSIG;
    579 		}
    580 
    581 		verifier.clear();
    582 		out = new DNSOutput();
    583 		out.writeU16(tsig.getSignature().length);
    584 		verifier.update(out.toByteArray());
    585 		verifier.update(tsig.getSignature());
    586 
    587 		m.tsigState = Message.TSIG_VERIFIED;
    588 		return Rcode.NOERROR;
    589 	}
    590 }
    591 
    592 }
    593