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