//////////////////////////////////////////////////////////////////////////// 
// 
// Copyright (C) DSTC Pty Ltd (ACN 052 372 577) 1993, 1994, 1995.
// Unpublished work.  All Rights Reserved.
// 
// The software contained on this media is the property of the
// DSTC Pty Ltd.  Use of this software is strictly in accordance
// with the license agreement in the accompanying LICENSE.DOC 
// file. If your distribution of this software does not contain 
// a LICENSE.DOC file then you have no rights to use this 
// software in any manner and should contact DSTC at the address 
// below to determine an appropriate licensing arrangement.
// 
//      DSTC Pty Ltd
//      Level 7, GP South
//      University of Queensland
//      St Lucia, 4072
//      Australia
//      Tel: +61 7 3365 4310
//      Fax: +61 7 3365 4311
//      Email: jcsi@dstc.qut.edu.au
// 
// This software is being provided "AS IS" without warranty of
// any kind.  In no event shall DSTC Pty Ltd be liable for
// damage of any kind arising out of or in connection with
// the use or performance of this software.
// 
//////////////////////////////////////////////////////////////////////////// 

package com.dstc.security.kerberos.gssapi;

import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.security.SecureRandom;
import java.security.MessageDigest;
import java.util.Properties;
import java.util.Vector;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;

import com.dstc.security.asn1.Asn1;
import com.dstc.security.asn1.Explicit;
import com.dstc.security.kerberos.Kerberos;
import com.dstc.security.kerberos.ApplicationResponse;
import com.dstc.security.kerberos.ApplicationRequest;
import com.dstc.security.kerberos.KerberosCredential;
import com.dstc.security.kerberos.KerberosError;
import com.dstc.security.kerberos.KerberosException;
import com.dstc.security.kerberos.creds.KerberosTicket;
import com.dstc.security.kerberos.creds.CredentialStore;
import com.dstc.security.kerberos.creds.Credential;
import com.dstc.security.kerberos.creds.KeyTab;
import com.dstc.security.kerberos.creds.KeyTabEntry;
import com.dstc.security.kerberos.crypto.KerberosKey;
import com.dstc.security.kerberos.crypto.KerberosCipher;
import com.dstc.security.kerberos.v5.base.APOptions;
import com.dstc.security.kerberos.v5.base.KerberosTime;
import com.dstc.security.kerberos.v5.base.PrincipalName;

/**
 * This class represents the GSS-API security context and its associated
 * operations. Security contexts are established between peers using
 * locally acquired credentials. Multiple contexts may exist
 * simultaneously between a pair of peers, using the same or different
 * set of credentials. GSS-API functions in a manner independent of the
 * underlying transport protocol and depends on its calling application
 * to transport its tokens between peers.
 *
 * <p>The GSSContext object can be thought of as having 3 implicit states:
 * before it is established, during its context establishment, and after
 * a fully established context exists.
 *
 * <p>Before the context establishment phase is initiated, the context
 * initiator may request specific characteristics desired of the
 * established context. These can be set using the set methods. After
 * the context is established, the caller can check the actual
 * characteristic and services offered by the context using the query
 * methods.
 *
 * <p>The context establishment phase begins with the first call to the
 * init method by the context initiator. During this phase the init and
 * accept methods will produce GSS-API authentication tokens which the
 * calling application needs to send to its peer. The init and accept
 * methods may return a CONTINUE_NEEDED code which indicates that a
 * token is needed from its peer in order to continue the context
 * establishment phase. A return code of COMPLETE signals that the local
 * end of the context is established. This may still require that a
 * token be sent to the peer, depending if one is produced by GSS-API.
 * The isEstablished method can also be used to determine if the local
 * end of the context has been fully established. During the context
 * establishment phase, the isProtReady method may be called to
 * determine if the context can be used for the per-message operations.
 * This allows implementation to use per-message operations on contexts
 * which aren't fully established.
 *
 * <p>After the context has been established or the isProtReady method
 * returns "true", the query routines can be invoked to determine the
 * actual characteristics and services of the established context. The
 * application can also start using the per-message methods of wrap and
 * getMIC to obtain cryptographic operations on application supplied
 * data.
 *
 * @author Anthony Ho
 * @author Ming Yung
 */
public class GSSContext
{
  public static final int INDEFINITE = Integer.MAX_VALUE;
  public static final int COMPLETE = 0;
  public static final int CONTINUE_NEEDED = 1;

  private static final int UNINITIALIZED = 0x00;
  private static final int CLIENT_AUTHENTICATED = 0x01;
  private static final int SERVER_AUTHENTICATED = 0x02;

  private static final int DELEG_FLAG = 1;
  private static final int MUTUAL_FLAG = 2;
  private static final int REPLAY_FLAG = 4;
  private static final int SEQUENCE_FLAG = 8;
  private static final int CONF_FLAG = 16;
  private static final int INTEG_FLAG = 32;
  private static final int ANON_FLAG = 64;
  private static final int PROT_READY_FLAG = 128;
  private static final int TRANS_FLAG = 256;

  private static final byte[] DES_MAC_MD5 = {0x00, 0x00};
  private static final byte[] DES_CBC = {0x00, 0x00};
  private static final byte[] NONE = {(byte)0xff, (byte)0xff};
  private static final byte[] MIC_FILLER = {(byte)0xff, (byte)0xff,
                                            (byte)0xff, (byte)0xff};
  private static final byte[] WRAP_FILLER = {(byte)0xff, (byte)0xff};
  private static final byte[] INITIATOR = {0x00, 0x00, 0x00, 0x00};
  private static final byte[] ACCEPTOR = {(byte)0xff, (byte)0xff,
                                          (byte)0xff, (byte)0xff};
  private static final int BLOCKSIZE = 8;

  private static final int INITIATE = 0;
  private static final int ACCEPT = 1;

  private GSSName peer;
  private int role;
  private Oid mechOid;
  private GSSCredential myCred;
  private GSSCredential delegCred;
  private int startTime;
  private int lifetime;
  private int state;
  private int sendSeqNum;
  private int recSeqNum;
  private byte[] cksumBytes;
  private KerberosKey contextKey = null;
  private SecureRandom rand = new SecureRandom(new byte[8]);

  // Kerberos V specific
  private Date ctime;
  private int cusec;

  private static final int KRB5_CKSUM_TYPE = 0x8003;
  private static final int FLAGS_BYTE = 20;
  private Kerberos kerberos = Kerberos.getDefault();
  private Credential serviceCred = null;
  private KerberosCipher krbCipher = null;

