blob: d9e697287809402db3f573f241a93e1511c0b13f [file] [log] [blame]
Shuyi Chend7955ce2013-05-22 14:51:55 -07001// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
2
3package org.xbill.DNS;
4
5import java.util.*;
6import 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
16public class TSIG {
17
18private static final String HMAC_MD5_STR = "HMAC-MD5.SIG-ALG.REG.INT.";
19private static final String HMAC_SHA1_STR = "hmac-sha1.";
20private static final String HMAC_SHA224_STR = "hmac-sha224.";
21private static final String HMAC_SHA256_STR = "hmac-sha256.";
22private static final String HMAC_SHA384_STR = "hmac-sha384.";
23private static final String HMAC_SHA512_STR = "hmac-sha512.";
24
25/** The domain name representing the HMAC-MD5 algorithm. */
26public static final Name HMAC_MD5 = Name.fromConstantString(HMAC_MD5_STR);
27
28/** The domain name representing the HMAC-MD5 algorithm (deprecated). */
29public static final Name HMAC = HMAC_MD5;
30
31/** The domain name representing the HMAC-SHA1 algorithm. */
32public 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 */
39public static final Name HMAC_SHA224 = Name.fromConstantString(HMAC_SHA224_STR);
40
41/** The domain name representing the HMAC-SHA256 algorithm. */
42public static final Name HMAC_SHA256 = Name.fromConstantString(HMAC_SHA256_STR);
43
44/** The domain name representing the HMAC-SHA384 algorithm. */
45public static final Name HMAC_SHA384 = Name.fromConstantString(HMAC_SHA384_STR);
46
47/** The domain name representing the HMAC-SHA512 algorithm. */
48public 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 */
54public static final short FUDGE = 300;
55
56private Name name, alg;
57private String digest;
58private int digestBlockLength;
59private byte [] key;
60
61private void
62getDigest() {
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 */
91public
92TSIG(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 */
105public
106TSIG(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 */
117public
118TSIG(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 */
142public
143TSIG(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 */
170public
171TSIG(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 */
185static public TSIG
186fromString(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 */
210public TSIGRecord
211generate(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 */
285public void
286apply(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 */
297public void
298apply(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 */
307public void
308applyStream(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 */
363public byte
364verify(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 */
457public int
458verify(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 */
466public int
467recordLength() {
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
476public 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}