--- old/src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.java 2018-05-11 15:05:46.657745500 -0700 +++ new/src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.java 2018-05-11 15:05:45.906625600 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2018, 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 @@ -25,761 +25,464 @@ package sun.security.ssl; -import java.io.*; -import java.nio.*; -import java.security.*; -import java.util.*; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ReadOnlyBufferException; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.List; +import java.util.Map; import java.util.function.BiFunction; - -import javax.crypto.BadPaddingException; - -import javax.net.ssl.*; -import javax.net.ssl.SSLEngineResult.*; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLEngineResult.HandshakeStatus; +import javax.net.ssl.SSLEngineResult.Status; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLKeyException; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLProtocolException; +import javax.net.ssl.SSLSession; /** * 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. - * * @author Brad Wetmore */ -public final class SSLEngineImpl extends SSLEngine { +final class SSLEngineImpl extends SSLEngine implements SSLTransport { + private final SSLContextImpl sslContext; + final TransportContext conContext; - // - // 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<--------<----+ + /** + * Constructor for an SSLEngine from SSLContext, without + * host/port hints. * - * ALSO, note that 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. - */ - private boolean inboundDone = false; - private boolean outboundDone = false; - - /* - * 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; - - /* - * 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. + * This Engine will not be able to cache sessions, but must renegotiate + * everything by hand. */ - 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; + SSLEngineImpl(SSLContextImpl sslContext) { + this(sslContext, null, -1); + } - /* - * 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. + /** + * Constructor for an SSLEngine from SSLContext. */ - private SSLException closeReason; + SSLEngineImpl(SSLContextImpl sslContext, + String host, int port) { + super(host, port); + this.sslContext = sslContext; + HandshakeHash handshakeHash = new HandshakeHash(); + if (sslContext.isDTLS()) { + this.conContext = new TransportContext(sslContext, this, + new DTLSInputRecord(handshakeHash), + new DTLSOutputRecord(handshakeHash)); + } else { + this.conContext = new TransportContext(sslContext, this, + new SSLEngineInputRecord(handshakeHash), + new SSLEngineOutputRecord(handshakeHash)); + } - /* - * Per-connection private state that doesn't change when the - * session is changed. - */ - private ClientAuthType doClientAuth = - ClientAuthType.CLIENT_AUTH_NONE; - private boolean enableSessionCreation = true; - InputRecord inputRecord; - OutputRecord 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; - - // The server name indication and matchers - List serverNames = - Collections.emptyList(); - Collection sniMatchers = - Collections.emptyList(); - - // Configured application protocol values - String[] applicationProtocols = new String[0]; - - // Negotiated application protocol value. - // - // The value under negotiation will be obtained from handshaker. - String applicationProtocol = null; - - // Callback function that selects the application protocol value during - // the SSL/TLS handshake. - BiFunction, String> applicationProtocolSelector; - - // Have we been told whether we're client or server? - private boolean serverModeSet = false; - private boolean roleIsServer; + // Server name indication is a connection scope extension. + if (host != null) { + this.conContext.sslConfig.serverNames = + Utilities.addToSNIServerNameList( + conContext.sslConfig.serverNames, host); + } + } - /* - * 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; + @Override + public synchronized void beginHandshake() throws SSLException { + if (conContext.isUnsureMode) { + throw new IllegalStateException( + "Client/Server mode has not yet been set."); + } - /* - * The SSL version associated with this connection. - */ - private ProtocolVersion protocolVersion; + try { + conContext.kickstart(); + } catch (IOException ioe) { + conContext.fatal(Alert.HANDSHAKE_FAILURE, + "Couldn't kickstart handshaking", ioe); + } catch (Exception ex) { // including RuntimeException + conContext.fatal(Alert.INTERNAL_ERROR, + "Fail to begin handshake", ex); + } + } - /* - * 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 this lock after - * writeLock or run the risk of deadlock. - * - * Grab some coffee, and be careful with any code changes. - */ - private Object wrapLock; - private Object unwrapLock; - Object writeLock; - - /* - * Whether local cipher suites preference in server side should be - * honored during handshaking? - */ - private boolean preferLocalCipherSuites = false; + @Override + public synchronized SSLEngineResult wrap(ByteBuffer[] appData, + int offset, int length, ByteBuffer netData) throws SSLException { + return wrap( + appData, offset, length, new ByteBuffer[]{ netData }, 0, 1); + } - /* - * whether DTLS handshake retransmissions should be enabled? - */ - private boolean enableRetransmissions = false; + // @Override + public synchronized SSLEngineResult wrap( + ByteBuffer[] srcs, int srcsOffset, int srcsLength, + ByteBuffer[] dsts, int dstsOffset, int dstsLength) throws SSLException { - /* - * The maximum expected network packet size for SSL/TLS/DTLS records. - */ - private int maximumPacketSize = 0; + if (conContext.isUnsureMode) { + throw new IllegalStateException( + "Client/Server mode has not yet been set."); + } - /* - * Is this an instance for Datagram Transport Layer Security (DTLS)? - */ - private final boolean isDTLS; + // See if the handshaker needs to report back some SSLException. + if (conContext.outputRecord.isEmpty()) { + checkTaskThrown(); + } // Otherwise, deliver cached records before throwing task exception. - /* - * Class and subclass dynamic debugging support - */ - private static final Debug debug = Debug.getInstance("ssl"); + // check parameters + checkParams(srcs, srcsOffset, srcsLength, dsts, dstsOffset, dstsLength); - // - // Initialization/Constructors - // + try { + return writeRecord( + srcs, srcsOffset, srcsLength, dsts, dstsOffset, dstsLength); + } catch (SSLProtocolException spe) { + // may be an unexpected handshake message + conContext.fatal(Alert.UNEXPECTED_MESSAGE, spe); + } catch (IOException ioe) { + conContext.fatal(Alert.INTERNAL_ERROR, + "problem wrapping app data", ioe); + } catch (Exception ex) { // including RuntimeException + conContext.fatal(Alert.INTERNAL_ERROR, + "Fail to wrap application data", ex); + } - /** - * 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, boolean isDTLS) { - super(); - this.isDTLS = isDTLS; - init(ctx, isDTLS); + return null; // make compiler happy } - /** - * Constructor for an SSLEngine from SSLContext. - */ - SSLEngineImpl(SSLContextImpl ctx, String host, int port, boolean isDTLS) { - super(host, port); - this.isDTLS = isDTLS; - init(ctx, isDTLS); - } + private SSLEngineResult writeRecord( + ByteBuffer[] srcs, int srcsOffset, int srcsLength, + ByteBuffer[] dsts, int dstsOffset, int dstsLength) throws IOException { - /** - * Initializes the Engine - */ - private void init(SSLContextImpl ctx, boolean isDTLS) { - if (debug != null && Debug.isOn("ssl")) { - System.out.println("Using SSLEngineImpl."); + if (isOutboundDone()) { + return new SSLEngineResult( + Status.CLOSED, getHandshakeStatus(), 0, 0); } - sslContext = ctx; - sess = SSLSessionImpl.nullSession; - handshakeSession = null; - protocolVersion = isDTLS ? - ProtocolVersion.DEFAULT_DTLS : ProtocolVersion.DEFAULT_TLS; + HandshakeContext hc = conContext.handshakeContext; + HandshakeStatus hsStatus = null; + if (!conContext.isNegotiated) { + conContext.kickstart(); - /* - * 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; + hsStatus = getHandshakeStatus(); + if (hsStatus == HandshakeStatus.NEED_UNWRAP) { + /* + * For DTLS, if the handshake state is + * HandshakeStatus.NEED_UNWRAP, a call to SSLEngine.wrap() + * means that the previous handshake packets (if delivered) + * get lost, and need retransmit the handshake messages. + */ + if (!sslContext.isDTLS() || hc == null || + !hc.sslConfig.enableRetransmissions || + conContext.outputRecord.firstMessage) { - // default server name indication - serverNames = - Utilities.addToSNIServerNameList(serverNames, getPeerHost()); - - // 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(); + return new SSLEngineResult(Status.OK, hsStatus, 0, 0); + } // otherwise, need retransmission + } + } - /* - * 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(); + if (hsStatus == null) { + hsStatus = getHandshakeStatus(); + } /* - * 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. + * If we have a task outstanding, this *MUST* be done before + * doing any more wrapping, because we could be in the middle + * of receiving a handshake message, for example, a finished + * message which would change the ciphers. */ - if (isDTLS) { - enableRetransmissions = true; + if (hsStatus == HandshakeStatus.NEED_TASK) { + return new SSLEngineResult(Status.OK, hsStatus, 0, 0); + } - // SSLEngine needs no record local buffer - outputRecord = new DTLSOutputRecord(); - inputRecord = new DTLSInputRecord(); + int dstsRemains = 0; + for (int i = dstsOffset; i < dstsOffset + dstsLength; i++) { + dstsRemains += dsts[i].remaining(); + } - } else { - outputRecord = new SSLEngineOutputRecord(); - inputRecord = new SSLEngineInputRecord(); + // Check destination buffer size. + // + // We can be smarter about using smaller buffer sizes later. For + // now, force it to be large enough to handle any valid record. + if (dstsRemains < conContext.conSession.getPacketBufferSize()) { + return new SSLEngineResult( + Status.BUFFER_OVERFLOW, getHandshakeStatus(), 0, 0); } - maximumPacketSize = outputRecord.getMaxPacketSize(); - } + int srcsRemains = 0; + for (int i = srcsOffset; i < srcsOffset + srcsLength; i++) { + srcsRemains += srcs[i].remaining(); + } - /** - * 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) { + Ciphertext ciphertext = null; + try { + // Acquire the buffered to-be-delivered records or retransmissions. + // + // May have buffered records, or need retransmission if handshaking. + if (!conContext.outputRecord.isEmpty() || (hc != null && + hc.sslConfig.enableRetransmissions && + hc.sslContext.isDTLS() && + hsStatus == HandshakeStatus.NEED_UNWRAP)) { + ciphertext = encode(null, 0, 0, + dsts, dstsOffset, dstsLength); + } - // - // Starting a new handshake. - // - case cs_START: - case cs_DATA: - break; + if (ciphertext == null && srcsRemains != 0) { + ciphertext = encode(srcs, srcsOffset, srcsLength, + dsts, dstsOffset, dstsLength); + } + } catch (IOException ioe) { + if (ioe instanceof SSLException) { + throw ioe; + } else { + throw new SSLException("Write problems", ioe); + } + } - // - // We're already in the middle of a handshake. - // - case cs_HANDSHAKE: - case cs_RENEGOTIATE: - return; + /* + * Check for status. + */ + Status status = (isOutboundDone() ? Status.CLOSED : Status.OK); + if (ciphertext != null && ciphertext.handshakeStatus != null) { + hsStatus = ciphertext.handshakeStatus; + } else { + hsStatus = getHandshakeStatus(); + } - // - // Anyone allowed to call this routine is required to - // do so ONLY if the connection state is reasonable... - // - default: - throw new IllegalStateException("Internal error"); + int deltaSrcs = srcsRemains; + for (int i = srcsOffset; i < srcsOffset + srcsLength; i++) { + deltaSrcs -= srcs[i].remaining(); } - // 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, - isDTLS); - handshaker.setSNIMatchers(sniMatchers); - handshaker.setUseCipherSuitesOrder(preferLocalCipherSuites); - } else { - handshaker = new ClientHandshaker(this, sslContext, - enabledProtocols, - protocolVersion, connectionState == cs_HANDSHAKE, - secureRenegotiation, clientVerifyData, serverVerifyData, - isDTLS); - handshaker.setSNIServerNames(serverNames); - } - handshaker.setMaximumPacketSize(maximumPacketSize); - handshaker.setEnabledCipherSuites(enabledCipherSuites); - handshaker.setEnableSessionCreation(enableSessionCreation); - handshaker.setApplicationProtocols(applicationProtocols); - handshaker.setApplicationProtocolSelectorSSLEngine( - applicationProtocolSelector); + int deltaDsts = dstsRemains; + for (int i = dstsOffset; i < dstsOffset + dstsLength; i++) { + deltaDsts -= dsts[i].remaining(); + } - outputRecord.initHandshaker(); + return new SSLEngineResult(status, hsStatus, deltaSrcs, deltaDsts, + ciphertext != null ? ciphertext.recordSN : -1L); } - /* - * Report the current status of the Handshaker - */ - private HandshakeStatus getHSStatus(HandshakeStatus hss) { + private Ciphertext encode( + ByteBuffer[] srcs, int srcsOffset, int srcsLength, + ByteBuffer[] dsts, int dstsOffset, int dstsLength) throws IOException { - if (hss != null) { - return hss; + Ciphertext ciphertext = null; + try { + ciphertext = conContext.outputRecord.encode( + srcs, srcsOffset, srcsLength, dsts, dstsOffset, dstsLength); + } catch (SSLHandshakeException she) { + // may be record sequence number overflow + conContext.fatal(Alert.HANDSHAKE_FAILURE, she); + } catch (IOException e) { + conContext.fatal(Alert.UNEXPECTED_MESSAGE, e); } - synchronized (this) { - if (!outputRecord.isEmpty()) { - // If no handshaking, special case to wrap alters. - return HandshakeStatus.NEED_WRAP; - } else if (handshaker != null) { - if (handshaker.taskOutstanding()) { - return HandshakeStatus.NEED_TASK; - } else if (isDTLS && !inputRecord.isEmpty()) { - return HandshakeStatus.NEED_UNWRAP_AGAIN; - } 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 + if (ciphertext == null) { + return Ciphertext.CIPHERTEXT_NULL; + } + + // Is the handshake completed? + boolean needRetransmission = + conContext.sslContext.isDTLS() && + conContext.handshakeContext != null && + conContext.handshakeContext.sslConfig.enableRetransmissions; + HandshakeStatus hsStatus = + tryToFinishHandshake(ciphertext.contentType); + if (needRetransmission && + hsStatus == HandshakeStatus.FINISHED && + conContext.sslContext.isDTLS() && + ciphertext.handshakeType == SSLHandshake.FINISHED.id) { + // Retransmit the last flight for DTLS. + // + // The application data transactions may begin immediately + // after the last flight. If the last flight get lost, the + // application data may be discarded accordingly. As could + // be an issue for some applications. This impact can be + // mitigated by sending the last fligth twice. + if (SSLLogger.isOn && SSLLogger.isOn("ssl,verbose")) { + SSLLogger.finest("retransmit the last flight messages"); } - return HandshakeStatus.NOT_HANDSHAKING; + conContext.outputRecord.launchRetransmission(); + hsStatus = HandshakeStatus.NEED_WRAP; } - } - private synchronized void checkTaskThrown() throws SSLException { - if (handshaker != null) { - handshaker.checkThrown(); + if (hsStatus == null) { + hsStatus = conContext.getHandshakeStatus(); } - } - // - // Handshaking and connection state code - // + // Is the sequence number is nearly overflow? + if (conContext.outputRecord.seqNumIsHuge()) { + hsStatus = tryKeyUpdate(hsStatus); + } - /* - * Provides "this" synchronization for connection state. - * Otherwise, you can access it directly. - */ - private synchronized int getConnectionState() { - return connectionState; - } + // update context status + ciphertext.handshakeStatus = hsStatus; - private synchronized void setConnectionState(int state) { - connectionState = state; + return ciphertext; } - /* - * Get the Access Control Context. - * - * Used for a known context to - * run tasks in, and for determining which credentials - * to use for Subject-based (JAAS) decisions. - */ - AccessControlContext getAcc() { - return acc; - } + private HandshakeStatus tryToFinishHandshake(byte contentType) { + HandshakeStatus hsStatus = null; + if ((contentType == ContentType.HANDSHAKE.id) && + conContext.outputRecord.isEmpty()) { + if (conContext.handshakeContext == null) { + hsStatus = HandshakeStatus.FINISHED; + } else if (conContext.handshakeContext.handshakeFinished) { + hsStatus = conContext.finishHandshake(); + } + } // Otherwise, the followed call to getHSStatus() will help. - /* - * Is a handshake currently underway? - */ - @Override - public SSLEngineResult.HandshakeStatus getHandshakeStatus() { - return getHSStatus(null); + return hsStatus; } - /* - * used by Handshaker to change the active write cipher, follows - * the output of the CCS message. + /** + * Try renegotiation or key update for sequence number wrap. * - * Also synchronized on "this" from readRecord/delegatedTask. + * Note that in order to maintain the handshake status properly, we check + * the sequence number after the last record reading/writing process. As + * we request renegotiation or close the connection for wrapped sequence + * number when there is enough sequence number space left to handle a few + * more records, so the sequence number of the last record cannot be + * wrapped. */ - void changeWriteCiphers() throws IOException { - - Authenticator writeAuthenticator; - CipherBox writeCipher; - try { - writeCipher = handshaker.newWriteCipher(); - writeAuthenticator = handshaker.newWriteAuthenticator(); - } catch (GeneralSecurityException e) { - // "can't happen" - throw new SSLException("Algorithm missing: ", e); + private HandshakeStatus tryKeyUpdate( + HandshakeStatus currentHandshakeStatus) throws IOException { + // Don't bother to kickstart the renegotiation or key update when the + // local is asking for it. + if ((conContext.handshakeContext == null) && + !conContext.isClosed() && !conContext.isBroken) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + SSLLogger.finest("key update to wrap sequence number"); + } + conContext.keyUpdate(); + return conContext.getHandshakeStatus(); } - outputRecord.changeWriteCiphers(writeAuthenticator, writeCipher); + return currentHandshakeStatus; } - /* - * Updates the SSL version associated with this connection. - * Called from Handshaker once it has determined the negotiated version. - */ - synchronized void setVersion(ProtocolVersion protocolVersion) { - this.protocolVersion = protocolVersion; - outputRecord.setVersion(protocolVersion); - } + private static void checkParams( + ByteBuffer[] srcs, int srcsOffset, int srcsLength, + ByteBuffer[] dsts, int dstsOffset, int dstsLength) { + if ((srcs == null) || (dsts == null)) { + throw new IllegalArgumentException( + "source or destination buffer is null"); + } - /** - * Kickstart the handshake if it is not already in progress. - * This means: - * - * . if handshaking is already underway, do nothing and return - * - * . if the engine is not connected or already closed, throw an - * Exception. - * - * . otherwise, call initHandshake() to initialize the handshaker - * object and progress the state. Then, send the initial - * handshaking message if appropriate (always on clients and - * on servers when renegotiating). - */ - private synchronized void kickstartHandshake() throws IOException { - switch (connectionState) { + if ((srcsOffset < 0) || (srcsLength < 0) || + (srcsOffset > srcs.length - srcsLength)) { + throw new IndexOutOfBoundsException( + "index out of bound of the source buffers"); + } - case cs_START: - if (!serverModeSet) { - throw new IllegalStateException( - "Client/Server mode not yet set."); - } - initHandshaker(); - break; - - case cs_HANDSHAKE: - // handshaker already setup, proceed - break; - - case cs_DATA: - if (!secureRenegotiation && !Handshaker.allowUnsafeRenegotiation) { - throw new SSLHandshakeException( - "Insecure renegotiation is not allowed"); - } - - if (!secureRenegotiation) { - if (debug != null && Debug.isOn("handshake")) { - System.out.println( - "Warning: Using insecure renegotiation"); - } - } + if ((dstsOffset < 0) || (dstsLength < 0) || + (dstsOffset > dsts.length - dstsLength)) { + throw new IndexOutOfBoundsException( + "index out of bound of the destination buffers"); + } - // initialize the handshaker, move to cs_RENEGOTIATE - initHandshaker(); - break; - - case cs_RENEGOTIATE: - // handshaking already in progress, return - return; - - default: - // cs_ERROR/cs_CLOSED - throw new SSLException("SSLEngine is closing/closed"); + for (int i = srcsOffset; i < srcsOffset + srcsLength; i++) { + if (srcs[i] == null) { + throw new IllegalArgumentException( + "source buffer[" + i + "] == null"); + } } - // - // Kickstart handshake state machine if we need to ... - // - if (!handshaker.activated()) { - // prior to handshaking, activate the handshake - if (connectionState == cs_RENEGOTIATE) { - // don't use SSLv2Hello when renegotiating - handshaker.activate(protocolVersion); - } else { - handshaker.activate(null); + for (int i = dstsOffset; i < dstsOffset + dstsLength; i++) { + if (dsts[i] == null) { + throw new IllegalArgumentException( + "destination buffer[" + i + "] == null"); } - if (handshaker instanceof ClientHandshaker) { - // send client hello - handshaker.kickstart(); - } else { // instanceof ServerHandshaker - if (connectionState == cs_HANDSHAKE) { - // initial handshake, no kickstart message to send - } else { - // we want to renegotiate, send hello request - handshaker.kickstart(); - } + /* + * Make sure the destination bufffers are writable. + */ + if (dsts[i].isReadOnly()) { + throw new ReadOnlyBufferException(); } } } - /* - * Start a SSLEngine handshake - */ @Override - public void beginHandshake() throws SSLException { - try { - kickstartHandshake(); - } catch (Exception e) { - fatal(Alerts.alert_handshake_failure, - "Couldn't kickstart handshaking", e); - } + public synchronized SSLEngineResult unwrap(ByteBuffer src, + ByteBuffer[] dsts, int offset, int length) throws SSLException { + return unwrap( + new ByteBuffer[]{src}, 0, 1, dsts, offset, length); } + // @Override + public synchronized SSLEngineResult unwrap( + ByteBuffer[] srcs, int srcsOffset, int srcsLength, + ByteBuffer[] dsts, int dstsOffset, int dstsLength) throws SSLException { - // - // Read/unwrap side - // - + if (conContext.isUnsureMode) { + throw new IllegalStateException( + "Client/Server mode has not yet been set."); + } - /** - * Unwraps a buffer. Does a variety of checks before grabbing - * the unwrapLock, which blocks multiple unwraps from occurring. - */ - @Override - public SSLEngineResult unwrap(ByteBuffer netData, ByteBuffer[] appData, - int offset, int length) throws SSLException { + // See if the handshaker needs to report back some SSLException. + checkTaskThrown(); - // check engine parameters - checkEngineParas(netData, appData, offset, length, false); + // check parameters + checkParams(srcs, srcsOffset, srcsLength, dsts, dstsOffset, dstsLength); try { - synchronized (unwrapLock) { - return readNetRecord(netData, appData, offset, length); - } + return readRecord( + srcs, srcsOffset, srcsLength, dsts, dstsOffset, dstsLength); } catch (SSLProtocolException spe) { // may be an unexpected handshake message - fatal(Alerts.alert_unexpected_message, spe.getMessage(), spe); - return null; // make compiler happy - } catch (Exception e) { + conContext.fatal(Alert.UNEXPECTED_MESSAGE, + spe.getMessage(), spe); + } catch (IOException ioe) { /* * Don't reset position so it looks like we didn't * consume anything. We did consume something, and it * got us into this situation, so report that much back. * Our days of consuming are now over anyway. */ - fatal(Alerts.alert_internal_error, - "problem unwrapping net record", e); - return null; // make compiler happy - } - } - - private static void checkEngineParas(ByteBuffer netData, - ByteBuffer[] appData, int offset, int len, boolean isForWrap) { - - if ((netData == null) || (appData == null)) { - throw new IllegalArgumentException("src/dst is null"); - } - - if ((offset < 0) || (len < 0) || (offset > appData.length - len)) { - throw new IndexOutOfBoundsException(); - } - - /* - * If wrapping, make sure the destination bufffer is writable. - */ - if (isForWrap && netData.isReadOnly()) { - throw new ReadOnlyBufferException(); + conContext.fatal(Alert.INTERNAL_ERROR, + "problem unwrapping net record", ioe); + } catch (Exception ex) { // including RuntimeException + conContext.fatal(Alert.INTERNAL_ERROR, + "Fail to unwrap network record", ex); } - for (int i = offset; i < offset + len; i++) { - if (appData[i] == null) { - throw new IllegalArgumentException( - "appData[" + i + "] == null"); - } - - /* - * If unwrapping, make sure the destination bufffers are writable. - */ - if (!isForWrap && appData[i].isReadOnly()) { - throw new ReadOnlyBufferException(); - } - } + return null; // make compiler happy } - /* - * Makes additional checks for unwrap, but this time more - * specific to this packet and the current state of the machine. - */ - private SSLEngineResult readNetRecord(ByteBuffer netData, - ByteBuffer[] appData, int offset, int length) throws IOException { - - Status status = null; - HandshakeStatus hsStatus = null; - - /* - * See if the handshaker needs to report back some SSLException. - */ - checkTaskThrown(); + private SSLEngineResult readRecord( + ByteBuffer[] srcs, int srcsOffset, int srcsLength, + ByteBuffer[] dsts, int dstsOffset, int dstsLength) throws IOException { /* * Check if we are closing/closed. */ if (isInboundDone()) { - return new SSLEngineResult(Status.CLOSED, getHSStatus(null), 0, 0); + return new SSLEngineResult( + Status.CLOSED, getHandshakeStatus(), 0, 0); } - /* - * If we're still in cs_HANDSHAKE, make sure it's been - * started. - */ - synchronized (this) { - if ((connectionState == cs_HANDSHAKE) || - (connectionState == cs_START)) { - kickstartHandshake(); - - /* - * If there's still outbound data to flush, we - * can return without trying to unwrap anything. - */ - hsStatus = getHSStatus(null); + HandshakeStatus hsStatus = null; + if (!conContext.isNegotiated) { + conContext.kickstart(); - if (hsStatus == HandshakeStatus.NEED_WRAP) { - return new SSLEngineResult(Status.OK, hsStatus, 0, 0); - } + /* + * If there's still outbound data to flush, we + * can return without trying to unwrap anything. + */ + hsStatus = getHandshakeStatus(); + if (hsStatus == HandshakeStatus.NEED_WRAP) { + return new SSLEngineResult(Status.OK, hsStatus, 0, 0); } } - /* - * Grab a copy of this if it doesn't already exist, - * and we can use it several places before anything major - * happens on this side. Races aren't critical - * here. - */ if (hsStatus == null) { - hsStatus = getHSStatus(null); + hsStatus = getHandshakeStatus(); } /* @@ -795,42 +498,61 @@ if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP_AGAIN) { Plaintext plainText = null; try { - plainText = readRecord(null, null, 0, 0); - } catch (SSLException e) { - throw e; - } catch (IOException e) { - throw new SSLException("readRecord", e); + plainText = decode(null, 0, 0, + dsts, dstsOffset, dstsLength); + } catch (IOException ioe) { + if (ioe instanceof SSLException) { + throw ioe; + } else { + throw new SSLException("readRecord", ioe); + } } - status = (isInboundDone() ? Status.CLOSED : Status.OK); - hsStatus = getHSStatus(plainText.handshakeStatus); + Status status = (isInboundDone() ? Status.CLOSED : Status.OK); + if (plainText.handshakeStatus != null) { + hsStatus = plainText.handshakeStatus; + } else { + hsStatus = getHandshakeStatus(); + } return new SSLEngineResult( status, hsStatus, 0, 0, plainText.recordSN); } + int srcsRemains = 0; + for (int i = srcsOffset; i < srcsOffset + srcsLength; i++) { + srcsRemains += srcs[i].remaining(); + } + + if (srcsRemains == 0) { + return new SSLEngineResult(Status.OK, getHandshakeStatus(), 0, 0); + } + /* * Check the packet to make sure enough is here. * This will also indirectly check for 0 len packets. */ int packetLen = 0; try { - packetLen = inputRecord.bytesInCompletePacket(netData); + packetLen = conContext.inputRecord.bytesInCompletePacket( + srcs, srcsOffset, srcsLength); } catch (SSLException ssle) { // Need to discard invalid records for DTLS protocols. - if (isDTLS) { - if (debug != null && Debug.isOn("ssl")) { - System.out.println( - Thread.currentThread().getName() + - " discard invalid record: " + ssle); + if (sslContext.isDTLS()) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,verbose")) { + SSLLogger.finest("Discard invalid DTLS records", ssle); } // invalid, discard the entire data [section 4.1.2.7, RFC 6347] - int deltaNet = netData.remaining(); - netData.position(netData.limit()); - - status = (isInboundDone() ? Status.CLOSED : Status.OK); - hsStatus = getHSStatus(hsStatus); + // TODO + int deltaNet = 0; + // int deltaNet = netData.remaining(); + // netData.position(netData.limit()); + + Status status = (isInboundDone() ? Status.CLOSED : Status.OK); + if (hsStatus == null) { + hsStatus = getHandshakeStatus(); + } return new SSLEngineResult(status, hsStatus, deltaNet, 0, -1L); } else { @@ -839,10 +561,10 @@ } // Is this packet bigger than SSL/TLS normally allows? - if (packetLen > sess.getPacketBufferSize()) { - int largestRecordSize = isDTLS ? + if (packetLen > conContext.conSession.getPacketBufferSize()) { + int largestRecordSize = sslContext.isDTLS() ? DTLSRecord.maxRecordSize : SSLRecord.maxLargeRecordSize; - if ((packetLen <= largestRecordSize) && !isDTLS) { + if ((packetLen <= largestRecordSize) && !sslContext.isDTLS()) { // Expand the expected maximum packet/application buffer // sizes. // @@ -850,11 +572,11 @@ // Old behavior: shall we honor the System Property // "jsse.SSLEngine.acceptLargeFragments" if it is "false"? - sess.expandBufferSizes(); + conContext.conSession.expandBufferSizes(); } // check the packet again - largestRecordSize = sess.getPacketBufferSize(); + largestRecordSize = conContext.conSession.getPacketBufferSize(); if (packetLen > largestRecordSize) { throw new SSLProtocolException( "Input record too big: max = " + @@ -862,35 +584,28 @@ } } - int netPos = netData.position(); - int appRemains = 0; - for (int i = offset; i < offset + length; i++) { - if (appData[i] == null) { - throw new IllegalArgumentException( - "appData[" + i + "] == null"); - } - appRemains += appData[i].remaining(); - } - /* * Check for OVERFLOW. * * Delay enforcing the application buffer free space requirement * until after the initial handshaking. */ - // synchronize connectionState? - if ((connectionState == cs_DATA) || - (connectionState == cs_RENEGOTIATE)) { + int dstsRemains = 0; + for (int i = dstsOffset; i < dstsOffset + dstsLength; i++) { + dstsRemains += dsts[i].remaining(); + } - int FragLen = inputRecord.estimateFragmentSize(packetLen); - if (FragLen > appRemains) { + if (conContext.isNegotiated) { + int FragLen = + conContext.inputRecord.estimateFragmentSize(packetLen); + if (FragLen > dstsRemains) { return new SSLEngineResult( Status.BUFFER_OVERFLOW, hsStatus, 0, 0); } } // check for UNDERFLOW. - if ((packetLen == -1) || (netData.remaining() < packetLen)) { + if ((packetLen == -1) || (srcsRemains < packetLen)) { return new SSLEngineResult(Status.BUFFER_UNDERFLOW, hsStatus, 0, 0); } @@ -899,1412 +614,372 @@ */ Plaintext plainText = null; try { - plainText = readRecord(netData, appData, offset, length); - } catch (SSLException e) { - throw e; - } catch (IOException e) { - throw new SSLException("readRecord", e); - } - - /* - * Check the various condition that we could be reporting. - * - * It's *possible* something might have happened between the - * above and now, but it was better to minimally lock "this" - * during the read process. We'll return the current - * status, which is more representative of the current state. - * - * status above should cover: FINISHED, NEED_TASK - */ - status = (isInboundDone() ? Status.CLOSED : Status.OK); - hsStatus = getHSStatus(plainText.handshakeStatus); - - int deltaNet = netData.position() - netPos; - int deltaApp = appRemains; - for (int i = offset; i < offset + length; i++) { - deltaApp -= appData[i].remaining(); - } - - return new SSLEngineResult( - status, hsStatus, deltaNet, deltaApp, plainText.recordSN); - } - - // the caller have synchronized readLock - void expectingFinishFlight() { - inputRecord.expectingFinishFlight(); - } - - /* - * Actually do the read record processing. - * - * Returns a Status if it can make specific determinations - * of the engine state. In particular, we need to signal - * that a handshake just completed. - * - * It would be nice to be symmetrical with the write side and move - * the majority of this to SSLInputRecord, but there's too much - * SSLEngine state to do that cleanly. It must still live here. - */ - private Plaintext readRecord(ByteBuffer netData, - ByteBuffer[] appData, int offset, int length) throws IOException { - - /* - * The various operations will return new sliced BB's, - * this will avoid having to worry about positions and - * limits in the netBB. - */ - Plaintext plainText = null; - - if (getConnectionState() == cs_ERROR) { - return Plaintext.PLAINTEXT_NULL; - } - - /* - * Read a record ... maybe emitting an alert if we get a - * comprehensible but unsupported "hello" message during - * format checking (e.g. V2). - */ - try { - if (isDTLS) { - // Don't process the incoming record until all of the - // buffered records get handled. - plainText = inputRecord.acquirePlaintext(); - } - - if ((!isDTLS || plainText == null) && netData != null) { - plainText = inputRecord.decode(netData); - } - } catch (UnsupportedOperationException unsoe) { // SSLv2Hello - // Hack code to deliver SSLv2 error message for SSL/TLS connections. - if (!isDTLS) { - outputRecord.encodeV2NoCipher(); - } - - fatal(Alerts.alert_unexpected_message, unsoe); - } catch (BadPaddingException e) { - /* - * The basic SSLv3 record protection involves (optional) - * encryption for privacy, and an integrity check ensuring - * data origin authentication. We do them both here, and - * throw a fatal alert if the integrity check fails. - */ - byte alertType = (connectionState != cs_DATA) ? - Alerts.alert_handshake_failure : - Alerts.alert_bad_record_mac; - fatal(alertType, e.getMessage(), e); - } catch (SSLHandshakeException she) { - // may be record sequence number overflow - fatal(Alerts.alert_handshake_failure, she); + plainText = decode(srcs, srcsOffset, srcsLength, + dsts, dstsOffset, dstsLength); } catch (IOException ioe) { - fatal(Alerts.alert_unexpected_message, ioe); - } - - // plainText should never be null for TLS protocols - HandshakeStatus hsStatus = null; - if (plainText == Plaintext.PLAINTEXT_NULL) { - // Only happens for DTLS protocols. - // - // Received a retransmitted flight, and need to retransmit the - // previous delivered handshake flight messages. - if (enableRetransmissions) { - if (debug != null && Debug.isOn("verbose")) { - Debug.log( - "Retransmit the previous handshake flight messages."); - } - - synchronized (this) { - outputRecord.launchRetransmission(); - } - } // Otherwise, discard the retransmitted flight. - } else if (!isDTLS || plainText != null) { - hsStatus = processInputRecord(plainText, appData, offset, length); - } - - if (hsStatus == null) { - hsStatus = getHSStatus(null); - } - - if (plainText == null) { - plainText = Plaintext.PLAINTEXT_NULL; - } - plainText.handshakeStatus = hsStatus; - - return plainText; - } - - /* - * Process the record. - */ - private synchronized HandshakeStatus processInputRecord( - Plaintext plainText, - ByteBuffer[] appData, int offset, int length) throws IOException { - - HandshakeStatus hsStatus = null; - switch (plainText.contentType) { - case Record.ct_handshake: - /* - * Handshake messages always go to a pending session - * handshaker ... if there isn't one, create one. This - * must work asynchronously, for renegotiation. - * - * NOTE that handshaking will either resume a session - * which was in the cache (and which might have other - * connections in it already), or else will start a new - * session (new keys exchanged) with just this connection - * in it. - */ - initHandshaker(); - if (!handshaker.activated()) { - // prior to handshaking, activate the handshake - if (connectionState == cs_RENEGOTIATE) { - // don't use SSLv2Hello when renegotiating - handshaker.activate(protocolVersion); - } else { - handshaker.activate(null); - } - } - - /* - * process the handshake record ... may contain just - * a partial handshake message or multiple messages. - * - * The handshaker state machine will ensure that it's - * a finished message. - */ - handshaker.processRecord(plainText.fragment, expectingFinished); - expectingFinished = false; - - if (handshaker.invalidated) { - finishHandshake(); - - // if state is cs_RENEGOTIATE, revert it to cs_DATA - if (connectionState == cs_RENEGOTIATE) { - connectionState = cs_DATA; - } - } else if (handshaker.isDone()) { - // reset the parameters for secure renegotiation. - secureRenegotiation = - handshaker.isSecureRenegotiation(); - clientVerifyData = handshaker.getClientVerifyData(); - serverVerifyData = handshaker.getServerVerifyData(); - // set connection ALPN value - applicationProtocol = - handshaker.getHandshakeApplicationProtocol(); - - sess = handshaker.getSession(); - handshakeSession = null; - if (outputRecord.isEmpty()) { - hsStatus = finishHandshake(); - connectionState = cs_DATA; - } - - // No handshakeListeners here. That's a - // SSLSocket thing. - } else if (handshaker.taskOutstanding()) { - hsStatus = HandshakeStatus.NEED_TASK; - } - break; - - case Record.ct_application_data: - // Pass this right back up to the application. - if ((connectionState != cs_DATA) - && (connectionState != cs_RENEGOTIATE) - && (connectionState != cs_CLOSED)) { - throw new SSLProtocolException( - "Data received in non-data state: " + - connectionState); - } - - if (expectingFinished) { - throw new SSLProtocolException - ("Expecting finished message, received data"); - } - - if (!inboundDone) { - ByteBuffer fragment = plainText.fragment; - int remains = fragment.remaining(); - - // Should have enough room in appData. - for (int i = offset; - ((i < (offset + length)) && (remains > 0)); i++) { - int amount = Math.min(appData[i].remaining(), remains); - fragment.limit(fragment.position() + amount); - appData[i].put(fragment); - remains -= amount; - } - } - - break; - - case Record.ct_alert: - recvAlert(plainText.fragment); - break; - - case Record.ct_change_cipher_spec: - if ((connectionState != cs_HANDSHAKE - && connectionState != cs_RENEGOTIATE)) { - // For the CCS message arriving in the wrong state - fatal(Alerts.alert_unexpected_message, - "illegal change cipher spec msg, conn state = " - + connectionState); - } else if (plainText.fragment.remaining() != 1 - || plainText.fragment.get() != 1) { - // For structural/content issues with the CCS - fatal(Alerts.alert_unexpected_message, - "Malformed change cipher spec msg"); - } - - // - // The first message after a change_cipher_spec - // record MUST be a "Finished" handshake record, - // else it's a protocol violation. We force this - // to be checked by a minor tweak to the state - // machine. - // - handshaker.receiveChangeCipherSpec(); - - CipherBox readCipher; - Authenticator readAuthenticator; - try { - readCipher = handshaker.newReadCipher(); - readAuthenticator = handshaker.newReadAuthenticator(); - } catch (GeneralSecurityException e) { - // can't happen - throw new SSLException("Algorithm missing: ", e); - } - inputRecord.changeReadCiphers(readAuthenticator, readCipher); - - // next message MUST be a finished message - expectingFinished = true; - break; - - default: - // - // TLS requires that unrecognized records be ignored. - // - if (debug != null && Debug.isOn("ssl")) { - System.out.println(Thread.currentThread().getName() + - ", Received record type: " + plainText.contentType); - } - break; - } // switch - - /* - * We only need to check the sequence number state for - * non-handshaking record. - * - * Note that in order to maintain the handshake status - * properly, we check the sequence number after the last - * record reading process. As we request renegotiation - * or close the connection for wrapped sequence number - * when there is enough sequence number space left to - * handle a few more records, so the sequence number - * of the last record cannot be wrapped. - */ - hsStatus = getHSStatus(hsStatus); - if (connectionState < cs_ERROR && !isInboundDone() && - (hsStatus == HandshakeStatus.NOT_HANDSHAKING) && - (inputRecord.seqNumIsHuge())) { - /* - * Ask for renegotiation when need to renew sequence number. - * - * Don't bother to kickstart the renegotiation when the local is - * asking for it. - */ - if (debug != null && Debug.isOn("ssl")) { - System.out.println(Thread.currentThread().getName() + - ", request renegotiation " + - "to avoid sequence number overflow"); - } - - beginHandshake(); - - hsStatus = getHSStatus(null); - } - - return hsStatus; - } - - - // - // write/wrap side - // - - - /** - * Wraps a buffer. Does a variety of checks before grabbing - * the wrapLock, which blocks multiple wraps from occurring. - */ - @Override - public SSLEngineResult wrap(ByteBuffer[] appData, - int offset, int length, ByteBuffer netData) throws SSLException { - - // check engine parameters - checkEngineParas(netData, appData, offset, length, true); - - /* - * We can be smarter about using smaller buffer sizes later. - * For now, force it to be large enough to handle any valid record. - */ - if (netData.remaining() < sess.getPacketBufferSize()) { - return new SSLEngineResult( - Status.BUFFER_OVERFLOW, getHSStatus(null), 0, 0); - } - - try { - synchronized (wrapLock) { - return writeAppRecord(appData, offset, length, netData); - } - } catch (SSLProtocolException spe) { - // may be an unexpected handshake message - fatal(Alerts.alert_unexpected_message, spe.getMessage(), spe); - return null; // make compiler happy - } catch (Exception e) { - fatal(Alerts.alert_internal_error, - "problem wrapping app data", e); - return null; // make compiler happy - } - } - - /* - * Makes additional checks for unwrap, but this time more - * specific to this packet and the current state of the machine. - */ - private SSLEngineResult writeAppRecord(ByteBuffer[] appData, - int offset, int length, ByteBuffer netData) throws IOException { - - Status status = null; - HandshakeStatus hsStatus = null; - - /* - * See if the handshaker needs to report back some SSLException. - */ - checkTaskThrown(); - - /* - * short circuit if we're closed/closing. - */ - if (isOutboundDone()) { - return new SSLEngineResult(Status.CLOSED, getHSStatus(null), 0, 0); - } - - /* - * If we're still in cs_HANDSHAKE, make sure it's been - * started. - */ - synchronized (this) { - if ((connectionState == cs_HANDSHAKE) || - (connectionState == cs_START)) { - - kickstartHandshake(); - - /* - * If there's no HS data available to write, we can return - * without trying to wrap anything. - */ - hsStatus = getHSStatus(null); - if (hsStatus == HandshakeStatus.NEED_UNWRAP) { - /* - * For DTLS, if the handshake state is - * HandshakeStatus.NEED_UNWRAP, a call to SSLEngine.wrap() - * means that the previous handshake packets (if delivered) - * get lost, and need retransmit the handshake messages. - */ - if (!isDTLS || !enableRetransmissions || - (handshaker == null) || outputRecord.firstMessage) { - - return new SSLEngineResult(Status.OK, hsStatus, 0, 0); - } // otherwise, need retransmission - } - } - } - - /* - * Grab a copy of this if it doesn't already exist, - * and we can use it several places before anything major - * happens on this side. Races aren't critical - * here. - */ - if (hsStatus == null) { - hsStatus = getHSStatus(null); - } - - /* - * If we have a task outstanding, this *MUST* be done before - * doing any more wrapping, because we could be in the middle - * of receiving a handshake message, for example, a finished - * message which would change the ciphers. - */ - if (hsStatus == HandshakeStatus.NEED_TASK) { - return new SSLEngineResult(Status.OK, hsStatus, 0, 0); - } - - /* - * This will obtain any waiting outbound data, or will - * process the outbound appData. - */ - int netPos = netData.position(); - int appRemains = 0; - for (int i = offset; i < offset + length; i++) { - if (appData[i] == null) { - throw new IllegalArgumentException( - "appData[" + i + "] == null"); - } - appRemains += appData[i].remaining(); - } - - Ciphertext ciphertext = null; - try { - if (appRemains != 0) { - synchronized (writeLock) { - ciphertext = writeRecord(appData, offset, length, netData); - } + if (ioe instanceof SSLException) { + throw ioe; } else { - synchronized (writeLock) { - ciphertext = writeRecord(null, 0, 0, netData); - } + throw new SSLException("readRecord", ioe); } - } catch (SSLException e) { - throw e; - } catch (IOException e) { - throw new SSLException("Write problems", e); } /* - * writeRecord might have reported some status. - * Now check for the remaining cases. - * - * status above should cover: NEED_WRAP/FINISHED - */ - status = (isOutboundDone() ? Status.CLOSED : Status.OK); - hsStatus = getHSStatus(ciphertext.handshakeStatus); - - int deltaNet = netData.position() - netPos; - int deltaApp = appRemains; - for (int i = offset; i < offset + length; i++) { - deltaApp -= appData[i].remaining(); - } - - return new SSLEngineResult( - status, hsStatus, deltaApp, deltaNet, ciphertext.recordSN); - } - - /* - * Central point to write/get all of the outgoing data. - */ - private Ciphertext writeRecord(ByteBuffer[] appData, - int offset, int length, ByteBuffer netData) throws IOException { - - Ciphertext ciphertext = null; - try { - // Acquire the buffered to-be-delivered records or retransmissions. - // - // May have buffered records, or need retransmission if handshaking. - if (!outputRecord.isEmpty() || - (enableRetransmissions && handshaker != null)) { - ciphertext = outputRecord.acquireCiphertext(netData); - } - - if ((ciphertext == null) && (appData != null)) { - ciphertext = outputRecord.encode( - appData, offset, length, netData); - } - } catch (SSLHandshakeException she) { - // may be record sequence number overflow - fatal(Alerts.alert_handshake_failure, she); - - return Ciphertext.CIPHERTEXT_NULL; // make the complier happy - } catch (IOException e) { - fatal(Alerts.alert_unexpected_message, e); - - return Ciphertext.CIPHERTEXT_NULL; // make the complier happy - } - - if (ciphertext == null) { - return Ciphertext.CIPHERTEXT_NULL; - } - - HandshakeStatus hsStatus = null; - Ciphertext.RecordType recordType = ciphertext.recordType; - if ((recordType.contentType == Record.ct_handshake) && - (recordType.handshakeType == HandshakeMessage.ht_finished) && - outputRecord.isEmpty()) { - - if (handshaker == null) { - hsStatus = HandshakeStatus.FINISHED; - } else if (handshaker.isDone()) { - hsStatus = finishHandshake(); - connectionState = cs_DATA; - - // Retransmit the last flight twice. - // - // The application data transactions may begin immediately - // after the last flight. If the last flight get lost, the - // application data may be discarded accordingly. As could - // be an issue for some applications. This impact can be - // mitigated by sending the last fligth twice. - if (isDTLS && enableRetransmissions) { - if (debug != null && Debug.isOn("verbose")) { - Debug.log( - "Retransmit the last flight messages."); - } - - synchronized (this) { - outputRecord.launchRetransmission(); - } - - hsStatus = HandshakeStatus.NEED_WRAP; - } - } - } // Otherwise, the followed call to getHSStatus() will help. - - /* - * We only need to check the sequence number state for - * non-handshaking record. + * Check the various condition that we could be reporting. * - * Note that in order to maintain the handshake status - * properly, we check the sequence number after the last - * record writing process. As we request renegotiation - * or close the connection for wrapped sequence number - * when there is enough sequence number space left to - * handle a few more records, so the sequence number - * of the last record cannot be wrapped. - */ - hsStatus = getHSStatus(hsStatus); - if (connectionState < cs_ERROR && !isOutboundDone() && - (hsStatus == HandshakeStatus.NOT_HANDSHAKING) && - (outputRecord.seqNumIsHuge())) { - /* - * Ask for renegotiation when need to renew sequence number. - * - * Don't bother to kickstart the renegotiation when the local is - * asking for it. - */ - if (debug != null && Debug.isOn("ssl")) { - System.out.println(Thread.currentThread().getName() + - ", request renegotiation " + - "to avoid sequence number overflow"); - } - - beginHandshake(); - - hsStatus = getHSStatus(null); - } - ciphertext.handshakeStatus = hsStatus; - - return ciphertext; - } - - private HandshakeStatus finishHandshake() { - handshaker = null; - inputRecord.setHandshakeHash(null); - outputRecord.setHandshakeHash(null); - connectionState = cs_DATA; - - return HandshakeStatus.FINISHED; - } - - // - // Close code - // - - /** - * Signals that no more outbound application data will be sent - * on this SSLEngine. - */ - private void closeOutboundInternal() { - - if ((debug != null) && Debug.isOn("ssl")) { - System.out.println(Thread.currentThread().getName() + - ", closeOutboundInternal()"); - } - - /* - * Already closed, ignore - */ - if (outboundDone) { - return; - } - - switch (connectionState) { - - /* - * If we haven't even started yet, don't bother reading inbound. - */ - case cs_START: - try { - outputRecord.close(); - } catch (IOException ioe) { - // ignore - } - outboundDone = true; - - try { - inputRecord.close(); - } catch (IOException ioe) { - // ignore - } - inboundDone = true; - break; - - case cs_ERROR: - case cs_CLOSED: - break; - - /* - * Otherwise we indicate clean termination. - */ - // case cs_HANDSHAKE: - // case cs_DATA: - // case cs_RENEGOTIATE: - default: - warning(Alerts.alert_close_notify); - try { - outputRecord.close(); - } catch (IOException ioe) { - // ignore - } - outboundDone = true; - break; - } - - connectionState = cs_CLOSED; - } - - @Override - public synchronized void closeOutbound() { - /* - * Dump out a close_notify to the remote side - */ - if ((debug != null) && Debug.isOn("ssl")) { - System.out.println(Thread.currentThread().getName() + - ", called closeOutbound()"); - } - - closeOutboundInternal(); - } - - /** - * Returns the outbound application data closure state - */ - @Override - public boolean isOutboundDone() { - return outboundDone && outputRecord.isEmpty(); - } - - /** - * Signals that no more inbound network data will be sent - * to this SSLEngine. - */ - private void closeInboundInternal() { - - if ((debug != null) && Debug.isOn("ssl")) { - System.out.println(Thread.currentThread().getName() + - ", closeInboundInternal()"); - } - - /* - * Already closed, ignore - */ - if (inboundDone) { - return; - } - - closeOutboundInternal(); - - try { - inputRecord.close(); - } catch (IOException ioe) { - // ignore - } - inboundDone = true; - - connectionState = cs_CLOSED; - } - - /* - * Close the inbound side of the connection. We grab the - * lock here, and do the real work in the internal verison. - * We do check for truncation attacks. - */ - @Override - public synchronized void closeInbound() throws SSLException { - /* - * Currently closes the outbound side as well. The IETF TLS - * working group has expressed the opinion that 1/2 open - * connections are not allowed by the spec. May change - * someday in the future. - */ - if ((debug != null) && Debug.isOn("ssl")) { - System.out.println(Thread.currentThread().getName() + - ", called closeInbound()"); - } - - /* - * No need to throw an Exception if we haven't even started yet. + * It's *possible* something might have happened between the + * above and now, but it was better to minimally lock "this" + * during the read process. We'll return the current + * status, which is more representative of the current state. + * + * status above should cover: FINISHED, NEED_TASK */ - if ((connectionState != cs_START) && !recvCN) { - recvCN = true; // Only receive the Exception once - fatal(Alerts.alert_internal_error, - "Inbound closed before receiving peer's close_notify: " + - "possible truncation attack?"); + Status status = (isInboundDone() ? Status.CLOSED : Status.OK); + if (plainText.handshakeStatus != null) { + hsStatus = plainText.handshakeStatus; } else { - /* - * Currently, this is a no-op, but in case we change - * the close inbound code later. - */ - closeInboundInternal(); + hsStatus = getHandshakeStatus(); } - } - /** - * Returns the network inbound data closure state - */ - @Override - public synchronized boolean isInboundDone() { - return inboundDone; - } - - - // - // Misc stuff - // + int deltaNet = srcsRemains; + for (int i = srcsOffset; i < srcsOffset + srcsLength; i++) { + deltaNet -= srcs[i].remaining(); + } + int deltaApp = dstsRemains; + for (int i = dstsOffset; i < dstsOffset + dstsLength; i++) { + deltaApp -= dsts[i].remaining(); + } - /** - * Returns the current SSLSession for this - * SSLEngine - *

- * These can be long lived, and frequently correspond to an - * entire login session for some user. - */ - @Override - public synchronized SSLSession getSession() { - return sess; + return new SSLEngineResult( + status, hsStatus, deltaNet, deltaApp, plainText.recordSN); } - @Override - public synchronized SSLSession getHandshakeSession() { - return handshakeSession; - } + private Plaintext decode( + ByteBuffer[] srcs, int srcsOffset, int srcsLength, + ByteBuffer[] dsts, int dstsOffset, int dstsLength) throws IOException { + + Plaintext pt = SSLTransport.decode(conContext, + srcs, srcsOffset, srcsLength, + dsts, dstsOffset, dstsLength); + + // Is the handshake completed? + if (pt != Plaintext.PLAINTEXT_NULL) { + HandshakeStatus hsStatus = tryToFinishHandshake(pt.contentType); + if (hsStatus == null) { + pt.handshakeStatus = conContext.getHandshakeStatus(); + } else { + pt.handshakeStatus = hsStatus; + } - synchronized void setHandshakeSession(SSLSessionImpl session) { - // update the fragment size, which may be negotiated during handshaking - inputRecord.changeFragmentSize(session.getNegotiatedMaxFragSize()); - outputRecord.changeFragmentSize(session.getNegotiatedMaxFragSize()); + // Is the sequence number is nearly overflow? + if (conContext.inputRecord.seqNumIsHuge()) { + pt.handshakeStatus = + tryKeyUpdate(pt.handshakeStatus); + } + } - handshakeSession = session; + return pt; } - /** - * Returns a delegated Runnable task for - * this SSLEngine. - */ @Override public synchronized Runnable getDelegatedTask() { - if (handshaker != null) { - return handshaker.getTask(); + if (conContext.handshakeContext != null && + !conContext.handshakeContext.taskDelegated && + !conContext.handshakeContext.delegatedActions.isEmpty()) { + conContext.handshakeContext.taskDelegated = true; + return new DelegatedTask(this); } + return null; } - - // - // EXCEPTION AND ALERT HANDLING - // - - /* - * Send a warning alert. - */ - void warning(byte description) { - sendAlert(Alerts.alert_warning, description); + @Override + public synchronized void closeInbound() throws SSLException { + conContext.closeInbound(); } - synchronized void fatal(byte description, String diagnostic) - throws SSLException { - fatal(description, diagnostic, null, false); + @Override + public synchronized boolean isInboundDone() { + return conContext.isInboundDone(); } - synchronized void fatal(byte description, Throwable cause) - throws SSLException { - fatal(description, null, cause, false); + @Override + public synchronized void closeOutbound() { + conContext.closeOutbound(); } - synchronized void fatal(byte description, String diagnostic, - Throwable cause) throws SSLException { - fatal(description, diagnostic, cause, false); + @Override + public synchronized boolean isOutboundDone() { + return conContext.isOutboundDone(); } - /* - * We've got a fatal error here, so start the shutdown process. - * - * Because of the way the code was written, we have some code - * calling fatal directly when the "description" is known - * and some throwing Exceptions which are then caught by higher - * levels which then call here. This code needs to determine - * if one of the lower levels has already started the process. - * - * We won't worry about Errors, if we have one of those, - * we're in worse trouble. Note: the networking code doesn't - * deal with Errors either. - */ - synchronized void fatal(byte description, String diagnostic, - Throwable cause, boolean recvFatalAlert) throws SSLException { - - /* - * If we have no further information, make a general-purpose - * message for folks to see. We generally have one or the other. - */ - if (diagnostic == null) { - diagnostic = "General SSLEngine problem"; - } - if (cause == null) { - cause = Alerts.getSSLException(description, cause, diagnostic); - } - - /* - * If we've already shutdown because of an error, - * there is nothing we can do except rethrow the exception. - * - * Most exceptions seen here will be SSLExceptions. - * We may find the occasional Exception which hasn't been - * converted to a SSLException, so we'll do it here. - */ - if (closeReason != null) { - if ((debug != null) && Debug.isOn("ssl")) { - System.out.println(Thread.currentThread().getName() + - ", fatal: engine already closed. Rethrowing " + - cause.toString()); - } - if (cause instanceof RuntimeException) { - throw (RuntimeException)cause; - } else if (cause instanceof SSLException) { - throw (SSLException)cause; - } else if (cause instanceof Exception) { - throw new SSLException("fatal SSLEngine condition", cause); - } - } - - if ((debug != null) && Debug.isOn("ssl")) { - System.out.println(Thread.currentThread().getName() - + ", fatal error: " + description + - ": " + diagnostic + "\n" + cause.toString()); - } - - /* - * Ok, this engine's going down. - */ - int oldState = connectionState; - connectionState = cs_ERROR; - - try { - inputRecord.close(); - } catch (IOException ioe) { - // ignore - } - inboundDone = true; - - sess.invalidate(); - if (handshakeSession != null) { - handshakeSession.invalidate(); - } - - /* - * If we haven't even started handshaking yet, or we are the - * recipient of a fatal alert, no need to generate a fatal close - * alert. - */ - if (oldState != cs_START && !recvFatalAlert) { - sendAlert(Alerts.alert_fatal, description); - } - - if (cause instanceof SSLException) { // only true if != null - closeReason = (SSLException)cause; - } else { - /* - * Including RuntimeExceptions, but we'll throw those - * down below. The closeReason isn't used again, - * except for null checks. - */ - closeReason = - Alerts.getSSLException(description, cause, diagnostic); - } - - try { - outputRecord.close(); - } catch (IOException ioe) { - // ignore - } - outboundDone = true; - - connectionState = cs_CLOSED; - - if (cause instanceof RuntimeException) { - throw (RuntimeException)cause; - } else { - throw closeReason; - } + @Override + public String[] getSupportedCipherSuites() { + return CipherSuite.namesOf(sslContext.getSupportedCipherSuites()); } - /* - * Process an incoming alert ... caller must already have synchronized - * access to "this". - */ - private void recvAlert(ByteBuffer fragment) throws IOException { - byte level = fragment.get(); - byte description = fragment.get(); - - if (debug != null && (Debug.isOn("record") || - Debug.isOn("handshake"))) { - synchronized (System.out) { - System.out.print(Thread.currentThread().getName()); - System.out.print(", RECV " + protocolVersion + " ALERT: "); - if (level == Alerts.alert_fatal) { - System.out.print("fatal, "); - } else if (level == Alerts.alert_warning) { - System.out.print("warning, "); - } else { - System.out.print(", "); - } - System.out.println(Alerts.alertDescription(description)); - } - } - - if (level == Alerts.alert_warning) { - if (description == -1) { // check for short message - fatal(Alerts.alert_illegal_parameter, "Short alert message"); - } else if (description == Alerts.alert_close_notify) { - if (connectionState == cs_HANDSHAKE) { - fatal(Alerts.alert_unexpected_message, - "Received close_notify during handshake"); - } else { - recvCN = true; - closeInboundInternal(); // reply to close - } - } else { - - // - // The other legal warnings relate to certificates, - // e.g. no_certificate, bad_certificate, etc; these - // are important to the handshaking code, which can - // also handle illegal protocol alerts if needed. - // - if (handshaker != null) { - handshaker.handshakeAlert(description); - } - } - } else { // fatal or unknown level - String reason = "Received fatal alert: " - + Alerts.alertDescription(description); - - // The inbound and outbound queues will be closed as part of - // the call to fatal. The handhaker to needs to be set to null - // so subsequent calls to getHandshakeStatus will return - // NOT_HANDSHAKING. - handshaker = null; - Throwable cause = Alerts.getSSLException(description, reason); - fatal(description, null, cause, true); - } + @Override + public synchronized String[] getEnabledCipherSuites() { + return CipherSuite.namesOf(conContext.sslConfig.enabledCipherSuites); } - - /* - * Emit alerts. Caller must have synchronized with "this". - */ - private void sendAlert(byte level, byte description) { - // the connectionState cannot be cs_START - if (connectionState >= cs_CLOSED) { - return; - } - - // For initial handshaking, don't send alert message to peer if - // handshaker has not started. - // - // Shall we send an fatal alter to terminate the connection gracefully? - if (connectionState <= cs_HANDSHAKE && - (handshaker == null || !handshaker.started() || - !handshaker.activated())) { - return; + @Override + public synchronized void setEnabledCipherSuites(String[] suites) { + if (suites == null) { + throw new IllegalArgumentException("CipherSuites cannot be null"); } - try { - outputRecord.encodeAlert(level, description); - } catch (IOException ioe) { - // ignore - } + conContext.sslConfig.enabledCipherSuites = + CipherSuite.validValuesOf(suites); } - - // - // VARIOUS OTHER METHODS (COMMON TO SSLSocket) - // - - - /** - * Controls whether new connections may cause creation of new SSL - * sessions. - * - * As long as handshaking has not started, we can change - * whether we enable session creations. Otherwise, - * we will need to wait for the next handshake. - */ @Override - public synchronized void setEnableSessionCreation(boolean flag) { - enableSessionCreation = flag; - - if ((handshaker != null) && !handshaker.activated()) { - handshaker.setEnableSessionCreation(enableSessionCreation); - } + public String[] getSupportedProtocols() { + return ProtocolVersion.toStringArray( + sslContext.getSuportedProtocolVersions()); } - /** - * Returns true if new connections may cause creation of new SSL - * sessions. - */ @Override - public synchronized boolean getEnableSessionCreation() { - return enableSessionCreation; + public synchronized String[] getEnabledProtocols() { + return ProtocolVersion.toStringArray( + conContext.sslConfig.enabledProtocols); } - - /** - * Sets the flag controlling whether a server mode engine - * *REQUIRES* SSL client authentication. - * - * As long as handshaking has not started, we can change - * whether client authentication is needed. Otherwise, - * we will need to wait for the next handshake. - */ @Override - public synchronized void setNeedClientAuth(boolean flag) { - doClientAuth = (flag ? - ClientAuthType.CLIENT_AUTH_REQUIRED : - ClientAuthType.CLIENT_AUTH_NONE); - - if ((handshaker != null) && - (handshaker instanceof ServerHandshaker) && - !handshaker.activated()) { - ((ServerHandshaker) handshaker).setClientAuth(doClientAuth); + public synchronized void setEnabledProtocols(String[] protocols) { + if (protocols == null) { + throw new IllegalArgumentException("Protocols cannot be null"); } + + conContext.sslConfig.enabledProtocols = + ProtocolVersion.namesOf(protocols); } @Override - public synchronized boolean getNeedClientAuth() { - return (doClientAuth == ClientAuthType.CLIENT_AUTH_REQUIRED); + public synchronized SSLSession getSession() { + return conContext.conSession; } - /** - * Sets the flag controlling whether a server mode engine - * *REQUESTS* SSL client authentication. - * - * As long as handshaking has not started, we can change - * whether client authentication is requested. Otherwise, - * we will need to wait for the next handshake. - */ @Override - public synchronized void setWantClientAuth(boolean flag) { - doClientAuth = (flag ? - ClientAuthType.CLIENT_AUTH_REQUESTED : - ClientAuthType.CLIENT_AUTH_NONE); - - if ((handshaker != null) && - (handshaker instanceof ServerHandshaker) && - !handshaker.activated()) { - ((ServerHandshaker) handshaker).setClientAuth(doClientAuth); - } + public synchronized SSLSession getHandshakeSession() { + return conContext.handshakeContext == null ? + null : conContext.handshakeContext.handshakeSession; } @Override - public synchronized boolean getWantClientAuth() { - return (doClientAuth == ClientAuthType.CLIENT_AUTH_REQUESTED); + public synchronized SSLEngineResult.HandshakeStatus getHandshakeStatus() { + return conContext.getHandshakeStatus(); } - - /** - * Sets the flag controlling whether the engine is in SSL - * client or server mode. Must be called before any SSL - * traffic has started. - */ @Override - @SuppressWarnings("fallthrough") - public synchronized void setUseClientMode(boolean flag) { - switch (connectionState) { - - case cs_START: - /* - * If we need to change the socket mode and the enabled - * protocols and cipher suites haven't specifically been - * set by the user, change them to the corresponding - * default ones. - */ - if (roleIsServer != (!flag)) { - if (sslContext.isDefaultProtocolList(enabledProtocols)) { - enabledProtocols = - sslContext.getDefaultProtocolList(!flag); - } - - if (sslContext.isDefaultCipherSuiteList(enabledCipherSuites)) { - enabledCipherSuites = - sslContext.getDefaultCipherSuiteList(!flag); - } - } - - roleIsServer = !flag; - serverModeSet = true; - break; - - case cs_HANDSHAKE: - /* - * If we have a handshaker, but haven't started - * SSL traffic, we can throw away our current - * handshaker, and start from scratch. Don't - * need to call doneConnect() again, we already - * have the streams. - */ - assert(handshaker != null); - if (!handshaker.activated()) { - /* - * If we need to change the socket mode and the enabled - * protocols and cipher suites haven't specifically been - * set by the user, change them to the corresponding - * default ones. - */ - if (roleIsServer != (!flag)) { - if (sslContext.isDefaultProtocolList(enabledProtocols)) { - enabledProtocols = - sslContext.getDefaultProtocolList(!flag); - } - - if (sslContext.isDefaultCipherSuiteList( - enabledCipherSuites)) { - enabledCipherSuites = - sslContext.getDefaultCipherSuiteList(!flag); - } - } - - roleIsServer = !flag; - connectionState = cs_START; - initHandshaker(); - break; - } - - // If handshake has started, that's an error. Fall through... - - default: - if (debug != null && Debug.isOn("ssl")) { - System.out.println(Thread.currentThread().getName() + - ", setUseClientMode() invoked in state = " + - connectionState); - } - - /* - * We can let them continue if they catch this correctly, - * we don't need to shut this down. - */ - throw new IllegalArgumentException( - "Cannot change mode after SSL traffic has started"); - } + public synchronized void setUseClientMode(boolean mode) { + conContext.setUseClientMode(mode); } @Override public synchronized boolean getUseClientMode() { - return !roleIsServer; + return conContext.sslConfig.isClientMode; } - - /** - * Returns the names of the cipher suites which could be enabled for use - * on an SSL connection. Normally, only a subset of these will actually - * be enabled by default, since this list may include cipher suites which - * do not support the mutual authentication of servers and clients, or - * which do not protect data confidentiality. Servers may also need - * certain kinds of certificates to use certain cipher suites. - * - * @return an array of cipher suite names - */ @Override - public String[] getSupportedCipherSuites() { - return sslContext.getSupportedCipherSuiteList().toStringArray(); + public synchronized void setNeedClientAuth(boolean need) { + conContext.sslConfig.clientAuthType = + (need ? ClientAuthType.CLIENT_AUTH_REQUIRED : + ClientAuthType.CLIENT_AUTH_NONE); } - /** - * Controls which particular cipher suites are enabled for use on - * this connection. The cipher suites must have been listed by - * getCipherSuites() as being supported. Even if a suite has been - * enabled, it might never be used if no peer supports it or the - * requisite certificates (and private keys) are not available. - * - * @param suites Names of all the cipher suites to enable. - */ @Override - public synchronized void setEnabledCipherSuites(String[] suites) { - enabledCipherSuites = new CipherSuiteList(suites); - if ((handshaker != null) && !handshaker.activated()) { - handshaker.setEnabledCipherSuites(enabledCipherSuites); - } + public synchronized boolean getNeedClientAuth() { + return (conContext.sslConfig.clientAuthType == + ClientAuthType.CLIENT_AUTH_REQUIRED); } - /** - * Returns the names of the SSL cipher suites which are currently enabled - * for use on this connection. When an SSL engine is first created, - * all enabled cipher suites (a) protect data confidentiality, - * by traffic encryption, and (b) can mutually authenticate - * both clients and servers. Thus, in some environments, this value - * might be empty. - * - * @return an array of cipher suite names - */ @Override - public synchronized String[] getEnabledCipherSuites() { - return enabledCipherSuites.toStringArray(); + public synchronized void setWantClientAuth(boolean want) { + conContext.sslConfig.clientAuthType = + (want ? ClientAuthType.CLIENT_AUTH_REQUESTED : + ClientAuthType.CLIENT_AUTH_NONE); } - - /** - * Returns the protocols that are supported by this implementation. - * A subset of the supported protocols may be enabled for this connection - * @return an array of protocol names. - */ @Override - public String[] getSupportedProtocols() { - return sslContext.getSuportedProtocolList().toStringArray(); + public synchronized boolean getWantClientAuth() { + return (conContext.sslConfig.clientAuthType == + ClientAuthType.CLIENT_AUTH_REQUESTED); } - /** - * Controls which protocols are enabled for use on - * this connection. The protocols must have been listed by - * getSupportedProtocols() as being supported. - * - * @param protocols protocols to enable. - * @exception IllegalArgumentException when one of the protocols - * named by the parameter is not supported. - */ @Override - public synchronized void setEnabledProtocols(String[] protocols) { - enabledProtocols = new ProtocolList(protocols); - if ((handshaker != null) && !handshaker.activated()) { - handshaker.setEnabledProtocols(enabledProtocols); - } + public synchronized void setEnableSessionCreation(boolean flag) { + conContext.sslConfig.enableSessionCreation = flag; } @Override - public synchronized String[] getEnabledProtocols() { - return enabledProtocols.toStringArray(); + public synchronized boolean getEnableSessionCreation() { + return conContext.sslConfig.enableSessionCreation; } - /** - * Returns the SSLParameters in effect for this SSLEngine. - */ @Override public synchronized SSLParameters getSSLParameters() { - SSLParameters params = super.getSSLParameters(); - - // the super implementation does not handle the following parameters - params.setEndpointIdentificationAlgorithm(identificationProtocol); - params.setAlgorithmConstraints(algorithmConstraints); - params.setSNIMatchers(sniMatchers); - params.setServerNames(serverNames); - params.setUseCipherSuitesOrder(preferLocalCipherSuites); - params.setEnableRetransmissions(enableRetransmissions); - params.setMaximumPacketSize(maximumPacketSize); - params.setApplicationProtocols(applicationProtocols); - - return params; + return conContext.sslConfig.getSSLParameters(); } - /** - * Applies SSLParameters to this engine. - */ @Override public synchronized void setSSLParameters(SSLParameters params) { - super.setSSLParameters(params); - - // the super implementation does not handle the following parameters - identificationProtocol = params.getEndpointIdentificationAlgorithm(); - algorithmConstraints = params.getAlgorithmConstraints(); - preferLocalCipherSuites = params.getUseCipherSuitesOrder(); - enableRetransmissions = params.getEnableRetransmissions(); - maximumPacketSize = params.getMaximumPacketSize(); - - if (maximumPacketSize != 0) { - outputRecord.changePacketSize(maximumPacketSize); - } else { - // use the implicit maximum packet size. - maximumPacketSize = outputRecord.getMaxPacketSize(); - } - - List sniNames = params.getServerNames(); - if (sniNames != null) { - serverNames = sniNames; - } + conContext.sslConfig.setSSLParameters(params); - Collection matchers = params.getSNIMatchers(); - if (matchers != null) { - sniMatchers = matchers; - } - applicationProtocols = params.getApplicationProtocols(); - - if ((handshaker != null) && !handshaker.activated()) { - handshaker.setIdentificationProtocol(identificationProtocol); - handshaker.setAlgorithmConstraints(algorithmConstraints); - handshaker.setMaximumPacketSize(maximumPacketSize); - handshaker.setApplicationProtocols(applicationProtocols); - if (roleIsServer) { - handshaker.setSNIMatchers(sniMatchers); - handshaker.setUseCipherSuitesOrder(preferLocalCipherSuites); - } else { - handshaker.setSNIServerNames(serverNames); - } + if (conContext.sslConfig.maximumPacketSize != 0) { + conContext.outputRecord.changePacketSize( + conContext.sslConfig.maximumPacketSize); } } @Override public synchronized String getApplicationProtocol() { - return applicationProtocol; + return conContext.applicationProtocol; } @Override public synchronized String getHandshakeApplicationProtocol() { - if ((handshaker != null) && handshaker.started()) { - return handshaker.getHandshakeApplicationProtocol(); - } - return null; + return conContext.handshakeContext == null ? + null : conContext.handshakeContext.applicationProtocol; } @Override public synchronized void setHandshakeApplicationProtocolSelector( - BiFunction, String> selector) { - applicationProtocolSelector = selector; - if ((handshaker != null) && !handshaker.activated()) { - handshaker.setApplicationProtocolSelectorSSLEngine(selector); - } + BiFunction, String> selector) { + conContext.sslConfig.engineAPSelector = selector; } @Override public synchronized BiFunction, String> - getHandshakeApplicationProtocolSelector() { - return this.applicationProtocolSelector; + getHandshakeApplicationProtocolSelector() { + return conContext.sslConfig.engineAPSelector; + } + + @Override + public boolean useDelegatedTask() { + return true; + } + + private synchronized void checkTaskThrown() throws SSLException { + HandshakeContext hc = conContext.handshakeContext; + if (hc != null && hc.delegatedThrown != null) { + try { + throw getTaskThrown(hc.delegatedThrown); + } finally { + hc.delegatedThrown = null; + } + } + + if (conContext.isBroken && conContext.closeReason != null) { + throw getTaskThrown(conContext.closeReason); + } + } + + private static SSLException getTaskThrown(Exception taskThrown) { + String msg = taskThrown.getMessage(); + + if (msg == null) { + msg = "Delegated task threw Exception or Error"; + } + + if (taskThrown instanceof RuntimeException) { + throw new RuntimeException(msg, taskThrown); + } else if (taskThrown instanceof SSLHandshakeException) { + return (SSLHandshakeException) + new SSLHandshakeException(msg).initCause(taskThrown); + } else if (taskThrown instanceof SSLKeyException) { + return (SSLKeyException) + new SSLKeyException(msg).initCause(taskThrown); + } else if (taskThrown instanceof SSLPeerUnverifiedException) { + return (SSLPeerUnverifiedException) + new SSLPeerUnverifiedException(msg).initCause(taskThrown); + } else if (taskThrown instanceof SSLProtocolException) { + return (SSLProtocolException) + new SSLProtocolException(msg).initCause(taskThrown); + } else if (taskThrown instanceof SSLException) { + return (SSLException)taskThrown; + } else { + return new SSLException(msg, taskThrown); + } } /** - * Returns a printable representation of this end of the connection. + * Implement a simple task delegator. */ - @Override - public String toString() { - StringBuilder retval = new StringBuilder(80); + private static class DelegatedTask implements Runnable { + private final SSLEngineImpl engine; + + DelegatedTask(SSLEngineImpl engineInstance) { + this.engine = engineInstance; + } - retval.append(Integer.toHexString(hashCode())); - retval.append("["); - retval.append("SSLEngine[hostname="); - String host = getPeerHost(); - retval.append((host == null) ? "null" : host); - retval.append(" port="); - retval.append(Integer.toString(getPeerPort())); - retval.append(" role=" + (roleIsServer ? "Server" : "Client")); - retval.append("] "); - retval.append(getSession().getCipherSuite()); - retval.append("]"); + @Override + public void run() { + synchronized (engine) { + HandshakeContext hc = engine.conContext.handshakeContext; + if (hc == null || hc.delegatedActions.isEmpty()) { + return; + } + + try { + AccessController.doPrivileged( + new DelegatedAction(hc), engine.conContext.acc); + } catch (PrivilegedActionException pae) { + // Get the handshake context again in case the + // handshaking has completed. + hc = engine.conContext.handshakeContext; + if (hc != null) { + hc.delegatedThrown = pae.getException(); + } else if (engine.conContext.closeReason != null) { + engine.conContext.closeReason = + getTaskThrown(pae.getException()); + } + } catch (RuntimeException rte) { + // Get the handshake context again in case the + // handshaking has completed. + hc = engine.conContext.handshakeContext; + if (hc != null) { + hc.delegatedThrown = rte; + } else if (engine.conContext.closeReason != null) { + engine.conContext.closeReason = rte; + } + } + + // Get the handshake context again in case the + // handshaking has completed. + hc = engine.conContext.handshakeContext; + if (hc != null) { + hc.taskDelegated = false; + } + } + } + + private static class DelegatedAction + implements PrivilegedExceptionAction { + final HandshakeContext context; + DelegatedAction(HandshakeContext context) { + this.context = context; + } + + @Override + public Void run() throws Exception { + while (!context.delegatedActions.isEmpty()) { + // Report back the task SSLException + if (context.delegatedThrown != null) { + Exception delegatedThrown = context.delegatedThrown; + context.delegatedThrown = null; + throw getTaskThrown(delegatedThrown); + } - return retval.toString(); + Map.Entry me = + context.delegatedActions.poll(); + if (me != null) { + context.dispatch(me.getKey(), me.getValue()); + } + } + return null; + } + } } }