  /**
   * Constructor for creating a context on the initiator's side. Context
   * flags may be modified through the mutator methods prior to calling
   * init.
   */
  public GSSContext(GSSName peer, Oid mechOid, GSSCredential myCred,
                    int lifetime) throws GSSException
  {
    // Use default mechanism if none given
    if (mechOid == null)
      mechOid = GSSManager.getDefaultMech();

    // Use default credential if none given
    if (myCred == null)
      myCred = new GSSCredential(null, lifetime, mechOid,
                                 GSSCredential.INITIATE_ONLY);

    // Check credential can be used for context initiation
    else if (myCred.getUsage() == GSSCredential.ACCEPT_ONLY)
      throw new GSSException(GSSException.NO_CRED);

    // One block for each supported mechanism
    if (mechOid.equals(GSSManager.KRB5))
    {
      this.mechOid = GSSManager.KRB5;

      // Initialise checksum
      this.cksumBytes = new byte[24];
      this.cksumBytes[0] = (byte)16;

      // Assert peer over Kerberos V mechanism if not already
      if (peer.getStringNameType() != GSSName.KRB5_NT_PRINCIPAL_NAME)
        this.peer = peer.canonicalize(GSSManager.KRB5);
      else
        this.peer = peer;
    }
    else
      throw new GSSException(GSSException.BAD_MECH);

    this.role = INITIATE;
    this.myCred = myCred;
    this.lifetime = this.myCred.getRemainingLifetime();
    this.state = UNINITIALIZED;
  }

  /**
   * Constructor for creating a context on the acceptor' side. The
   * context's properties will be determined from the input token supplied
   * to the accept method.
   */
  public GSSContext(GSSCredential myCred) throws GSSException
  {
    // Use default credential if none given
    if (myCred == null)
      myCred = new GSSCredential(GSSCredential.ACCEPT_ONLY);

    // Check credential can be used for context acceptance
    else if (myCred.getUsage() == GSSCredential.INITIATE_ONLY)
      throw new GSSException(GSSException.NO_CRED);

    this.role = ACCEPT;
    this.myCred = myCred;
    this.lifetime = this.myCred.getRemainingLifetime();
    this.state = UNINITIALIZED;
  }

  /**
   * Constructor for creating a previously exported context. The context
   * properties will be determined from the input token and can't be
   * modified through the set methods.
   */
  public GSSContext(byte[] interProcessToken) throws GSSException
  {
  
  }

  /**
   * Called by the context initiator to start the context creation
   * process. This is equivalent to the stream based method except that
   * the token buffers are handled as byte arrays instead of using stream
   * objects. This method may return an output token which the application
   * will need to send to the peer for processing by the accept call.
   * "null" return value indicates that no token needs to be sent to the
   * peer. The application can call isEstablished to determine if the
   * context establishment phase is complete for this peer. A return value
   * of "false" from isEstablished indicates that more tokens are expected
   * to be supplied to the init method. Please note that the init method
   * may return a token for the peer, and isEstablished return "true"
   * also. This indicates that the token needs to be sent to the peer, but
   * the local end of the context is now fully established.
   *
   * <p>Upon completion of the context establishment, the available context
   * options may be queried through the get methods.
   */
  public byte[] init(byte inputBuf[], int offset, int len) throws GSSException
  {
    try
    {
      if (state == UNINITIALIZED)
      {
        String serverName = this.peer.toString();
        String serviceName = serverName.substring(0, serverName.indexOf('@'));

        String realm = kerberos.getKerberosContext().getRealm();

        serviceCred 
          = kerberos.getServiceTicket(myCred.getTGTCredential(), serverName);

        // Get session key
        this.contextKey 
          = new KerberosKey(serviceCred.getKeyType(), 
              serviceCred.getKeyBytes());

        // Set flags required by Kerberos V

        // Kerberos V always uses integrity services
        this.cksumBytes[FLAGS_BYTE] |= INTEG_FLAG;

        // Kerberos V requires mutual authentication
        // for sequence detection
        if (this.getSequenceDetState())
          this.cksumBytes[FLAGS_BYTE] |= MUTUAL_FLAG;

        this.cksumBytes[FLAGS_BYTE] |= PROT_READY_FLAG;
        this.cksumBytes[FLAGS_BYTE + 1] |= (TRANS_FLAG >>> 8);

        // Save current time as start time
        this.startTime = (int)(System.currentTimeMillis() / 1000);

        if (this.getCredDelegState())
        {
          String host = serviceName.substring(serviceName.indexOf('/') + 1);
          String hostAddr = InetAddress.getByName(host).getHostAddress();

          // Create vector containing credential to be delegated
          Vector creds = new Vector();
          try
          {
            creds.addElement(kerberos.getDelegationCredential(hostAddr));
          }
          catch (Exception e)
          {
            throw new GSSException(GSSException.FAILURE);
          }

          // Create delegation bytes
          KerberosCredential krbCred = new KerberosCredential(contextKey, rand);
          krbCred.setCredentials(creds);
          krbCred.encrypt();

          byte[] deleg = krbCred.getEncoded();
          byte[] dlgOpt = this.intToBytes(1, 2);
          byte[] dlgth = this.intToBytes(deleg.length, 2);

          // Extend checksum to handle delegation
          byte[] delegCksum = new byte[28 + deleg.length];
          System.arraycopy(cksumBytes, 0, delegCksum, 0, 24);
          System.arraycopy(dlgOpt, 0, delegCksum, 24, 2);
          System.arraycopy(dlgth, 0, delegCksum, 26, 2);
          System.arraycopy(deleg, 0, delegCksum, 28, deleg.length);
          this.cksumBytes = delegCksum;
        }

        this.sendSeqNum = (int)(Math.random() * 16384);

        this.ctime = Calendar.getInstance().getTime();
        this.cusec = 0;

        ApplicationRequest apReq = new ApplicationRequest(serviceCred, rand);

        apReq.setUseSessionKey(false);
        apReq.setMutualAuthRequired(this.getMutualAuthState());
        apReq.setRealm(realm);
        apReq.setClientName(this.myCred.getGSSName().export());
        apReq.setChecksumType(KRB5_CKSUM_TYPE);
        apReq.setChecksum(cksumBytes);
        apReq.setCusec(this.cusec);
        apReq.setClientTime(this.ctime);
        apReq.setSequenceNumber(this.sendSeqNum);
        apReq.encrypt();

        this.state = CLIENT_AUTHENTICATED;

        return (new InitialToken(Token.KRB_AP_REQ, apReq)).encode();
      }
      else if (state == CLIENT_AUTHENTICATED)
      {
        // Extract token from input message
        InitialToken token;
        try
        {
          byte[] inBuf = new byte[len];
          System.arraycopy(inputBuf, offset, inBuf, 0, len);
          token = new InitialToken(inBuf);
        }
        catch (Exception e)
        {
          throw new GSSException(GSSException.DEFECTIVE_TOKEN);
        }
        if (token == null)
          throw new GSSException(GSSException.DEFECTIVE_TOKEN);

        // Check for error message
        try
        {
          KerberosError error = (KerberosError)(token.getKrbMessage());
          throw new KerberosException(error.getMessage());
        }
        catch (ClassCastException e)
        {
          //ignore
        }

        // Must be a good reply
        // Decode the reply
        ApplicationResponse apRep = (ApplicationResponse)token.getKrbMessage();

        apRep.initDecrypt(serviceCred);
        apRep.decrypt();
        
        this.recSeqNum = apRep.getSequenceNumber();

        // Authenticate server
        // XXX: Dates are converted to String to prevent intermittent
        //      error where same Dates do not equal.
        if ((this.cusec == apRep.getCusec())
            && this.ctime.toString().equals(
                 apRep.getClientTime().toString()))
        {
          this.state = SERVER_AUTHENTICATED;
          return null;
        }
        else
          throw new GSSException(GSSException.FAILURE);
      }
      else
        return null;
    }
    catch (GSSException e)
    {
      throw e;
    }
    catch (Exception e)
    {
      e.printStackTrace();
      throw new GSSException(GSSException.FAILURE);
    }
  }

