/*
 * Copyright (c) 1996, 2011, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */


package sun.security.ssl;

import java.io.*;
import java.util.*;
import java.security.*;
import java.security.NoSuchAlgorithmException;
import java.security.AccessController;
import java.security.AlgorithmConstraints;
import java.security.AccessControlContext;
import java.security.PrivilegedExceptionAction;
import java.security.PrivilegedActionException;

import javax.crypto.*;
import javax.crypto.spec.*;

import javax.net.ssl.*;
import sun.misc.HexDumpEncoder;

import sun.security.internal.spec.*;
import sun.security.internal.interfaces.TlsMasterSecret;

import sun.security.ssl.HandshakeMessage.*;
import sun.security.ssl.CipherSuite.*;

import static sun.security.ssl.CipherSuite.PRF.*;

/**
 * Handshaker ... processes handshake records from an SSL V3.0
 * data stream, handling all the details of the handshake protocol.
 *
 * Note that the real protocol work is done in two subclasses, the  base
 * class just provides the control flow and key generation framework.
 *
 * @author David Brownell
 */
abstract class Handshaker {

    // protocol version being established using this Handshaker
    ProtocolVersion protocolVersion;

    // the currently active protocol version during a renegotiation
    ProtocolVersion     activeProtocolVersion;

    // security parameters for secure renegotiation.
    boolean             secureRenegotiation;
    byte[]              clientVerifyData;
    byte[]              serverVerifyData;

    // Is it an initial negotiation  or a renegotiation?
    boolean                     isInitialHandshake;

    // List of enabled protocols
    private ProtocolList        enabledProtocols;

    // List of enabled CipherSuites
    private CipherSuiteList     enabledCipherSuites;

    // The endpoint identification protocol
    String              identificationProtocol;

    // The cryptographic algorithm constraints
    private AlgorithmConstraints    algorithmConstraints = null;

    // Local supported signature and algorithms
    Collection<SignatureAndHashAlgorithm> localSupportedSignAlgs;

    // Peer supported signature and algorithms
    Collection<SignatureAndHashAlgorithm> peerSupportedSignAlgs;

    /*

    /*
     * List of active protocols
     *
     * Active protocols is a subset of enabled protocols, and will
     * contain only those protocols that have vaild cipher suites
     * enabled.
     */
    private ProtocolList       activeProtocols;

    /*
     * List of active cipher suites
     *
     * Active cipher suites is a subset of enabled cipher suites, and will
     * contain only those cipher suites available for the active protocols.
     */
    private CipherSuiteList    activeCipherSuites;

    private boolean             isClient;
    private boolean             needCertVerify;

    SSLSocketImpl               conn = null;
    SSLEngineImpl               engine = null;

    HandshakeHash               handshakeHash;
    HandshakeInStream           input;
    HandshakeOutStream          output;
    int                         state;
    SSLContextImpl              sslContext;
    RandomCookie                clnt_random, svr_random;
    SSLSessionImpl              session;

    // current CipherSuite. Never null, initially SSL_NULL_WITH_NULL_NULL
    CipherSuite         cipherSuite;

    // current key exchange. Never null, initially K_NULL
    KeyExchange         keyExchange;

    /* True if this session is being resumed (fast handshake) */
    boolean             resumingSession;

    /* True if it's OK to start a new SSL session */
    boolean             enableNewSession;

    // Temporary storage for the individual keys. Set by
    // calculateConnectionKeys() and cleared once the ciphers are
    // activated.
    private SecretKey clntWriteKey, svrWriteKey;
    private IvParameterSpec clntWriteIV, svrWriteIV;
    private SecretKey clntMacSecret, svrMacSecret;

    /*
     * Delegated task subsystem data structures.
     *
     * If thrown is set, we need to propagate this back immediately
     * on entry into processMessage().
     *
     * Data is protected by the SSLEngine.this lock.
     */
    private volatile boolean taskDelegated = false;
    private volatile DelegatedTask delegatedTask = null;
    private volatile Exception thrown = null;

    // Could probably use a java.util.concurrent.atomic.AtomicReference
    // here instead of using this lock.  Consider changing.
    private Object thrownLock = new Object();

    /* Class and subclass dynamic debugging support */
    static final Debug debug = Debug.getInstance("ssl");

    // By default, disable the unsafe legacy session renegotiation
    static final boolean allowUnsafeRenegotiation = Debug.getBooleanProperty(
                    "sun.security.ssl.allowUnsafeRenegotiation", false);

    // For maximum interoperability and backward compatibility, RFC 5746
    // allows server (or client) to accept ClientHello (or ServerHello)
    // message without the secure renegotiation_info extension or SCSV.
    //
    // For maximum security, RFC 5746 also allows server (or client) to
    // reject such message with a fatal "handshake_failure" alert.
    //
    // By default, allow such legacy hello messages.
    static final boolean allowLegacyHelloMessages = Debug.getBooleanProperty(
                    "sun.security.ssl.allowLegacyHelloMessages", true);

    // need to dispose the object when it is invalidated
    boolean invalidated;

    Handshaker(SSLSocketImpl c, SSLContextImpl context,
            ProtocolList enabledProtocols, boolean needCertVerify,
            boolean isClient, ProtocolVersion activeProtocolVersion,
            boolean isInitialHandshake, boolean secureRenegotiation,
            byte[] clientVerifyData, byte[] serverVerifyData) {
        this.conn = c;
        init(context, enabledProtocols, needCertVerify, isClient,
            activeProtocolVersion, isInitialHandshake, secureRenegotiation,
            clientVerifyData, serverVerifyData);
    }

