/*
 * Copyright 1999-2010 University of Chicago
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS,WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied.
 *
 * See the License for the specific language governing permissions and limitations under the License.
 */
package org.globus.gsi.gssapi;

import org.globus.gsi.util.CertificateUtil;
import org.globus.gsi.util.ProxyCertificateUtil;


import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSException;
import org.ietf.jgss.GSSContext;
import org.ietf.jgss.GSSManager;
import org.ietf.jgss.GSSName;
import org.ietf.jgss.Oid;
import org.ietf.jgss.MessageProp;
import org.ietf.jgss.ChannelBinding;

import org.gridforum.jgss.ExtendedGSSContext;
import org.gridforum.jgss.ExtendedGSSCredential;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Date;
import java.util.Calendar;
import java.util.Map;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.security.KeyPair;
import java.security.GeneralSecurityException;
import java.security.interfaces.RSAPublicKey;
import java.security.interfaces.RSAPrivateKey;

import org.globus.gsi.ProviderLoader;
import org.globus.gsi.stores.ResourceSigningPolicyStore;

import java.security.cert.CertStore;
import java.security.cert.CertificateFactory;
import java.security.KeyStore;

import org.globus.gsi.GSIConstants;
import org.globus.gsi.TrustedCertificates;
import org.globus.gsi.X509Credential;
import org.globus.gsi.util.CertificateLoadUtil;
import org.globus.gsi.bc.BouncyCastleUtil;
import org.globus.gsi.bc.BouncyCastleCertProcessingFactory;
import org.globus.util.I18n;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLPeerUnverifiedException;
import org.globus.gsi.jsse.SSLConfigurator;

import org.bouncycastle.jce.provider.X509CertificateObject;

/*
import COM.claymoresystems.ptls.SSLConn;
import COM.claymoresystems.ptls.SSLRecord;
import COM.claymoresystems.ptls.SSLDebug;
import COM.claymoresystems.ptls.SSLCipherSuite;
import COM.claymoresystems.ptls.SSLCipherState;
import COM.claymoresystems.ptls.SSLHandshake;
import COM.claymoresystems.sslg.SSLPolicyInt;
import COM.claymoresystems.sslg.CertVerifyPolicyInt;
import COM.claymoresystems.cert.X509Cert;
import COM.claymoresystems.util.Util;
*/

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.globus.gsi.stores.Stores;

/**
 * Implementation of SSL/GSI mechanism for Java GSS-API. The implementation
 * is based on JSSE (for SSL API) and the
 * <a href="http://www.bouncycastle.org/">BouncyCastle library</a>
 * (for certificate processing API).
 * <BR>
 * The implementation is not designed to be thread-safe.
 */
public class GlobusGSSContextImpl implements ExtendedGSSContext {

    private static Log logger =
        LogFactory.getLog(GlobusGSSContextImpl.class.getName());

    private static I18n i18n =
            I18n.getI18n("org.globus.gsi.gssapi.errors",
                         GlobusGSSContextImpl.class.getClassLoader());

    static {
        new ProviderLoader();
    }

/*DEL
    private static Log sslLog =
        LogFactory.getLog(SSLDebug.class.getName());
*/


    /**
     * KeyPair generation with cache of keypairs if configured

     */

    private KeyPairCache keyPairCache = KeyPairCache.getKeyPairCache();


    /** Used to distinguish between a token created by
     * <code>wrap</code> with {@link GSSConstants#GSI_BIG
     * GSSConstants.GSI_BIG}
     * QoP and a regular token created by <code>wrap</code>. */
    public static final int GSI_WRAP = 26; /** SSL3_RT_GSSAPI_OPENSSL */

    private static final int GSI_SEQUENCE_SIZE = 8;

    private static final int GSI_MESSAGE_DIGEST_PADDING = 12;

    private static final String [] ENABLED_PROTOCOLS = {"TLSv1", "SSLv3"};
    // TODO: Delete this once GRAM server is fixed and we no longer
    //       would be talking to old GRAM servers.
    private static final String [] GRAM_PROTOCOLS = {"SSLv3"};

/*DEL
    private static final short [] NO_ENCRYPTION = {SSLPolicyInt.TLS_RSA_WITH_NULL_MD5};
*/
    private static final String [] NO_ENCRYPTION =
                    {"SSL_RSA_WITH_NULL_SHA", "SSL_RSA_WITH_NULL_MD5"};

    // TODO: Delete these once GRAM server is fixed and we no longer
    //       would be talking to old GRAM servers.
    private static final String [] GRAM_ENCRYPTION_CIPHER_SUITES =
		{"SSL_RSA_WITH_3DES_EDE_CBC_SHA"};
    private static final String [] GRAM_NO_ENCRYPTION_CIPHER_SUITES =
		{"SSL_RSA_WITH_NULL_SHA"};

    private static final byte[] DELEGATION_TOKEN = new byte[] {GSIConstants.DELEGATION_CHAR};

    private static final int
        UNDEFINED = 0,
        INITIATE = 1,
        ACCEPT = 2;

    /** Handshake state */
    protected int state = HANDSHAKE;

    /* handshake states */
    private static final int
        HANDSHAKE = 0,
        CLIENT_START_DEL = 2,
        CLIENT_END_DEL = 3,
        SERVER_START_DEL = 4,
        SERVER_END_DEL = 5;

    /** Delegation state */
    protected int delegationState = DELEGATION_START;

    /* delegation states */
    private static final int
        DELEGATION_START = 0,
        DELEGATION_SIGN_CERT = 1,
        DELEGATION_COMPLETE_CRED = 2;

    /** Credential delegated using delegation API */
    protected ExtendedGSSCredential delegatedCred;

    /** Delegation finished indicator */
    protected boolean delegationFinished = false;

    // gss context state variables
    protected boolean credentialDelegation = false;
    protected boolean anonymity = false;
    protected boolean encryption = true;
    protected boolean established = false;

    /** The name of the context initiator */
    protected GSSName sourceName = null;

    /** The name of the context acceptor */
    protected GSSName targetName = null;

    /** Context role */
    protected int role = UNDEFINED;

    /** Credential delegated during context establishment */
    protected ExtendedGSSCredential delegCred;

    // these can be set via setOption
/*DEL
    protected Integer delegationType = GSIConstants.DELEGATION_TYPE_LIMITED;
*/
    protected GSIConstants.DelegationType delegationType =
                                      GSIConstants.DelegationType.LIMITED;
    protected Integer gssMode = GSIConstants.MODE_GSI;
    protected Boolean checkContextExpiration = Boolean.FALSE;
    protected Boolean rejectLimitedProxy = Boolean.FALSE;
    protected Boolean requireClientAuth = Boolean.TRUE;
    protected Boolean acceptNoClientCerts = Boolean.FALSE;
    protected Boolean requireAuthzWithDelegation = Boolean.TRUE;
    protected Boolean forceSSLv3AndConstrainCipherSuitesForGram =
                                      Boolean.FALSE;

    // *** implementation-specific variables ***

    /** Credential of this context. Might be anonymous */
    protected GlobusGSSCredentialImpl ctxCred;

    /** Expected target name. Used for authorization in initiator */
    protected GSSName expectedTargetName = null;

    /** Context expiration date. */
    protected Date goodUntil = null;

    protected SSLConfigurator sslConfigurator = null;
    protected SSLContext sslContext = null;
    protected SSLEngine sslEngine = null;

/*DEL
    protected SSLConn conn;
*/
    protected boolean conn = false;
/*DEL
    protected PureTLSContext context;
    protected SSLPolicyInt policy;
    protected TokenInputStream in;
    protected ByteArrayOutputStream out;
*/
    private byte[] savedInBytes = null;
    private ByteBuffer outByteBuff = null;
    protected BouncyCastleCertProcessingFactory certFactory;

    /** Used during delegation */
    protected KeyPair keyPair;

    protected TrustedCertificates tc;

    protected Map proxyPolicyHandlers;

    /** Limited peer credentials */
    protected Boolean peerLimited = null;

    private String[] bannedCiphers = new String[0];

    /**
     * @param target expected target name. Can be null.
     * @param cred credential. Cannot be null. Might be anonymous.
     */
    public GlobusGSSContextImpl(GSSName target,
                                GlobusGSSCredentialImpl cred)
        throws GSSException {

        if (cred == null) {
            throw new GSSException(GSSException.NO_CRED);
        }

        this.expectedTargetName = target;
        this.ctxCred = cred;

/*DEL
        this.context = new PureTLSContext();
*/
	try {

            this.sslConfigurator = new SSLConfigurator();

            // Need to set this so we are able to communicate properly with
            // GT4.0.8 servers that use only SSLv3 (no TLSv1). Thanks to
            // Jon Siwek for pointing this and the following link out:
            // http://java.sun.com/j2se/1.4.2/relnotes.html#security
            if (System.getProperty("com.sun.net.ssl.rsaPreMasterSecretFix") == null)
               System.setProperty("com.sun.net.ssl.rsaPreMasterSecretFix", "true");

            // WARNING WARNING:
            // The new jglobus2-based srm-client is not compatible with old bestman2
            // servers UNLESS we change this setting.
            //
            // The protection we are turning off helps against the BEAST attack.
            // When enabled, it will insert empty TLS application records into the
            // stream.  However, the old server will deadlock on the extra records.
            //
            // To our knowledge, the BEAST attack is not applicable to this client as
            // we don't have any concurrent insecure connections.  Regardless, we ought
            // to remove this as soon as we can drop support for the old servers.
            //
            // -BB.  Sept 24, 2012.
            //
            System.setProperty("jsse.enableCBCProtection", "false");

	} catch  (Exception e) {
                throw new GlobusGSSException(GSSException.FAILURE, e);
	}

/*DEL
        CertVerifyPolicyInt certPolicy = PureTLSUtil.getDefaultCertVerifyPolicy();

        this.policy = new SSLPolicyInt();
        this.policy.negotiateTLS(false);
        this.policy.waitOnClose(false);
        this.policy.setCertVerifyPolicy(certPolicy);
        this.context.setPolicy(policy);

	// TODO
        setSSLDebugging();
*/
    }

/*DEL
    private void setSSLDebugging() {
        if (sslLog.isTraceEnabled()) {
            SSLDebug.setDebug( 0xffff );
        } else if (sslLog.isDebugEnabled()) {
            SSLDebug.setDebug( SSLDebug.DEBUG_CERT );
        }
    }
*/