  /**
   * Called by the context initiator to start the context creation
   * process. This is equivalent to the byte array based method.  This
   * method may write an output token to the outputBuf, which the
   * application will need to send to the peer for processing by the
   * accept call. 0 bytes written to the output stream indicate that no
   * token needs to be sent to the peer. The method will return either
   * COMPLETE or CONTINUE_NEEDED indicating the status of the current
   * context. A return value of COMPLETE indicates that the context
   * establishment phase is complete for this peer, while CONTINUE_NEEDED
   * means that another token is expected from the peer. The isEstablished
   * method can also be used to determine this state. Note that it is
   * possible to have a token for the peer while this method returns
   * COMPLETE. This indicates that the local end of the context is
   * established, but the token needs to be sent to the peer to complete
   * the context establishment.
   */
  public int init(InputStream inputBuf, OutputStream outputBuf)
                  throws GSSException
  {
    try
    {
      byte[] token = Token.readToken(inputBuf);
      token = this.init(token, 0, token.length);
      (new InitialToken(token)).write(outputBuf);
      if (this.isEstablished())
        return COMPLETE;
      else
        return CONTINUE_NEEDED;
    }
    catch (GSSException e)
    {
      throw e;
    }
    catch (Exception e)
    {
      throw new GSSException(GSSException.FAILURE);
    }
  }

  /**
   * Called by the context acceptor upon receiving a token from the peer.
   * This call is equivalent to the stream based method except that the
   * token buffers are handled as byte arrays instead of using stream
   * objects.
   *
   * <p>This method may return an output token which the application will
   * need to send to the peer for further processing by the init call.
   * "null" return value indicates that no token needs to be sent to the
   * peer. The application can call isEstablished to determine if the
   * context establishment phase is complete for this peer. A return value
   * of "false" from isEstablished indicates that more tokens are expected
   * to be supplied to this method.
   *
   * <p>Please note that the accept method may return a token for the peer,
   * and isEstablished return "true" also. This indicates that the token
   * needs to be sent to the peer, but the local end of the context is now
   * fully established.
   *
   * <p>Upon completion of the context establishment, the available context
   * options may be queried through the get methods.
   */
  public byte[] accept(byte inTok[], int offset, int len)
                       throws GSSException
  {
    try
    {
      // Get the server key from the keytab
      KeyTab keyTab = KeyTab.getDefault();

      String serverName = myCred.getGSSName().toString();

      KeyTabEntry keyTabEntry = keyTab.getKeyTabEntry(serverName);

      // Check that server is in the keytab
      if (keyTabEntry == null)
        throw new GSSException(GSSException.FAILURE);

      byte[] key = keyTabEntry.getKeyBytes();

      // Extract token from input message
      InitialToken token;
      try
      {
        byte[] inBuf = new byte[len];
        System.arraycopy(inTok, offset, inBuf, 0, len);
        token = new InitialToken(inBuf);
      }
      catch (Exception e)
      {
        e.printStackTrace();
        throw new GSSException(GSSException.DEFECTIVE_TOKEN);
      }
      if (token == null)
        throw new GSSException(GSSException.DEFECTIVE_TOKEN);

      ApplicationRequest apReq = (ApplicationRequest)token.getKrbMessage();

      // Retrieve the ticket
      KerberosTicket ticket = apReq.getTicket();

      // Decrypt the encrypted ticket part with the server key
      // to get the session key
      ticket.initDecrypt(keyTabEntry);
      ticket.decrypt();

      contextKey = ticket.getContextKey();
      apReq.initDecrypt(contextKey);
      apReq.decrypt();

      if (apReq.getSubKeyBytes() != null)
      {
        contextKey.setKeyBytes(apReq.getSubKeyBytes());
      }

      // Get checksum bytes and client name
      this.cksumBytes = apReq.getChecksumBytes();
      this.peer = new GSSName(apReq.getEncodedClientName(),
                              GSSName.NT_EXPORT_NAME);

      // Get delegated credential if present
      if (this.getCredDelegState())
      {
        // 26..27 Credential length
        byte[] dlgth = new byte[2];
        System.arraycopy(cksumBytes, 26, dlgth, 0, 2);
        int n = this.bytesToInt(dlgth, 2);

        // 28..n Delegated credential
        byte[] deleg = new byte[n];
        System.arraycopy(cksumBytes, 28, deleg, 0, n);

        KerberosCredential krbCred = new KerberosCredential(deleg);
        krbCred.initDecrypt(contextKey);
        krbCred.decrypt();

        Credential cred = (Credential)(krbCred.getCredentials().firstElement());
        this.delegCred = new GSSCredential(this.peer,
                                           GSSCredential.INITIATE_ONLY, cred);
      }

      // Save current time as start time
      this.startTime = (int)(System.currentTimeMillis() / 1000);

      this.state = CLIENT_AUTHENTICATED;

      if (this.getMutualAuthState())
      {
        this.sendSeqNum = (int)(Math.random() * 16384);
        this.recSeqNum = apReq.getSequenceNumber();

        // Construct an APRep
        ApplicationResponse apRep = new ApplicationResponse(contextKey, rand);

        apRep.setClientTime(apReq.getClientTime());
        apRep.setCusec(apReq.getCusec());
        apRep.setSequenceNumber(this.sendSeqNum);
        apRep.encrypt();

        return (new InitialToken(Token.KRB_AP_REP, apRep)).encode();
      }
      else
        return null;
    }
    catch (GSSException e)
    {
      throw e;
    }
    catch (Exception e)
    {
      e.printStackTrace();
      throw new GSSException(GSSException.FAILURE);
    }
  }

  /**
   * Called by the context acceptor upon receiving a token from the peer.
   * This call is equivalent to the byte array method.  It may write an
   * output token to the outputBuf, which the application will need to
   * send to the peer for processing by its init method. 0 bytes written
   * to the output stream indicate that no token needs to be sent to the
   * peer. The method will return either COMPLETE or CONTINUE_NEEDED
   * indicating the status of the current context. A return value of
   * COMPLETE indicates that the context establishment phase is complete
   * for this peer, while CONTINUE_NEEDED means that another token is
   * expected from the peer. The isEstablished method can also be used to
   * determine this state.  Note that it is possible to have a token for
   * the peer while this method returns COMPLETE. This indicates that the
   * local end of the context is established, but the token needs to be
   * sent to the peer to complete the context establishment.
   *
   * <p>The GSS-API authentication tokens contain a definitive start and end.
   * This method will attempt to read one of these tokens per invocation,
   * and may block on the stream if only part of the token is available.
   *
   * Upon completion of the context establishment, the available context
   * options may be queried through the get methods.
   */
  public int accept(InputStream inputBuf, OutputStream outputBuf)
                    throws GSSException
  {
    try
    {
      byte[] token = Token.readToken(inputBuf);
      token = this.accept(token, 0, token.length);
      (new InitialToken(token)).write(outputBuf);
      if (this.isEstablished())
        return COMPLETE;
      else
        return CONTINUE_NEEDED;
    }
    catch (GSSException e)
    {
      throw e;
    }
    catch (Exception e)
    {
      throw new GSSException(GSSException.FAILURE);
    }
  }