    Handshaker(SSLEngineImpl engine, SSLContextImpl context,
            ProtocolList enabledProtocols, boolean needCertVerify,
            boolean isClient, ProtocolVersion activeProtocolVersion,
            boolean isInitialHandshake, boolean secureRenegotiation,
            byte[] clientVerifyData, byte[] serverVerifyData) {
        this.engine = engine;
        init(context, enabledProtocols, needCertVerify, isClient,
            activeProtocolVersion, isInitialHandshake, secureRenegotiation,
            clientVerifyData, serverVerifyData);
    }

    private void init(SSLContextImpl context, ProtocolList enabledProtocols,
            boolean needCertVerify, boolean isClient,
            ProtocolVersion activeProtocolVersion,
            boolean isInitialHandshake, boolean secureRenegotiation,
            byte[] clientVerifyData, byte[] serverVerifyData) {

        if (debug != null && Debug.isOn("handshake")) {
            System.out.println(
                "Allow unsafe renegotiation: " + allowUnsafeRenegotiation +
                "\nAllow legacy hello messages: " + allowLegacyHelloMessages +
                "\nIs initial handshake: " + isInitialHandshake +
                "\nIs secure renegotiation: " + secureRenegotiation);
        }

        this.sslContext = context;
        this.isClient = isClient;
        this.needCertVerify = needCertVerify;
        this.activeProtocolVersion = activeProtocolVersion;
        this.isInitialHandshake = isInitialHandshake;
        this.secureRenegotiation = secureRenegotiation;
        this.clientVerifyData = clientVerifyData;
        this.serverVerifyData = serverVerifyData;
        enableNewSession = true;
        invalidated = false;

        setCipherSuite(CipherSuite.C_NULL);
        setEnabledProtocols(enabledProtocols);

        if (conn != null) {
            algorithmConstraints = new SSLAlgorithmConstraints(conn, true);
        } else {        // engine != null
            algorithmConstraints = new SSLAlgorithmConstraints(engine, true);
        }


        //
        // In addition to the connection state machine, controlling
        // how the connection deals with the different sorts of records
        // that get sent (notably handshake transitions!), there's
        // also a handshaking state machine that controls message
        // sequencing.
        //
        // It's a convenient artifact of the protocol that this can,
        // with only a couple of minor exceptions, be driven by the
        // type constant for the last message seen:  except for the
        // client's cert verify, those constants are in a convenient
        // order to drastically simplify state machine checking.
        //
        state = -2;  // initialized but not activated
    }

    /*
     * Reroutes calls to the SSLSocket or SSLEngine (*SE).
     *
     * We could have also done it by extra classes
     * and letting them override, but this seemed much
     * less involved.
     */
    void fatalSE(byte b, String diagnostic) throws IOException {
        fatalSE(b, diagnostic, null);
    }

    void fatalSE(byte b, Throwable cause) throws IOException {
        fatalSE(b, null, cause);
    }

    void fatalSE(byte b, String diagnostic, Throwable cause)
            throws IOException {
        if (conn != null) {
            conn.fatal(b, diagnostic, cause);
        } else {
            engine.fatal(b, diagnostic, cause);
        }
    }

    void warningSE(byte b) {
        if (conn != null) {
            conn.warning(b);
        } else {
            engine.warning(b);
        }
    }

    String getRawHostnameSE() {
        if (conn != null) {
            return conn.getRawHostname();
        } else {
            return engine.getPeerHost();
        }
    }

    String getHostSE() {
        if (conn != null) {
            return conn.getHost();
        } else {
            return engine.getPeerHost();
        }
    }

    String getHostAddressSE() {
        if (conn != null) {
            return conn.getInetAddress().getHostAddress();
        } else {
            /*
             * This is for caching only, doesn't matter that's is really
             * a hostname.  The main thing is that it doesn't do
             * a reverse DNS lookup, potentially slowing things down.
             */
            return engine.getPeerHost();
        }
    }

    boolean isLoopbackSE() {
        if (conn != null) {
            return conn.getInetAddress().isLoopbackAddress();
        } else {
            return false;
        }
    }

    int getPortSE() {
        if (conn != null) {
            return conn.getPort();
        } else {
            return engine.getPeerPort();
        }
    }

    int getLocalPortSE() {
        if (conn != null) {
            return conn.getLocalPort();
        } else {
            return -1;
        }
    }

    AccessControlContext getAccSE() {
        if (conn != null) {
            return conn.getAcc();
        } else {
            return engine.getAcc();
        }
    }

    private void setVersionSE(ProtocolVersion protocolVersion) {
        if (conn != null) {
            conn.setVersion(protocolVersion);
        } else {
            engine.setVersion(protocolVersion);
        }
    }

    /**
     * Set the active protocol version and propagate it to the SSLSocket
     * and our handshake streams. Called from ClientHandshaker
     * and ServerHandshaker with the negotiated protocol version.
     */
    void setVersion(ProtocolVersion protocolVersion) {
        this.protocolVersion = protocolVersion;
        setVersionSE(protocolVersion);

        output.r.setVersion(protocolVersion);
    }