    /*
     * If the result indicates that we have outstanding tasks to do,
     * go ahead and run them in this thread.
     */
    private void runDelegatedTasks(SSLEngine engine) throws Exception {

            Runnable runnable;
            while ((runnable = engine.getDelegatedTask()) != null) {
                logger.debug("\trunning delegated task...");
                runnable.run();
            }
            SSLEngineResult.HandshakeStatus hsStatus =
                    engine.getHandshakeStatus();
            if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK) {
                throw new Exception(
                        "handshake shouldn't need additional tasks");
            }
            logger.debug("\tnew HandshakeStatus: " + hsStatus);
    }

    private X509Certificate bcConvert(X509Certificate cert)
            throws GSSException {
        if (!(cert instanceof X509CertificateObject)) {
        	ByteArrayInputStream inputStream = null;
            try {
            	inputStream = new ByteArrayInputStream(cert.getEncoded());
                return CertificateLoadUtil.loadCertificate(inputStream);
            } catch (Exception e) {
                throw new GlobusGSSException(GSSException.FAILURE, e);
            }finally{
            	if (inputStream != null) {
                    try {
                    	inputStream.close();
                    } catch (Exception e) {
                        logger.warn("Unable to close streamreader.");
                    }
                }
            }
        } else {
                return cert;
        }
    }


    /**
     * This function drives the accepting side of the context establishment
     * process. It is expected to be called in tandem with the
     * {@link #initSecContext(byte[], int, int) initSecContext} function.
     * <BR>
     * The behavior of context establishment process can be modified by
     * {@link GSSConstants#GSS_MODE GSSConstants.GSS_MODE}
     * and {@link GSSConstants#REJECT_LIMITED_PROXY
     * GSSConstants.REJECT_LIMITED_PROXY} context options. If the
     * {@link GSSConstants#GSS_MODE GSSConstants.GSS_MODE}
     * option is set to
     * {@link GSIConstants#MODE_SSL GSIConstants.MODE_SSL}
     * the context establishment process will be compatible with regular SSL
     * (no credential delegation support). If the option is set to
     * {@link GSIConstants#MODE_GSI GSIConstants.MODE_GSI}
     * credential delegation during context establishment process will be accepted.
     * If the {@link GSSConstants#REJECT_LIMITED_PROXY
     * GSSConstants.REJECT_LIMITED_PROXY} option is enabled, a peer
     * presenting limited proxy credential will be automatically
     * rejected and the context establishment process will be aborted.
     *
     * @return a byte[] containing the token to be sent to the peer.
     *         null indicates that no token is generated (needs more data)
     */
    public byte[] acceptSecContext(byte[] inBuff, int off, int len)
        throws GSSException {
        logger.debug("enter acceptSecContext");

        if (!this.conn) {
            this.role = ACCEPT;

	    logger.debug("enter initializing in acceptSecContext");

            if (this.ctxCred.getName().isAnonymous()) {
                throw new GlobusGSSException(GSSException.DEFECTIVE_CREDENTIAL,
                                             GlobusGSSException.UNKNOWN,
                                             "acceptCtx00");
            }

            if (this.ctxCred.getUsage() != GSSCredential.ACCEPT_ONLY &&
                this.ctxCred.getUsage() != GSSCredential.INITIATE_AND_ACCEPT) {
                throw new GlobusGSSException(GSSException.DEFECTIVE_CREDENTIAL,
                                             GlobusGSSException.UNKNOWN,
                                             "badCredUsage");
            }

            setCredential();

	    try {
                init(this.role);
	    } catch (SSLException e) {
                throw new GlobusGSSException(GSSException.FAILURE, e);
            }

	    this.conn = true;
	    logger.debug("done initializing in acceptSecContext");
        }

/*DEL
        this.out.reset();
        this.in.putToken(inBuff, off, len);
*/
	this.outByteBuff.clear();
	ByteBuffer inByteBuff;
        if (savedInBytes != null) {
            if (len > 0) {
                byte[] allInBytes = new byte[savedInBytes.length + len];
                logger.debug("ALLOCATED for allInBytes " + savedInBytes.length + " + " + len + " bytes\n");
                System.arraycopy(savedInBytes, 0, allInBytes, 0, savedInBytes.length);
                System.arraycopy(inBuff, off, allInBytes, savedInBytes.length, len);
                inByteBuff = ByteBuffer.wrap(allInBytes, 0, allInBytes.length);
            } else {
                inByteBuff = ByteBuffer.wrap(savedInBytes, 0, savedInBytes.length);
            }
            savedInBytes = null;
        } else {
            inByteBuff = ByteBuffer.wrap(inBuff, off, len);
        }

        switch (state) {

        case HANDSHAKE:

            try {
		logger.debug("STATUS BEFORE: " +
			this.sslEngine.getHandshakeStatus().toString());
                SSLEngineResult.HandshakeStatus handshake_status =
                        sslEngine.getHandshakeStatus();

                if (handshake_status ==
                        SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
                        // return null;
                    throw new Exception("GSSAPI in HANDSHAKE state but " +
                         "SSLEngine in NOT_HANDSHAKING state!");
                } else {
                        outByteBuff = this.sslProcessHandshake(inByteBuff, outByteBuff);
                }

		logger.debug("STATUS AFTER: " + this.sslEngine.getHandshakeStatus().toString());

            outByteBuff.flip();

/*DEL
                if (this.conn.getHandshake().finishedP()) {
*/
		if (this.sslEngine.getHandshakeStatus() ==
                        SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
                        // the wrap/unwrap above has resulted in handshaking
                        // being complete on our end.

                    logger.debug("acceptSecContext handshake finished");
                    handshakeFinished();

                    // acceptor
                    for (X509Certificate cert : this.ctxCred.getCertificateChain()) {
                        setGoodUntil(cert.getNotAfter());
                    }
                    this.targetName = this.ctxCred.getName();

                    // initiator - peer
/*DEL
                    Vector chain = this.conn.getCertificateChain();
*/
		    Certificate[] chain;
		    try {
			chain = this.sslEngine.getSession().getPeerCertificates();
		    } catch (SSLPeerUnverifiedException e) {
                        chain = null;
                    }
                    if (chain == null || chain.length == 0) {
                        this.sourceName = new GlobusGSSName();
                        this.anonymity = true;
                    } else {
/*DEL
                        X509Cert crt = (X509Cert)chain.elementAt(chain.size()-1);
                        setGoodUntil(crt.getValidityNotAfter());

                        String identity = verifyChain(chain);
*/
                        for (X509Certificate cert : (X509Certificate[])chain) {
                            setGoodUntil(cert.getNotAfter());
                        }

                        String identity = BouncyCastleUtil.getIdentity(bcConvert(BouncyCastleUtil.getIdentityCertificate((X509Certificate [])chain)));
                        this.sourceName = new GlobusGSSName(CertificateUtil.toGlobusID(identity, false));
			this.peerLimited = Boolean.valueOf(ProxyCertificateUtil.isLimitedProxy(BouncyCastleUtil.getCertificateType((X509Certificate)chain[0])));

			logger.debug("Peer Identity is: " + identity
				 + " Target name is: " + this.targetName +
				" Limited Proxy: " + this.peerLimited.toString());

                        this.anonymity = false;
                    }

                    if (this.gssMode == GSIConstants.MODE_GSI) {
                        this.state = SERVER_START_DEL;
                    } else {
                        setDone();
                    }
                }
            } catch (IOException e) {
                throw new GlobusGSSException(GSSException.FAILURE, e);
            } catch (Exception e) {
                throw new GlobusGSSException(GSSException.FAILURE, e);
            }

            break;

        case SERVER_START_DEL:

            try {
                if (inByteBuff.remaining() <= 0) {
                    return null;
                }

/*DEL
                int delChar = this.conn.getInStream().read();
*/
		outByteBuff = sslDataUnwrap(inByteBuff, outByteBuff);
                outByteBuff.flip();
		byte [] delChar = new byte[outByteBuff.remaining()];
		outByteBuff.get(delChar, 0, delChar.length);
/*DEL
                if (delChar != GSIConstants.DELEGATION_CHAR) {
*/
		if (!Arrays.equals(delChar, DELEGATION_TOKEN)) {
                    setDone();
                    break;
                }

/*DEL
                Vector chain = this.conn.getCertificateChain();
*/
		Certificate[] chain;
		try {
		    chain = this.sslEngine.getSession().getPeerCertificates();
		} catch (SSLPeerUnverifiedException e) {
                    chain = null;
                }
                if (chain == null || chain.length == 0) {
                    throw new GlobusGSSException(GSSException.FAILURE,
                                                 GlobusGSSException.DELEGATION_ERROR,
                                                 "noClientCert");
                }

                X509Certificate tmpCert = (X509Certificate) chain[0];
/*DEL
                    PureTLSUtil.convertCert((X509Cert)chain.lastElement());
*/
                byte [] req = generateCertRequest(tmpCert);
/*DEL
                this.conn.getOutStream().write(req, 0, req.length);
*/
		inByteBuff = ByteBuffer.wrap(req, 0, req.length);
                outByteBuff.clear();
                outByteBuff = sslDataWrap(inByteBuff, outByteBuff);
                outByteBuff.flip();

            } catch (GeneralSecurityException e) {
                throw new GlobusGSSException(GSSException.FAILURE, e);
            }

            this.state = SERVER_END_DEL;
            break;

        case SERVER_END_DEL:

            try {
                if (inByteBuff.remaining() <= 0) {
                    return null;
                }

/*DEL
                X509Certificate certificate = CertUtil.loadCertificate(this.conn.getInStream());
*/
		outByteBuff = sslDataUnwrap(inByteBuff, outByteBuff);
                outByteBuff.flip();
                if (!outByteBuff.hasRemaining())
                    break;
                byte [] buf = new byte[outByteBuff.remaining()];
                outByteBuff.get(buf, 0, buf.length);
		ByteArrayInputStream inStream = new ByteArrayInputStream(buf, 0, buf.length);
		CertificateFactory cf = null;
		X509Certificate certificate = null;
		try{
			cf = CertificateFactory.getInstance("X.509");
			certificate = (X509Certificate)cf.generateCertificate(inStream);
		}finally{
			inStream.close();
		}

                if (logger.isTraceEnabled()) {
                    logger.trace("Received delegated cert: " +
                               certificate.toString());
                }

                verifyDelegatedCert(certificate);

/*DEL
                Vector chain = this.conn.getCertificateChain();
*/
		Certificate[] chain = this.sslEngine.getSession().getPeerCertificates();
                int chainLen = chain.length;
                X509Certificate [] newChain = new X509Certificate[chainLen + 1];
                newChain[0] = bcConvert((X509Certificate)certificate);
                for (int i=0;i<chainLen;i++) {
/*DEL
                    newChain[i+1] = PureTLSUtil.convertCert((X509Cert)chain.elementAt(chainLen - 1 - i));
*/
		    newChain[i+1] = bcConvert((X509Certificate)chain[i]);
                }

                X509Credential proxy =
                    new X509Credential(this.keyPair.getPrivate(), newChain);

                this.delegCred =
                    new GlobusGSSCredentialImpl(proxy,
                                                GSSCredential.INITIATE_AND_ACCEPT);

            } catch (GeneralSecurityException e) {
                throw new GlobusGSSException(GSSException.FAILURE, e);
            } catch (IOException e) {
                throw new GlobusGSSException(GSSException.FAILURE, e);
            }
            setDone();
            break;

        default:
            throw new GSSException(GSSException.FAILURE);
        }

        if (inByteBuff.hasRemaining()) {
            // Likely BUFFER_UNDERFLOW; save the
            // inByteBuff bytes here like in the unwrap() case
	    logger.debug("Not all data processed; Original: " + len
                        + " Remaining: " + inByteBuff.remaining() +
                        " Handshaking status: " + sslEngine.getHandshakeStatus());
               logger.debug("SAVING unprocessed " + inByteBuff.remaining() + "BYTES\n");
               savedInBytes = new byte[inByteBuff.remaining()];
               inByteBuff.get(savedInBytes, 0, savedInBytes.length);
	}

        logger.debug("exit acceptSecContext");
/*DEL
        return (this.out.size() > 0) ? this.out.toByteArray() : null;
*/
	if (this.outByteBuff.hasRemaining()) {
                // TODO can we avoid this copy if the ByteBuffer is array based
                // and we return that array, each time allocating a new array
                // for outByteBuff?
                byte [] out = new byte[this.outByteBuff.remaining()];
                this.outByteBuff.get(out, 0, out.length);
                return out;
	} else
                return null;
    }

    // Meant for non-handshake processing
    private ByteBuffer sslDataWrap(ByteBuffer inBBuff, ByteBuffer outBBuff)
                      throws GSSException {
	try {

              if (sslEngine.getHandshakeStatus() !=
			SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
                  throw new Exception("SSLEngine handshaking needed! " +
                        "HandshakeStatus: " +
                        sslEngine.getHandshakeStatus().toString());
              }
		int iter = 0;
	      do {
		logger.debug("PROCESSING DATA (WRAP) " + ++iter +
					 ": " + inBBuff.remaining());
		SSLEngineResult result = sslEngine.wrap(inBBuff, outBBuff);
		if (result.getHandshakeStatus() ==
			SSLEngineResult.HandshakeStatus.NEED_TASK) {
			runDelegatedTasks(sslEngine);
			continue;
		}
		if (result.getStatus() ==
			SSLEngineResult.Status.BUFFER_OVERFLOW) {
		        // just increase it to the size needed.
		        int pktSize = sslEngine.getSession().getPacketBufferSize();
		        ByteBuffer b = ByteBuffer.allocate(pktSize + outBBuff.position());
		        outBBuff.flip();
		        b.put(outBBuff);
		        outBBuff = b;
			continue;
		} else if (result.getStatus() ==
                            SSLEngineResult.Status.BUFFER_UNDERFLOW) {
                	throw new GlobusGSSException(GSSException.FAILURE,
		 		new Exception("Unexpected BUFFER_UNDERFLOW;" +
                        " Handshaking status: " + sslEngine.getHandshakeStatus()));
                }
		if (result.getStatus() !=
			SSLEngineResult.Status.OK) {
               	throw new GlobusGSSException(GSSException.FAILURE,
                                         GlobusGSSException.TOKEN_FAIL,
                                         result.getStatus().toString());
		}
              } while (inBBuff.hasRemaining());

		return outBBuff;
	} catch (Exception e) {
                throw new GlobusGSSException(GSSException.FAILURE, e);
	}
    }

    // Not all in inBBuff might be consumed by this method!!!
    private ByteBuffer sslDataUnwrap(ByteBuffer inBBuff, ByteBuffer outBBuff)
                      throws GSSException {
	try {
		int iter = 0;
              if (sslEngine.getHandshakeStatus() !=
			SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
                  throw new Exception("SSLEngine handshaking needed! " +
                        "HandshakeStatus: " +
                        sslEngine.getHandshakeStatus().toString());
              }
	      do {
		logger.debug("PROCESSING DATA (UNWRAP) " + ++iter +
			 ": " + inBBuff.remaining());
		SSLEngineResult result = sslEngine.unwrap(
			inBBuff, outBBuff);
		if (result.getHandshakeStatus() ==
			SSLEngineResult.HandshakeStatus.NEED_TASK) {
			runDelegatedTasks(sslEngine);
			continue;
		}
		if (result.getStatus() ==
			SSLEngineResult.Status.BUFFER_OVERFLOW) {
		        // increase it to the size needed.
		        int appSize = sslEngine.getSession().getApplicationBufferSize();
		        ByteBuffer b = ByteBuffer.allocate(appSize + outBBuff.position());
		        outBBuff.flip();
		        b.put(outBBuff);
		        outBBuff = b;
			continue;
		}
		else if (result.getStatus() ==
				SSLEngineResult.Status.BUFFER_UNDERFLOW) {
			// More data needed from peer
			break;
		}
                else if (result.getStatus() ==
                        SSLEngineResult.Status.CLOSED) {
                    throw new ClosedGSSException();
                }

		if (result.getStatus() !=
			SSLEngineResult.Status.OK) {
                	throw new GlobusGSSException(GSSException.FAILURE,
                                             GlobusGSSException.TOKEN_FAIL,
                                         result.getStatus().toString());
		}
              } while (inBBuff.hasRemaining());
		return outBBuff;
	} catch (IllegalArgumentException e) {
            throw new GlobusGSSException(GSSException.DEFECTIVE_TOKEN, e);
        } catch (SSLException e) {
            if (e.toString().endsWith("bad record MAC"))
                throw new GlobusGSSException(GSSException.BAD_MIC, e);
            else if (e.toString().endsWith("ciphertext sanity check failed"))
                throw new GlobusGSSException(GSSException.DEFECTIVE_TOKEN, e);
            else
                throw new GlobusGSSException(GSSException.FAILURE, e);
        } catch (GSSException e) {
            throw e;
	} catch (Exception e) {
            throw new GlobusGSSException(GSSException.FAILURE, e);
	}
    }

    private ByteBuffer sslProcessHandshake(ByteBuffer inBBuff, ByteBuffer outBBuff)
                      throws GSSException {
	// Loopon until we need more from peer or we are done with handshaking.
	try {
done:      do {
              while (sslEngine.getHandshakeStatus() ==
			SSLEngineResult.HandshakeStatus.NEED_WRAP) {
		SSLEngineResult result = sslEngine.wrap(inBBuff, outBBuff);
		if (result.getHandshakeStatus() ==
			SSLEngineResult.HandshakeStatus.NEED_TASK) {
			runDelegatedTasks(sslEngine);
			continue;
		}
		if (result.getStatus() ==
			SSLEngineResult.Status.BUFFER_OVERFLOW) {
		        // increase it to the size needed.
		        int pktSize = sslEngine.getSession().getPacketBufferSize();
		        ByteBuffer b = ByteBuffer.allocate(pktSize + outBBuff.position());
		        outBBuff.flip();
		        b.put(outBBuff);
		        outBBuff = b;
			continue;
		} else if (result.getStatus() ==
                            SSLEngineResult.Status.BUFFER_UNDERFLOW) {
                	throw new GlobusGSSException(GSSException.FAILURE,
		 		new Exception("Unexpected BUFFER_UNDERFLOW;" +
                        " Handshaking status: " + sslEngine.getHandshakeStatus()));
                }
		if (result.getStatus() !=
			SSLEngineResult.Status.OK) {
                	throw new GlobusGSSException(GSSException.FAILURE,
                                             GlobusGSSException.TOKEN_FAIL,
                                         result.getStatus().toString());
		}
              }

		int iter = 0;
              while (sslEngine.getHandshakeStatus() ==
			SSLEngineResult.HandshakeStatus.NEED_UNWRAP) {
		logger.debug("PROCESSING " + ++iter + ": " +
					inBBuff.remaining());
		SSLEngineResult result = sslEngine.unwrap(
			inBBuff, outBBuff);
		if (result.getHandshakeStatus() ==
			SSLEngineResult.HandshakeStatus.NEED_TASK) {
			runDelegatedTasks(sslEngine);
			continue;
		}
		if (result.getStatus() ==
			SSLEngineResult.Status.BUFFER_OVERFLOW) {
		        // increase it to the size needed.
		        int appSize = sslEngine.getSession().getApplicationBufferSize();
		        ByteBuffer b = ByteBuffer.allocate(appSize + outBBuff.position());
		        outBBuff.flip();
		        b.put(outBBuff);
		        outBBuff = b;
			continue;
		}
		else if (result.getStatus() ==
				SSLEngineResult.Status.BUFFER_UNDERFLOW) {
			// More data needed from peer
			// break out of outer loop
			break done;
		}
		if (result.getStatus() !=
			SSLEngineResult.Status.OK) {
                	throw new GlobusGSSException(GSSException.FAILURE,
                                             GlobusGSSException.TOKEN_FAIL,
                                         result.getStatus().toString());
		}
              }
           } while (sslEngine.getHandshakeStatus() !=
			SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING);

	   return outBBuff;
	} catch (Exception e) {
                throw new GlobusGSSException(GSSException.FAILURE, e);
	}
    }

    /**
     * This function drives the initiating side of the context establishment
     * process. It is expected to be called in tandem with the
     * {@link #acceptSecContext(byte[], int, int) acceptSecContext} function.
     * <BR>
     * The behavior of context establishment process can be modified by
     * {@link GSSConstants#GSS_MODE GSSConstants.GSS_MODE},
     * {@link GSSConstants#DELEGATION_TYPE GSSConstants.DELEGATION_TYPE}, and
     * {@link GSSConstants#REJECT_LIMITED_PROXY GSSConstants.REJECT_LIMITED_PROXY}
     * context options. If the {@link GSSConstants#GSS_MODE GSSConstants.GSS_MODE}
     * option is set to {@link GSIConstants#MODE_SSL GSIConstants.MODE_SSL}
     * the context establishment process will be compatible with regular SSL
     * (no credential delegation support). If the option is set to
     * {@link GSIConstants#MODE_GSI GSIConstants.GSS_MODE_GSI}
     * credential delegation during context establishment process will performed.
     * The delegation type to be performed can be set using the
     * {@link GSSConstants#DELEGATION_TYPE GSSConstants.DELEGATION_TYPE}
     * context option. If the {@link GSSConstants#REJECT_LIMITED_PROXY
     * GSSConstants.REJECT_LIMITED_PROXY} option is enabled,
     * a peer presenting limited proxy credential will be automatically
     * rejected and the context establishment process will be aborted.
     *
     * @return a byte[] containing the token to be sent to the peer.
     *         null indicates that no token is generated (needs more data).
     */
    public byte[] initSecContext(byte[] inBuff, int off, int len)
        throws GSSException {
        logger.debug("enter initSecContext");

        if (!this.conn) {
            this.role = INITIATE;

		logger.debug("enter initializing in initSecContext");

            if (this.anonymity || this.ctxCred.getName().isAnonymous()) {
                this.anonymity = true;
            } else {
                this.anonymity = false;

                setCredential();

                if (this.ctxCred.getUsage() != GSSCredential.INITIATE_ONLY &&
                    this.ctxCred.getUsage() != GSSCredential.INITIATE_AND_ACCEPT) {
                    throw new GlobusGSSException(GSSException.DEFECTIVE_CREDENTIAL,
                                                 GlobusGSSException.UNKNOWN,
                                                 "badCredUsage");
                }
            }

            if (getCredDelegState()) {
                if (this.gssMode == GSIConstants.MODE_SSL) {
                    throw new GlobusGSSException(GSSException.FAILURE,
                                                 GlobusGSSException.BAD_ARGUMENT,
                                                 "initCtx00");
                }
                if (this.anonymity) {
                    throw new GlobusGSSException(GSSException.FAILURE,
                                                 GlobusGSSException.BAD_ARGUMENT,
                                                 "initCtx01");
                }
            }

	    try {
            	init(this.role);
            } catch (SSLException e) {
                throw new GlobusGSSException(GSSException.FAILURE, e);
            }

	    this.conn = true;
	    logger.debug("done initializing in initSecContext");
        }

        // Unless explicitly disabled, check if delegation is
        // requested and expected target is null
        logger.debug("Require authz with delegation: "
                     + this.requireAuthzWithDelegation);
        if (!Boolean.FALSE.equals(this.requireAuthzWithDelegation)) {

            if (this.expectedTargetName == null &&
                getCredDelegState()) {
                throw new GlobusGSSException(GSSException.FAILURE,
                                             GlobusGSSException.BAD_ARGUMENT,
                                         "initCtx02");
            }
        }

/*DEL
        this.out.reset();
        this.in.putToken(inBuff, off, len);
*/

        this.outByteBuff.clear();
	ByteBuffer inByteBuff;
        if (savedInBytes != null) {
            if (len > 0) {
                byte[] allInBytes = new byte[savedInBytes.length + len];
                logger.debug("ALLOCATED for allInBytes " + savedInBytes.length + " + " + len + " bytes\n");
                System.arraycopy(savedInBytes, 0, allInBytes, 0, savedInBytes.length);
                System.arraycopy(inBuff, off, allInBytes, savedInBytes.length, len);
                inByteBuff = ByteBuffer.wrap(allInBytes, 0, allInBytes.length);
            } else {
                inByteBuff = ByteBuffer.wrap(savedInBytes, 0, savedInBytes.length);
            }
            savedInBytes = null;
        } else {
            inByteBuff = ByteBuffer.wrap(inBuff, off, len);
        }

        switch (state) {

        case HANDSHAKE:
            try {

		logger.debug("STATUS BEFORE: " +
			this.sslEngine.getHandshakeStatus().toString());
		SSLEngineResult.HandshakeStatus handshake_status =
			sslEngine.getHandshakeStatus();

        	if (handshake_status ==
			SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
			// return null;
                    throw new Exception("GSSAPI in HANDSHAKE state but " +
                         "SSLEngine in NOT_HANDSHAKING state!");
		} else {
			outByteBuff = this.sslProcessHandshake(inByteBuff, outByteBuff);
		}

		logger.debug("STATUS AFTER: " +
			this.sslEngine.getHandshakeStatus().toString());

	    outByteBuff.flip();
/*DEL
                this.conn.getHandshake().processHandshake();
                if (this.conn.getHandshake().finishedP()) {
*/
                if (this.sslEngine.getHandshakeStatus() ==
			SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
			// the wrap/unwrap above has resulted in handshaking
			// being complete on our end.
                    logger.debug("initSecContext handshake finished");
                    handshakeFinished();

/*DEL
                    Vector chain = this.conn.getCertificateChain();
                    X509Cert crt = (X509Cert)chain.elementAt(chain.size()-1);
                    setGoodUntil(crt.getValidityNotAfter());
*/
                    Certificate[] chain = this.sslEngine.getSession().getPeerCertificates();
		    if (!(chain instanceof X509Certificate[])) {
			throw new Exception("Certificate chain not of type X509Certificate");
		    }

                    for (X509Certificate cert : (X509Certificate[])chain) {
                        setGoodUntil(cert.getNotAfter());
                    }

                    // acceptor - peer

/*DEL
                    String identity = verifyChain(chain);
*/
			// chain verification would have already been done by
			// JSSE

                    String identity = BouncyCastleUtil.getIdentity(bcConvert(BouncyCastleUtil.getIdentityCertificate((X509Certificate [])chain)));
                    this.targetName = new GlobusGSSName(CertificateUtil.toGlobusID(identity, false));

                    this.peerLimited = Boolean.valueOf(ProxyCertificateUtil.isLimitedProxy(BouncyCastleUtil.getCertificateType((X509Certificate)chain[0])));

		    logger.debug("Peer Identity is: " + identity +
			 " Target name is: " + this.targetName +
			 " Limited Proxy: " + this.peerLimited.toString());

                    // initiator
                    if (this.anonymity) {
                        this.sourceName = new GlobusGSSName();
                    } else {
                        for (X509Certificate cert : this.ctxCred.getCertificateChain()) {
                            setGoodUntil(cert.getNotAfter());
                        }
                        this.sourceName = this.ctxCred.getName();
                    }

                    // mutual authentication test
                    if (this.expectedTargetName != null &&
                        !this.expectedTargetName.equals(this.targetName)) {
                        throw new GlobusGSSException(GSSException.UNAUTHORIZED,
                                                     GlobusGSSException.BAD_NAME,
                                                     "authFailed00",
                                                     new Object[] {this.expectedTargetName,
                                                                   this.targetName});
                    }

                    if (this.gssMode == GSIConstants.MODE_GSI) {
                        this.state = CLIENT_START_DEL;
                        // if there is data to return then
                        // break. otherwise we fall through!!!
                        if (this.outByteBuff.remaining() > 0) {
                            break;
                        }
                    } else {
                        setDone();
                        break;
                    }

                } else {
                    break;
                }
            } catch (IOException e) {
                throw new GlobusGSSException(GSSException.FAILURE, e);
            } catch (Exception e) {
                throw new GlobusGSSException(GSSException.FAILURE, e);
            }

        case CLIENT_START_DEL:

            logger.debug("CLIENT_START_DEL");
            // sanity check - might be invalid state
            if (this.state != CLIENT_START_DEL || this.outByteBuff.remaining() > 0) {
                throw new GSSException(GSSException.FAILURE);
            }
	    if (inByteBuff.hasRemaining()) {
               		throw new GlobusGSSException(GSSException.FAILURE,
		 		new Exception("Not all data processed; Original: " + len
                        + " Remaining: " + inByteBuff.remaining() +
                        " Handshaking status: " + sslEngine.getHandshakeStatus()));
	    }
            this.outByteBuff.clear();

            try {
		String deleg;

                if (getCredDelegState()) {
                    deleg = Character.toString(GSIConstants.DELEGATION_CHAR);
                    this.state = CLIENT_END_DEL;
                } else {
		    deleg = Character.toString('0');
                    setDone();
		}

		byte[] a = deleg.getBytes("US-ASCII");
		inByteBuff = ByteBuffer.wrap(a, 0, a.length);
                outByteBuff = sslDataWrap(inByteBuff, outByteBuff);
		outByteBuff.flip();

            } catch (Exception e) {
                throw new GlobusGSSException(GSSException.FAILURE, e);
            }

            break;

        case CLIENT_END_DEL:

            logger.debug("CLIENT_END_DEL");
	    if (!inByteBuff.hasRemaining()) {
                throw new GSSException(GSSException.DEFECTIVE_TOKEN);
	    }

	    ByteArrayInputStream byteArrayInputStream = null;
            try {
/*DEL
                if (this.in.available() <= 0) {
                    return null;
                }
*/
                outByteBuff = sslDataUnwrap(inByteBuff, outByteBuff);
		outByteBuff.flip();
                if (!outByteBuff.hasRemaining())
                    break;

		byte [] certReq = new byte[outByteBuff.remaining()];
		outByteBuff.get(certReq, 0, certReq.length);

                X509Certificate [] chain = this.ctxCred.getCertificateChain();

                byteArrayInputStream = new ByteArrayInputStream(certReq);
                X509Certificate cert =
                    this.certFactory.createCertificate(byteArrayInputStream,
                                                       chain[0],
                                                       this.ctxCred.getPrivateKey(),
                                                       -1,
/*DEL
                                                       getDelegationType(chain[0]));
*/
                                                       BouncyCastleCertProcessingFactory.decideProxyType(chain[0], this.delegationType));

                byte [] enc = cert.getEncoded();
/*DEL
                this.conn.getOutStream().write(enc, 0, enc.length);
*/
		inByteBuff = ByteBuffer.wrap(enc, 0, enc.length);
		outByteBuff.clear();
                outByteBuff = sslDataWrap(inByteBuff, outByteBuff);
		outByteBuff.flip();

                setDone();
            } catch (GeneralSecurityException e) {
                throw new GlobusGSSException(GSSException.FAILURE, e);
            } catch (IOException e) {
                throw new GlobusGSSException(GSSException.FAILURE, e);
            }finally{
            	if (byteArrayInputStream != null) {
                    try {
                    	byteArrayInputStream.close();
                    } catch (Exception e) {
                        logger.warn("Unable to close stream.");
                    }
                }
            }

            break;

        default:
            throw new GSSException(GSSException.FAILURE);
        }

        if (inByteBuff.hasRemaining()) {
            // Likely BUFFER_UNDERFLOW; save the
            // inByteBuff bytes here like in the unwrap() case
	    logger.debug("Not all data processed; Original: " + len
                        + " Remaining: " + inByteBuff.remaining() +
                        " Handshaking status: " + sslEngine.getHandshakeStatus());
               logger.debug("SAVING unprocessed " + inByteBuff.remaining() + "BYTES\n");
               savedInBytes = new byte[inByteBuff.remaining()];
               inByteBuff.get(savedInBytes, 0, savedInBytes.length);
	}

        logger.debug("exit initSecContext");
	//XXX: Why is here a check for CLIENT_START_DEL?
        // if (this.outByteBuff.hasRemaining() || this.state == CLIENT_START_DEL) {
        if (this.outByteBuff.hasRemaining()) {
                // TODO can we avoid this copy if the ByteBuffer is array based
                // and we return that array, each time allocating a new array
                // for outByteBuff?
		byte [] out = new byte[this.outByteBuff.remaining()];
		this.outByteBuff.get(out, 0, out.length);
		return out;
	} else
		return null;
    }

    private void setDone() {
	logger.debug("DONE with Handshaking and any initial cred delegation");
        this.established = true;
    }

    private void setGoodUntil(Date date) {
        if (this.goodUntil == null) {
            this.goodUntil = date;
        } else if (date.before(this.goodUntil)) {
            this.goodUntil = date;
        }
    }

    private void init(int how)
        throws GSSException, SSLException {

/*DEL
        short [] cs;
        if (this.encryption) {
            // always make sure to add NULL cipher at the end
            short [] ciphers = this.policy.getCipherSuites();
            short [] newCiphers = new short[ciphers.length + 1];
            System.arraycopy(ciphers, 0, newCiphers, 0, ciphers.length);
            newCiphers[ciphers.length] = SSLPolicyInt.TLS_RSA_WITH_NULL_MD5;
            cs = newCiphers;
        } else {
            // encryption not requested - accept only one cipher
            // XXX: in the future might want to iterate through
            // all cipher and enable only the null encryption ones
            cs = NO_ENCRYPTION;
        }
        this.policy.setCipherSuites(cs);
        this.policy.requireClientAuth(this.requireClientAuth.booleanValue());
        this.policy.setAcceptNoClientCert(this.acceptNoClientCerts.booleanValue());

        setTrustedCertificates();

        this.in = new TokenInputStream();
        this.out = new ByteArrayOutputStream();

        try {
            this.conn = new SSLConn(null,
                                    this.in,
                                    this.out,
                                    this.context,
                                    how);
        } catch (IOException e) {
            throw new GlobusGSSException(GSSException.FAILURE, e);
        }

        this.conn.init();
*/
	try {
	    // set trust parameters in SSLConfigurator
		if(this.tc == null){
	        KeyStore trustStore = Stores.getDefaultTrustStore();
	        sslConfigurator.setTrustAnchorStore(trustStore);

	        CertStore crlStore = Stores.getDefaultCRLStore();
	        sslConfigurator.setCrlStore(crlStore);

	        ResourceSigningPolicyStore sigPolStore = Stores.getDefaultSigningPolicyStore();
	        sslConfigurator.setPolicyStore(sigPolStore);
		}

		this.sslConfigurator.setRejectLimitProxy(rejectLimitedProxy);
                if (proxyPolicyHandlers != null)
                    sslConfigurator.setHandlers(proxyPolicyHandlers);

        	this.sslContext = this.sslConfigurator.getSSLContext();
        	this.sslEngine = this.sslContext.createSSLEngine();
	} catch (Exception e) {
            throw new GlobusGSSException(GSSException.FAILURE, e);
        }

	if (this.forceSSLv3AndConstrainCipherSuitesForGram.booleanValue())
           this.sslEngine.setEnabledProtocols(GRAM_PROTOCOLS);
        else
           this.sslEngine.setEnabledProtocols(ENABLED_PROTOCOLS);
	logger.debug("SUPPORTED PROTOCOLS: " +
                    Arrays.toString(this.sslEngine.getSupportedProtocols()) +
                    "; ENABLED PROTOCOLS: " +
                    Arrays.toString(this.sslEngine.getEnabledProtocols()));

        ArrayList<String> cs = new ArrayList();
        if (this.encryption) {
            if (this.forceSSLv3AndConstrainCipherSuitesForGram.booleanValue())
                for (String cipherSuite : GRAM_ENCRYPTION_CIPHER_SUITES)
                    cs.add(cipherSuite);
            else // Simply retain the default-enabled Cipher Suites
               cs.addAll(Arrays.asList(this.sslEngine.getEnabledCipherSuites()));
        } else {
            if (this.forceSSLv3AndConstrainCipherSuitesForGram.booleanValue())
                for (String cipherSuite : GRAM_NO_ENCRYPTION_CIPHER_SUITES)
                    cs.add(cipherSuite);
            else {
               for (String cipherSuite : NO_ENCRYPTION)
                   cs.add(cipherSuite);
               cs.addAll(Arrays.asList(this.sslEngine.getEnabledCipherSuites()));
            }
        }
        cs.removeAll(Arrays.asList(bannedCiphers));
        String[] testSuite = new String[0];
        this.sslEngine.setEnabledCipherSuites(cs.toArray(testSuite));
        logger.debug("CIPHER SUITE IS: " + Arrays.toString(
                      this.sslEngine.getEnabledCipherSuites()));

	// TODO: Document the following behavior
	// NOTE: requireClientAuth Vs. acceptNoClientCerts
	// which one takes precedence? for now err on the side of security
	 if (this.requireClientAuth.booleanValue() == Boolean.TRUE) {
             this.sslEngine.setNeedClientAuth(this.requireClientAuth.booleanValue());
	 } else
             this.sslEngine.setWantClientAuth(!this.acceptNoClientCerts.booleanValue());

        this.sslEngine.setUseClientMode(how == INITIATE);

        this.certFactory = BouncyCastleCertProcessingFactory.getDefault();
        this.state = HANDSHAKE;
	int appSize = sslEngine.getSession().getApplicationBufferSize();
	this.outByteBuff = ByteBuffer.allocate(appSize);
	this.sslEngine.beginHandshake();
    }

    /* this is called when handshake is done */
    private void handshakeFinished()
        throws IOException {
/*DEL
        // this call just forces some internal library
        // variables to be initailized
        this.conn.finishHandshake();
*/
        String cs =
            this.sslEngine.getSession().getCipherSuite();
        this.encryption = !cs.contains("WITH_NULL");
        logger.debug("encryption alg: " + cs);
    }