  /**
   * Returns "true" if this is a fully established context. Used after the
   * init and accept methods to check if more tokens are needed from the
   * peer.
   */
  public boolean isEstablished()
  {
    if ((this.role == INITIATE) && this.getMutualAuthState())
      return (state == SERVER_AUTHENTICATED);
    else
      return (state == CLIENT_AUTHENTICATED);
  }

  /**
   * Releases any system resources and cryptographic information stored in
   * the context object. This will invalidate the context.
   */
  public void dispose() throws GSSException
  {
    this.myCred = null;
    this.delegCred = null;
    this.contextKey = null;
  }

  /**
   * Returns the maximum message size that, if presented to the wrap
   * method with the same confReq and qop parameters, will result in an
   * output token containing no more than the maxTokenSize bytes.
   *
   * <p>This call is intended for use by applications that communicate over
   * protocols that impose a maximum message size.  It enables the
   * application to fragment messages prior to applying protection.
   *
   * <p>GSS-API implementations are recommended but not required to detect
   * invalid QOP values when getWrapSizeLimit is called. This routine
   * guarantees only a maximum message size, not the availability of
   * specific QOP values for message protection.
   *
   * <p>Successful completion of this call does not guarantee that wrap will
   * be able to protect a message of the computed length, since this
   * ability may depend on the availability of system resources at the
   * time that wrap is called.  However, if the implementation itself
   * imposes an upper limit on the length of messages that may be
   * processed by wrap, the implementation should not return a value that
   * is greater than this length.
   */
  public int getWrapSizeLimit(int qop, boolean confReq,
                              int maxTokenSize) throws GSSException
  {
    return 0;
  }

  /**
   * Allows to apply per-message security services over the established
   * security context. The method will return a token with a cryptographic
   * MIC and may optionally encrypt the specified inBuf.  This method is
   * equivalent in functionality to its stream counterpart. The returned
   * byte array will contain both the MIC and the message.  The msgProp
   * object is used to specify a QOP value which selects cryptographic
   * algorithms, and a privacy service, if supported by the chosen
   * mechanism.
   *
   * <p>Since some application-level protocols may wish to use tokens emitted
   * by wrap to provide "secure framing", implementations should support
   * the wrapping of zero-length messages.
   *
   * <p>The application will be responsible for sending the token to the
   * peer.
   */
  public byte[] wrap(byte inBuf[], int offset, int len,
                     MessageProp msgProp) throws GSSException
  {
    // Check if per-message operations can be applied
    if (!this.isProtReady())
      throw new GSSException(GSSException.UNAVAILABLE);

    // Check if context has expired
    if (this.getLifetime() == 0)
      throw new GSSException(GSSException.CONTEXT_EXPIRED);

    try
    {
      byte[] plainText = new byte[len];
      System.arraycopy(inBuf, offset, plainText, 0, len);

      ByteArrayOutputStream bos = new ByteArrayOutputStream();

      bos.write(Token.WRAP);

      bos.write(DES_MAC_MD5);

      if (this.getConfState() && msgProp.getPrivacy())
        bos.write(DES_CBC);
      else
        bos.write(NONE);

      bos.write(WRAP_FILLER);
  
      // PlainText is padded with PKCS5Padding and a confounder  
      byte[] confounder = new byte[BLOCKSIZE];
  
      int size = plainText.length % BLOCKSIZE;
      int extra = (size != 0) ?  BLOCKSIZE - size : BLOCKSIZE;
  
      byte[] paddedPlainText = new byte[plainText.length + BLOCKSIZE + extra];
      System.arraycopy(confounder, 0, paddedPlainText, 0, BLOCKSIZE);
      System.arraycopy(plainText, 0, 
                       paddedPlainText, BLOCKSIZE, plainText.length);
  
      for (int i = 0; i < extra; i++)
        paddedPlainText[plainText.length + BLOCKSIZE + i] = (byte)extra;
  
      // Generate secret key
      SecretKeyFactory keyFact = SecretKeyFactory.getInstance("DES");
      SecretKey key 
        = keyFact.generateSecret(new DESKeySpec(contextKey.getKeyBytes()));
  
      // Checksum
      byte[] cksum = this.computeChecksum(key, bos.toByteArray(),
                                          paddedPlainText);
  
      // SeqNum encryption : DES-CBC with the session key and IV from SND_CKSUM

      // SND_SEQ
      bos.write(this.encryptSeqNum(key, cksum));
  
      // SGN_CKSUM
      bos.write(cksum);
  
      // Data
      if (this.getConfState() && msgProp.getPrivacy())
      // Encrypt the padded plainText data
      {
        // Data encryption with DES-CBC needs a modified session key
        byte[] encryptionKey = new byte[8];

        for (int i = 0; i < 8; i++)
          encryptionKey[i] = (byte)(contextKey.getKeyBytes()[i] ^ (byte)0xf0);

        Cipher cipher = Cipher.getInstance("DES/CBC/NoPadding");
        cipher.init(Cipher.ENCRYPT_MODE,
                    keyFact.generateSecret(new DESKeySpec(encryptionKey)),
                    new IvParameterSpec(new byte[BLOCKSIZE]), rand);
        byte[] encrypted = cipher.doFinal(paddedPlainText);
        bos.write(encrypted);

        // Set message properties
        msgProp.setQOP(0);
        msgProp.setPrivacy(true);
      }
      else
      {
        bos.write(paddedPlainText);

        // Set message properties
        msgProp.setQOP(0);
        msgProp.setPrivacy(false);
      }

      return (new PerMessageToken(Token.WRAP, bos.toByteArray())).encode();
    }
    catch (Exception e)
    {
      e.printStackTrace();
      throw new GSSException(GSSException.FAILURE);
    }
  }

  /**
   * Allows to apply per-message security services over the established
   * security context. The method will produce a token with a
   * cryptographic MIC and may optionally encrypt the specified inBuf.
   * The outBuf will contain both the MIC and the message.  The msgProp
   * object is used to specify a QOP value to select cryptographic
   * algorithms, and a privacy service, if supported by the chosen
   * mechanism.
   *
   * <p>Since some application-level protocols may wish to use tokens emitted
   * by wrap to provide "secure framing", implementations should support
   * the wrapping of zero-length messages.
   *
   * The application will be responsible for sending the token to the
   * peer.
   */
  public void wrap(InputStream inBuf, OutputStream outBuf,
                   MessageProp msgProp) throws GSSException
  {
    try
    {
      byte[] token = Token.readToken(inBuf);
      token = this.wrap(token, 0, token.length, msgProp);
      (new PerMessageToken(token)).write(outBuf);
    }
    catch (GSSException e)
    {
      throw e;
    }
    catch (Exception e)
    {
      throw new GSSException(GSSException.FAILURE);
    }
  }