    /**
     * Set the enabled protocols. Called from the constructor or
     * SSLSocketImpl/SSLEngineImpl.setEnabledProtocols() (if the
     * handshake is not yet in progress).
     */
    void setEnabledProtocols(ProtocolList enabledProtocols) {
        activeCipherSuites = null;
        activeProtocols = null;

        this.enabledProtocols = enabledProtocols;
    }

    /**
     * Set the enabled cipher suites. Called from
     * SSLSocketImpl/SSLEngineImpl.setEnabledCipherSuites() (if the
     * handshake is not yet in progress).
     */
    void setEnabledCipherSuites(CipherSuiteList enabledCipherSuites) {
        activeCipherSuites = null;
        activeProtocols = null;
        this.enabledCipherSuites = enabledCipherSuites;
    }

    /**
     * Set the algorithm constraints. Called from the constructor or
     * SSLSocketImpl/SSLEngineImpl.setAlgorithmConstraints() (if the
     * handshake is not yet in progress).
     */
    void setAlgorithmConstraints(AlgorithmConstraints algorithmConstraints) {
        activeCipherSuites = null;
        activeProtocols = null;

        this.algorithmConstraints =
            new SSLAlgorithmConstraints(algorithmConstraints);
        this.localSupportedSignAlgs = null;
    }

    Collection<SignatureAndHashAlgorithm> getLocalSupportedSignAlgs() {
        if (localSupportedSignAlgs == null) {
            localSupportedSignAlgs =
                SignatureAndHashAlgorithm.getSupportedAlgorithms(
                                                    algorithmConstraints);
        }

        return localSupportedSignAlgs;
    }

    void setPeerSupportedSignAlgs(
            Collection<SignatureAndHashAlgorithm> algorithms) {
        peerSupportedSignAlgs =
            new ArrayList<SignatureAndHashAlgorithm>(algorithms);
    }

    Collection<SignatureAndHashAlgorithm> getPeerSupportedSignAlgs() {
        return peerSupportedSignAlgs;
    }


    /**
     * Set the identification protocol. Called from the constructor or
     * SSLSocketImpl/SSLEngineImpl.setIdentificationProtocol() (if the
     * handshake is not yet in progress).
     */
    void setIdentificationProtocol(String protocol) {
        this.identificationProtocol = protocol;
    }

    /**
     * Prior to handshaking, activate the handshake and initialize the version,
     * input stream and output stream.
     */
    void activate(ProtocolVersion helloVersion) throws IOException {
        if (activeProtocols == null) {
            activeProtocols = getActiveProtocols();
        }

        if (activeProtocols.collection().isEmpty() ||
                activeProtocols.max.v == ProtocolVersion.NONE.v) {
            throw new SSLHandshakeException("No appropriate protocol");
        }

        if (activeCipherSuites == null) {
            activeCipherSuites = getActiveCipherSuites();
        }

        if (activeCipherSuites.collection().isEmpty()) {
            throw new SSLHandshakeException("No appropriate cipher suite");
        }

        // temporary protocol version until the actual protocol version
        // is negotiated in the Hello exchange. This affects the record
        // version we sent with the ClientHello.
        if (!isInitialHandshake) {
            protocolVersion = activeProtocolVersion;
        } else {
            protocolVersion = activeProtocols.max;
        }

        if (helloVersion == null || helloVersion.v == ProtocolVersion.NONE.v) {
            helloVersion = activeProtocols.helloVersion;
        }

        // We accumulate digests of the handshake messages so that
        // we can read/write CertificateVerify and Finished messages,
        // getting assurance against some particular active attacks.
        Set<String> localSupportedHashAlgorithms =
            SignatureAndHashAlgorithm.getHashAlgorithmNames(
                getLocalSupportedSignAlgs());
        handshakeHash = new HandshakeHash(!isClient, needCertVerify,
            localSupportedHashAlgorithms);

        // Generate handshake input/output stream.
        input = new HandshakeInStream(handshakeHash);
        if (conn != null) {
            output = new HandshakeOutStream(protocolVersion, helloVersion,
                                        handshakeHash, conn);
            conn.getAppInputStream().r.setHandshakeHash(handshakeHash);
            conn.getAppInputStream().r.setHelloVersion(helloVersion);
            conn.getAppOutputStream().r.setHelloVersion(helloVersion);
        } else {
            output = new HandshakeOutStream(protocolVersion, helloVersion,
                                        handshakeHash, engine);
            engine.inputRecord.setHandshakeHash(handshakeHash);
            engine.inputRecord.setHelloVersion(helloVersion);
            engine.outputRecord.setHelloVersion(helloVersion);
        }

        // move state to activated
        state = -1;
    }

    /**
     * Set cipherSuite and keyExchange to the given CipherSuite.
     * Does not perform any verification that this is a valid selection,
     * this must be done before calling this method.
     */
    void setCipherSuite(CipherSuite s) {
        this.cipherSuite = s;
        this.keyExchange = s.keyExchange;
    }

    /**
     * Check if the given ciphersuite is enabled and available.
     * Does not check if the required server certificates are available.
     */
    boolean isNegotiable(CipherSuite s) {
        if (activeCipherSuites == null) {
            activeCipherSuites = getActiveCipherSuites();
        }

        return activeCipherSuites.contains(s) && s.isNegotiable();
    }

