001: // Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
002:
003: package org.xbill.DNS;
004:
005: import java.util.*;
006: import org.xbill.DNS.utils.*;
007:
008: /**
009: * Transaction signature handling. This class generates and verifies
010: * TSIG records on messages, which provide transaction security.
011: * @see TSIGRecord
012: *
013: * @author Brian Wellington
014: */
015:
016: public class TSIG {
017:
018: private static final String HMAC_MD5_STR = "HMAC-MD5.SIG-ALG.REG.INT.";
019: private static final String HMAC_SHA1_STR = "hmac-sha1.";
020: private static final String HMAC_SHA256_STR = "hmac-sha256.";
021:
022: /** The domain name representing the HMAC-MD5 algorithm. */
023: public static final Name HMAC_MD5 = Name
024: .fromConstantString(HMAC_MD5_STR);
025:
026: /** The domain name representing the HMAC-MD5 algorithm (deprecated). */
027: public static final Name HMAC = HMAC_MD5;
028:
029: /** The domain name representing the HMAC-SHA1 algorithm. */
030: public static final Name HMAC_SHA1 = Name
031: .fromConstantString(HMAC_SHA1_STR);
032:
033: /** The domain name representing the HMAC-SHA256 algorithm. */
034: public static final Name HMAC_SHA256 = Name
035: .fromConstantString(HMAC_SHA256_STR);
036:
037: /**
038: * The default fudge value for outgoing packets. Can be overriden by the
039: * tsigfudge option.
040: */
041: public static final short FUDGE = 300;
042:
043: private Name name, alg;
044: private String digest;
045: private byte[] key;
046:
047: private void getDigest() {
048: if (alg.equals(HMAC_MD5))
049: digest = "md5";
050: else if (alg.equals(HMAC_SHA1))
051: digest = "sha-1";
052: else if (alg.equals(HMAC_SHA256))
053: digest = "sha-256";
054: else
055: throw new IllegalArgumentException("Invalid algorithm");
056: }
057:
058: /**
059: * Creates a new TSIG key, which can be used to sign or verify a message.
060: * @param algorithm The algorithm of the shared key.
061: * @param name The name of the shared key.
062: * @param key The shared key's data.
063: */
064: public TSIG(Name algorithm, Name name, byte[] key) {
065: this .name = name;
066: this .alg = algorithm;
067: this .key = key;
068: getDigest();
069: }
070:
071: /**
072: * Creates a new TSIG key with the hmac-md5 algorithm, which can be used to
073: * sign or verify a message.
074: * @param name The name of the shared key.
075: * @param key The shared key's data.
076: */
077: public TSIG(Name name, byte[] key) {
078: this (HMAC_MD5, name, key);
079: }
080:
081: /**
082: * Creates a new TSIG object, which can be used to sign or verify a message.
083: * @param name The name of the shared key.
084: * @param key The shared key's data represented as a base64 encoded string.
085: * @throws IllegalArgumentException The key name is an invalid name
086: * @throws IllegalArgumentException The key data is improperly encoded
087: */
088: public TSIG(Name algorithm, String name, String key) {
089: this .key = base64.fromString(key);
090: if (this .key == null)
091: throw new IllegalArgumentException(
092: "Invalid TSIG key string");
093: try {
094: this .name = Name.fromString(name, Name.root);
095: } catch (TextParseException e) {
096: throw new IllegalArgumentException("Invalid TSIG key name");
097: }
098: this .alg = algorithm;
099: getDigest();
100: }
101:
102: /**
103: * Creates a new TSIG object, which can be used to sign or verify a message.
104: * @param name The name of the shared key. The legal values are "hmac-md5",
105: * "hmac-sha1", and "hmac-sha256".
106: * @param key The shared key's data represented as a base64 encoded string.
107: * @throws IllegalArgumentException The key name is an invalid name
108: * @throws IllegalArgumentException The key data is improperly encoded
109: */
110: public TSIG(String algorithm, String name, String key) {
111: this (HMAC_MD5, name, key);
112: if (algorithm.equalsIgnoreCase("hmac-md5"))
113: this .alg = HMAC_MD5;
114: else if (algorithm.equalsIgnoreCase("hmac-sha1"))
115: this .alg = HMAC_SHA1;
116: else if (algorithm.equalsIgnoreCase("hmac-sha256"))
117: this .alg = HMAC_SHA256;
118: else
119: throw new IllegalArgumentException("Invalid TSIG algorithm");
120: getDigest();
121: }
122:
123: /**
124: * Creates a new TSIG object with the hmac-md5 algorithm, which can be used to
125: * sign or verify a message.
126: * @param name The name of the shared key
127: * @param key The shared key's data, represented as a base64 encoded string.
128: * @throws IllegalArgumentException The key name is an invalid name
129: * @throws IllegalArgumentException The key data is improperly encoded
130: */
131: public TSIG(String name, String key) {
132: this (HMAC_MD5, name, key);
133: }
134:
135: /**
136: * Creates a new TSIG object with the hmac-md5 algorithm, which can be used to
137: * sign or verify a message.
138: * @param str The TSIG key, in the form name:secret, name/secret,
139: * alg:name:secret, or alg/name/secret. If an algorithm is specified, it must
140: * be "hmac-md5", "hmac-sha1", or "hmac-sha256".
141: * @throws IllegalArgumentException The string does not contain both a name
142: * and secret.
143: * @throws IllegalArgumentException The key name is an invalid name
144: * @throws IllegalArgumentException The key data is improperly encoded
145: */
146: static public TSIG fromString(String str) {
147: String[] parts = str.split("[:/]");
148: if (parts.length < 2 || parts.length > 3)
149: throw new IllegalArgumentException("Invalid TSIG key "
150: + "specification");
151: if (parts.length == 3)
152: return new TSIG(parts[0], parts[1], parts[2]);
153: else
154: return new TSIG(HMAC_MD5, parts[0], parts[1]);
155: }
156:
157: /**
158: * Generates a TSIG record with a specific error for a message that has
159: * been rendered.
160: * @param m The message
161: * @param b The rendered message
162: * @param error The error
163: * @param old If this message is a response, the TSIG from the request
164: * @return The TSIG record to be added to the message
165: */
166: public TSIGRecord generate(Message m, byte[] b, int error,
167: TSIGRecord old) {
168: Date timeSigned;
169: if (error != Rcode.BADTIME)
170: timeSigned = new Date();
171: else
172: timeSigned = old.getTimeSigned();
173: int fudge;
174: HMAC hmac = null;
175: if (error == Rcode.NOERROR || error == Rcode.BADTIME)
176: hmac = new HMAC(digest, key);
177:
178: fudge = Options.intValue("tsigfudge");
179: if (fudge < 0 || fudge > 0x7FFF)
180: fudge = FUDGE;
181:
182: if (old != null) {
183: DNSOutput out = new DNSOutput();
184: out.writeU16(old.getSignature().length);
185: if (hmac != null) {
186: hmac.update(out.toByteArray());
187: hmac.update(old.getSignature());
188: }
189: }
190:
191: /* Digest the message */
192: if (hmac != null)
193: hmac.update(b);
194:
195: DNSOutput out = new DNSOutput();
196: name.toWireCanonical(out);
197: out.writeU16(DClass.ANY); /* class */
198: out.writeU32(0); /* ttl */
199: alg.toWireCanonical(out);
200: long time = timeSigned.getTime() / 1000;
201: int timeHigh = (int) (time >> 32);
202: long timeLow = (time & 0xFFFFFFFFL);
203: out.writeU16(timeHigh);
204: out.writeU32(timeLow);
205: out.writeU16(fudge);
206:
207: out.writeU16(error);
208: out.writeU16(0); /* No other data */
209:
210: if (hmac != null)
211: hmac.update(out.toByteArray());
212:
213: byte[] signature;
214: if (hmac != null)
215: signature = hmac.sign();
216: else
217: signature = new byte[0];
218:
219: byte[] other = null;
220: if (error == Rcode.BADTIME) {
221: out = new DNSOutput();
222: time = new Date().getTime() / 1000;
223: timeHigh = (int) (time >> 32);
224: timeLow = (time & 0xFFFFFFFFL);
225: out.writeU16(timeHigh);
226: out.writeU32(timeLow);
227: other = out.toByteArray();
228: }
229:
230: return (new TSIGRecord(name, DClass.ANY, 0, alg, timeSigned,
231: fudge, signature, m.getHeader().getID(), error, other));
232: }
233:
234: /**
235: * Generates a TSIG record with a specific error for a message and adds it
236: * to the message.
237: * @param m The message
238: * @param error The error
239: * @param old If this message is a response, the TSIG from the request
240: */
241: public void apply(Message m, int error, TSIGRecord old) {
242: Record r = generate(m, m.toWire(), error, old);
243: m.addRecord(r, Section.ADDITIONAL);
244: m.tsigState = Message.TSIG_SIGNED;
245: }
246:
247: /**
248: * Generates a TSIG record for a message and adds it to the message
249: * @param m The message
250: * @param old If this message is a response, the TSIG from the request
251: */
252: public void apply(Message m, TSIGRecord old) {
253: apply(m, Rcode.NOERROR, old);
254: }
255:
256: /**
257: * Generates a TSIG record for a message and adds it to the message
258: * @param m The message
259: * @param old If this message is a response, the TSIG from the request
260: */
261: public void applyStream(Message m, TSIGRecord old, boolean first) {
262: if (first) {
263: apply(m, old);
264: return;
265: }
266: Date timeSigned = new Date();
267: int fudge;
268: HMAC hmac = new HMAC(digest, key);
269:
270: fudge = Options.intValue("tsigfudge");
271: if (fudge < 0 || fudge > 0x7FFF)
272: fudge = FUDGE;
273:
274: DNSOutput out = new DNSOutput();
275: out.writeU16(old.getSignature().length);
276: hmac.update(out.toByteArray());
277: hmac.update(old.getSignature());
278:
279: /* Digest the message */
280: hmac.update(m.toWire());
281:
282: out = new DNSOutput();
283: long time = timeSigned.getTime() / 1000;
284: int timeHigh = (int) (time >> 32);
285: long timeLow = (time & 0xFFFFFFFFL);
286: out.writeU16(timeHigh);
287: out.writeU32(timeLow);
288: out.writeU16(fudge);
289:
290: hmac.update(out.toByteArray());
291:
292: byte[] signature = hmac.sign();
293: byte[] other = null;
294:
295: Record r = new TSIGRecord(name, DClass.ANY, 0, alg, timeSigned,
296: fudge, signature, m.getHeader().getID(), Rcode.NOERROR,
297: other);
298: m.addRecord(r, Section.ADDITIONAL);
299: m.tsigState = Message.TSIG_SIGNED;
300: }
301:
302: /**
303: * Verifies a TSIG record on an incoming message. Since this is only called
304: * in the context where a TSIG is expected to be present, it is an error
305: * if one is not present.
306: * @param m The message
307: * @param b An array containing the message in unparsed form. This is
308: * necessary since TSIG signs the message in wire format, and we can't
309: * recreate the exact wire format (with the same name compression).
310: * @param length The length of the message in the array.
311: * @param old If this message is a response, the TSIG from the request
312: * @return The result of the verification (as an Rcode)
313: * @see Rcode
314: */
315: public byte verify(Message m, byte[] b, int length, TSIGRecord old) {
316: TSIGRecord tsig = m.getTSIG();
317: HMAC hmac = new HMAC(digest, key);
318: if (tsig == null)
319: return Rcode.FORMERR;
320:
321: if (!tsig.getName().equals(name)
322: || !tsig.getAlgorithm().equals(alg)) {
323: if (Options.check("verbose"))
324: System.err.println("BADKEY failure");
325: return Rcode.BADKEY;
326: }
327: long now = System.currentTimeMillis();
328: long then = tsig.getTimeSigned().getTime();
329: long fudge = tsig.getFudge();
330: if (Math.abs(now - then) > fudge * 1000) {
331: if (Options.check("verbose"))
332: System.err.println("BADTIME failure");
333: return Rcode.BADTIME;
334: }
335:
336: if (old != null && tsig.getError() != Rcode.BADKEY
337: && tsig.getError() != Rcode.BADSIG) {
338: DNSOutput out = new DNSOutput();
339: out.writeU16(old.getSignature().length);
340: hmac.update(out.toByteArray());
341: hmac.update(old.getSignature());
342: }
343: m.getHeader().decCount(Section.ADDITIONAL);
344: byte[] header = m.getHeader().toWire();
345: m.getHeader().incCount(Section.ADDITIONAL);
346: hmac.update(header);
347:
348: int len = m.tsigstart - header.length;
349: hmac.update(b, header.length, len);
350:
351: DNSOutput out = new DNSOutput();
352: tsig.getName().toWireCanonical(out);
353: out.writeU16(tsig.dclass);
354: out.writeU32(tsig.ttl);
355: tsig.getAlgorithm().toWireCanonical(out);
356: long time = tsig.getTimeSigned().getTime() / 1000;
357: int timeHigh = (int) (time >> 32);
358: long timeLow = (time & 0xFFFFFFFFL);
359: out.writeU16(timeHigh);
360: out.writeU32(timeLow);
361: out.writeU16(tsig.getFudge());
362: out.writeU16(tsig.getError());
363: if (tsig.getOther() != null) {
364: out.writeU16(tsig.getOther().length);
365: out.writeByteArray(tsig.getOther());
366: } else {
367: out.writeU16(0);
368: }
369:
370: hmac.update(out.toByteArray());
371:
372: if (hmac.verify(tsig.getSignature()))
373: return Rcode.NOERROR;
374: else {
375: if (Options.check("verbose"))
376: System.err.println("BADSIG failure");
377: return Rcode.BADSIG;
378: }
379: }
380:
381: /**
382: * Verifies a TSIG record on an incoming message. Since this is only called
383: * in the context where a TSIG is expected to be present, it is an error
384: * if one is not present.
385: * @param m The message
386: * @param b The message in unparsed form. This is necessary since TSIG
387: * signs the message in wire format, and we can't recreate the exact wire
388: * format (with the same name compression).
389: * @param old If this message is a response, the TSIG from the request
390: * @return The result of the verification (as an Rcode)
391: * @see Rcode
392: */
393: public int verify(Message m, byte[] b, TSIGRecord old) {
394: return verify(m, b, b.length, old);
395: }
396:
397: /**
398: * Returns the maximum length of a TSIG record generated by this key.
399: * @see TSIGRecord
400: */
401: public int recordLength() {
402: return (name.length() + 10 + alg.length() + 8 + // time signed, fudge
403: 18 + // 2 byte MAC length, 16 byte MAC
404: 4 + // original id, error
405: 8); // 2 byte error length, 6 byte max error field.
406: }
407:
408: public static class StreamVerifier {
409: /**
410: * A helper class for verifying multiple message responses.
411: */
412:
413: private TSIG key;
414: private HMAC verifier;
415: private int nresponses;
416: private int lastsigned;
417: private TSIGRecord lastTSIG;
418:
419: /** Creates an object to verify a multiple message response */
420: public StreamVerifier(TSIG tsig, TSIGRecord old) {
421: key = tsig;
422: verifier = new HMAC(key.digest, key.key);
423: nresponses = 0;
424: lastTSIG = old;
425: }
426:
427: /**
428: * Verifies a TSIG record on an incoming message that is part of a
429: * multiple message response.
430: * TSIG records must be present on the first and last messages, and
431: * at least every 100 records in between.
432: * @param m The message
433: * @param b The message in unparsed form
434: * @return The result of the verification (as an Rcode)
435: * @see Rcode
436: */
437: public int verify(Message m, byte[] b) {
438: TSIGRecord tsig = m.getTSIG();
439:
440: nresponses++;
441:
442: if (nresponses == 1) {
443: int result = key.verify(m, b, lastTSIG);
444: if (result == Rcode.NOERROR) {
445: byte[] signature = tsig.getSignature();
446: DNSOutput out = new DNSOutput();
447: out.writeU16(signature.length);
448: verifier.update(out.toByteArray());
449: verifier.update(signature);
450: }
451: lastTSIG = tsig;
452: return result;
453: }
454:
455: if (tsig != null)
456: m.getHeader().decCount(Section.ADDITIONAL);
457: byte[] header = m.getHeader().toWire();
458: if (tsig != null)
459: m.getHeader().incCount(Section.ADDITIONAL);
460: verifier.update(header);
461:
462: int len;
463: if (tsig == null)
464: len = b.length - header.length;
465: else
466: len = m.tsigstart - header.length;
467: verifier.update(b, header.length, len);
468:
469: if (tsig != null) {
470: lastsigned = nresponses;
471: lastTSIG = tsig;
472: } else {
473: boolean required = (nresponses - lastsigned >= 100);
474: if (required)
475: return Rcode.FORMERR;
476: else
477: return Rcode.NOERROR;
478: }
479:
480: if (!tsig.getName().equals(key.name)
481: || !tsig.getAlgorithm().equals(key.alg)) {
482: if (Options.check("verbose"))
483: System.err.println("BADKEY failure");
484: return Rcode.BADKEY;
485: }
486:
487: DNSOutput out = new DNSOutput();
488: long time = tsig.getTimeSigned().getTime() / 1000;
489: int timeHigh = (int) (time >> 32);
490: long timeLow = (time & 0xFFFFFFFFL);
491: out.writeU16(timeHigh);
492: out.writeU32(timeLow);
493: out.writeU16(tsig.getFudge());
494: verifier.update(out.toByteArray());
495:
496: if (verifier.verify(tsig.getSignature()) == false) {
497: if (Options.check("verbose"))
498: System.err.println("BADSIG failure");
499: return Rcode.BADSIG;
500: }
501:
502: verifier.clear();
503: out = new DNSOutput();
504: out.writeU16(tsig.getSignature().length);
505: verifier.update(out.toByteArray());
506: verifier.update(tsig.getSignature());
507:
508: return Rcode.NOERROR;
509: }
510: }
511:
512: }
|