2073 lines · 71.5 KB · Java
RawDownload
/* * Copyright (c) 2003, 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.nio.*;import java.nio.ReadOnlyBufferException;import java.util.LinkedList;import java.security.*; import javax.crypto.BadPaddingException; import javax.net.ssl.*;import javax.net.ssl.SSLEngineResult.*; import com.sun.net.ssl.internal.ssl.X509ExtendedTrustManager; /** * Implementation of an non-blocking SSLEngine. * * *Currently*, the SSLEngine code exists in parallel with the current * SSLSocket.  As such, the current implementation is using legacy code * with many of the same abstractions.  However, it varies in many * areas, most dramatically in the IO handling. * * There are three main I/O threads that can be existing in parallel: * wrap(), unwrap(), and beginHandshake().  We are encouraging users to * not call multiple instances of wrap or unwrap, because the data could * appear to flow out of the SSLEngine in a non-sequential order.  We * take all steps we can to at least make sure the ordering remains * consistent, but once the calls returns, anything can happen.  For * example, thread1 and thread2 both call wrap, thread1 gets the first * packet, thread2 gets the second packet, but thread2 gets control back * before thread1, and sends the data.  The receiving side would see an * out-of-order error. * * Handshaking is still done the same way as SSLSocket using the normal * InputStream/OutputStream abstactions.  We create * ClientHandshakers/ServerHandshakers, which produce/consume the * handshaking data.  The transfer of the data is largely handled by the * HandshakeInStream/HandshakeOutStreams.  Lastly, the * InputRecord/OutputRecords still have the same functionality, except * that they are overridden with EngineInputRecord/EngineOutputRecord, * which provide SSLEngine-specific functionality. * * Some of the major differences are: * * EngineInputRecord/EngineOutputRecord/EngineWriter: * *      In order to avoid writing whole new control flows for *      handshaking, and to reuse most of the same code, we kept most *      of the actual handshake code the same.  As usual, reading *      handshake data may trigger output of more handshake data, so *      what we do is write this data to internal buffers, and wait for *      wrap() to be called to give that data a ride. * *      All data is routed through *      EngineInputRecord/EngineOutputRecord.  However, all handshake *      data (ct_alert/ct_change_cipher_spec/ct_handshake) are passed *      through to the the underlying InputRecord/OutputRecord, and *      the data uses the internal buffers. * *      Application data is handled slightly different, we copy the data *      directly from the src to the dst buffers, and do all operations *      on those buffers, saving the overhead of multiple copies. * *      In the case of an inbound record, unwrap passes the inbound *      ByteBuffer to the InputRecord.  If the data is handshake data, *      the data is read into the InputRecord's internal buffer.  If *      the data is application data, the data is decoded directly into *      the dst buffer. * *      In the case of an outbound record, when the write to the *      "real" OutputStream's would normally take place, instead we *      call back up to the EngineOutputRecord's version of *      writeBuffer, at which time we capture the resulting output in a *      ByteBuffer, and send that back to the EngineWriter for internal *      storage. * *      EngineWriter is responsible for "handling" all outbound *      data, be it handshake or app data, and for returning the data *      to wrap() in the proper order. * * ClientHandshaker/ServerHandshaker/Handshaker: *      Methods which relied on SSLSocket now have work on either *      SSLSockets or SSLEngines. * * @author Brad Wetmore */final public class SSLEngineImpl extends SSLEngine {     //    // Fields and global comments    //     /*     * There's a state machine associated with each connection, which     * among other roles serves to negotiate session changes.     *     * - START with constructor, until the TCP connection's around.     * - HANDSHAKE picks session parameters before allowing traffic.     *          There are many substates due to sequencing requirements     *          for handshake messages.     * - DATA may be transmitted.     * - RENEGOTIATE state allows concurrent data and handshaking     *          traffic ("same" substates as HANDSHAKE), and terminates     *          in selection of new session (and connection) parameters     * - ERROR state immediately precedes abortive disconnect.     * - CLOSED when one side closes down, used to start the shutdown     *          process.  SSL connection objects are not reused.     *     * State affects what SSL record types may legally be sent:     *     * - Handshake ... only in HANDSHAKE and RENEGOTIATE states     * - App Data ... only in DATA and RENEGOTIATE states     * - Alert ... in HANDSHAKE, DATA, RENEGOTIATE     *     * Re what may be received:  same as what may be sent, except that     * HandshakeRequest handshaking messages can come from servers even     * in the application data state, to request entry to RENEGOTIATE.     *     * The state machine within HANDSHAKE and RENEGOTIATE states controls     * the pending session, not the connection state, until the change     * cipher spec and "Finished" handshake messages are processed and     * make the "new" session become the current one.     *     * NOTE: details of the SMs always need to be nailed down better.     * The text above illustrates the core ideas.     *     *                +---->-------+------>--------->-------+     *                |            |                        |     *     <-----<    ^            ^  <-----<               |     *START>----->HANDSHAKE>----->DATA>----->RENEGOTIATE    |     *                v            v               v        |     *                |            |               |        |     *                +------------+---------------+        |     *                |                                     |     *                v                                     |     *               ERROR>------>----->CLOSED<--------<----+     *     * ALSO, note that the the purpose of handshaking (renegotiation is     * included) is to assign a different, and perhaps new, session to     * the connection.  The SSLv3 spec is a bit confusing on that new     * protocol feature.     */    private int                 connectionState;     private static final int    cs_START = 0;    private static final int    cs_HANDSHAKE = 1;    private static final int    cs_DATA = 2;    private static final int    cs_RENEGOTIATE = 3;    private static final int    cs_ERROR = 4;    private static final int    cs_CLOSED = 6;     /*     * Once we're in state cs_CLOSED, we can continue to     * wrap/unwrap until we finish sending/receiving the messages     * for close_notify.  EngineWriter handles outboundDone.     */    private boolean             inboundDone = false;     EngineWriter                writer;     /*     * The authentication context holds all information used to establish     * who this end of the connection is (certificate chains, private keys,     * etc) and who is trusted (e.g. as CAs or websites).     */    private SSLContextImpl      sslContext;     /*     * This connection is one of (potentially) many associated with     * any given session.  The output of the handshake protocol is a     * new session ... although all the protocol description talks     * about changing the cipher spec (and it does change), in fact     * that's incidental since it's done by changing everything that     * is associated with a session at the same time.  (TLS/IETF may     * change that to add client authentication w/o new key exchg.)     */    private Handshaker                  handshaker;    private SSLSessionImpl              sess;    private volatile SSLSessionImpl     handshakeSession;      /*     * Client authentication be off, requested, or required.     *     * This will be used by both this class and SSLSocket's variants.     */    static final byte           clauth_none = 0;    static final byte           clauth_requested = 1;    static final byte           clauth_required = 2;     /*     * Flag indicating if the next record we receive MUST be a Finished     * message. Temporarily set during the handshake to ensure that     * a change cipher spec message is followed by a finished message.     */    private boolean             expectingFinished;      /*     * If someone tries to closeInbound() (say at End-Of-Stream)     * our engine having received a close_notify, we need to     * notify the app that we may have a truncation attack underway.     */    private boolean             recvCN;     /*     * For improved diagnostics, we detail connection closure     * If the engine is closed (connectionState >= cs_ERROR),     * closeReason != null indicates if the engine was closed     * because of an error or because or normal shutdown.     */    private SSLException        closeReason;     /*     * Per-connection private state that doesn't change when the     * session is changed.     */    private byte                        doClientAuth;    private boolean                     enableSessionCreation = true;    EngineInputRecord                   inputRecord;    EngineOutputRecord                  outputRecord;    private AccessControlContext        acc;     // The cipher suites enabled for use on this connection.    private CipherSuiteList             enabledCipherSuites;     // the endpoint identification protocol    private String                      identificationProtocol = null;     // The cryptographic algorithm constraints    private AlgorithmConstraints        algorithmConstraints = null;     // Have we been told whether we're client or server?    private boolean                     serverModeSet = false;    private boolean                     roleIsServer;     /*     * The protocol versions enabled for use on this connection.     *     * Note: we support a pseudo protocol called SSLv2Hello which when     * set will result in an SSL v2 Hello being sent with SSL (version 3.0)     * or TLS (version 3.1, 3.2, etc.) version info.     */    private ProtocolList        enabledProtocols;     /*     * The SSL version associated with this connection.     */    private ProtocolVersion     protocolVersion = ProtocolVersion.DEFAULT;     /*     * Crypto state that's reinitialized when the session changes.     */    private MAC                 readMAC, writeMAC;    private CipherBox           readCipher, writeCipher;    // NOTE: compression state would be saved here     /*     * security parameters for secure renegotiation.     */    private boolean             secureRenegotiation;    private byte[]              clientVerifyData;    private byte[]              serverVerifyData;     /*     * READ ME * READ ME * READ ME * READ ME * READ ME * READ ME *     * IMPORTANT STUFF TO UNDERSTANDING THE SYNCHRONIZATION ISSUES.     * READ ME * READ ME * READ ME * READ ME * READ ME * READ ME *     *     * There are several locks here.     *     * The primary lock is the per-instance lock used by     * synchronized(this) and the synchronized methods.  It controls all     * access to things such as the connection state and variables which     * affect handshaking.  If we are inside a synchronized method, we     * can access the state directly, otherwise, we must use the     * synchronized equivalents.     *     * Note that we must never acquire the <code>this</code> lock after     * <code>writeLock</code> or run the risk of deadlock.     *     * Grab some coffee, and be careful with any code changes.     */    private Object              wrapLock;    private Object              unwrapLock;    Object                      writeLock;     /*     * Class and subclass dynamic debugging support     */    private static final Debug debug = Debug.getInstance("ssl");     //    // Initialization/Constructors    //     /**     * Constructor for an SSLEngine from SSLContext, without     * host/port hints.  This Engine will not be able to cache     * sessions, but must renegotiate everything by hand.     */    SSLEngineImpl(SSLContextImpl ctx) {        super();        init(ctx);    }     /**     * Constructor for an SSLEngine from SSLContext.     */    SSLEngineImpl(SSLContextImpl ctx, String host, int port) {        super(host, port);        init(ctx);    }     /**     * Initializes the Engine     */    private void init(SSLContextImpl ctx) {        if (debug != null && Debug.isOn("ssl")) {            System.out.println("Using SSLEngineImpl.");        }         sslContext = ctx;        sess = SSLSessionImpl.nullSession;        handshakeSession = null;         /*         * State is cs_START until we initialize the handshaker.         *         * Apps using SSLEngine are probably going to be server.         * Somewhat arbitrary choice.         */        roleIsServer = true;        connectionState = cs_START;         /*         * default read and write side cipher and MAC support         *         * Note:  compression support would go here too         */        readCipher = CipherBox.NULL;        readMAC = MAC.NULL;        writeCipher = CipherBox.NULL;        writeMAC = MAC.NULL;         // default security parameters for secure renegotiation        secureRenegotiation = false;        clientVerifyData = new byte[0];        serverVerifyData = new byte[0];         enabledCipherSuites =                sslContext.getDefaultCipherSuiteList(roleIsServer);        enabledProtocols =                sslContext.getDefaultProtocolList(roleIsServer);         wrapLock = new Object();        unwrapLock = new Object();        writeLock = new Object();         /*         * Save the Access Control Context.  This will be used later         * for a couple of things, including providing a context to         * run tasks in, and for determining which credentials         * to use for Subject based (JAAS) decisions         */        acc = AccessController.getContext();         /*         * All outbound application data goes through this OutputRecord,         * other data goes through their respective records created         * elsewhere.  All inbound data goes through this one         * input record.         */        outputRecord =            new EngineOutputRecord(Record.ct_application_data, this);        inputRecord = new EngineInputRecord(this);        inputRecord.enableFormatChecks();         writer = new EngineWriter();    }     /**     * Initialize the handshaker object. This means:     *     *  . if a handshake is already in progress (state is cs_HANDSHAKE     *    or cs_RENEGOTIATE), do nothing and return     *     *  . if the engine is already closed, throw an Exception (internal error)     *     *  . otherwise (cs_START or cs_DATA), create the appropriate handshaker     *    object and advance the connection state (to cs_HANDSHAKE or     *    cs_RENEGOTIATE, respectively).     *     * This method is called right after a new engine is created, when     * starting renegotiation, or when changing client/server mode of the     * engine.     */    private void initHandshaker() {        switch (connectionState) {         //        // Starting a new handshake.        //        case cs_START:        case cs_DATA:            break;         //        // We're already in the middle of a handshake.        //        case cs_HANDSHAKE:        case cs_RENEGOTIATE:            return;         //        // Anyone allowed to call this routine is required to        // do so ONLY if the connection state is reasonable...        //        default:            throw new IllegalStateException("Internal error");        }         // state is either cs_START or cs_DATA        if (connectionState == cs_START) {            connectionState = cs_HANDSHAKE;        } else { // cs_DATA            connectionState = cs_RENEGOTIATE;        }        if (roleIsServer) {            handshaker = new ServerHandshaker(this, sslContext,                    enabledProtocols, doClientAuth,                    protocolVersion, connectionState == cs_HANDSHAKE,                    secureRenegotiation, clientVerifyData, serverVerifyData);        } else {            handshaker = new ClientHandshaker(this, sslContext,                    enabledProtocols,                    protocolVersion, connectionState == cs_HANDSHAKE,                    secureRenegotiation, clientVerifyData, serverVerifyData);        }        handshaker.setEnabledCipherSuites(enabledCipherSuites);        handshaker.setEnableSessionCreation(enableSessionCreation);    }     /*     * Report the current status of the Handshaker     */    private HandshakeStatus getHSStatus(HandshakeStatus hss) {         if (hss != null) {            return hss;        }         synchronized (this) {            if (writer.hasOutboundData()) {                return HandshakeStatus.NEED_WRAP;            } else if (handshaker != null) {                if (handshaker.taskOutstanding()) {                    return HandshakeStatus.NEED_TASK;                } else {                    return HandshakeStatus.NEED_UNWRAP;                }            } else if (connectionState == cs_CLOSED) {                /*                 * Special case where we're closing, but                 * still need the close_notify before we                 * can officially be closed.                 *                 * Note isOutboundDone is taken care of by                 * hasOutboundData() above.                 */                if (!isInboundDone()) {                    return HandshakeStatus.NEED_UNWRAP;                } // else not handshaking            }

Lines 1–500 of 2073.