Package org.xbill.DNS

Source Code of org.xbill.DNS.TSIG

// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)

package org.xbill.DNS;

import org.xbill.DNS.utils.HMAC;
import org.xbill.DNS.utils.base64;

import java.util.Date;

/**
* Transaction signature handling.  This class generates and verifies
* TSIG records on messages, which provide transaction security.
* @see TSIGRecord
*
* @author Brian Wellington
*/

public class TSIG {

private static final String HMAC_MD5_STR = "HMAC-MD5.SIG-ALG.REG.INT.";
private static final String HMAC_SHA1_STR = "hmac-sha1.";
private static final String HMAC_SHA256_STR = "hmac-sha256.";

/** The domain name representing the HMAC-MD5 algorithm. */
public static final Name HMAC_MD5 = Name.fromConstantString(HMAC_MD5_STR);

/** The domain name representing the HMAC-MD5 algorithm (deprecated). */
public static final Name HMAC = HMAC_MD5;

/** The domain name representing the HMAC-SHA1 algorithm. */
public static final Name HMAC_SHA1 = Name.fromConstantString(HMAC_SHA1_STR);

/** The domain name representing the HMAC-SHA256 algorithm. */
public static final Name HMAC_SHA256 = Name.fromConstantString(HMAC_SHA256_STR);

/**
* The default fudge value for outgoing packets.  Can be overriden by the
* tsigfudge option.
*/
public static final short FUDGE    = 300;

private Name name, alg;
private String digest;
private byte [] key;

private void
getDigest() {
  if (alg.equals(HMAC_MD5))
    digest = "md5";
  else if (alg.equals(HMAC_SHA1))
    digest = "sha-1";
  else if (alg.equals(HMAC_SHA256))
    digest = "sha-256";
  else
    throw new IllegalArgumentException("Invalid algorithm");
}

/**
* Creates a new TSIG key, which can be used to sign or verify a message.
* @param algorithm The algorithm of the shared key.
* @param name The name of the shared key.
* @param key The shared key's data.
*/
public
TSIG(Name algorithm, Name name, byte [] key) {
  this.name = name;
  this.alg = algorithm;
  this.key = key;
  getDigest();
}

/**
* Creates a new TSIG key with the hmac-md5 algorithm, which can be used to
* sign or verify a message.
* @param name The name of the shared key.
* @param key The shared key's data.
*/
public
TSIG(Name name, byte [] key) {
  this(HMAC_MD5, name, key);
}

/**
* Creates a new TSIG object, which can be used to sign or verify a message.
* @param name The name of the shared key.
* @param key The shared key's data represented as a base64 encoded string.
* @throws IllegalArgumentException The key name is an invalid name
* @throws IllegalArgumentException The key data is improperly encoded
*/
public
TSIG(Name algorithm, String name, String key) {
  this.key = base64.fromString(key);
  if (this.key == null)
    throw new IllegalArgumentException("Invalid TSIG key string");
  try {
    this.name = Name.fromString(name, Name.root);
  }
  catch (TextParseException e) {
    throw new IllegalArgumentException("Invalid TSIG key name");
  }
  this.alg = algorithm;
  getDigest();
}

/**
* Creates a new TSIG object, which can be used to sign or verify a message.
* @param name The name of the shared key.  The legal values are "hmac-md5",
* "hmac-sha1", and "hmac-sha256".
* @param key The shared key's data represented as a base64 encoded string.
* @throws IllegalArgumentException The key name is an invalid name
* @throws IllegalArgumentException The key data is improperly encoded
*/
public
TSIG(String algorithm, String name, String key) {
  this(HMAC_MD5, name, key);
  if (algorithm.equalsIgnoreCase("hmac-md5"))
    this.alg = HMAC_MD5;
  else if (algorithm.equalsIgnoreCase("hmac-sha1"))
    this.alg = HMAC_SHA1;
  else if (algorithm.equalsIgnoreCase("hmac-sha256"))
    this.alg = HMAC_SHA256;
  else
    throw new IllegalArgumentException("Invalid TSIG algorithm");
  getDigest();
}

/**
* Creates a new TSIG object with the hmac-md5 algorithm, which can be used to
* sign or verify a message.
* @param name The name of the shared key
* @param key The shared key's data, represented as a base64 encoded string.
* @throws IllegalArgumentException The key name is an invalid name
* @throws IllegalArgumentException The key data is improperly encoded
*/
public
TSIG(String name, String key) {
  this(HMAC_MD5, name, key);
}

/**
* Creates a new TSIG object with the hmac-md5 algorithm, which can be used to
* sign or verify a message.
* @param str The TSIG key, in the form name:secret, name/secret,
* alg:name:secret, or alg/name/secret.  If an algorithm is specified, it must
* be "hmac-md5", "hmac-sha1", or "hmac-sha256".
* @throws IllegalArgumentException The string does not contain both a name
* and secret.
* @throws IllegalArgumentException The key name is an invalid name
* @throws IllegalArgumentException The key data is improperly encoded
*/
static public TSIG
fromString(String str) {
  String [] parts = str.split("[:/]");
  if (parts.length < 2 || parts.length > 3)
    throw new IllegalArgumentException("Invalid TSIG key " +
               "specification");
  if (parts.length == 3)
    return new TSIG(parts[0], parts[1], parts[2]);
  else
    return new TSIG(HMAC_MD5, parts[0], parts[1]);
}

/**
* Generates a TSIG record with a specific error for a message that has
* been rendered.
* @param m The message
* @param b The rendered message
* @param error The error
* @param old If this message is a response, the TSIG from the request
* @return The TSIG record to be added to the message
*/
public TSIGRecord
generate(Message m, byte [] b, int error, TSIGRecord old) {
  Date timeSigned;
  if (error != Rcode.BADTIME)
    timeSigned = new Date();
  else
    timeSigned = old.getTimeSigned();
  int fudge;
  HMAC hmac = null;
  if (error == Rcode.NOERROR || error == Rcode.BADTIME)
    hmac = new HMAC(digest, key);

  fudge = Options.intValue("tsigfudge");
  if (fudge < 0 || fudge > 0x7FFF)
    fudge = FUDGE;

  if (old != null) {
    DNSOutput out = new DNSOutput();
    out.writeU16(old.getSignature().length);
    if (hmac != null) {
      hmac.update(out.toByteArray());
      hmac.update(old.getSignature());
    }
  }

  /* Digest the message */
  if (hmac != null)
    hmac.update(b);

  DNSOutput out = new DNSOutput();
  name.toWireCanonical(out);
  out.writeU16(DClass.ANY)/* class */
  out.writeU32(0);    /* ttl */
  alg.toWireCanonical(out);
  long time = timeSigned.getTime() / 1000;
  int timeHigh = (int) (time >> 32);
  long timeLow = (time & 0xFFFFFFFFL);
  out.writeU16(timeHigh);
  out.writeU32(timeLow);
  out.writeU16(fudge);

  out.writeU16(error);
  out.writeU16(0); /* No other data */

  if (hmac != null)
    hmac.update(out.toByteArray());

  byte [] signature;
  if (hmac != null)
    signature = hmac.sign();
  else
    signature = new byte[0];

  byte [] other = null;
  if (error == Rcode.BADTIME) {
    out = new DNSOutput();
    time = new Date().getTime() / 1000;
    timeHigh = (int) (time >> 32);
    timeLow = (time & 0xFFFFFFFFL);
    out.writeU16(timeHigh);
    out.writeU32(timeLow);
    other = out.toByteArray();
  }

  return (new TSIGRecord(name, DClass.ANY, 0, alg, timeSigned, fudge,
             signature, m.getHeader().getID(), error, other));
}

/**
* Generates a TSIG record with a specific error for a message and adds it
* to the message.
* @param m The message
* @param error The error
* @param old If this message is a response, the TSIG from the request
*/
public void
apply(Message m, int error, TSIGRecord old) {
  Record r = generate(m, m.toWire(), error, old);
  m.addRecord(r, Section.ADDITIONAL);
  m.tsigState = Message.TSIG_SIGNED;
}

/**
* Generates a TSIG record for a message and adds it to the message
* @param m The message
* @param old If this message is a response, the TSIG from the request
*/
public void
apply(Message m, TSIGRecord old) {
  apply(m, Rcode.NOERROR, old);
}

/**
* Generates a TSIG record for a message and adds it to the message
* @param m The message
* @param old If this message is a response, the TSIG from the request
*/
public void
applyStream(Message m, TSIGRecord old, boolean first) {
  if (first) {
    apply(m, old);
    return;
  }
  Date timeSigned = new Date();
  int fudge;
  HMAC hmac = new HMAC(digest, key);

  fudge = Options.intValue("tsigfudge");
  if (fudge < 0 || fudge > 0x7FFF)
    fudge = FUDGE;

  DNSOutput out = new DNSOutput();
  out.writeU16(old.getSignature().length);
  hmac.update(out.toByteArray());
  hmac.update(old.getSignature());

  /* Digest the message */
  hmac.update(m.toWire());

  out = new DNSOutput();
  long time = timeSigned.getTime() / 1000;
  int timeHigh = (int) (time >> 32);
  long timeLow = (time & 0xFFFFFFFFL);
  out.writeU16(timeHigh);
  out.writeU32(timeLow);
  out.writeU16(fudge);

  hmac.update(out.toByteArray());

  byte [] signature = hmac.sign();
  byte [] other = null;

  Record r = new TSIGRecord(name, DClass.ANY, 0, alg, timeSigned, fudge,
          signature, m.getHeader().getID(),
          Rcode.NOERROR, other);
  m.addRecord(r, Section.ADDITIONAL);
  m.tsigState = Message.TSIG_SIGNED;
}

/**
* Verifies a TSIG record on an incoming message.  Since this is only called
* in the context where a TSIG is expected to be present, it is an error
* if one is not present.  After calling this routine, Message.isVerified() may
* be called on this message.
* @param m The message
* @param b An array containing the message in unparsed form.  This is
* necessary since TSIG signs the message in wire format, and we can't
* recreate the exact wire format (with the same name compression).
* @param length The length of the message in the array.
* @param old If this message is a response, the TSIG from the request
* @return The result of the verification (as an Rcode)
* @see Rcode
*/
public byte
verify(Message m, byte [] b, int length, TSIGRecord old) {
  m.tsigState = Message.TSIG_FAILED;
  TSIGRecord tsig = m.getTSIG();
  HMAC hmac = new HMAC(digest, key);
  if (tsig == null)
    return Rcode.FORMERR;

  if (!tsig.getName().equals(name) || !tsig.getAlgorithm().equals(alg)) {
    if (Options.check("verbose"))
      System.err.println("BADKEY failure");
    return Rcode.BADKEY;
  }
  long now = System.currentTimeMillis();
  long then = tsig.getTimeSigned().getTime();
  long fudge = tsig.getFudge();
  if (Math.abs(now - then) > fudge * 1000) {
    if (Options.check("verbose"))
      System.err.println("BADTIME failure");
    return Rcode.BADTIME;
  }

  if (old != null && tsig.getError() != Rcode.BADKEY &&
      tsig.getError() != Rcode.BADSIG)
  {
    DNSOutput out = new DNSOutput();
    out.writeU16(old.getSignature().length);
    hmac.update(out.toByteArray());
    hmac.update(old.getSignature());
  }
  m.getHeader().decCount(Section.ADDITIONAL);
  byte [] header = m.getHeader().toWire();
  m.getHeader().incCount(Section.ADDITIONAL);
  hmac.update(header);

  int len = m.tsigstart - header.length; 
  hmac.update(b, header.length, len);

  DNSOutput out = new DNSOutput();
  tsig.getName().toWireCanonical(out);
  out.writeU16(tsig.dclass);
  out.writeU32(tsig.ttl);
  tsig.getAlgorithm().toWireCanonical(out);
  long time = tsig.getTimeSigned().getTime() / 1000;
  int timeHigh = (int) (time >> 32);
  long timeLow = (time & 0xFFFFFFFFL);
  out.writeU16(timeHigh);
  out.writeU32(timeLow);
  out.writeU16(tsig.getFudge());
  out.writeU16(tsig.getError());
  if (tsig.getOther() != null) {
    out.writeU16(tsig.getOther().length);
    out.writeByteArray(tsig.getOther());
  } else {
    out.writeU16(0);
  }

  hmac.update(out.toByteArray());

  if (hmac.verify(tsig.getSignature())) {
    m.tsigState = Message.TSIG_VERIFIED;
    return Rcode.NOERROR;
  } else {
    if (Options.check("verbose"))
      System.err.println("BADSIG failure");
    return Rcode.BADSIG;
  }
}

/**
* Verifies a TSIG record on an incoming message.  Since this is only called
* in the context where a TSIG is expected to be present, it is an error
* if one is not present.  After calling this routine, Message.isVerified() may
* be called on this message.
* @param m The message
* @param b The message in unparsed form.  This is necessary since TSIG
* signs the message in wire format, and we can't recreate the exact wire
* format (with the same name compression).
* @param old If this message is a response, the TSIG from the request
* @return The result of the verification (as an Rcode)
* @see Rcode
*/
public int
verify(Message m, byte [] b, TSIGRecord old) {
  return verify(m, b, b.length, old);
}

/**
* Returns the maximum length of a TSIG record generated by this key.
* @see TSIGRecord
*/
public int
recordLength() {
  return (name.length() + 10 +
    alg.length() +
    8 // time signed, fudge
    18 // 2 byte MAC length, 16 byte MAC
    4 // original id, error
    8)// 2 byte error length, 6 byte max error field.
}

public static class StreamVerifier {
  /**
   * A helper class for verifying multiple message responses.
   */