    /**
     * Check if the given protocol version is enabled and available.
     */
    boolean isNegotiable(ProtocolVersion protocolVersion) {
        if (activeProtocols == null) {
            activeProtocols = getActiveProtocols();
        }

        return activeProtocols.contains(protocolVersion);
    }

    /**
     * Select a protocol version from the list. Called from
     * ServerHandshaker to negotiate protocol version.
     *
     * Return the lower of the protocol version suggested in the
     * clien hello and the highest supported by the server.
     */
    ProtocolVersion selectProtocolVersion(ProtocolVersion protocolVersion) {
        if (activeProtocols == null) {
            activeProtocols = getActiveProtocols();
        }

        return activeProtocols.selectProtocolVersion(protocolVersion);
    }

    /**
     * Get the active cipher suites.
     *
     * In TLS 1.1, many weak or vulnerable cipher suites were obsoleted,
     * such as TLS_RSA_EXPORT_WITH_RC4_40_MD5. The implementation MUST NOT
     * negotiate these cipher suites in TLS 1.1 or later mode.
     *
     * Therefore, when the active protocols only include TLS 1.1 or later,
     * the client cannot request to negotiate those obsoleted cipher
     * suites.  That is, the obsoleted suites should not be included in the
     * client hello. So we need to create a subset of the enabled cipher
     * suites, the active cipher suites, which does not contain obsoleted
     * cipher suites of the minimum active protocol.
     *
     * Return empty list instead of null if no active cipher suites.
     */
    CipherSuiteList getActiveCipherSuites() {
        if (activeCipherSuites == null) {
            if (activeProtocols == null) {
                activeProtocols = getActiveProtocols();
            }

            ArrayList<CipherSuite> suites = new ArrayList<>();
            if (!(activeProtocols.collection().isEmpty()) &&
                    activeProtocols.min.v != ProtocolVersion.NONE.v) {
                for (CipherSuite suite : enabledCipherSuites.collection()) {
                    if (suite.obsoleted > activeProtocols.min.v &&
                            suite.supported <= activeProtocols.max.v) {
                        if (algorithmConstraints.permits(
                                EnumSet.of(CryptoPrimitive.KEY_AGREEMENT),
                                suite.name, null)) {
                            suites.add(suite);
                        }
                    } else if (debug != null && Debug.isOn("verbose")) {
                        if (suite.obsoleted <= activeProtocols.min.v) {
                            System.out.println(
                                "Ignoring obsoleted cipher suite: " + suite);
                        } else {
                            System.out.println(
                                "Ignoring unsupported cipher suite: " + suite);
                        }
                    }
                }
            }
            activeCipherSuites = new CipherSuiteList(suites);
        }

        return activeCipherSuites;
    }

    /*
     * Get the active protocol versions.
     *
     * In TLS 1.1, many weak or vulnerable cipher suites were obsoleted,
     * such as TLS_RSA_EXPORT_WITH_RC4_40_MD5. The implementation MUST NOT
     * negotiate these cipher suites in TLS 1.1 or later mode.
     *
     * For example, if "TLS_RSA_EXPORT_WITH_RC4_40_MD5" is the
     * only enabled cipher suite, the client cannot request TLS 1.1 or
     * later, even though TLS 1.1 or later is enabled.  We need to create a
     * subset of the enabled protocols, called the active protocols, which
     * contains protocols appropriate to the list of enabled Ciphersuites.
     *
     * Return empty list instead of null if no active protocol versions.
     */
    ProtocolList getActiveProtocols() {
        if (activeProtocols == null) {
            ArrayList<ProtocolVersion> protocols = new ArrayList<>(4);
            for (ProtocolVersion protocol : enabledProtocols.collection()) {
                boolean found = false;
                for (CipherSuite suite : enabledCipherSuites.collection()) {
                    if (suite.isAvailable() && suite.obsoleted > protocol.v &&
                                               suite.supported <= protocol.v) {
                        if (algorithmConstraints.permits(
                                EnumSet.of(CryptoPrimitive.KEY_AGREEMENT),
                                suite.name, null)) {
                            protocols.add(protocol);
                            found = true;
                            break;
                        } else if (debug != null && Debug.isOn("verbose")) {
                            System.out.println(
                                "Ignoring disabled cipher suite: " + suite +
                                 " for " + protocol);
                        }
                    } else if (debug != null && Debug.isOn("verbose")) {
                        System.out.println(
                            "Ignoring unsupported cipher suite: " + suite +
                                 " for " + protocol);
                    }
                }
                if (!found && (debug != null) && Debug.isOn("handshake")) {
                    System.out.println(
                        "No available cipher suite for " + protocol);
                }
            }
            activeProtocols = new ProtocolList(protocols);
        }

        return activeProtocols;
    }

    /**
     * As long as handshaking has not activated, we can
     * change whether session creations are allowed.
     *
     * Callers should do their own checking if handshaking
     * has activated.
     */
    void setEnableSessionCreation(boolean newSessions) {
        enableNewSession = newSessions;
    }

    /**
     * Create a new read cipher and return it to caller.
     */
    CipherBox newReadCipher() throws NoSuchAlgorithmException {
        BulkCipher cipher = cipherSuite.cipher;
        CipherBox box;
        if (isClient) {
            box = cipher.newCipher(protocolVersion, svrWriteKey, svrWriteIV,
                                   sslContext.getSecureRandom(), false);
            svrWriteKey = null;
            svrWriteIV = null;
        } else {
            box = cipher.newCipher(protocolVersion, clntWriteKey, clntWriteIV,
                                   sslContext.getSecureRandom(), false);
            clntWriteKey = null;
            clntWriteIV = null;
        }
        return box;
    }