/*DEL
    // allows bypass of PureTLS checks - since they were
    // already performed during SSL hashshake
    static class GSSProxyPathValidator extends ProxyPathValidator {
        public void validate(X509Certificate [] certPath,
                             TrustedCertificates trustedCerts,
                             CertificateRevocationLists crlsList)
            throws ProxyPathValidatorException {
            super.validate(certPath, trustedCerts, crlsList);
        }
    }

    private String verifyChain(Vector peerCerts)
        throws GSSException {

        X509Certificate[] peerChain = null;
        try {
            peerChain = PureTLSUtil.certificateChainToArray(peerCerts);
        } catch (GeneralSecurityException e) {
            throw new GlobusGSSException(GSSException.DEFECTIVE_CREDENTIAL,
                                         e);
        }

        GSSProxyPathValidator validator = new GSSProxyPathValidator();

        if (this.proxyPolicyHandlers != null) {
            Iterator iter = this.proxyPolicyHandlers.keySet().iterator();
            String oid;
            ProxyPolicyHandler handler;
            while(iter.hasNext()) {
                oid = (String)iter.next();
                handler =
                    (ProxyPolicyHandler)this.proxyPolicyHandlers.get(oid);
                validator.setProxyPolicyHandler(oid, handler);
            }
        }

        CertificateRevocationLists certRevList =
            CertificateRevocationLists.getDefaultCertificateRevocationLists();

        validator.setRejectLimitedProxyCheck(
                  this.rejectLimitedProxy.booleanValue());

        try {
            validator.validate(peerChain, this.tc, certRevList);
        } catch (ProxyPathValidatorException e) {
            // COMMENT FIXME we don't have an error code
            if (e.getErrorCode() ==
                ProxyPathValidatorException.LIMITED_PROXY_ERROR) {
                throw new GlobusGSSException(GSSException.UNAUTHORIZED,
                                             e);
            } else {
                throw new GlobusGSSException(GSSException.DEFECTIVE_CREDENTIAL,
                                             e);
            }
        }

        // C code also sets a flag RECEIVED_LIMITED_PROXY
        // when recevied certs is a limited proxy
        this.peerLimited = (validator.isLimited()) ?
            Boolean.TRUE : Boolean.FALSE;

        return validator.getIdentity();
    }
*/

    private void setCredential()
        throws GSSException {
        try {
/*DEL
            this.context.setCredential(this.ctxCred.getX509Credential());
*/
        KeyStore keyStore = KeyStore.getInstance("JKS");
	    keyStore.load(null, null);
	    X509Credential cred = this.ctxCred.getX509Credential();

	    keyStore.setKeyEntry("default", cred.getPrivateKey(),
			"password".toCharArray(), cred.getCertificateChain());
	    this.sslConfigurator.setCredentialStore(keyStore);
	    this.sslConfigurator.setCredentialStorePassword("password");

        } catch (GeneralSecurityException e) {
            throw new GlobusGSSException(GSSException.DEFECTIVE_CREDENTIAL, e);
        } catch (Exception e) {
            throw new GlobusGSSException(GSSException.FAILURE, e);
        }
    }