  /**
   * Used by the peer application to process tokens generated with the
   * wrap call.  This call is equal in functionality to its stream
   * counterpart. The method will return the message supplied in the peer
   * application to the wrap call, verifying the embedded MIC.  The
   * msgProp instance will indicate whether the message was encrypted and
   * will contain the QOP indicating the strength of protection that was
   * used to provide the confidentiality and integrity services.
   *
   * Since some application-level protocols may wish to use tokens emitted
   * by wrap to provide "secure framing", implementations should support
   * the wrapping and unwrapping of zero-length messages.
   */
  public byte[] unwrap(byte[] inBuf, int offset, int len,
                       MessageProp msgProp) throws GSSException
  {
    // Check if per-message operations can be applied
    if (!this.isProtReady())
      throw new GSSException(GSSException.UNAVAILABLE);

    // Check if context has expired
    if (this.getLifetime() == 0)
      throw new GSSException(GSSException.CONTEXT_EXPIRED);

    try
    {
      // Extract token from input message
      byte[] innerToken;
      try
      {
        byte[] cipherText = new byte[len];
        System.arraycopy(inBuf, offset, cipherText, 0, len);
        innerToken = new PerMessageToken(cipherText).getInnerToken();
      }
      catch (Exception e)
      {
        throw new GSSException(GSSException.DEFECTIVE_TOKEN);
      }
      if (innerToken == null)
        throw new GSSException(GSSException.DEFECTIVE_TOKEN);

      // 8..15 SND_SEQ
      byte[] encSeq = new byte[8];
      System.arraycopy(innerToken, 8, encSeq, 0, 8);
      
      // 16..23 SGN_CKSUM
      byte[] cksum = new byte[8];
      System.arraycopy(innerToken, 16, cksum, 0, 8);

      // 24..length Data
      byte[] data = new byte[innerToken.length - 24];
      System.arraycopy(innerToken, 24, data, 0, data.length);
  
      // Generate secret key
      SecretKeyFactory keyFact = SecretKeyFactory.getInstance("DES");
      SecretKey key 
        = keyFact.generateSecret(new DESKeySpec(contextKey.getKeyBytes()));
  
      // Decrypt the encrypted padded plainText data if necessary
      byte[] paddedPlainText;
      byte[] twoBytes = new byte[2];
      System.arraycopy(innerToken, 4, twoBytes, 0, 2);

      if (Arrays.equals(twoBytes, NONE))
      {
        paddedPlainText = data;

        // Set message properties
        msgProp.setQOP(0);
        msgProp.setPrivacy(false);
      }
      else
      {
        // Data decryption with DES-CBC needs a modified session key
        byte[] encryptionKey = new byte[8];

        for (int i = 0; i < 8; i++)
          encryptionKey[i] = (byte)(contextKey.getKeyBytes()[i] ^ (byte)0xf0);

        Cipher cipher = Cipher.getInstance("DES/CBC/Zeroes");
        cipher.init(Cipher.DECRYPT_MODE,
                    keyFact.generateSecret(new DESKeySpec(encryptionKey)),
                    new IvParameterSpec(new byte[BLOCKSIZE]), rand);
        paddedPlainText = cipher.doFinal(data);

        // Set message properties
        msgProp.setQOP(0);
        msgProp.setPrivacy(true);
      }

      // Verify checksum by re-computing and comparing
      byte[] header = new byte[8];
      System.arraycopy(innerToken, 0, header, 0, 8);
       
      if (!Arrays.equals(cksum,
                         this.computeChecksum(key, header, paddedPlainText)))
        throw new GSSException(GSSException.BAD_MIC);

      // Decrypt encrypted sequence number
      this.decryptSeqNum(key, cksum, encSeq, msgProp);

      int n = paddedPlainText.length;
      byte[] retval = new byte[n - paddedPlainText[n - 1] - 8];
      System.arraycopy(paddedPlainText, 8, retval, 0, retval.length);

      return retval;
    }
    catch (GSSException e)
    {
      throw e;
    }
    catch (Exception e)
    {
      e.printStackTrace();
      throw new GSSException(GSSException.FAILURE);
    }
  }

  /**
   * Used by the peer application to process tokens generated with the
   * wrap call.  This call is equal in functionality to its byte array
   * counterpart. It will produce the message supplied in the peer
   * application to the wrap call, verifying the embedded MIC.  The
   * msgProp parameter will indicate whether the message was encrypted and
   * will contain the QOP indicating the strength of protection that was
   * used to provide the confidentiality and integrity services. The
   * msgProp object will also contain the supplementary status information
   * for the token.
   *
   * <p>Since some application-level protocols may wish to use tokens emitted
   * by wrap to provide "secure framing", implementations should support
   * the wrapping and unwrapping of zero-length messages.
   */
  public void unwrap(InputStream inBuf, OutputStream outBuf,
                     MessageProp msgProp) throws GSSException
  {
    try
    {
      byte[] token = Token.readToken(inBuf);
      token = this.unwrap(token, 0, token.length, msgProp);
      (new PerMessageToken(token)).write(outBuf);
    }
    catch (GSSException e)
    {
      throw e;
    }
    catch (Exception e)
    {
      throw new GSSException(GSSException.FAILURE);
    }
  }

  /**
   * Returns a token containing a cryptographic MIC for the supplied
   * message,  for transfer to the peer application.  Unlike wrap, which
   * encapsulates the user message in the returned token, only the message
   * MIC is returned in the output token. This method is identical in
   * functionality to its stream counterpart.
   *
   * Note that privacy can only be applied through the wrap call.
   *
   * Since some application-level protocols may wish to use tokens emitted
   * by getMIC to provide "secure framing", implementations should support
   * derivation of MICs from zero-length messages.
   */
  public byte[] getMIC(byte[] inMsg, int offset, int len,
                       MessageProp msgProp) throws GSSException
  {
    // Check if per-message operations can be applied
    if (!this.isProtReady())
      throw new GSSException(GSSException.UNAVAILABLE);

    // Check if context has expired
    if (this.getLifetime() == 0)
      throw new GSSException(GSSException.CONTEXT_EXPIRED);

    try
    {
      byte[] data = new byte[len];
      System.arraycopy(inMsg, offset, data, 0, len);

      ByteArrayOutputStream bos = new ByteArrayOutputStream();

      // 0..1 TOK_ID
      bos.write(Token.MIC);

      // 2..3 SGN_ALG
      bos.write(DES_MAC_MD5);

      // 4..7 Filler
      bos.write(MIC_FILLER);

      // Generate secret key
      SecretKeyFactory keyFact = SecretKeyFactory.getInstance("DES");
      SecretKey key 
        = keyFact.generateSecret(new DESKeySpec(contextKey.getKeyBytes()));

      // Checksum
      byte[] cksum = this.computeChecksum(key, bos.toByteArray(), data);

      // 8..15 SND_SEQ
      bos.write(this.encryptSeqNum(key, cksum));

      // 16..23 SGN_CKSUM
      bos.write(cksum);

      // Set message properties
      msgProp.setQOP(0);

      return (new PerMessageToken(Token.MIC, bos.toByteArray())).encode();
    }
    catch (Exception e)
    {
      e.printStackTrace();
      throw new GSSException(GSSException.FAILURE);
    }
  }

