//////////////////////////////////////////////////////////////////////////// 
// 
// 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.io.FileInputStream;
import java.util.Date;
import java.util.Properties;
import com.dstc.security.kerberos.creds.Credential;
import com.dstc.security.kerberos.Kerberos;

/**
 * This class manages GSS-API credentials and their associated
 * operations. A credential contains all the necessary cryptographic
 * information to enable the creation of a context on behalf of the
 * entity that it represents. It may contain multiple, distinct,
 * mechanism specific credential elements, each containing information
 * for a specific security mechanism, but all referring to the same
 * entity.
 *
 * <p>A credential may be used to perform context initiation, acceptance,
 * or both.
 *
 * @author Anthony Ho
 */
public class GSSCredential
{
  public static final int INITIATE_AND_ACCEPT = 0;
  public static final int INITIATE_ONLY = 1;
  public static final int ACCEPT_ONLY = 2;
  public static final int INDEFINITE = Integer.MAX_VALUE;

  private GSSName name;
  private int usage;
  private Oid mechOid;
  private int initLifetime;
  private int acceptLifetime;
  private int startTime;

  // Kerberos V specific
  private Credential kerberosCredential;

  /**
   * Constructor for default credentials.  This will use the default
   * mechanism, name, and an INDEFINITE lifetime.
   */
  public GSSCredential(int usage) throws GSSException
  {
    this(null, INDEFINITE, (Oid)null, usage);
  }

  /**
   * Constructor for default mechanism credential. Uses default mechanism
   * and INDEFINITE lifetime.
   */
  public GSSCredential(GSSName aName, int usage) throws GSSException
  {
    this(aName, INDEFINITE, (Oid)null, usage);
  }

  /**
   * Constructor for a single mechanism credential. "null" values can be
   * specified for name and mechanism to obtain system specific defaults.
   */
  public GSSCredential(GSSName aName, int lifetime, Oid mechOid,
                       int usage) throws GSSException
  {
    // Save current time as start time
    this.startTime = (int)(System.currentTimeMillis() / 1000);

    // Check for valid usage
    if (usage == INITIATE_AND_ACCEPT)
    {
      this.initLifetime = lifetime;
      this.acceptLifetime = lifetime;
      this.usage = INITIATE_AND_ACCEPT;
    }
    else if (usage == INITIATE_ONLY)
    {
      this.initLifetime = lifetime;
      this.usage = INITIATE_ONLY;
    }
    else if (usage == ACCEPT_ONLY)
    {
      this.acceptLifetime = lifetime;
      this.usage = ACCEPT_ONLY;
    }
    else
      throw new GSSException(GSSException.BAD_STATUS);

    // Use default mechanism if none given
    if (mechOid == null)
      mechOid = GSSManager.getDefaultMech();

    // One block for each supported mechanism
    if (mechOid.equals(GSSManager.KRB5))
    {
      try
      {
        Kerberos kerberos = Kerberos.getDefault();

        // Use default principal if none given
        if (aName == null)
          this.name = new GSSName(kerberos.getKerberosContext().getUsername(),
                                  GSSName.NT_USER_NAME, GSSManager.KRB5);

        // Assert principal over Kerberos V mechanism if not already
        else if (aName.getStringNameType() != GSSName.KRB5_NT_PRINCIPAL_NAME)
          this.name = aName.canonicalize(GSSManager.KRB5);
        else
          this.name = aName;

        if ((usage == INITIATE_ONLY) || (usage == INITIATE_AND_ACCEPT))
        {
          this.kerberosCredential = kerberos.getTGTCredential();

          // Check if credential has already expired
          if (this.kerberosCredential.getExpiryTime().before(
            new Date(startTime)))
            throw new GSSException(GSSException.CREDENTIALS_EXPIRED);
        }

        this.mechOid = GSSManager.KRB5;
      }
      catch (Exception e)
      {
        e.printStackTrace();
        throw new GSSException(GSSException.FAILURE);
      }
    }
    else
      throw new GSSException(GSSException.BAD_MECH);
  }

  /**
   * Constructor for a credential over a set of mechanisms. Acquires
   * credentials for each of the mechanisms specified in mechs array.
   * "null" value can be used for aName to obtain system specific default.
   * To determine which mechanism's acquisition of the credential was
   * successful use the getMechs method.  This call is equivalent to
   * creating a single mechanism credential and using addCred to extend
   * the credential over other mechanisms.
   */
  public GSSCredential(GSSName aName, int lifetime, Oid[] mechs,
                       int usage) throws GSSException
  {
    this(aName, lifetime, mechs[0], usage);
    for (int i = 1; i < mechs.length; i++)
      add(aName, lifetime, lifetime, mechs[i], usage);
  }

  /**
   * Releases any sensitive information that the GSSCredential may be
   * containing.  Applications should call this method as soon as the
   * credential is no longer needed to minimize the time sensitive
   * information is maintained.
   */
  public void dispose() throws GSSException
  {
    this.kerberosCredential = null;
  }

  /**
   * Retrieves the name of the entity that the credential asserts.
   */
  public GSSName getGSSName() throws GSSException
  {
    return this.name;
  }

  /**
   * Retrieves per-mechanism name of the entity that the credential
   * asserts.
   */
  public GSSName getGSSName(Oid mechOid) throws GSSException
  {
    // Check that mechanism given is asserted by credential
    if (mechOid.equals(this.mechOid))
      return this.name;
    else
      throw new GSSException(GSSException.BAD_MECH);
  }