/*DEL
    private void setTrustedCertificates()
        throws GSSException {
        if (this.tc == null) {
            this.tc = PureTLSTrustedCertificates.getDefaultPureTLSTrustedCertificates();
        }
        if (this.tc == null) {
            throw new GlobusGSSException(GSSException.DEFECTIVE_CREDENTIAL,
                                         GlobusGSSException.UNKNOWN,
                                         "noCaCerts");
        }
        try {
            // COMMENT: move use of PureTLS from TrustCertificates
            this.context.setRootList(PureTLSUtil.certificateChainToVector(this.tc.getCertificates()));
        } catch (GeneralSecurityException e) {
            throw new GlobusGSSException(GSSException.FAILURE, e);
        }
    }
*/

    /**
     * Wraps a message for integrity and protection.
     * A regular SSL-wrapped token is returned.
     */
    public byte[] wrap(byte []inBuf, int off, int len, MessageProp prop)
        throws GSSException {

        checkContext();

        logger.debug("enter wrap");

        byte [] token = null;
        boolean doGSIWrap = false;

        if (prop != null) {
            if (prop.getQOP() != 0 && prop.getQOP() != GSSConstants.GSI_BIG) {
                throw new GSSException(GSSException.BAD_QOP);
            }
            doGSIWrap = (!prop.getPrivacy() && prop.getQOP() == GSSConstants.GSI_BIG);
        }

        if (doGSIWrap) {
            throw new GSSException(GSSException.UNAVAILABLE);
/*DEL

            byte [] mic = getMIC(inBuf, off, len, null);

            byte [] wtoken = new byte[5 + len + mic.length];
            wtoken[0] = GSI_WRAP;
            wtoken[1] = 3;
            wtoken[2] = 0;
            wtoken[3] = (byte)(mic.length >>> 8);
            wtoken[4] = (byte)(mic.length >>> 0);
            System.arraycopy(mic, 0, wtoken, 5, mic.length);
            System.arraycopy(inBuf, off, wtoken, 5+mic.length, len);

            token = wtoken;
*/
        } else {
            token = wrap(inBuf, off, len);

            if (prop != null) {
                prop.setPrivacy(this.encryption);
                prop.setQOP(0);
            }
        }

        logger.debug("exit wrap");
        return token;
    }

    private byte[] wrap(byte[] inBuf, int off, int len)
        throws GSSException {
        try {
/*DEL
            this.conn.getOutStream().write(inBuf, off, len);
*/
	    ByteBuffer inByteBuff = ByteBuffer.wrap(inBuf, off, len);
	    this.outByteBuff.clear();
	    outByteBuff = this.sslDataWrap(inByteBuff, outByteBuff);
	    outByteBuff.flip();

	    if (inByteBuff.hasRemaining()) {
		throw new Exception("Not all data processed; Original: " + len
                        + " Remaining: " + inByteBuff.remaining() +
                        " Handshaking status: " + sslEngine.getHandshakeStatus());
	    }
        } catch (Exception e) {
            throw new GlobusGSSException(GSSException.FAILURE, e);
        }

        if (this.outByteBuff.hasRemaining()) {
                // TODO can we avoid this copy if the ByteBuffer is array based
                // and we return that array, each time allocating a new array
                // for outByteBuff?
		byte [] out = new byte[this.outByteBuff.remaining()];
		this.outByteBuff.get(out, 0, out.length);
		return out;
	} else
		return null;
/*DEL
        return this.out.toByteArray();
*/
    }

    /**
     * Unwraps a token generated by <code>wrap</code> method on the other side of the context.
     */
    public byte[] unwrap(byte []inBuf, int off, int len, MessageProp prop)
        throws GSSException {

        checkContext();

        logger.debug("enter unwrap");

        byte [] token = null;

        /*
         * see if the token is a straight SSL packet or
         * one of ours made by wrap using get_mic
         */
        if (inBuf[off] == GSI_WRAP &&
            inBuf[off+1] == 3 &&
            inBuf[off+2] == 0) {
            throw new GSSException(GSSException.UNAVAILABLE);
/*DEL

            int micLen = SSLUtil.toShort(inBuf[off+3], inBuf[off+4]);
            int msgLen = len - 5 - micLen;

            if (micLen > len-5 || msgLen < 0) {
                throw new GSSException(GSSException.DEFECTIVE_TOKEN);
            }

            verifyMIC(inBuf, off+5, micLen,
                      inBuf, off+5+micLen, msgLen,
                      null);

            if (prop != null) {
                prop.setPrivacy(false);
                prop.setQOP(GSSConstants.GSI_BIG);
            }

            // extract the data
            token = new byte[msgLen];
            System.arraycopy(inBuf, off+5+micLen, token, 0, msgLen);
*/

        } else {
            token = unwrap(inBuf, off, len);

            if (prop != null) {
                prop.setPrivacy(this.encryption);
                prop.setQOP(0);
            }
        }

        logger.debug("exit unwrap");
        return token;
    }

    private byte[] unwrap(byte[] inBuf, int off, int len)
        throws GSSException {

/*DEL
        ByteArrayInputStream in =
            new ByteArrayInputStream(inBuf, off, len);
        ByteArrayOutputStream out =
            new ByteArrayOutputStream();

        // TODO: this might need to be rewritten
        // to catch lower level exceptions
        // e.g. mac too long, etc.
        try {
            while(in.available() > 0) {
                SSLRecord r = new SSLRecord(null);
                r.decode(this.conn, in);
                switch (r.getType().getValue()) {
                case SSLRecord.SSL_CT_APPLICATION_DATA:
                    out.write(r.getData().getValue());
                    break;
                case SSLRecord.SSL_CT_ALERT:
                    this.conn.getRecordReader().processAlert(r.getData().getValue());
                    break;
                default:
                    throw new Exception(i18n.getMessage("tokenFail03"));
                }
            }
        } catch (IOException e) {
            throw new GlobusGSSException(GSSException.BAD_MIC, e);
        } catch (Exception e) {
            throw new GlobusGSSException(GSSException.DEFECTIVE_TOKEN, e);
        }

        return out.toByteArray();
*/
	ByteBuffer inByteBuff;
        if (savedInBytes != null) {
            if (len > 0) {
                byte[] allInBytes = new byte[savedInBytes.length + len];
                logger.debug("ALLOCATED for allInBytes " + savedInBytes.length + " + " + len + " bytes\n");
                System.arraycopy(savedInBytes, 0, allInBytes, 0, savedInBytes.length);
                System.arraycopy(inBuf, off, allInBytes, savedInBytes.length, len);
                inByteBuff = ByteBuffer.wrap(allInBytes, 0, allInBytes.length);
            } else {
                inByteBuff = ByteBuffer.wrap(savedInBytes, 0, savedInBytes.length);
            }
            savedInBytes = null;
        } else {
            inByteBuff = ByteBuffer.wrap(inBuf, off, len);
        }
	this.outByteBuff.clear();
	outByteBuff = this.sslDataUnwrap(inByteBuff, outByteBuff);
	outByteBuff.flip();

	if (inByteBuff.hasRemaining()) {
	    logger.debug("Not all data processed; Original: " + len
                        + " Remaining: " + inByteBuff.remaining() +
                        " Handshaking status: " + sslEngine.getHandshakeStatus());
               logger.debug("SAVING unprocessed " + inByteBuff.remaining() + "BYTES\n");
               savedInBytes = new byte[inByteBuff.remaining()];
               inByteBuff.get(savedInBytes, 0, savedInBytes.length);
	}

        if (this.outByteBuff.hasRemaining()) {
                // TODO can we avoid this copy if the ByteBuffer is array based
                // and we return that array, each time allocating a new array
                // for outByteBuff?
		byte [] out = new byte[this.outByteBuff.remaining()];
		this.outByteBuff.get(out, 0, out.length);
		return out;
	} else
		return null;

    }

    public void dispose()
        throws GSSException {
        // doesn't do anything right now
        logger.debug("dipose");
    }

    public boolean isEstablished() {
        return this.established;
    }

    public void requestCredDeleg(boolean state) throws GSSException {
        this.credentialDelegation = state;
    }

    public boolean getCredDelegState() {
        return this.credentialDelegation;
    }

    public boolean isInitiator()
        throws GSSException {
        if (this.role == UNDEFINED) {
            throw new GSSException(GSSException.FAILURE);
        }
        return (this.role == INITIATE);
    }

    public boolean isProtReady() {
        return isEstablished();
    }

    public void requestLifetime(int lifetime)
        throws GSSException {
        if (lifetime == GSSContext.INDEFINITE_LIFETIME) {
            throw new GlobusGSSException(GSSException.FAILURE,
                                         GlobusGSSException.UNKNOWN,
                                         "badLifetime00");
        }

        if (lifetime != GSSContext.DEFAULT_LIFETIME) {
            Calendar calendar = Calendar.getInstance();
            calendar.add(Calendar.SECOND, lifetime);
            setGoodUntil(calendar.getTime());
        }
    }

    public int getLifetime() {
        if (this.goodUntil != null) {
            return (int)((this.goodUntil.getTime() - System.currentTimeMillis())/1000);
        } else {
            return -1;
        }
    }

    public Oid getMech() throws GSSException {
        return GSSConstants.MECH_OID;
    }

    public GSSCredential getDelegCred() throws GSSException {
        return this.delegCred;
    }

    public void requestConf(boolean state)
        throws GSSException {
        // enabled encryption
        this.encryption = state;
    }

    public boolean getConfState() {
        return this.encryption;
    }

    /**
     * Returns a cryptographic MIC (message integrity check)
     * of a specified message.
     */
    public byte[] getMIC(byte [] inBuf,
                         int off,
                         int len,
                         MessageProp prop)
        throws GSSException {
        throw new GSSException(GSSException.UNAVAILABLE);
/*TODO

        checkContext();

        logger.debug("enter getMic");

        if (prop != null && (prop.getQOP() != 0 || prop.getPrivacy())) {
            throw new GSSException(GSSException.BAD_QOP);
        }

        SSLCipherState st = this.conn.getWriteCipherState();
        SSLCipherSuite cs = st.getCipherSuite();
        long sequence = this.conn.getWriteSequence();

        byte [] mic = new byte[GSI_MESSAGE_DIGEST_PADDING + cs.getDigestOutputLength()];

        System.arraycopy(Util.toBytes(sequence), 0, mic, 0, GSI_SEQUENCE_SIZE);
        System.arraycopy(Util.toBytes(len, 4), 0, mic, GSI_SEQUENCE_SIZE, 4);

        this.conn.incrementWriteSequence();

        int pad_ct = (cs.getDigestOutputLength()==16) ? 48 : 40;

        try {
            MessageDigest md =
                MessageDigest.getInstance(cs.getDigestAlg());

            md.update(st.getMacKey());
            for(int i=0;i<pad_ct;i++) {
                md.update(SSLHandshake.pad_1);
            }
            md.update(mic, 0, GSI_MESSAGE_DIGEST_PADDING);
            md.update(inBuf, off, len);

            byte[] digest = md.digest();

            System.arraycopy(digest, 0, mic, GSI_MESSAGE_DIGEST_PADDING, digest.length);
        } catch (NoSuchAlgorithmException e) {
            throw new GlobusGSSException(GSSException.FAILURE, e);
        }

        if (prop != null) {
            prop.setPrivacy(false);
            prop.setQOP(0);
        }

        logger.debug("exit getMic");
        return mic;
*/
    }

    /**
     * Verifies a cryptographic MIC (message integrity check)
     * of a specified message.
     */
    public void verifyMIC(byte[] inTok, int tokOff, int tokLen, // mic
                          byte[] inMsg, int msgOff, int msgLen, // real msg
                          MessageProp prop)
        throws GSSException {
        throw new GSSException(GSSException.UNAVAILABLE);
/*TODO

        checkContext();

        logger.debug("enter verifyMic");

        SSLCipherState st = this.conn.getReadCipherState();
        SSLCipherSuite cs = st.getCipherSuite();

        logger.debug("digest algorithm: " + cs.getDigestAlg());

        if (tokLen != (GSI_MESSAGE_DIGEST_PADDING + cs.getDigestOutputLength())) {
            throw new GlobusGSSException(GSSException.DEFECTIVE_TOKEN,
                                         GlobusGSSException.TOKEN_FAIL,
                                         "tokenFail00",
                                         new Object[] {new Integer(tokLen),
                                                       new Integer(GSI_MESSAGE_DIGEST_PADDING +
                                                                   cs.getDigestOutputLength())});
        }

        int bufLen = SSLUtil.toInt(inTok, tokOff+GSI_SEQUENCE_SIZE);
        if (bufLen != msgLen) {
            throw new GlobusGSSException(GSSException.DEFECTIVE_TOKEN,
                                         GlobusGSSException.TOKEN_FAIL,
                                         "tokenFail01",
                                         new Object[] {new Integer(msgLen), new Integer(bufLen)});
        }

        int pad_ct = (cs.getDigestOutputLength()==16) ? 48 : 40;

        byte [] digest = null;

        try {
            MessageDigest md =
                MessageDigest.getInstance(cs.getDigestAlg());

            md.update(st.getMacKey());
            for(int i=0;i<pad_ct;i++) {
                md.update(SSLHandshake.pad_1);
            }
            md.update(inTok, tokOff, GSI_MESSAGE_DIGEST_PADDING);
            md.update(inMsg, msgOff, msgLen);

            digest = md.digest();
        } catch (NoSuchAlgorithmException e) {
            throw new GlobusGSSException(GSSException.FAILURE, e);
        }

        byte [] token = new byte[tokLen-GSI_MESSAGE_DIGEST_PADDING];
        System.arraycopy(inTok, tokOff+GSI_MESSAGE_DIGEST_PADDING, token, 0, token.length);

        if (!Arrays.equals(digest, token)) {
            throw new GlobusGSSException(GSSException.BAD_MIC,
                                         GlobusGSSException.BAD_MIC,
                                         "tokenFail02");
        }

        long tokSeq = SSLUtil.toLong(inTok, tokOff);
        long readSeq = this.conn.getReadSequence();
        long seqTest = tokSeq - readSeq;

        logger.debug("Token seq#   : " + tokSeq);
        logger.debug("Current seq# : " + readSeq);

        if (seqTest > 0) {
            // gap token
            throw new GSSException(GSSException.GAP_TOKEN);
        } else if (seqTest < 0) {
            // old token
            throw new GSSException(GSSException.OLD_TOKEN);
        } else {
            this.conn.incrementReadSequence();
        }

        if (prop != null) {
            prop.setPrivacy(false);
            prop.setQOP(0);
        }

        logger.debug("exit verifyMic");
*/
    }


    /**
     * It works just like {@link #initSecContext(byte[], int, int) initSecContext} method.
     * It reads one SSL token from input stream, calls
     * {@link #initSecContext(byte[], int, int) initSecContext} method and
     * writes the output token to the output stream (if any)
     * SSL token is not read on the initial call.
     */
    public int initSecContext(InputStream in, OutputStream out)
        throws GSSException {
        byte [] inToken = null;
        try {
            if (!this.conn) {
                inToken = new byte[0];
            } else {
                inToken = SSLUtil.readSslMessage(in);
            }
            byte [] outToken = initSecContext(inToken, 0, inToken.length);
            if (outToken != null) {
                out.write(outToken);
                return outToken.length;
            } else {
                return 0;
            }
        } catch (IOException e) {
            throw new GlobusGSSException(GSSException.FAILURE, e);
        }
    }

    /**
     * It works just like {@link #acceptSecContext(byte[], int, int) acceptSecContext}
     * method. It reads one SSL token from input stream, calls
     * {@link #acceptSecContext(byte[], int, int) acceptSecContext}
     * method and writes the output token to the output stream (if any)
     */
    public void acceptSecContext(InputStream in, OutputStream out)
        throws GSSException {
        try {
            byte [] inToken = SSLUtil.readSslMessage(in);
            byte [] outToken = acceptSecContext(inToken, 0, inToken.length);
            if (outToken != null) {
                out.write(outToken);
            }
        } catch (IOException e) {
            throw new GlobusGSSException(GSSException.FAILURE, e);
        }
    }

    public GSSName getSrcName() throws GSSException {
        return this.sourceName;
    }

    public GSSName getTargName() throws GSSException {
        return this.targetName;
    }

    public void requestInteg(boolean state)
        throws GSSException {
        if (!state) {
            throw new GlobusGSSException(GSSException.FAILURE,
                                         GlobusGSSException.BAD_OPTION,
                                         "integOn");
        }
    }

    public boolean getIntegState() {
        return true; // it is always on with ssl
    }

    public void requestSequenceDet(boolean state)
        throws GSSException {
        if (!state) {
            throw new GlobusGSSException(GSSException.FAILURE,
                                         GlobusGSSException.BAD_OPTION,
                                         "seqDet");
        }
    }

    public boolean getSequenceDetState() {
        return true; // it is always on with ssl
    }

    public void requestReplayDet(boolean state)
        throws GSSException {
        if (!state) {
            throw new GlobusGSSException(GSSException.FAILURE,
                                         GlobusGSSException.BAD_OPTION,
                                         "replayDet");
        }
    }

    public boolean getReplayDetState() {
        return true; // is is always on with ssl
    }

    public void requestAnonymity(boolean state)
        throws GSSException {
        this.anonymity = state;
    }

    public boolean getAnonymityState() {
        return this.anonymity;
    }

    public void requestMutualAuth(boolean state)
        throws GSSException {
        if (!state) {
            throw new GlobusGSSException(GSSException.FAILURE,
                                         GlobusGSSException.BAD_OPTION,
                                         "mutualAuthOn");
        }
    }

    public boolean getMutualAuthState() {
        return true; // always on with gsi i guess
    }

    protected byte[] generateCertRequest(X509Certificate cert)
        throws GeneralSecurityException {

        int bits =
            ((RSAPublicKey)cert.getPublicKey()).getModulus().bitLength();

        this.keyPair = keyPairCache.getKeyPair(bits);

        return this.certFactory.createCertificateRequest(cert, this.keyPair);
    }

    protected void verifyDelegatedCert(X509Certificate certificate)
        throws GeneralSecurityException {
        RSAPublicKey pubKey = (RSAPublicKey)certificate.getPublicKey();
        RSAPrivateKey privKey = (RSAPrivateKey)this.keyPair.getPrivate();

        if (!pubKey.getModulus().equals(privKey.getModulus())) {
            throw new GeneralSecurityException(i18n.getMessage("keyMismatch"));
        }
    }

    protected void checkContext()
        throws GSSException {
        if (!this.conn || !isEstablished()) {
            throw new GSSException(GSSException.NO_CONTEXT);
        }

        if (this.checkContextExpiration.booleanValue() && getLifetime() <= 0) {
            throw new GSSException(GSSException.CONTEXT_EXPIRED);
        }
    }