  /**
   * Produces a token containing a cryptographic MIC for the supplied
   * message, for transfer to the peer application.  Unlike wrap, which
   * encapsulates the user message in the returned token, only the message
   * MIC is produced in the output token. This method is identical in
   * functionality to its byte array counterpart.
   *
   * Note that privacy can only be applied through the wrap call.
   *
   * Since some application-level protocols may wish to use tokens emitted
   * by getMIC to provide "secure framing", implementations should support
   * derivation of MICs from zero-length messages.
   */
  public void getMIC(InputStream inMsg, OutputStream outBuf,
                     MessageProp msgProp) throws GSSException
  {
    try
    {
      byte[] token = Token.readToken(inMsg);
      token = this.getMIC(token, 0, token.length, msgProp);
      (new PerMessageToken(token)).write(outBuf);
    }
    catch (GSSException e)
    {
      throw e;
    }
    catch (Exception e)
    {
      throw new GSSException(GSSException.FAILURE);
    }
  }

  /**
   * Verifies the cryptographic MIC, contained in the token parameter,
   * over the supplied message.  The msgProp parameter will contain the
   * QOP indicating the strength of protection that was applied to the
   * message. This method is equivalent in functionality to its stream
   * counterpart.
   *
   * Since some application-level protocols may wish to use tokens emitted
   * by getMIC to provide "secure framing", implementations should support
   * the calculation and verification of MICs over zero-length messages.
   */
  public void verifyMIC(byte[] inTok, int tokOffset, int tokLen,
                        byte[] inMsg, int msgOffset, int msgLen,
                        MessageProp msgProp) throws GSSException
  {
    // Check if per-message operations can be applied
    if (!this.isProtReady())
      throw new GSSException(GSSException.UNAVAILABLE);

    // Check if context has expired
    if (this.getLifetime() == 0)
      throw new GSSException(GSSException.CONTEXT_EXPIRED);

    try
    {
      // Extract token from input message
      byte[] innerToken;
      try
      {
        byte[] micToken = new byte[tokLen];
        System.arraycopy(inTok, tokOffset, micToken, 0, tokLen);
        innerToken = new PerMessageToken(micToken).getInnerToken();
      }
      catch (Exception e)
      {
        throw new GSSException(GSSException.DEFECTIVE_TOKEN);
      }
      if (innerToken == null)
        throw new GSSException(GSSException.DEFECTIVE_TOKEN);

      byte[] data = new byte[msgLen];
      System.arraycopy(inMsg, msgOffset, data, 0, msgLen);

      // 0..1 TOK_ID

      // 2..3 SGN_ALG

      // 4..7 Filler

      // 8..15 SND_SEQ
      byte[] encSeq = new byte[8];
      System.arraycopy(innerToken, 8, encSeq, 0, 8);

      // 16..23 SGN_CKSUM
      byte[] cksum = new byte[8];
      System.arraycopy(innerToken, 16, cksum, 0, 8);

      // Generate secret key
      SecretKeyFactory keyFact = SecretKeyFactory.getInstance("DES");
      SecretKey key 
        = keyFact.generateSecret(new DESKeySpec(contextKey.getKeyBytes()));

      // Verify checksum by re-computing and comparing
      byte[] header = new byte[8];
      System.arraycopy(innerToken, 0, header, 0, 8);
       
      if (!Arrays.equals(cksum, this.computeChecksum(key, header, data)))
        throw new GSSException(GSSException.BAD_MIC);

      // Decrypt encrypted sequence number
      this.decryptSeqNum(key, cksum, encSeq, msgProp);

      // Set message properties
      msgProp.setQOP(0);
      msgProp.setPrivacy(false);
    }
    catch (GSSException e)
    {
      throw e;
    }
    catch (Exception e)
    {
      e.printStackTrace();
      throw new GSSException(GSSException.FAILURE);
    }
  }

  /**
   * Verifies the cryptographic MIC, contained in the token parameter,
   * over the supplied message.  The msgProp parameter will contain the
   * QOP indicating the strength of protection that was applied to the
   * message. This method is equivalent in functionality to its byte array
   * counterpart.
   *
   * Since some application-level protocols may wish to use tokens emitted
   * by getMIC to provide "secure framing", implementations should support
   * the calculation and verification of MICs over zero-length messages.
   */
  public void verifyMIC(InputStream inTok, InputStream inMsg,
                         MessageProp msgProp) throws GSSException
  {
    try
    {
      byte[] token = Token.readToken(inTok);
      byte[] msg = Token.readToken(inMsg);
      this.verifyMIC(token, 0, token.length, msg, 0, msg.length, msgProp);
    }
    catch (Exception e)
    {
      throw new GSSException(GSSException.FAILURE);
    }
  }

  /**
   * Provided to support the sharing of work between multiple processes.
   * This routine will typically be used by the context-acceptor, in an
   * application where a single process receives incoming connection
   * requests and accepts security contexts over them, then passes the
   * established context to one or more other processes for message
   * exchange.
   *
   * <p>This method deactivates the security context and creates an
   * interprocess token which, when passed to the byte array constructor
   * of the GSSContext class in another process, will re-activate the
   * context in the second process. Only a single instantiation of a given
   * context may be active at any one time; a subsequent attempt by a
   * context exporter to access the exported security context will fail.
   *
   * <p>The implementation may constrain the set of processes by which the
   * interprocess token may be imported, either as a function of local
   * security policy, or as a result of implementation decisions.  For
   * example, some implementations may constrain contexts to be passed
   * only between processes that run under the same account, or which are
   * part of the same process group.
   *
   * <p>The interprocess token may contain security-sensitive information
   * (for example cryptographic keys).  While mechanisms are encouraged to
   * either avoid placing such sensitive information within interprocess
   * tokens, or to encrypt the token before returning it to the
   * application, in a typical GSS-API implementation this may not be
   * possible. Thus the application must take care to protect the
   * interprocess token, and ensure that any process to which the token is
   * transferred is trustworthy.
   */
  public byte[] export() throws GSSException
  {
    // Check if context has expired
    if (this.getLifetime() == 0)
      throw new GSSException(GSSException.CONTEXT_EXPIRED);

    if (this.isTransferable())
      return null;
    else
      throw new GSSException(GSSException.UNAVAILABLE);
  }