    /**
     * Create a new write cipher and return it to caller.
     */
    CipherBox newWriteCipher() throws NoSuchAlgorithmException {
        BulkCipher cipher = cipherSuite.cipher;
        CipherBox box;
        if (isClient) {
            box = cipher.newCipher(protocolVersion, clntWriteKey, clntWriteIV,
                                   sslContext.getSecureRandom(), true);
            clntWriteKey = null;
            clntWriteIV = null;
        } else {
            box = cipher.newCipher(protocolVersion, svrWriteKey, svrWriteIV,
                                   sslContext.getSecureRandom(), true);
            svrWriteKey = null;
            svrWriteIV = null;
        }
        return box;
    }

    /**
     * Create a new read MAC and return it to caller.
     */
    MAC newReadMAC() throws NoSuchAlgorithmException, InvalidKeyException {
        MacAlg macAlg = cipherSuite.macAlg;
        MAC mac;
        if (isClient) {
            mac = macAlg.newMac(protocolVersion, svrMacSecret);
            svrMacSecret = null;
        } else {
            mac = macAlg.newMac(protocolVersion, clntMacSecret);
            clntMacSecret = null;
        }
        return mac;
    }

    /**
     * Create a new write MAC and return it to caller.
     */
    MAC newWriteMAC() throws NoSuchAlgorithmException, InvalidKeyException {
        MacAlg macAlg = cipherSuite.macAlg;
        MAC mac;
        if (isClient) {
            mac = macAlg.newMac(protocolVersion, clntMacSecret);
            clntMacSecret = null;
        } else {
            mac = macAlg.newMac(protocolVersion, svrMacSecret);
            svrMacSecret = null;
        }
        return mac;
    }

    /*
     * Returns true iff the handshake sequence is done, so that
     * this freshly created session can become the current one.
     */
    boolean isDone() {
        return state == HandshakeMessage.ht_finished;
    }


    /*
     * Returns the session which was created through this
     * handshake sequence ... should be called after isDone()
     * returns true.
     */
    SSLSessionImpl getSession() {
        return session;
    }

    /*
     * Set the handshake session
     */
    void setHandshakeSessionSE(SSLSessionImpl handshakeSession) {
        if (conn != null) {
            conn.setHandshakeSession(handshakeSession);
        } else {
            engine.setHandshakeSession(handshakeSession);
        }
    }

    /*
     * Returns true if renegotiation is in use for this connection.
     */
    boolean isSecureRenegotiation() {
        return secureRenegotiation;
    }

    /*
     * Returns the verify_data from the Finished message sent by the client.
     */
    byte[] getClientVerifyData() {
        return clientVerifyData;
    }

    /*
     * Returns the verify_data from the Finished message sent by the server.
     */
    byte[] getServerVerifyData() {
        return serverVerifyData;
    }

    /*
     * This routine is fed SSL handshake records when they become available,
     * and processes messages found therein.
     */
    void process_record(InputRecord r, boolean expectingFinished)
            throws IOException {

        checkThrown();

        /*
         * Store the incoming handshake data, then see if we can
         * now process any completed handshake messages
         */
        input.incomingRecord(r);

        /*
         * We don't need to create a separate delegatable task
         * for finished messages.
         */
        if ((conn != null) || expectingFinished) {
            processLoop();
        } else {
            delegateTask(new PrivilegedExceptionAction<Void>() {
                public Void run() throws Exception {
                    processLoop();
                    return null;
                }
            });
        }
    }

    /*
     * On input, we hash messages one at a time since servers may need
     * to access an intermediate hash to validate a CertificateVerify
     * message.
     *
     * Note that many handshake messages can come in one record (and often
     * do, to reduce network resource utilization), and one message can also
     * require multiple records (e.g. very large Certificate messages).
     */
    void processLoop() throws IOException {

        // need to read off 4 bytes at least to get the handshake
        // message type and length.
        while (input.available() >= 4) {
            byte messageType;
            int messageLen;

            /*
             * See if we can read the handshake message header, and
             * then the entire handshake message.  If not, wait till
             * we can read and process an entire message.
             */
            input.mark(4);

            messageType = (byte)input.getInt8();
            messageLen = input.getInt24();

            if (input.available() < messageLen) {
                input.reset();
                return;
            }

            /*
             * Process the messsage.  We require
             * that processMessage() consumes the entire message.  In
             * lieu of explicit error checks (how?!) we assume that the
             * data will look like garbage on encoding/processing errors,
             * and that other protocol code will detect such errors.
             *
             * Note that digesting is normally deferred till after the
             * message has been processed, though to process at least the
             * client's Finished message (i.e. send the server's) we need
             * to acccelerate that digesting.
             *
             * Also, note that hello request messages are never hashed;
             * that includes the hello request header, too.
             */
            if (messageType == HandshakeMessage.ht_hello_request) {
                input.reset();
                processMessage(messageType, messageLen);
                input.ignore(4 + messageLen);
            } else {
                input.mark(messageLen);
                processMessage(messageType, messageLen);
                input.digestNow();
            }
        }
    }


    /**
     * Returns true iff the handshaker has been activated.
     *
     * In activated state, the handshaker may not send any messages out.
     */
    boolean activated() {
        return state >= -1;
    }