/*DEL
    protected int getDelegationType(X509Certificate issuer)
        throws GeneralSecurityException, GSSException {

        // GSIConstants.CertificateType certType = BouncyCastleUtil.getCertificateType(issuer, this.tc);
	// TODO: Is this alright without this.tc being passed?
        GSIConstants.CertificateType certType = BouncyCastleUtil.getCertificateType(issuer);
        int dType = this.delegationType.intValue();

        if (logger.isDebugEnabled()) {
            logger.debug("Issuer type: " + certType + " delg. type requested: " + dType);
        }

        if (certType == GSIConstants.CertificateType.EEC) {
            if (dType == GSIConstants.DELEGATION_LIMITED) {
                if (VersionUtil.isGsi2Enabled()) {
                    return GSIConstants.GSI_2_LIMITED_PROXY;
                } else if (VersionUtil.isGsi3Enabled()) {
                    return GSIConstants.GSI_3_LIMITED_PROXY;
                } else {
                    return GSIConstants.GSI_4_LIMITED_PROXY;
                }
            } else if (dType == GSIConstants.DELEGATION_FULL) {
                if (VersionUtil.isGsi2Enabled()) {
                    return GSIConstants.GSI_2_PROXY;
                } else if (VersionUtil.isGsi3Enabled()) {
                    return GSIConstants.GSI_3_IMPERSONATION_PROXY;
                } else {
                    return GSIConstants.GSI_4_IMPERSONATION_PROXY;
                }
            } else if (ProxyCertificateUtil.isProxy(GSIConstants.CertificateType.get(dType))) {
                return dType;
            }
        } else if (ProxyCertificateUtil.isGsi2Proxy(certType)) {
            if (dType == GSIConstants.DELEGATION_LIMITED) {
                return GSIConstants.GSI_2_LIMITED_PROXY;
            } else if (dType == GSIConstants.DELEGATION_FULL) {
                return GSIConstants.GSI_2_PROXY;
            } else if (ProxyCertificateUtil.isGsi2Proxy(GSIConstants.CertificateType.get(dType))) {
                return dType;
            }
        } else if (ProxyCertificateUtil.isGsi3Proxy(certType)) {
            if (dType == GSIConstants.DELEGATION_LIMITED) {
                return GSIConstants.GSI_3_LIMITED_PROXY;
            } else if (dType == GSIConstants.DELEGATION_FULL) {
                return GSIConstants.GSI_3_IMPERSONATION_PROXY;
            } else if (ProxyCertificateUtil.isGsi3Proxy(GSIConstants.CertificateType.get(dType))) {
                return dType;
            }
        } else if (ProxyCertificateUtil.isGsi4Proxy(certType)) {
            if (dType == GSIConstants.DELEGATION_LIMITED) {
                return GSIConstants.GSI_4_LIMITED_PROXY;
            } else if (dType == GSIConstants.DELEGATION_FULL) {
                return GSIConstants.GSI_4_IMPERSONATION_PROXY;
            } else if (ProxyCertificateUtil.isGsi4Proxy(GSIConstants.CertificateType.get(dType))) {
                return dType;
            }
        }
        throw new GSSException(GSSException.FAILURE);
    }
*/


    // -----------------------------------

    protected void setGssMode(Object value)
        throws GSSException {
        if (!(value instanceof Integer)) {
            throw new GlobusGSSException(GSSException.FAILURE,
                                         GlobusGSSException.BAD_OPTION_TYPE,
                                         "badType",
                                         new Object [] {"GSS mode", Integer.class});
        }
        Integer v = (Integer)value;
        if (v == GSIConstants.MODE_GSI ||
            v == GSIConstants.MODE_SSL) {
            this.gssMode = v;
        } else {
            throw new GlobusGSSException(GSSException.FAILURE,
                                         GlobusGSSException.BAD_OPTION,
                                         "badGssMode");
        }
    }

    protected void setDelegationType(Object value)
        throws GSSException {
        GSIConstants.DelegationType v;
        if (value instanceof GSIConstants.DelegationType)
            v = (GSIConstants.DelegationType) value;
        else if (value instanceof Integer)
            v = GSIConstants.DelegationType.get(((Integer) value).intValue());
        else {
            throw new GlobusGSSException(GSSException.FAILURE,
                                         GlobusGSSException.BAD_OPTION_TYPE,
                                         "badType",
                                         new Object[] {"delegation type",  GSIConstants.DelegationType.class});
        }
/*DEL
        Integer v = (Integer)value;
*/
        if (v == GSIConstants.DelegationType.FULL ||
            v == GSIConstants.DelegationType.LIMITED) {
            this.delegationType = v;
        } else {
            throw new GlobusGSSException(GSSException.FAILURE,
                                         GlobusGSSException.BAD_OPTION,
                                         "badDelegType");
        }
    }

    protected void setCheckContextExpired(Object value)
        throws GSSException {
        if (!(value instanceof Boolean)) {
            throw new GlobusGSSException(GSSException.FAILURE,
                                         GlobusGSSException.BAD_OPTION_TYPE,
                                         "badType",
                                         new Object[] {"check context expired", Boolean.class});
        }
        this.checkContextExpiration = (Boolean)value;
    }

    protected void setRejectLimitedProxy(Object value)
        throws GSSException {
        if (!(value instanceof Boolean)) {
            throw new GlobusGSSException(GSSException.FAILURE,
                                         GlobusGSSException.BAD_OPTION_TYPE,
                                         "badType",
                                         new Object[] {"reject limited proxy", Boolean.class});
        }
        this.rejectLimitedProxy = (Boolean)value;
    }

    protected void setRequireClientAuth(Object value)
        throws GSSException {
        if (!(value instanceof Boolean)) {
            throw new GlobusGSSException(GSSException.FAILURE,
                                         GlobusGSSException.BAD_OPTION_TYPE,
                                         "badType",
                                         new Object[] {"require client auth", Boolean.class});
        }
        this.requireClientAuth = (Boolean)value;
    }

    protected void setRequireAuthzWithDelegation(Object value)
        throws GSSException {

        if (!(value instanceof Boolean)) {
            throw new GlobusGSSException(GSSException.FAILURE,
                                         GlobusGSSException.BAD_OPTION_TYPE,
                                         "badType",
                                         new Object[] {"require authz with delehation", Boolean.class});
        }
        this.requireAuthzWithDelegation = (Boolean)value;
    }

    protected void setAcceptNoClientCerts(Object value)
        throws GSSException {
        if (!(value instanceof Boolean)) {
            throw new GlobusGSSException(GSSException.FAILURE,
                                         GlobusGSSException.BAD_OPTION_TYPE,
                                         "badType",
                                         new Object[] {"accept no client certs", Boolean.class});
        }
        this.acceptNoClientCerts = (Boolean)value;
    }

    protected void setForceSslV3AndConstrainCipherSuitesForGram(
                             Object value)
        throws GSSException {
        if (!(value instanceof Boolean)) {
            throw new GlobusGSSException(GSSException.FAILURE,
                                         GlobusGSSException.BAD_OPTION_TYPE,
                                         "badType",
                                         new Object[] {"adjust cipher suites for GRAM", Boolean.class});
        }
        this.forceSSLv3AndConstrainCipherSuitesForGram = (Boolean)value;
    }