  /**
   * Sets the request state of the mutual authentication flag for the
   * context. This method is only valid before the context creation
   * process begins and only for the initiator.
   */
  public void requestMutualAuth(boolean state) throws GSSException
  {
    if ((this.state == UNINITIALIZED) && (this.role == INITIATE))
    {
      if (state)
        this.cksumBytes[FLAGS_BYTE] |= MUTUAL_FLAG;
    }
    else
      throw new GSSException(GSSException.UNAVAILABLE);
  }

  /**
   * Sets the request state of the replay detection service for the
   * context. This method is only valid before the context creation
   * process begins and only for the initiator.
   */
  public void requestReplayDet(boolean state) throws GSSException
  {
    if ((this.state == UNINITIALIZED) && (this.role == INITIATE))
    {
      if (state)
        this.cksumBytes[FLAGS_BYTE] |= REPLAY_FLAG;
    }
    else
      throw new GSSException(GSSException.UNAVAILABLE);
  }

  /**
   * Sets the request state for the sequence checking service of the
   * context. This method is only valid before the context creation
   * process begins and only for the initiator.
   */
  public void requestSequenceDet(boolean state) throws GSSException
  {
    if ((this.state == UNINITIALIZED) && (this.role == INITIATE))
    {
      if (state)
        this.cksumBytes[FLAGS_BYTE] |= SEQUENCE_FLAG;
    }
    else
      throw new GSSException(GSSException.UNAVAILABLE);
  }

  /**
   * Sets the request state for the credential delegation flag for the
   * context. This method is only valid before the context creation
   * process begins and only for the initiator.
   */
  public void requestCredDeleg(boolean state) throws GSSException
  {
    if ((this.state == UNINITIALIZED) && (this.role == INITIATE))
    {
      if (state)
        this.cksumBytes[FLAGS_BYTE] |= DELEG_FLAG;
    }
    else
      throw new GSSException(GSSException.UNAVAILABLE);
  }

  /**
   * Requests anonymous support over the context. This method is only
   * valid before the context creation process begins and only for the
   * initiator.
   */
  public void requestAnonymity(boolean state) throws GSSException
  {
    if ((this.state == UNINITIALIZED) && (this.role == INITIATE))
    {
      if (state)
        // Kerberos V does not support anonymous authentication
        throw new GSSException(GSSException.UNAVAILABLE);
    }
    else
      throw new GSSException(GSSException.UNAVAILABLE);
  }

  /**
   * Requests that confidentiality service be available over the context.
   * This method is only valid before the context creation process begins
   * and only for the initiator.
   */
  public void requestConf(boolean state) throws GSSException
  {
    if ((this.state == UNINITIALIZED) && (this.role == INITIATE))
    {
      if (state)
      {
        this.cksumBytes[FLAGS_BYTE] |= CONF_FLAG;
      }
    }
    else
      throw new GSSException(GSSException.UNAVAILABLE);
  }

  /**
   * Requests that integrity services be available over the context. This
   * method is only valid before the context creation process begins and
   * only for the initiator.
   */
  public void requestInteg(boolean state) throws GSSException
  {
    // Kerberos V requires integrity to always be available
    if ((this.state == UNINITIALIZED) && (this.role == INITIATE) && state)
      this.cksumBytes[FLAGS_BYTE] |= INTEG_FLAG;
    else
      throw new GSSException(GSSException.UNAVAILABLE);
  }

  /**
   * Sets the desired lifetime for the context in seconds. This method is
   * only valid before the context creation process begins and only for
   * the initiator.
   */
  public void requestLifetime(int lifetime) throws GSSException
  {
    if ((this.state == UNINITIALIZED) && (this.role == INITIATE))
      this.lifetime = lifetime;
    else
      throw new GSSException(GSSException.UNAVAILABLE);
  }

  /**
   * Sets the channel bindings to be used during context establishment.
   * This method is only valid before the context creation process begins.
   */
  public void setChannelBinding(ChannelBinding cb) throws GSSException
  {
    // Use of channel bindings unsupported
    throw new GSSException(GSSException.UNAVAILABLE);
  }

  /**
   * Returns the state of the delegated credentials for the context. When
   * issued before context establishment is completed or when the
   * isProtReady method returns "false", it returns the desired state,
   * otherwise it will indicate the actual state over the established
   * context.
   */
  public boolean getCredDelegState()
  {
    return ((this.cksumBytes[FLAGS_BYTE] & DELEG_FLAG) == DELEG_FLAG);
  }

  /**
   * Returns the state of the mutual authentication option for the
   * context. When issued before context establishment completes or when
   * the isProtReady method returns "false", it returns the desired state,
   * otherwise it will indicate the actual state over the established
   * context.
   */
  public boolean getMutualAuthState()
  {
    return ((this.cksumBytes[FLAGS_BYTE] & MUTUAL_FLAG) == MUTUAL_FLAG);
  }

  /**
   * Returns the state of the replay detection option for the context.
   * When issued before context establishment completes or when the
   * isProtReady method returns "false", it returns the desired state,
   * otherwise it will indicate the actual state over the established
   * context.
   */
  public boolean getReplayDetState()
  {
    return ((this.cksumBytes[FLAGS_BYTE] & REPLAY_FLAG) == REPLAY_FLAG);
  }

  /**
   * Returns the state of the sequence detection option for the context.
   * When issued before context establishment completes or when the
   * isProtReady method returns "false", it returns the desired state,
   * otherwise it will indicate the actual state over the established
   * context.
   */
  public boolean getSequenceDetState()
  {
    return ((this.cksumBytes[FLAGS_BYTE] & SEQUENCE_FLAG) == SEQUENCE_FLAG);
  }

  /**
   * Returns "true" if this is an anonymous context. When issued before
   * context establishment completes or when the isProtReady method
   * returns "false", it returns the desired state, otherwise it will
   * indicate the actual state over the established context.
   */
  public boolean getAnonymityState()
  {
    return ((this.cksumBytes[FLAGS_BYTE] & ANON_FLAG) == ANON_FLAG);
  }

  /**
   * Returns "true" if the context is transferable to other processes
   * through the use of the export method. This call is only valid on
   * fully established contexts.
   */
  public boolean isTransferable() throws GSSException
  {
    if (this.isEstablished())
      return ((this.cksumBytes[FLAGS_BYTE + 1] & (TRANS_FLAG >>> 8))
              == (TRANS_FLAG >>> 8));
    else
      throw new GSSException(GSSException.UNAVAILABLE);
  }

  /**
   * Returns "true" if the per message operations can be applied over the
   * context. Some mechanisms may allow the usage of per-message
   * operations before the context is fully established. This will also
   * indicate that the get methods will return actual context state
   * characteristics instead of the desired ones.
   */
  public boolean isProtReady()
  {
    return ((this.cksumBytes[FLAGS_BYTE] & PROT_READY_FLAG)
            == PROT_READY_FLAG);
  }

  /**
   * Returns the confidentiality service state over the context. When
   * issued before context establishment completes or when the isProtReady
   * method returns "false", it returns the desired state, otherwise it
   * will indicate the actual state over the established context.
   */
  public boolean getConfState()
  {
    return ((this.cksumBytes[FLAGS_BYTE] & CONF_FLAG) == CONF_FLAG);
  }