    /**
     * Returns true iff the handshaker has sent any messages.
     */
    boolean started() {
        return state >= 0;  // 0: HandshakeMessage.ht_hello_request
                            // 1: HandshakeMessage.ht_client_hello
    }


    /*
     * Used to kickstart the negotiation ... either writing a
     * ClientHello or a HelloRequest as appropriate, whichever
     * the subclass returns.  NOP if handshaking's already started.
     */
    void kickstart() throws IOException {
        if (state >= 0) {
            return;
        }

        HandshakeMessage m = getKickstartMessage();

        if (debug != null && Debug.isOn("handshake")) {
            m.print(System.out);
        }
        m.write(output);
        output.flush();

        state = m.messageType();
    }

    /**
     * Both client and server modes can start handshaking; but the
     * message they send to do so is different.
     */
    abstract HandshakeMessage getKickstartMessage() throws SSLException;

    /*
     * Client and Server side protocols are each driven though this
     * call, which processes a single message and drives the appropriate
     * side of the protocol state machine (depending on the subclass).
     */
    abstract void processMessage(byte messageType, int messageLen)
        throws IOException;

    /*
     * Most alerts in the protocol relate to handshaking problems.
     * Alerts are detected as the connection reads data.
     */
    abstract void handshakeAlert(byte description) throws SSLProtocolException;

    /*
     * Sends a change cipher spec message and updates the write side
     * cipher state so that future messages use the just-negotiated spec.
     */
    void sendChangeCipherSpec(Finished mesg, boolean lastMessage)
            throws IOException {

        output.flush(); // i.e. handshake data

        /*
         * The write cipher state is protected by the connection write lock
         * so we must grab it while making the change. We also
         * make sure no writes occur between sending the ChangeCipherSpec
         * message, installing the new cipher state, and sending the
         * Finished message.
         *
         * We already hold SSLEngine/SSLSocket "this" by virtue
         * of this being called from the readRecord code.
         */
        OutputRecord r;
        if (conn != null) {
            r = new OutputRecord(Record.ct_change_cipher_spec);
        } else {
            r = new EngineOutputRecord(Record.ct_change_cipher_spec, engine);
        }

        r.setVersion(protocolVersion);
        r.write(1);     // single byte of data

        if (conn != null) {
            conn.writeLock.lock();
            try {
                conn.writeRecord(r);
                conn.changeWriteCiphers();
                if (debug != null && Debug.isOn("handshake")) {
                    mesg.print(System.out);
                }
                mesg.write(output);
                output.flush();
            } finally {
                conn.writeLock.unlock();
            }
        } else {
            synchronized (engine.writeLock) {
                engine.writeRecord((EngineOutputRecord)r);
                engine.changeWriteCiphers();
                if (debug != null && Debug.isOn("handshake")) {
                    mesg.print(System.out);
                }
                mesg.write(output);

                if (lastMessage) {
                    output.setFinishedMsg();
                }
                output.flush();
            }
        }
    }

    /*
     * Single access point to key calculation logic.  Given the
     * pre-master secret and the nonces from client and server,
     * produce all the keying material to be used.
     */
    void calculateKeys(SecretKey preMasterSecret, ProtocolVersion version) {
        SecretKey master = calculateMasterSecret(preMasterSecret, version);
        session.setMasterSecret(master);
        calculateConnectionKeys(master);
    }


    /*
     * Calculate the master secret from its various components.  This is
     * used for key exchange by all cipher suites.
     *
     * The master secret is the catenation of three MD5 hashes, each
     * consisting of the pre-master secret and a SHA1 hash.  Those three
     * SHA1 hashes are of (different) constant strings, the pre-master
     * secret, and the nonces provided by the client and the server.
     */
    private SecretKey calculateMasterSecret(SecretKey preMasterSecret,
            ProtocolVersion requestedVersion) {

        if (debug != null && Debug.isOn("keygen")) {
            HexDumpEncoder      dump = new HexDumpEncoder();

            System.out.println("SESSION KEYGEN:");

            System.out.println("PreMaster Secret:");
            printHex(dump, preMasterSecret.getEncoded());

            // Nonces are dumped with connection keygen, no
            // benefit to doing it twice
        }

        // What algs/params do we need to use?
        String masterAlg;
        PRF prf;

        if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
            masterAlg = "SunTls12MasterSecret";
            prf = cipherSuite.prfAlg;
        } else {
            masterAlg = "SunTlsMasterSecret";
            prf = P_NONE;
        }

        String prfHashAlg = prf.getPRFHashAlg();
        int prfHashLength = prf.getPRFHashLength();
        int prfBlockSize = prf.getPRFBlockSize();

        TlsMasterSecretParameterSpec spec = new TlsMasterSecretParameterSpec(
                preMasterSecret, protocolVersion.major, protocolVersion.minor,
                clnt_random.random_bytes, svr_random.random_bytes,
                prfHashAlg, prfHashLength, prfBlockSize);