/*DEL
    protected void setGrimPolicyHandler(Object value)
        throws GSSException {
        if (!(value instanceof ProxyPolicyHandler)) {
            throw new GlobusGSSException(GSSException.FAILURE,
                                         GlobusGSSException.BAD_OPTION_TYPE,
                                         "badType",
                                         new Object[] {"GRIM policy handler",
                                                       ProxyPolicyHandler.class});
        }
        if (this.proxyPolicyHandlers == null) {
            this.proxyPolicyHandlers = new HashMap();
        }
        this.proxyPolicyHandlers.put("1.3.6.1.4.1.3536.1.1.1.7", value);
    }
*/

    protected void setProxyPolicyHandlers(Object value)
        throws GSSException {
        if (!(value instanceof Map)) {
            throw new GlobusGSSException(GSSException.FAILURE,
                                         GlobusGSSException.BAD_OPTION_TYPE,
                                         "badType",
                                        new Object[] {"Proxy policy handlers",
                                                      Map.class});
        }
        this.proxyPolicyHandlers = (Map)value;
    }

    protected void setTrustedCertificates(Object value)
        throws GSSException {
        if (!(value instanceof TrustedCertificates)) {
            throw new GlobusGSSException(GSSException.FAILURE,
                                         GlobusGSSException.BAD_OPTION_TYPE,
                                         "badType",
                                         new Object[] {"Trusted certificates",
                                                       TrustedCertificates.class});
        }
        this.tc = (TrustedCertificates) value;
        //TODO: set this in SSLConfigurator before creating SSLContext and engine?
        sslConfigurator.setTrustAnchorStore(((TrustedCertificates)value).getTrustStore());
        sslConfigurator.setCrlStore(((TrustedCertificates)value).getcrlStore());
        sslConfigurator.setPolicyStore(((TrustedCertificates)value).getsigPolStore());
    }


    public void setOption(Oid option, Object value)
        throws GSSException {
        if (option == null) {
            throw new GlobusGSSException(GSSException.FAILURE,
                                         GlobusGSSException.BAD_ARGUMENT,
                                         "nullOption");
        }
        if (value == null) {
            throw new GlobusGSSException(GSSException.FAILURE,
                                         GlobusGSSException.BAD_ARGUMENT,
                                         "nullOptionValue");
        }

        if (option.equals(GSSConstants.GSS_MODE)) {
            setGssMode(value);
        } else if (option.equals(GSSConstants.DELEGATION_TYPE)) {
            setDelegationType(value);
        } else if (option.equals(GSSConstants.CHECK_CONTEXT_EXPIRATION)) {
            setCheckContextExpired(value);
        } else if (option.equals(GSSConstants.REJECT_LIMITED_PROXY)) {
            setRejectLimitedProxy(value);
        } else if (option.equals(GSSConstants.REQUIRE_CLIENT_AUTH)) {
            setRequireClientAuth(value);
/*DEL
        } else if (option.equals(GSSConstants.GRIM_POLICY_HANDLER)) {
            setGrimPolicyHandler(value);
*/
        } else if (option.equals(GSSConstants.TRUSTED_CERTIFICATES)) {
            setTrustedCertificates(value);
        } else if (option.equals(GSSConstants.PROXY_POLICY_HANDLERS)) {
            setProxyPolicyHandlers(value);
        } else if (option.equals(GSSConstants.ACCEPT_NO_CLIENT_CERTS)) {
            setAcceptNoClientCerts(value);
        } else if (option.equals(GSSConstants
                                 .AUTHZ_REQUIRED_WITH_DELEGATION)) {
            setRequireAuthzWithDelegation(value);
        } else if (option.equals(GSSConstants
                     .FORCE_SSLV3_AND_CONSTRAIN_CIPHERSUITES_FOR_GRAM)) {
            setForceSslV3AndConstrainCipherSuitesForGram(value);
        } else {
            throw new GlobusGSSException(GSSException.FAILURE,
                                         GlobusGSSException.UNKNOWN_OPTION,
                                         "unknownOption",
                                         new Object[] {option});
        }
    }

    public Object getOption(Oid option)
        throws GSSException {
        if (option == null) {
            throw new GlobusGSSException(GSSException.FAILURE,
                                         GlobusGSSException.BAD_ARGUMENT,
                                         "nullOption");
        }

        if (option.equals(GSSConstants.GSS_MODE)) {
            return this.gssMode;
        } else if (option.equals(GSSConstants.DELEGATION_TYPE)) {
            return this.delegationType;
        } else if (option.equals(GSSConstants.CHECK_CONTEXT_EXPIRATION)) {
            return this.checkContextExpiration;
        } else if (option.equals(GSSConstants.REJECT_LIMITED_PROXY)) {
            return this.rejectLimitedProxy;
        } else if (option.equals(GSSConstants.REQUIRE_CLIENT_AUTH)) {
            return this.requireClientAuth;
        } else if (option.equals(GSSConstants.TRUSTED_CERTIFICATES)) {
            return this.tc;
        } else if (option.equals(GSSConstants.PROXY_POLICY_HANDLERS)) {
            // return this.proxyPolicyHandlers;
            throw new GSSException(GSSException.UNAVAILABLE);
        } else if (option.equals(GSSConstants.ACCEPT_NO_CLIENT_CERTS)) {
            return this.acceptNoClientCerts;
        }

        return null;
    }

    /**
     * Initiate the delegation of a credential.
     *
     * This function drives the initiating side of the credential
     * delegation process. It is expected to be called in tandem with the
     * {@link #acceptDelegation(int, byte[], int, int) acceptDelegation}
     * function.
     * <BR>
     * The behavior of this function can be modified by
     * {@link GSSConstants#DELEGATION_TYPE GSSConstants.DELEGATION_TYPE}
     * and
     * {@link GSSConstants#GSS_MODE GSSConstants.GSS_MODE} context
     * options.
     * The {@link GSSConstants#DELEGATION_TYPE GSSConstants.DELEGATION_TYPE}
     * option controls delegation type to be performed. The
     * {@link GSSConstants#GSS_MODE GSSConstants.GSS_MODE}
     * option if set to
     * {@link GSIConstants#MODE_SSL GSIConstants.MODE_SSL}
     * results in tokens that are not wrapped.
     *
     * @param credential
     *        The credential to be delegated. May be null
     *        in which case the credential associated with the security
     *        context is used.
     * @param mechanism
     *        The desired security mechanism. May be null.
     * @param lifetime
     *        The requested period of validity (seconds) of the delegated
     *        credential.
     * @return A token that should be passed to <code>acceptDelegation</code> if
     *         <code>isDelegationFinished</code> returns false. May be null.
     * @exception GSSException containing the following major error codes:
     *            <code>GSSException.FAILURE</code>
     */
    public byte[] initDelegation(GSSCredential credential,
                                 Oid mechanism,
                                 int lifetime,
                                 byte[] buf, int off, int len)
        throws GSSException {

        logger.debug("Enter initDelegation: " + delegationState);

        if (mechanism != null && !mechanism.equals(getMech())) {
            throw new GSSException(GSSException.BAD_MECH);
        }

        if (this.gssMode != GSIConstants.MODE_SSL && buf != null && len > 0) {
            buf = unwrap(buf, off, len);
            off = 0;
            len = buf.length;
        }

        byte [] token = null;

        switch (delegationState) {

        case DELEGATION_START:

            this.delegationFinished = false;
            token = DELEGATION_TOKEN;
            this.delegationState = DELEGATION_SIGN_CERT;
            break;

        case DELEGATION_SIGN_CERT:


            if (credential == null) {
                // get default credential
                GSSManager manager = new GlobusGSSManagerImpl();
                credential = manager.createCredential(GSSCredential.INITIATE_AND_ACCEPT);
            }

            if (!(credential instanceof GlobusGSSCredentialImpl)) {
                throw new GSSException(GSSException.DEFECTIVE_CREDENTIAL);
            }

            X509Credential cred =
                ((GlobusGSSCredentialImpl)credential).getX509Credential();

            X509Certificate [] chain = cred.getCertificateChain();

            int time = (lifetime == GSSCredential.DEFAULT_LIFETIME) ? -1 : lifetime;

            ByteArrayInputStream inData = null;
            ByteArrayOutputStream out = null;
            try {
            	inData = new ByteArrayInputStream(buf, off, len);
                X509Certificate cert =
                    this.certFactory.createCertificate(inData,
                                                       chain[0],
                                                       cred.getPrivateKey(),
                                                       time,
/*DEL
                                                       getDelegationType(chain[0]));
*/
                                                       BouncyCastleCertProcessingFactory.decideProxyType(chain[0], this.delegationType));


                out = new ByteArrayOutputStream();

                out.write(cert.getEncoded());
                for (int i=0;i<chain.length;i++) {
                    out.write(chain[i].getEncoded());
                }

                token = out.toByteArray();
            } catch (Exception e) {
                throw new GlobusGSSException(GSSException.FAILURE, e);
            }finally{
            	if (inData != null) {
                    try {
                    	inData.close();
                    } catch (Exception e) {
                        logger.warn("Unable to close stream.");
                    }
                }
            	if (out != null) {
                    try {
                    	out.close();
                    } catch (Exception e) {
                        logger.warn("Unable to close stream.");
                    }
                }
            }

            this.delegationState = DELEGATION_START;
            this.delegationFinished = true;
            break;

        default:
            throw new GSSException(GSSException.FAILURE);
        }

        logger.debug("Exit initDelegation");

        if (this.gssMode != GSIConstants.MODE_SSL && token != null) {
            // XXX: Why wrap() only when not in MODE_SSL?
            return wrap(token, 0, token.length);
        } else {
            return token;
        }
    }

    /**
     * Accept a delegated credential.
     *
     * This function drives the accepting side of the credential
     * delegation process. It is expected to be called in tandem with the
     * {@link #initDelegation(GSSCredential, Oid, int, byte[], int, int)
     * initDelegation} function.
     * <BR>
     * The behavior of this function can be modified by
     * {@link GSSConstants#GSS_MODE GSSConstants.GSS_MODE} context
     * option. The
     * {@link GSSConstants#GSS_MODE GSSConstants.GSS_MODE}
     * option if set to
     * {@link GSIConstants#MODE_SSL GSIConstants.MODE_SSL}
     * results in tokens that are not wrapped.
     *
     * @param lifetime
     *        The requested period of validity (seconds) of the delegated
     *        credential.
     * @return A token that should be passed to <code>initDelegation</code> if
     *        <code>isDelegationFinished</code> returns false. May be null.
     * @exception GSSException containing the following major error codes:
     *            <code>GSSException.FAILURE</code>
     */
    public byte[] acceptDelegation(int lifetime,
                                   byte[] buf, int off, int len)
        throws GSSException {

        logger.debug("Enter acceptDelegation: " + delegationState);

        if (this.gssMode != GSIConstants.MODE_SSL && buf != null && len > 0) {
            buf = unwrap(buf, off, len);
            off = 0;
            len = buf.length;
        }

        byte [] token = null;

        switch (delegationState) {

        case DELEGATION_START:

            this.delegationFinished = false;

            if (len != 1 && buf[off] != GSIConstants.DELEGATION_CHAR) {
                throw new GlobusGSSException(GSSException.FAILURE,
                                             GlobusGSSException.DELEGATION_ERROR,
                                             "delegError00",
                                             new Object[] {new Character((char)buf[off])});
            }

            try {
/*DEL
                Vector certChain = this.conn.getCertificateChain();
*/
		Certificate[] certChain;
		try {
		    certChain = this.sslEngine.getSession().getPeerCertificates();
		} catch (SSLPeerUnverifiedException e) {
                    certChain = null;
                }
                if (certChain == null || certChain.length == 0) {
                    throw new GlobusGSSException(GSSException.FAILURE,
                                                 GlobusGSSException.DELEGATION_ERROR,
                                                 "noClientCert");
                }

                X509Certificate tmpCert =
/*DEL
                    PureTLSUtil.convertCert((X509Cert)certChain.lastElement());
*/
                    (X509Certificate) certChain[0];

                token = generateCertRequest(tmpCert);
            } catch (GeneralSecurityException e) {
                throw new GlobusGSSException(GSSException.FAILURE, e);
            }

            this.delegationState = DELEGATION_COMPLETE_CRED;
            break;

        case DELEGATION_COMPLETE_CRED:

            ByteArrayInputStream in = null;
            X509Certificate [] chain = null;
            LinkedList certList = new LinkedList();
            X509Certificate cert = null;
            try {
            	in = new ByteArrayInputStream(buf, off, len);
                while(in.available() > 0) {
                    cert = CertificateLoadUtil.loadCertificate(in);
                    certList.add(cert);
                }

                chain = new X509Certificate[certList.size()];
                chain = (X509Certificate[])certList.toArray(chain);

                verifyDelegatedCert(chain[0]);

            } catch (GeneralSecurityException e) {
                throw new GlobusGSSException(GSSException.FAILURE, e);
            }finally{
            	if (in != null) {
                    try {
                    	in.close();
                    } catch (Exception e) {
                        logger.warn("Unable to close streamreader.");
                    }
                }
            }

            X509Credential proxy =
                new X509Credential(this.keyPair.getPrivate(), chain);

            this.delegatedCred =
                new GlobusGSSCredentialImpl(proxy,
                                            GSSCredential.INITIATE_AND_ACCEPT);

            this.delegationState = DELEGATION_START;
            this.delegationFinished = true;
            break;

        default:
            throw new GSSException(GSSException.FAILURE);
        }

        logger.debug("Exit acceptDelegation");

        if (this.gssMode != GSIConstants.MODE_SSL && token != null) {
            // XXX: Why wrap() only when not in MODE_SSL?
            return wrap(token, 0, token.length);
        } else {
            return token;
        }
    }

    public GSSCredential getDelegatedCredential() {
        return this.delegatedCred;
    }

    public boolean isDelegationFinished() {
        return this.delegationFinished;
    }

    /**
     * Retrieves arbitrary data about this context.
     * Currently supported oid: <UL>
     * <LI>
     * {@link GSSConstants#X509_CERT_CHAIN GSSConstants.X509_CERT_CHAIN}
     * returns certificate chain of the peer (<code>X509Certificate[]</code>).
     * </LI>
     * </UL>
     *
     * @param oid the oid of the information desired.
     * @return the information desired. Might be null.
     * @exception GSSException containing the following major error codes:
     *            <code>GSSException.FAILURE</code>
     */
    public Object inquireByOid(Oid oid)
        throws GSSException {
        if (oid == null) {
            throw new GlobusGSSException(GSSException.FAILURE,
                                         GlobusGSSException.BAD_ARGUMENT,
                                         "nullOption");
        }

        if (oid.equals(GSSConstants.X509_CERT_CHAIN)) {
            if (isEstablished()) {
                // converting certs is slower but keeping coverted certs
                // takes lots of memory.
                try {
/*DEL
                    Vector peerCerts = this.conn.getCertificateChain();
*/
                    Certificate[] peerCerts;
		    try {
			peerCerts = this.sslEngine.getSession().getPeerCertificates();
		    } catch (SSLPeerUnverifiedException e) {
			peerCerts = null;
		    }
                    if (peerCerts != null && peerCerts.length > 0) {
/*DEL
                        return PureTLSUtil.certificateChainToArray(peerCerts);
*/
                        return (X509Certificate[])peerCerts;
                    } else {
                        return null;
                    }
                } catch (Exception e) {
                    throw new GlobusGSSException(
                             GSSException.DEFECTIVE_CREDENTIAL,
                             e
                    );
                }
            }
        } else if (oid.equals(GSSConstants.RECEIVED_LIMITED_PROXY)) {
            return this.peerLimited;
        }

        return null;
    }

    public void setBannedCiphers(String[] ciphers) {
        bannedCiphers = new String[ciphers.length];
        System.arraycopy(ciphers, 0, bannedCiphers, 0, ciphers.length);
    }



    // ==================================================================
    // Not implemented below
    // ==================================================================

    /**
     * Currently not implemented.
     */
    public int getWrapSizeLimit(int qop, boolean confReq,
                                int maxTokenSize)
        throws GSSException {
        throw new GSSException(GSSException.UNAVAILABLE);
    }

    /**
     * Currently not implemented.
     */
    public void wrap(InputStream inStream, OutputStream outStream,
                     MessageProp msgProp)
        throws GSSException {
        throw new GSSException(GSSException.UNAVAILABLE);
    }

    /**
     * Currently not implemented.
     */
    public void unwrap(InputStream inStream, OutputStream outStream,
                       MessageProp msgProp)
        throws GSSException {
        throw new GSSException(GSSException.UNAVAILABLE);
    }

    /**
     * Currently not implemented.
     */
    public void getMIC(InputStream inStream, OutputStream outStream,
                       MessageProp msgProp)
        throws GSSException {
        throw new GSSException(GSSException.UNAVAILABLE);
    }

    /**
     * Currently not implemented.
     */
    public void verifyMIC(InputStream tokStream, InputStream msgStream,
                          MessageProp msgProp)
        throws GSSException {
        throw new GSSException(GSSException.UNAVAILABLE);
    }

    /**
     * Currently not implemented.
     */
    public void setChannelBinding(ChannelBinding cb)
        throws GSSException {
        throw new GSSException(GSSException.UNAVAILABLE);
    }

    /**
     * Currently not implemented.
     */
    public boolean isTransferable()
        throws GSSException {
        throw new GSSException(GSSException.UNAVAILABLE);
    }

    /**
     * Currently not implemented.
     */
    public byte [] export()
        throws GSSException {
        throw new GSSException(GSSException.UNAVAILABLE);
    }

}