  /**
   * Returns the integrity service state over the context. When issued
   * before context establishment completes or when the isProtReady method
   * returns "false", it returns the desired state, otherwise it will
   * indicate the actual state over the established context.
   */
  public boolean getIntegState()
  {
    return ((this.cksumBytes[FLAGS_BYTE] & INTEG_FLAG) == INTEG_FLAG);
  }

  /**
   * Returns the context lifetime in seconds.  When issued before context
   * establishment completes or when the isProtReady method returns
   * "false", it returns the desired lifetime, otherwise it will indicate
   * the remaining lifetime for the context.
   */
  public int getLifetime()
  {
    if (this.isEstablished() || this.isProtReady()
        || (this.lifetime == INDEFINITE))
      return this.lifetime;
    else
    {
      int remainingLifeTime = this.startTime + this.lifetime
                              - (int)(System.currentTimeMillis() / 1000);
      return (remainingLifeTime < 0 ? 0 : remainingLifeTime);
    }
  }

  /**
   * Returns the name of the context initiator.  This call is valid only
   * after the context is fully established or the isProtReady method
   * returns "true".
   */
  public GSSName getSrcName() throws GSSException
  {
    if (this.isEstablished() || this.isProtReady())
    {
      if (this.role == INITIATE)
        return this.myCred.getGSSName();
      else
        return this.peer;
    }
    else
      throw new GSSException(GSSException.UNAVAILABLE);
  }

  /**
   * Returns the name of the context target (acceptor).  This call is
   * valid only after the context is fully established or the isProtReady
   * method returns "true".
   */
  public GSSName getTargName() throws GSSException
  {
    if (this.isEstablished() || this.isProtReady())
    {
      if (this.role == ACCEPT)
        return this.myCred.getGSSName();
      else
        return this.peer;
    }
    else
      throw new GSSException(GSSException.UNAVAILABLE);
  }

  /**
   * Returns the mechanism oid for this context.
   */
  public Oid getMech() throws GSSException
  {
    return this.mechOid;
  }

  /**
   * Returns the delegated credential object on the acceptor's side. To
   * check for availability of delegated credentials call
   * getDelegCredState. This call is only valid on fully established
   * contexts.
   */
  public GSSCredential getDelegCred() throws GSSException
  {
    if (this.isEstablished() && this.getCredDelegState())
      return this.delegCred;
    else
      throw new GSSException(GSSException.UNAVAILABLE);
  }

  /**
   * Returns "true" if this is the initiator of the context. This call is
   * only valid after the context creation process has started.
   */
  public boolean isInitiator() throws GSSException
  {
    if (this.state != UNINITIALIZED)
      return (this.role == INITIATE);
    else
      throw new GSSException(GSSException.UNAVAILABLE);
  }
  
  /*
   * Computes and returns checksum.
   */
  private byte[] computeChecksum(SecretKey key, byte[] header, byte[] data)
                                 throws GSSException
  {
    try
    {
      MessageDigest md = MessageDigest.getInstance("MD5");
      md.update(header);
      md.update(data);

      Cipher cipher = Cipher.getInstance("DES/CBC/NoPadding");
      cipher.init(Cipher.ENCRYPT_MODE, key,
                  new IvParameterSpec(new byte[BLOCKSIZE]), rand);
      byte[] cksum = new byte[BLOCKSIZE];
      System.arraycopy(cipher.doFinal(md.digest()), BLOCKSIZE,
                       cksum, 0, BLOCKSIZE);

      return cksum;
    }
    catch (Exception e)
    {
      throw new GSSException(GSSException.FAILURE);
    }
  }

  /*
   * Returns encrypted sequence number.
   */
  private byte[] encryptSeqNum(SecretKey key, byte[] cksum)
                               throws GSSException
  {
    try
    {
      // Convert sequence number to bytes
      byte[] seq = this.intToBytes(this.sendSeqNum, 4);

      // Store sequence number bytes
      byte[] paddedSeq = new byte[8];
      System.arraycopy(seq, 0, paddedSeq, 0, 4);

      // Pad sequence number bytes
      if (this.role == INITIATE)
        System.arraycopy(INITIATOR, 0, paddedSeq, 4, 4);
      else
        System.arraycopy(ACCEPTOR, 0, paddedSeq, 4, 4);
    
      // Encrypt the padded sequence number
      Cipher cipher = Cipher.getInstance("DES/CBC/NoPadding");
      cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(cksum), rand);
      byte[] encSeq = cipher.doFinal(paddedSeq);

      // Increment sequence number
      this.sendSeqNum++;

      return encSeq;
    }
    catch (Exception e)
    {
      throw new GSSException(GSSException.FAILURE);
    }
  }

  /*
   * Decrypts encrypted sequence number, checking if necessary.
   */
  private void decryptSeqNum(SecretKey key, byte[] cksum, byte[] encSeq,
                             MessageProp msgProp) throws GSSException
  {
    try
    {
      // Decrypt sequence number
      Cipher cipher = Cipher.getInstance("DES/CBC/Zeroes");
      cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(cksum), rand);
      byte[] paddedSeq = cipher.doFinal(encSeq);

      // Check padding
      byte[] pad = new byte[4];
      System.arraycopy(paddedSeq, 4, pad, 0, 4);

      if (((this.role == INITIATE) && Arrays.equals(ACCEPTOR, pad))
          || ((this.role == ACCEPT) && Arrays.equals(INITIATOR, pad)))
      {
        // Check sequence number if required
        if (this.getSequenceDetState())
        {
          byte[] seq = new byte[4];
          System.arraycopy(paddedSeq, 0, seq, 0, 4);
          int seqNum = this.bytesToInt(seq, 4);

          if (this.recSeqNum == seqNum)
            recSeqNum++;
          else if (this.recSeqNum < seqNum)
            msgProp.setGapToken(true);
          else if (this.recSeqNum > seqNum)
            msgProp.setUnSeqToken(true);
        }
      }
      else
        throw new GSSException(GSSException.BAD_MIC);
    }
    catch (GSSException e)
    {
      throw e;
    }
    catch (Exception e)
    {
      throw new GSSException(GSSException.FAILURE);
    }
  }

  /*
   * Converts an integer into a little-endian byte ordering
   * of length n bytes.
   */
  private byte[] intToBytes(int num, int n)
  {
    if (n > 4)
      n = 4; // Max size of int is 4 bytes

    byte[] ret = new byte[n];       

    for (int i = 0; i < n; i++)
    {
      ret[i] = (byte)(num & 0xff);
      num >>>= 8;
    }
    return ret;
  }  

  /*
   * Converts a little-endian byte ordering of length n bytes
   * into an integer.
   */
  private int bytesToInt(byte[] num, int n)
  {
    if (n > 4)
      n = 4; // Max size of int is 4 bytes

    int ret = 0;
        
    while (--n >= 0)
    { 
      ret <<= 8;
      ret |= ((int)num[n] & 0xff);
    }
    return ret;
  }
}