        SecretKey masterSecret;
        try {
            KeyGenerator kg = JsseJce.getKeyGenerator(masterAlg);
            kg.init(spec);
            masterSecret = kg.generateKey();
        } catch (GeneralSecurityException e) {
            // For RSA premaster secrets, do not signal a protocol error
            // due to the Bleichenbacher attack. See comments further down.
            if (!preMasterSecret.getAlgorithm().equals(
                    "TlsRsaPremasterSecret")) {
                throw new ProviderException(e);
            }

            if (debug != null && Debug.isOn("handshake")) {
                System.out.println("RSA master secret generation error:");
                e.printStackTrace(System.out);
                System.out.println("Generating new random premaster secret");
            }

            if (requestedVersion != null) {
                preMasterSecret =
                    RSAClientKeyExchange.generateDummySecret(requestedVersion);
            } else {
                preMasterSecret =
                    RSAClientKeyExchange.generateDummySecret(protocolVersion);
            }

            // recursive call with new premaster secret
            return calculateMasterSecret(preMasterSecret, null);
        }

        // if no version check requested (client side handshake), or version
        // information is not available (not an RSA premaster secret),
        // return master secret immediately.
        if ((requestedVersion == null) ||
                !(masterSecret instanceof TlsMasterSecret)) {
            return masterSecret;
        }

        // we have checked the ClientKeyExchange message when reading TLS
        // record, the following check is necessary to ensure that
        // JCE provider does not ignore the checking, or the previous
        // checking process bypassed the premaster secret version checking.
        TlsMasterSecret tlsKey = (TlsMasterSecret)masterSecret;
        int major = tlsKey.getMajorVersion();
        int minor = tlsKey.getMinorVersion();
        if ((major < 0) || (minor < 0)) {
            return masterSecret;
        }

        // check if the premaster secret version is ok
        // the specification says that it must be the maximum version supported
        // by the client from its ClientHello message. However, many
        // implementations send the negotiated version, so accept both
        // for SSL v3.0 and TLS v1.0.
        // NOTE that we may be comparing two unsupported version numbers, which
        // is why we cannot use object reference equality in this special case.
        ProtocolVersion premasterVersion =
                                    ProtocolVersion.valueOf(major, minor);
        boolean versionMismatch = (premasterVersion.v != requestedVersion.v);

        /*
         * we never checked the client_version in server side
         * for TLS v1.0 and SSL v3.0. For compatibility, we
         * maintain this behavior.
         */
        if (versionMismatch && requestedVersion.v <= ProtocolVersion.TLS10.v) {
            versionMismatch = (premasterVersion.v != protocolVersion.v);
        }

        if (versionMismatch == false) {
            // check passed, return key
            return masterSecret;
        }

        // Due to the Bleichenbacher attack, do not signal a protocol error.
        // Generate a random premaster secret and continue with the handshake,
        // which will fail when verifying the finished messages.
        // For more information, see comments in PreMasterSecret.
        if (debug != null && Debug.isOn("handshake")) {
            System.out.println("RSA PreMasterSecret version error: expected"
                + protocolVersion + " or " + requestedVersion + ", decrypted: "
                + premasterVersion);
            System.out.println("Generating new random premaster secret");
        }
        preMasterSecret =
            RSAClientKeyExchange.generateDummySecret(requestedVersion);

