• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 1999-2004 Brian Wellington (bwelling@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
getDigest()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
TSIG(Name algorithm, Name name, byte [] key)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
TSIG(Name name, byte [] key)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
TSIG(Name algorithm, String name, String key)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
TSIG(String algorithm, String name, String key)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
TSIG(String name, String key)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
fromString(String str)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
generate(Message m, byte [] b, int error, TSIGRecord old)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
apply(Message m, int error, TSIGRecord old)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
apply(Message m, TSIGRecord old)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
applyStream(Message m, TSIGRecord old, boolean first)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
verify(Message m, byte [] b, int length, TSIGRecord old)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
verify(Message m, byte [] b, TSIGRecord old)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
recordLength()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
StreamVerifier(TSIG tsig, TSIGRecord old)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
verify(Message m, byte [] b)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