  private TSIG key;
  private HMAC verifier;
  private int nresponses;
  private int lastsigned;
  private TSIGRecord lastTSIG;

  /** Creates an object to verify a multiple message response */
  public
  StreamVerifier(TSIG tsig, TSIGRecord old) {
    key = tsig;
    verifier = new HMAC(key.digest, key.key);
    nresponses = 0;
    lastTSIG = old;
  }

  /**
   * Verifies a TSIG record on an incoming message that is part of a
   * multiple message response.
   * TSIG records must be present on the first and last messages, and
   * at least every 100 records in between.
   * After calling this routine, Message.isVerified() may be called on
   * this message.
   * @param m The message
   * @param b The message in unparsed form
   * @return The result of the verification (as an Rcode)
   * @see Rcode
   */
  public int
  verify(Message m, byte [] b) {
    TSIGRecord tsig = m.getTSIG();
 
    nresponses++;

    if (nresponses == 1) {
      int result = key.verify(m, b, lastTSIG);
      if (result == Rcode.NOERROR) {
        byte [] signature = tsig.getSignature();
        DNSOutput out = new DNSOutput();
        out.writeU16(signature.length);
        verifier.update(out.toByteArray());
        verifier.update(signature);
      }
      lastTSIG = tsig;
      return result;
    }

    if (tsig != null)
      m.getHeader().decCount(Section.ADDITIONAL);
    byte [] header = m.getHeader().toWire();
    if (tsig != null)
      m.getHeader().incCount(Section.ADDITIONAL);
    verifier.update(header);

    int len;
    if (tsig == null)
      len = b.length - header.length;
    else
      len = m.tsigstart - header.length;
    verifier.update(b, header.length, len);

    if (tsig != null) {
      lastsigned = nresponses;
      lastTSIG = tsig;
    }
    else {
      boolean required = (nresponses - lastsigned >= 100);
      if (required) {
        m.tsigState = Message.TSIG_FAILED;
        return Rcode.FORMERR;
      } else {
        m.tsigState = Message.TSIG_INTERMEDIATE;
        return Rcode.NOERROR;
      }
    }

    if (!tsig.getName().equals(key.name) ||
        !tsig.getAlgorithm().equals(key.alg))
    {
      if (Options.check("verbose"))
        System.err.println("BADKEY failure");
      m.tsigState = Message.TSIG_FAILED;
      return Rcode.BADKEY;
    }

    DNSOutput out = new DNSOutput();
    long time = tsig.getTimeSigned().getTime() / 1000;
    int timeHigh = (int) (time >> 32);
    long timeLow = (time & 0xFFFFFFFFL);
    out.writeU16(timeHigh);
    out.writeU32(timeLow);
    out.writeU16(tsig.getFudge());
    verifier.update(out.toByteArray());

    if (verifier.verify(tsig.getSignature()) == false) {
      if (Options.check("verbose"))
        System.err.println("BADSIG failure");
      return Rcode.BADSIG;
    }

    verifier.clear();
    out = new DNSOutput();
    out.writeU16(tsig.getSignature().length);
    verifier.update(out.toByteArray());
    verifier.update(tsig.getSignature());

    return Rcode.NOERROR;
  }
}

}
TOP

Related Classes of org.xbill.DNS.TSIG

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.