  /**
   * Returns the remaining lifetime in seconds for a credential. The
   * remaining lifetime is the minimum lifetime for any of the underlying
   * credential mechanisms.  A return value of GSSCredential.INDEFINITE
   * indicates that the credential does not expire.  A return value of 0
   * indicates that the credential is already expired.
   */
  public int getRemainingLifetime() throws GSSException
  {
    if (this.usage == INITIATE_ONLY)
      return (getRemainingInitLifetime(this.mechOid));
    else if (this.usage == ACCEPT_ONLY)
      return (getRemainingAcceptLifetime(this.mechOid));
    else
      return Math.min(getRemainingInitLifetime(this.mechOid),
                      getRemainingAcceptLifetime(this.mechOid)); 
  }

  /**
   * Returns the remaining lifetime is seconds for the credential to
   * remain capable of initiating security contexts under the specified
   * mechanism. A return value of GSSCredential.INDEFINITE indicates that
   * the credential does not expire for context initiation. A return value
   * of 0 indicates that the credential is already expired.
   */
  public int getRemainingInitLifetime(Oid mech) throws GSSException
  {
    if (mech.equals(this.mechOid))
    {
      if (this.initLifetime == INDEFINITE)
        return INDEFINITE;
      else
      {
        int remainingLifeTime = this.startTime + this.initLifetime
                                - (int)(System.currentTimeMillis() / 1000);
        return (remainingLifeTime < 0 ? 0 : remainingLifeTime);
      }
    }
    else
      throw new GSSException(GSSException.BAD_MECH);
  }

  /**
   * Returns the remaining lifetime is seconds for the credential to
   * remain capable of accepting security contexts under the specified
   * mechanism. A return value of GSSCredential.INDEFINITE indicates that
   * the credential does not expire for context acceptance. A return value
   * of 0 indicates that the credential is already expired.
   */
  public int getRemainingAcceptLifetime(Oid mech) throws GSSException
  {
    if (mech.equals(this.mechOid))
    {
      if (this.acceptLifetime == INDEFINITE)
        return INDEFINITE;
      else
      {
        int remainingLifeTime = this.startTime + this.acceptLifetime
                                - (int)(System.currentTimeMillis() / 1000);
        return (remainingLifeTime < 0 ? 0 : remainingLifeTime);
      }
    }
    else
      throw new GSSException(GSSException.BAD_MECH);
  }

  /**
   * Returns the credential usage flag. The return value will be one of
   * GSSCredential.INITIATE_ONLY, GSSCredential.ACCEPT_ONLY, or
   * GSSCredential.INITIATE_AND_ACCEPT.
   */
  public int getUsage() throws GSSException
  {
    return this.usage;
  }

  /**
   * Returns the credential usage flag for the specified credential
   * mechanism. The return value will be one of
   * GSSCredential.INITIATE_ONLY, GSSCredential.ACCEPT_ONLY, or
   * GSSCredential.INITIATE_AND_ACCEPT.
   */
  public int getUsage(Oid mechOid) throws GSSException
  {
    if (mechOid.equals(this.mechOid))
      return this.usage;
    else
      throw new GSSException(GSSException.BAD_MECH);
  }

  /**
   * Returns an array of mechanisms supported by this credential.
   */
  public Oid[] getMechs() throws GSSException
  {
    Oid[] mechs = {this.mechOid};
    return mechs;
  }

  /**
   * Adds a mechanism specific credential-element to an existing
   * credential. This method allows the construction of credentials one
   * mechanism at a time. This functionality is equivalent to using the
   * GSSCredential constructor which takes an Oid array as an input
   * parameter or calling this method once for each of the mechanisms in
   * the array.
   *
   * <p>This routine is envisioned to be used mainly by context acceptors
   * during the creation of acceptance credentials which are to be used
   * with a variety of clients using different security mechanisms.
   *
   * <p>To obtain a new credential object after the addition of the new
   * mechanism credential, the clone method can be called.
   */
  public void add(GSSName aName, int initLifetime, int acceptLifetime,
                  Oid mech, int usage) throws GSSException
  {
    // Multiple mechanism credentials not supported
    throw new GSSException(GSSException.UNAVAILABLE);
  }

  /**
   * Tests if this GSSCredential refers to the same entity as the supplied
   * object. The two GSSCredentials must be acquired over the same
   * mechanisms and must refer to the same principal. Returns "true" if
   * the two GSSCredentials refer to the same entity; "false" otherwise.
   */
  public boolean equals(Object another)
  {
    // Note that check is whether credentials refer to exactly the same
    // GSSName object, not two instances of GSSName which are equal.
    return (another instanceof GSSCredential
            && (this.name == ((GSSCredential)another).name));
  }

  /*
   * Constructor for a Kerberos V specific credential. "null" values can be
   * specified for name and mechanism to obtain system specific defaults.
   */
  GSSCredential(GSSName aName, int usage, Credential kerberosCredential)
                throws GSSException
  {
    this(aName, INDEFINITE, GSSManager.KRB5, usage);
    this.kerberosCredential = kerberosCredential;
  }

  /*
   * Returns Kerberos V TGT Credential
   */
  Credential getTGTCredential()
  {
    return this.kerberosCredential;
  }
}