        // recursive call with new premaster secret
        return calculateMasterSecret(preMasterSecret, null);
    }

    /*
     * Calculate the keys needed for this connection, once the session's
     * master secret has been calculated.  Uses the master key and nonces;
     * the amount of keying material generated is a function of the cipher
     * suite that's been negotiated.
     *
     * This gets called both on the "full handshake" (where we exchanged
     * a premaster secret and started a new session) as well as on the
     * "fast handshake" (where we just resumed a pre-existing session).
     */
    void calculateConnectionKeys(SecretKey masterKey) {
        /*
         * For both the read and write sides of the protocol, we use the
         * master to generate MAC secrets and cipher keying material.  Block
         * ciphers need initialization vectors, which we also generate.
         *
         * First we figure out how much keying material is needed.
         */
        int hashSize = cipherSuite.macAlg.size;
        boolean is_exportable = cipherSuite.exportable;
        BulkCipher cipher = cipherSuite.cipher;
        int expandedKeySize = is_exportable ? cipher.expandedKeySize : 0;

        // Which algs/params do we need to use?
        String keyMaterialAlg;
        PRF prf;

        if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
            keyMaterialAlg = "SunTls12KeyMaterial";
            prf = cipherSuite.prfAlg;
        } else {
            keyMaterialAlg = "SunTlsKeyMaterial";
            prf = P_NONE;
        }

        String prfHashAlg = prf.getPRFHashAlg();
        int prfHashLength = prf.getPRFHashLength();
        int prfBlockSize = prf.getPRFBlockSize();

        TlsKeyMaterialParameterSpec spec = new TlsKeyMaterialParameterSpec(
            masterKey, protocolVersion.major, protocolVersion.minor,
            clnt_random.random_bytes, svr_random.random_bytes,
            cipher.algorithm, cipher.keySize, expandedKeySize,
            cipher.ivSize, hashSize,
            prfHashAlg, prfHashLength, prfBlockSize);

        try {
            KeyGenerator kg = JsseJce.getKeyGenerator(keyMaterialAlg);
            kg.init(spec);
            TlsKeyMaterialSpec keySpec = (TlsKeyMaterialSpec)kg.generateKey();

            clntWriteKey = keySpec.getClientCipherKey();
            svrWriteKey = keySpec.getServerCipherKey();

            // Return null if IVs are not supposed to be generated.
            // e.g. TLS 1.1+.
            clntWriteIV = keySpec.getClientIv();
            svrWriteIV = keySpec.getServerIv();

            clntMacSecret = keySpec.getClientMacKey();
            svrMacSecret = keySpec.getServerMacKey();
        } catch (GeneralSecurityException e) {
            throw new ProviderException(e);
        }

        //
        // Dump the connection keys as they're generated.
        //
        if (debug != null && Debug.isOn("keygen")) {
            synchronized (System.out) {
                HexDumpEncoder  dump = new HexDumpEncoder();

                System.out.println("CONNECTION KEYGEN:");

                // Inputs:
                System.out.println("Client Nonce:");
                printHex(dump, clnt_random.random_bytes);
                System.out.println("Server Nonce:");
                printHex(dump, svr_random.random_bytes);
                System.out.println("Master Secret:");
                printHex(dump, masterKey.getEncoded());

                // Outputs:
                System.out.println("Client MAC write Secret:");
                printHex(dump, clntMacSecret.getEncoded());
                System.out.println("Server MAC write Secret:");
                printHex(dump, svrMacSecret.getEncoded());

                if (clntWriteKey != null) {
                    System.out.println("Client write key:");
                    printHex(dump, clntWriteKey.getEncoded());
                    System.out.println("Server write key:");
                    printHex(dump, svrWriteKey.getEncoded());
                } else {
                    System.out.println("... no encryption keys used");
                }

                if (clntWriteIV != null) {
                    System.out.println("Client write IV:");
                    printHex(dump, clntWriteIV.getIV());
                    System.out.println("Server write IV:");
                    printHex(dump, svrWriteIV.getIV());
                } else {
                    if (protocolVersion.v >= ProtocolVersion.TLS11.v) {
                        System.out.println(
                                "... no IV derived for this protocol");
                    } else {
                        System.out.println("... no IV used for this cipher");
                    }
                }
                System.out.flush();
            }
        }
    }

    private static void printHex(HexDumpEncoder dump, byte[] bytes) {
        if (bytes == null) {
            System.out.println("(key bytes not available)");
        } else {
            try {
                dump.encodeBuffer(bytes, System.out);
            } catch (IOException e) {
                // just for debugging, ignore this
            }
        }
    }

    /**
     * Throw an SSLException with the specified message and cause.
     * Shorthand until a new SSLException constructor is added.
     * This method never returns.
     */
    static void throwSSLException(String msg, Throwable cause)
            throws SSLException {
        SSLException e = new SSLException(msg);
        e.initCause(cause);
        throw e;
    }


    /*
     * Implement a simple task delegator.
     *
     * We are currently implementing this as a single delegator, may
     * try for parallel tasks later.  Client Authentication could
     * benefit from this, where ClientKeyExchange/CertificateVerify
     * could be carried out in parallel.
     */
    class DelegatedTask<E> implements Runnable {

        private PrivilegedExceptionAction<E> pea;

        DelegatedTask(PrivilegedExceptionAction<E> pea) {
            this.pea = pea;
        }

        public void run() {
            synchronized (engine) {
                try {
                    AccessController.doPrivileged(pea, engine.getAcc());
                } catch (PrivilegedActionException pae) {
                    thrown = pae.getException();
                } catch (RuntimeException rte) {
                    thrown = rte;
                }
                delegatedTask = null;
                taskDelegated = false;
            }
        }
    }

    private <T> void delegateTask(PrivilegedExceptionAction<T> pea) {
        delegatedTask = new DelegatedTask<T>(pea);
        taskDelegated = false;
        thrown = null;
    }

    DelegatedTask getTask() {
        if (!taskDelegated) {
            taskDelegated = true;
            return delegatedTask;
        } else {
            return null;
        }
    }

    /*
     * See if there are any tasks which need to be delegated
     *
     * Locked by SSLEngine.this.
     */
    boolean taskOutstanding() {
        return (delegatedTask != null);
    }

    /*
     * The previous caller failed for some reason, report back the
     * Exception.  We won't worry about Error's.
     *
     * Locked by SSLEngine.this.
     */
    void checkThrown() throws SSLException {
        synchronized (thrownLock) {
            if (thrown != null) {

                String msg = thrown.getMessage();

                if (msg == null) {
                    msg = "Delegated task threw Exception/Error";
                }

                /*
                 * See what the underlying type of exception is.  We should
                 * throw the same thing.  Chain thrown to the new exception.
                 */
                Exception e = thrown;
                thrown = null;

                if (e instanceof RuntimeException) {
                    throw (RuntimeException)
                        new RuntimeException(msg).initCause(e);
                } else if (e instanceof SSLHandshakeException) {
                    throw (SSLHandshakeException)
                        new SSLHandshakeException(msg).initCause(e);
                } else if (e instanceof SSLKeyException) {
                    throw (SSLKeyException)
                        new SSLKeyException(msg).initCause(e);
                } else if (e instanceof SSLPeerUnverifiedException) {
                    throw (SSLPeerUnverifiedException)
                        new SSLPeerUnverifiedException(msg).initCause(e);
                } else if (e instanceof SSLProtocolException) {
                    throw (SSLProtocolException)
                        new SSLProtocolException(msg).initCause(e);
                } else {
                    /*
                     * If it's SSLException or any other Exception,
                     * we'll wrap it in an SSLException.
                     */
                    throw (SSLException)
                        new SSLException(msg).initCause(e);
                }
            }
        }
    }
}
