jdk/src/share/classes/sun/security/ssl/Handshaker.java
changeset 2 90ce3da70b43
child 100 01ef29ca378f
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/sun/security/ssl/Handshaker.java	Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,973 @@
+/*
+ * Copyright 1996-2007 Sun Microsystems, Inc.  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.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.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.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.AccessController;
+import java.security.AccessControlContext;
+import java.security.PrivilegedExceptionAction;
+import java.security.PrivilegedActionException;
+import java.security.cert.X509Certificate;
+
+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.*;
+
+/**
+ * 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 {
+
+    // current protocol version
+    ProtocolVersion protocolVersion;
+
+    // list of enabled protocols
+    ProtocolList enabledProtocols;
+
+    private boolean             isClient;
+
+    SSLSocketImpl               conn = null;
+    SSLEngineImpl               engine = null;
+
+    HandshakeHash               handshakeHash;
+    HandshakeInStream           input;
+    HandshakeOutStream          output;
+    int                         state;
+    SSLContextImpl              sslContext;
+    RandomCookie                clnt_random, svr_random;
+    SSLSessionImpl              session;
+
+    // Temporary MD5 and SHA message digests. Must always be left
+    // in reset state after use.
+    private MessageDigest md5Tmp, shaTmp;
+
+    // list of enabled CipherSuites
+    CipherSuiteList     enabledCipherSuites;
+
+    // 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");
+
+    Handshaker(SSLSocketImpl c, SSLContextImpl context,
+            ProtocolList enabledProtocols, boolean needCertVerify,
+            boolean isClient) {
+        this.conn = c;
+        init(context, enabledProtocols, needCertVerify, isClient);
+    }
+
+    Handshaker(SSLEngineImpl engine, SSLContextImpl context,
+            ProtocolList enabledProtocols, boolean needCertVerify,
+            boolean isClient) {
+        this.engine = engine;
+        init(context, enabledProtocols, needCertVerify, isClient);
+    }
+
+    private void init(SSLContextImpl context, ProtocolList enabledProtocols,
+            boolean needCertVerify, boolean isClient) {
+
+        this.sslContext = context;
+        this.isClient = isClient;
+        enableNewSession = true;
+
+        setCipherSuite(CipherSuite.C_NULL);
+
+        md5Tmp = JsseJce.getMD5();
+        shaTmp = JsseJce.getSHA();
+
+        //
+        // We accumulate digests of the handshake messages so that
+        // we can read/write CertificateVerify and Finished messages,
+        // getting assurance against some particular active attacks.
+        //
+        handshakeHash = new HandshakeHash(needCertVerify);
+
+        setEnabledProtocols(enabledProtocols);
+
+        if (conn != null) {
+            conn.getAppInputStream().r.setHandshakeHash(handshakeHash);
+        } else {        // engine != null
+            engine.inputRecord.setHandshakeHash(handshakeHash);
+        }
+
+
+        //
+        // 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 = -1;
+    }
+
+    /*
+     * 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 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;
+        }
+    }
+
+    String getHostnameVerificationSE() {
+        if (conn != null) {
+            return conn.getHostnameVerification();
+        } else {
+            return engine.getHostnameVerification();
+        }
+    }
+
+    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.setEnabledProtocols() (if the handshake is not yet
+     * in progress).
+     */
+    void setEnabledProtocols(ProtocolList enabledProtocols) {
+        this.enabledProtocols = enabledProtocols;
+
+        // temporary protocol version until the actual protocol version
+        // is negotiated in the Hello exchange. This affects the record
+        // version we sent with the ClientHello. Using max() as the record
+        // version is not really correct but some implementations fail to
+        // correctly negotiate TLS otherwise.
+        protocolVersion = enabledProtocols.max;
+
+        ProtocolVersion helloVersion = enabledProtocols.helloVersion;
+
+        input = new HandshakeInStream(handshakeHash);
+
+        if (conn != null) {
+            output = new HandshakeOutStream(protocolVersion, helloVersion,
+                                        handshakeHash, conn);
+            conn.getAppInputStream().r.setHelloVersion(helloVersion);
+        } else {
+            output = new HandshakeOutStream(protocolVersion, helloVersion,
+                                        handshakeHash, engine);
+            engine.outputRecord.setHelloVersion(helloVersion);
+        }
+
+    }
+
+    /**
+     * 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.
+     * (Enabled ciphersuites are always available unless the status has
+     * changed due to change in JCE providers since it was enabled).
+     * Does not check if the required server certificates are available.
+     */
+    boolean isEnabled(CipherSuite s) {
+        return enabledCipherSuites.contains(s) && s.isAvailable();
+    }
+
+    /**
+     * As long as handshaking has not started, we can
+     * change whether session creations are allowed.
+     *
+     * Callers should do their own checking if handshaking
+     * has started.
+     */
+    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,
+                                   false);
+            svrWriteKey = null;
+            svrWriteIV = null;
+        } else {
+            box = cipher.newCipher(protocolVersion, clntWriteKey, clntWriteIV,
+                                   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,
+                                   true);
+            clntWriteKey = null;
+            clntWriteIV = null;
+        } else {
+            box = cipher.newCipher(protocolVersion, svrWriteKey, svrWriteIV,
+                                   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;
+    }
+
+    /*
+     * 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 {
+
+        while (input.available() > 0) {
+            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 sent any messages.
+     * Server kickstarting is not as neat as it should be; we
+     * need to create a new handshaker, this method lets us
+     * know if we should.
+     */
+    boolean started() {
+        return state >= 0;
+    }
+
+
+    /*
+     * 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) {
+            synchronized (conn.writeLock) {
+                conn.writeRecord(r);
+                conn.changeWriteCiphers();
+                if (debug != null && Debug.isOn("handshake")) {
+                    mesg.print(System.out);
+                }
+                mesg.write(output);
+                output.flush();
+            }
+        } 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) {
+        TlsMasterSecretParameterSpec spec = new TlsMasterSecretParameterSpec
+                (preMasterSecret, protocolVersion.major, protocolVersion.minor,
+                clnt_random.random_bytes, svr_random.random_bytes);
+
+        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
+        }
+
+        SecretKey masterSecret;
+        try {
+            KeyGenerator kg = JsseJce.getKeyGenerator("SunTlsMasterSecret");
+            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");
+            }
+            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;
+        }
+        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
+        // NOTE that we may be comparing two unsupported version numbers in
+        // the second case, which is why we cannot use object reference
+        // equality in this special case
+        ProtocolVersion premasterVersion = ProtocolVersion.valueOf(major, minor);
+        boolean versionMismatch = (premasterVersion != protocolVersion) &&
+                                  (premasterVersion.v != requestedVersion.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(protocolVersion);
+        // 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 keySize = cipher.keySize;
+        int ivSize = cipher.ivSize;
+        int expandedKeySize = is_exportable ? cipher.expandedKeySize : 0;
+
+        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);
+
+        try {
+            KeyGenerator kg = JsseJce.getKeyGenerator("SunTlsKeyMaterial");
+            kg.init(spec);
+            TlsKeyMaterialSpec keySpec = (TlsKeyMaterialSpec)kg.generateKey();
+
+            clntWriteKey = keySpec.getClientCipherKey();
+            svrWriteKey = keySpec.getServerCipherKey();
+
+            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 {
+                    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);
+                }
+            }
+        }
+    }
+}