< prev index next >
src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java
Print this page
*** 1,7 ****
/*
! * Copyright (c) 1996, 2016, 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
--- 1,7 ----
/*
! * Copyright (c) 1996, 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
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
*** 21,59 ****
* 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.net.*;
! import java.security.GeneralSecurityException;
! import java.security.AccessController;
! import java.security.AccessControlContext;
! import java.security.PrivilegedAction;
! import java.security.AlgorithmConstraints;
! import java.util.*;
! import java.util.concurrent.TimeUnit;
! import java.util.concurrent.locks.ReentrantLock;
import java.util.function.BiFunction;
!
! import javax.crypto.BadPaddingException;
! import javax.net.ssl.*;
!
import jdk.internal.misc.JavaNetInetAddressAccess;
import jdk.internal.misc.SharedSecrets;
/**
! * Implementation of an SSL socket. This is a normal connection type
! * socket, implementing SSL over some lower level socket, such as TCP.
! * Because it is layered over some lower level socket, it MUST override
! * all default socket methods.
! *
! * <P> This API offers a non-traditional option for establishing SSL
* connections. You may first establish the connection directly, then pass
* that connection to the SSL socket constructor with a flag saying which
* role should be taken in the handshake protocol. (The two ends of the
* connection must not choose the same role!) This allows setup of SSL
* proxying or tunneling, and also allows the kind of "role reversal"
--- 21,64 ----
* 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.IOException;
! import java.io.InputStream;
! import java.io.InterruptedIOException;
! import java.io.OutputStream;
! import java.net.InetAddress;
! import java.net.InetSocketAddress;
! import java.net.Socket;
! import java.net.SocketAddress;
! import java.net.SocketException;
! import java.net.UnknownHostException;
! import java.nio.ByteBuffer;
! import java.util.List;
import java.util.function.BiFunction;
! import javax.net.ssl.HandshakeCompletedListener;
! import javax.net.ssl.SSLException;
! import javax.net.ssl.SSLHandshakeException;
! import javax.net.ssl.SSLParameters;
! import javax.net.ssl.SSLProtocolException;
! import javax.net.ssl.SSLServerSocket;
! import javax.net.ssl.SSLSession;
! import javax.net.ssl.SSLSocket;
import jdk.internal.misc.JavaNetInetAddressAccess;
import jdk.internal.misc.SharedSecrets;
/**
! * Implementation of an SSL socket.
! * <P>
! * This is a normal connection type socket, implementing SSL over some lower
! * level socket, such as TCP. Because it is layered over some lower level
! * socket, it MUST override all default socket methods.
! * <P>
! * This API offers a non-traditional option for establishing SSL
* connections. You may first establish the connection directly, then pass
* that connection to the SSL socket constructor with a flag saying which
* role should be taken in the handshake protocol. (The two ends of the
* connection must not choose the same role!) This allows setup of SSL
* proxying or tunneling, and also allows the kind of "role reversal"
*** 62,651 ****
* @see javax.net.ssl.SSLSocket
* @see SSLServerSocket
*
* @author David Brownell
*/
! public final class SSLSocketImpl extends BaseSSLSocketImpl {
!
! /*
! * ERROR HANDLING GUIDELINES
! * (which exceptions to throw and catch and which not to throw and catch)
! *
! * . if there is an IOException (SocketException) when accessing the
! * underlying Socket, pass it through
! *
! * . do not throw IOExceptions, throw SSLExceptions (or a subclass)
! *
! * . for internal errors (things that indicate a bug in JSSE or a
! * grossly misconfigured J2RE), throw either an SSLException or
! * a RuntimeException at your convenience.
! *
! * . handshaking code (Handshaker or HandshakeMessage) should generally
! * pass through exceptions, but can handle them if they know what to
! * do.
! *
! * . exception chaining should be used for all new code. If you happen
! * to touch old code that does not use chaining, you should change it.
! *
! * . there is a top level exception handler that sits at all entry
! * points from application code to SSLSocket read/write code. It
! * makes sure that all errors are handled (see handleException()).
! *
! * . JSSE internal code should generally not call close(), call
! * closeInternal().
! */
!
! /*
! * 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.
! * - SENT_CLOSE sent a close_notify to the peer. For layered,
! * non-autoclose socket, must now read close_notify
! * from peer before closing the connection. For nonlayered or
! * non-autoclose socket, close connection and go onto
! * cs_CLOSED state.
! * - CLOSED after sending close_notify alert, & socket is closed.
! * SSL connection objects are not reused.
! * - APP_CLOSED once the application calls close(). Then it behaves like
! * a closed socket, e.g.. getInputStream() throws an Exception.
! *
! * 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.
! *
! * +---->-------+------>--------->-------+
! * | | |
! * <-----< ^ ^ <-----< v
! *START>----->HANDSHAKE>----->DATA>----->RENEGOTIATE SENT_CLOSE
! * v v v | |
! * | | | | v
! * +------------+---------------+ v ERROR
! * | | |
! * v | |
! * ERROR>------>----->CLOSED<--------<----+-- +
! * |
! * v
! * APP_CLOSED
! *
! * 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 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_SENT_CLOSE = 5;
! private static final int cs_CLOSED = 6;
! private static final int cs_APP_CLOSED = 7;
!
! /*
! * Drives the protocol state machine.
! */
! private volatile int connectionState;
!
! /*
! * 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;
!
! /*
! * For improved diagnostics, we detail connection closure
! * If the socket is closed (connectionState >= cs_ERROR),
! * closeReason != null indicates if the socket 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 ClientAuthType doClientAuth =
! ClientAuthType.CLIENT_AUTH_NONE;
! private boolean roleIsServer;
! private boolean enableSessionCreation = true;
! private String host;
! private boolean autoClose = true;
! 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<SNIServerName> serverNames =
! Collections.<SNIServerName>emptyList();
! Collection<SNIMatcher> sniMatchers =
! Collections.<SNIMatcher>emptyList();
!
! // Is the serverNames set to empty with SSLParameters.setServerNames()?
! private boolean noSniExtension = false;
!
! // Is the sniMatchers set to empty with SSLParameters.setSNIMatchers()?
! private boolean noSniMatcher = false;
!
! // 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<SSLSocket, List<String>, String> applicationProtocolSelector;
!
! /*
! * 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.
! *
! * The handshakeLock is used to ensure that only one thread performs
! * the *complete initial* handshake. If someone is handshaking, any
! * stray application or startHandshake() requests who find the
! * connection state is cs_HANDSHAKE will stall on handshakeLock
! * until handshaking is done. Once the handshake is done, we either
! * succeeded or failed, but we can never go back to the cs_HANDSHAKE
! * or cs_START state again.
! *
! * Note that the read/write() calls here in SSLSocketImpl are not
! * obviously synchronized. In fact, it's very nonintuitive, and
! * requires careful examination of code paths. Grab some coffee,
! * and be careful with any code changes.
! *
! * There can be only three threads active at a time in the I/O
! * subsection of this class.
! * 1. startHandshake
! * 2. AppInputStream
! * 3. AppOutputStream
! * One thread could call startHandshake().
! * AppInputStream/AppOutputStream read() and write() calls are each
! * synchronized on 'this' in their respective classes, so only one
! * app. thread will be doing a SSLSocketImpl.read() or .write()'s at
! * a time.
! *
! * If handshaking is required (state cs_HANDSHAKE), and
! * getConnectionState() for some/all threads returns cs_HANDSHAKE,
! * only one can grab the handshakeLock, and the rest will stall
! * either on getConnectionState(), or on the handshakeLock if they
! * happen to successfully race through the getConnectionState().
! *
! * If a writer is doing the initial handshaking, it must create a
! * temporary reader to read the responses from the other side. As a
! * side-effect, the writer's reader will have priority over any
! * other reader. However, the writer's reader is not allowed to
! * consume any application data. When handshakeLock is finally
! * released, we either have a cs_DATA connection, or a
! * cs_CLOSED/cs_ERROR socket.
! *
! * The writeLock is held while writing on a socket connection and
! * also to protect the MAC and cipher for their direction. The
! * writeLock is package private for Handshaker which holds it while
! * writing the ChangeCipherSpec message.
! *
! * To avoid the problem of a thread trying to change operational
! * modes on a socket while handshaking is going on, we synchronize
! * on 'this'. If handshaking has not started yet, we tell the
! * handshaker to change its mode. If handshaking has started,
! * we simply store that request until the next pending session
! * is created, at which time the new handshaker's state is set.
! *
! * The readLock is held during readRecord(), which is responsible
! * for reading an SSLInputRecord, decrypting it, and processing it.
! * The readLock ensures that these three steps are done atomically
! * and that once started, no other thread can block on SSLInputRecord.read.
! * This is necessary so that processing of close_notify alerts
! * from the peer are handled properly.
! */
! private final Object handshakeLock = new Object();
! final ReentrantLock writeLock = new ReentrantLock();
! private final Object readLock = new Object();
!
! InputRecord inputRecord;
! OutputRecord outputRecord;
!
! /*
! * security parameters for secure renegotiation.
! */
! private boolean secureRenegotiation;
! private byte[] clientVerifyData;
! private byte[] serverVerifyData;
!
! /*
! * 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;
!
!
! /*
! * If anyone wants to get notified about handshake completions,
! * they'll show up on this list.
! */
! private HashMap<HandshakeCompletedListener, AccessControlContext>
! handshakeListeners;
!
! /*
! * Reuse the same internal input/output streams.
! */
! private InputStream sockInput;
! private OutputStream sockOutput;
!
!
! /*
! * These input and output streams block their data in SSL records,
! * and usually arrange integrity and privacy protection for those
! * records. The guts of the SSL protocol are wrapped up in these
! * streams, and in the handshaking that establishes the details of
! * that integrity and privacy protection.
! */
! private AppInputStream input;
! private AppOutputStream output;
!
! /*
! * 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_TLS;
! /* Class and subclass dynamic debugging support */
! private static final Debug debug = Debug.getInstance("ssl");
! /*
! * Whether local cipher suites preference in server side should be
! * honored during handshaking?
! */
! private boolean preferLocalCipherSuites = false;
! /*
! * The maximum expected network packet size for SSL/TLS/DTLS records.
! */
! private int maximumPacketSize = 0;
/*
* Is the local name service trustworthy?
*
* If the local name service is not trustworthy, reverse host name
* resolution should not be performed for endpoint identification.
*/
static final boolean trustNameService =
! Debug.getBooleanProperty("jdk.tls.trustNameService", false);
!
! //
! // CONSTRUCTORS AND INITIALIZATION CODE
! //
/**
! * Constructs an SSL connection to a named host at a specified port,
! * using the authentication context provided. This endpoint acts as
! * the client, and may rejoin an existing SSL session if appropriate.
! *
! * @param context authentication context to use
! * @param host name of the host with which to connect
! * @param port number of the server's port
*/
! SSLSocketImpl(SSLContextImpl context, String host, int port)
! throws IOException, UnknownHostException {
super();
! this.host = host;
! this.serverNames =
! Utilities.addToSNIServerNameList(this.serverNames, this.host);
! init(context, false);
! SocketAddress socketAddress =
! host != null ? new InetSocketAddress(host, port) :
! new InetSocketAddress(InetAddress.getByName(null), port);
! connect(socketAddress, 0);
}
-
/**
! * Constructs an SSL connection to a server at a specified address.
! * and TCP port, using the authentication context provided. This
! * endpoint acts as the client, and may rejoin an existing SSL session
! * if appropriate.
*
! * @param context authentication context to use
! * @param address the server's host
! * @param port its port
*/
! SSLSocketImpl(SSLContextImpl context, InetAddress host, int port)
! throws IOException {
super();
! init(context, false);
! SocketAddress socketAddress = new InetSocketAddress(host, port);
! connect(socketAddress, 0);
}
/**
! * Constructs an SSL connection to a named host at a specified port,
! * using the authentication context provided. This endpoint acts as
! * the client, and may rejoin an existing SSL session if appropriate.
! *
! * @param context authentication context to use
! * @param host name of the host with which to connect
! * @param port number of the server's port
! * @param localAddr the local address the socket is bound to
! * @param localPort the local port the socket is bound to
! */
! SSLSocketImpl(SSLContextImpl context, String host, int port,
! InetAddress localAddr, int localPort)
! throws IOException, UnknownHostException {
super();
! this.host = host;
! this.serverNames =
! Utilities.addToSNIServerNameList(this.serverNames, this.host);
! init(context, false);
! bind(new InetSocketAddress(localAddr, localPort));
SocketAddress socketAddress =
! host != null ? new InetSocketAddress(host, port) :
! new InetSocketAddress(InetAddress.getByName(null), port);
connect(socketAddress, 0);
}
-
/**
! * Constructs an SSL connection to a server at a specified address.
! * and TCP port, using the authentication context provided. This
! * endpoint acts as the client, and may rejoin an existing SSL session
! * if appropriate.
*
! * @param context authentication context to use
! * @param address the server's host
! * @param port its port
! * @param localAddr the local address the socket is bound to
! * @param localPort the local port the socket is bound to
*/
! SSLSocketImpl(SSLContextImpl context, InetAddress host, int port,
! InetAddress localAddr, int localPort)
! throws IOException {
super();
! init(context, false);
! bind(new InetSocketAddress(localAddr, localPort));
! SocketAddress socketAddress = new InetSocketAddress(host, port);
connect(socketAddress, 0);
}
! /*
! * Package-private constructor used ONLY by SSLServerSocket. The
! * java.net package accepts the TCP connection after this call is
! * made. This just initializes handshake state to use "server mode",
! * giving control over the use of SSL client authentication.
! */
! SSLSocketImpl(SSLContextImpl context, boolean serverMode,
! CipherSuiteList suites, ClientAuthType clientAuth,
! boolean sessionCreation, ProtocolList protocols,
! String identificationProtocol,
! AlgorithmConstraints algorithmConstraints,
! Collection<SNIMatcher> sniMatchers,
! boolean preferLocalCipherSuites,
! String[] applicationProtocols) throws IOException {
!
super();
! doClientAuth = clientAuth;
! enableSessionCreation = sessionCreation;
! this.identificationProtocol = identificationProtocol;
! this.algorithmConstraints = algorithmConstraints;
! this.sniMatchers = sniMatchers;
! this.preferLocalCipherSuites = preferLocalCipherSuites;
! this.applicationProtocols = applicationProtocols;
! init(context, serverMode);
! /*
! * Override what was picked out for us.
! */
! enabledCipherSuites = suites;
! enabledProtocols = protocols;
}
-
/**
! * Package-private constructor used to instantiate an unconnected
! * socket. The java.net package will connect it, either when the
! * connect() call is made by the application. This instance is
! * meant to set handshake state to use "client mode".
! */
! SSLSocketImpl(SSLContextImpl context) {
super();
! init(context, false);
! }
!
! /**
! * Layer SSL traffic over an existing connection, rather than creating
! * a new connection. The existing connection may be used only for SSL
! * traffic (using this SSLSocket) until the SSLSocket.close() call
! * returns. However, if a protocol error is detected, that existing
! * connection is automatically closed.
! *
! * <P> This particular constructor always uses the socket in the
! * role of an SSL client. It may be useful in cases which start
! * using SSL after some initial data transfers, for example in some
! * SSL tunneling applications or as part of some kinds of application
! * protocols which negotiate use of a SSL based security.
! *
! * @param sock the existing connection
! * @param context the authentication context to use
! */
! SSLSocketImpl(SSLContextImpl context, Socket sock, String host,
! int port, boolean autoClose) throws IOException {
! super(sock);
! // We always layer over a connected socket
! if (!sock.isConnected()) {
! throw new SocketException("Underlying socket is not connected");
! }
! this.host = host;
! this.serverNames =
! Utilities.addToSNIServerNameList(this.serverNames, this.host);
! init(context, false);
! this.autoClose = autoClose;
! doneConnect();
}
/**
* Creates a server mode {@link Socket} layered over an
* existing connected socket, and is able to read data which has
* already been consumed/removed from the {@link Socket}'s
* underlying {@link InputStream}.
*/
! SSLSocketImpl(SSLContextImpl context, Socket sock,
InputStream consumed, boolean autoClose) throws IOException {
super(sock, consumed);
// We always layer over a connected socket
if (!sock.isConnected()) {
throw new SocketException("Underlying socket is not connected");
}
! // In server mode, it is not necessary to set host and serverNames.
! // Otherwise, would require a reverse DNS lookup to get the hostname.
!
! init(context, true);
this.autoClose = autoClose;
doneConnect();
}
/**
! * Initializes the client socket.
! */
! private void init(SSLContextImpl context, boolean isServer) {
! sslContext = context;
! sess = SSLSessionImpl.nullSession;
! handshakeSession = null;
!
! /*
! * role is as specified, state is START until after
! * the low level connection's established.
*/
! roleIsServer = isServer;
! connectionState = cs_START;
!
! // initial security parameters for secure renegotiation
! secureRenegotiation = false;
! clientVerifyData = new byte[0];
! serverVerifyData = new byte[0];
!
! enabledCipherSuites =
! sslContext.getDefaultCipherSuiteList(roleIsServer);
! enabledProtocols =
! sslContext.getDefaultProtocolList(roleIsServer);
!
! inputRecord = new SSLSocketInputRecord();;
! outputRecord = new SSLSocketOutputRecord();
!
! maximumPacketSize = outputRecord.getMaxPacketSize();
!
! // save the acc
! acc = AccessController.getContext();
! input = new AppInputStream(this);
! output = new AppOutputStream(this);
}
- /**
- * Connects this socket to the server with a specified timeout
- * value.
- *
- * This method is either called on an unconnected SSLSocketImpl by the
- * application, or it is called in the constructor of a regular
- * SSLSocketImpl. If we are layering on top on another socket, then
- * this method should not be called, because we assume that the
- * underlying socket is already connected by the time it is passed to
- * us.
- *
- * @param endpoint the <code>SocketAddress</code>
- * @param timeout the timeout value to be used, 0 is no timeout
- * @throws IOException if an error occurs during the connection
- * @throws SocketTimeoutException if timeout expires before connecting
- */
@Override
! public void connect(SocketAddress endpoint, int timeout)
! throws IOException {
if (isLayered()) {
throw new SocketException("Already connected");
}
--- 67,279 ----
* @see javax.net.ssl.SSLSocket
* @see SSLServerSocket
*
* @author David Brownell
*/
! public final class SSLSocketImpl
! extends BaseSSLSocketImpl implements SSLTransport {
! final SSLContextImpl sslContext;
! final TransportContext conContext;
! private final AppInputStream appInput = new AppInputStream();
! private final AppOutputStream appOutput = new AppOutputStream();
! private String peerHost;
! private boolean autoClose;
! private boolean isConnected = false;
! private boolean tlsIsClosed = false;
/*
* Is the local name service trustworthy?
*
* If the local name service is not trustworthy, reverse host name
* resolution should not be performed for endpoint identification.
*/
static final boolean trustNameService =
! Utilities.getBooleanProperty("jdk.tls.trustNameService", false);
/**
! * Package-private constructor used to instantiate an unconnected
! * socket.
! *
! * This instance is meant to set handshake state to use "client mode".
*/
! SSLSocketImpl(SSLContextImpl sslContext) {
super();
! this.sslContext = sslContext;
! HandshakeHash handshakeHash = new HandshakeHash();
! this.conContext = new TransportContext(sslContext, this,
! new SSLSocketInputRecord(handshakeHash),
! new SSLSocketOutputRecord(handshakeHash), true);
}
/**
! * Package-private constructor used to instantiate a server socket.
*
! * This instance is meant to set handshake state to use "server mode".
*/
! SSLSocketImpl(SSLContextImpl sslContext, SSLConfiguration sslConfig) {
super();
! this.sslContext = sslContext;
! HandshakeHash handshakeHash = new HandshakeHash();
! this.conContext = new TransportContext(sslContext, this, sslConfig,
! new SSLSocketInputRecord(handshakeHash),
! new SSLSocketOutputRecord(handshakeHash));
}
/**
! * Constructs an SSL connection to a named host at a specified
! * port, using the authentication context provided.
! *
! * This endpoint acts as the client, and may rejoin an existing SSL session
! * if appropriate.
! */
! SSLSocketImpl(SSLContextImpl sslContext, String peerHost,
! int peerPort) throws IOException, UnknownHostException {
super();
! this.sslContext = sslContext;
! HandshakeHash handshakeHash = new HandshakeHash();
! this.conContext = new TransportContext(sslContext, this,
! new SSLSocketInputRecord(handshakeHash),
! new SSLSocketOutputRecord(handshakeHash), true);
! this.peerHost = peerHost;
SocketAddress socketAddress =
! peerHost != null ? new InetSocketAddress(peerHost, peerPort) :
! new InetSocketAddress(InetAddress.getByName(null), peerPort);
connect(socketAddress, 0);
}
/**
! * Constructs an SSL connection to a server at a specified
! * address, and TCP port, using the authentication context
! * provided.
*
! * This endpoint acts as the client, and may rejoin an existing SSL
! * session if appropriate.
*/
! SSLSocketImpl(SSLContextImpl sslContext,
! InetAddress address, int peerPort) throws IOException {
super();
! this.sslContext = sslContext;
! HandshakeHash handshakeHash = new HandshakeHash();
! this.conContext = new TransportContext(sslContext, this,
! new SSLSocketInputRecord(handshakeHash),
! new SSLSocketOutputRecord(handshakeHash), true);
!
! SocketAddress socketAddress = new InetSocketAddress(address, peerPort);
connect(socketAddress, 0);
}
! /**
! * Constructs an SSL connection to a named host at a specified
! * port, using the authentication context provided.
! *
! * This endpoint acts as the client, and may rejoin an existing SSL
! * session if appropriate.
! */
! SSLSocketImpl(SSLContextImpl sslContext,
! String peerHost, int peerPort, InetAddress localAddr,
! int localPort) throws IOException, UnknownHostException {
super();
! this.sslContext = sslContext;
! HandshakeHash handshakeHash = new HandshakeHash();
! this.conContext = new TransportContext(sslContext, this,
! new SSLSocketInputRecord(handshakeHash),
! new SSLSocketOutputRecord(handshakeHash), true);
! this.peerHost = peerHost;
! bind(new InetSocketAddress(localAddr, localPort));
! SocketAddress socketAddress =
! peerHost != null ? new InetSocketAddress(peerHost, peerPort) :
! new InetSocketAddress(InetAddress.getByName(null), peerPort);
! connect(socketAddress, 0);
}
/**
! * Constructs an SSL connection to a server at a specified
! * address, and TCP port, using the authentication context
! * provided.
! *
! * This endpoint acts as the client, and may rejoin an existing SSL
! * session if appropriate.
! */
! SSLSocketImpl(SSLContextImpl sslContext,
! InetAddress peerAddr, int peerPort,
! InetAddress localAddr, int localPort) throws IOException {
super();
! this.sslContext = sslContext;
! HandshakeHash handshakeHash = new HandshakeHash();
! this.conContext = new TransportContext(sslContext, this,
! new SSLSocketInputRecord(handshakeHash),
! new SSLSocketOutputRecord(handshakeHash), true);
! bind(new InetSocketAddress(localAddr, localPort));
! SocketAddress socketAddress = new InetSocketAddress(peerAddr, peerPort);
! connect(socketAddress, 0);
}
/**
* Creates a server mode {@link Socket} layered over an
* existing connected socket, and is able to read data which has
* already been consumed/removed from the {@link Socket}'s
* underlying {@link InputStream}.
*/
! SSLSocketImpl(SSLContextImpl sslContext, Socket sock,
InputStream consumed, boolean autoClose) throws IOException {
+
super(sock, consumed);
// We always layer over a connected socket
if (!sock.isConnected()) {
throw new SocketException("Underlying socket is not connected");
}
! this.sslContext = sslContext;
! HandshakeHash handshakeHash = new HandshakeHash();
! this.conContext = new TransportContext(sslContext, this,
! new SSLSocketInputRecord(handshakeHash),
! new SSLSocketOutputRecord(handshakeHash), false);
this.autoClose = autoClose;
doneConnect();
}
/**
! * Layer SSL traffic over an existing connection, rather than
! * creating a new connection.
! *
! * The existing connection may be used only for SSL traffic (using this
! * SSLSocket) until the SSLSocket.close() call returns. However, if a
! * protocol error is detected, that existing connection is automatically
! * closed.
! * <p>
! * This particular constructor always uses the socket in the
! * role of an SSL client. It may be useful in cases which start
! * using SSL after some initial data transfers, for example in some
! * SSL tunneling applications or as part of some kinds of application
! * protocols which negotiate use of a SSL based security.
*/
! SSLSocketImpl(SSLContextImpl sslContext, Socket sock,
! String peerHost, int port, boolean autoClose) throws IOException {
! super(sock);
! // We always layer over a connected socket
! if (!sock.isConnected()) {
! throw new SocketException("Underlying socket is not connected");
! }
! this.sslContext = sslContext;
! HandshakeHash handshakeHash = new HandshakeHash();
! this.conContext = new TransportContext(sslContext, this,
! new SSLSocketInputRecord(handshakeHash),
! new SSLSocketOutputRecord(handshakeHash), true);
! this.peerHost = peerHost;
! this.autoClose = autoClose;
! doneConnect();
}
@Override
! public void connect(SocketAddress endpoint,
! int timeout) throws IOException {
if (isLayered()) {
throw new SocketException("Already connected");
}
*** 653,2743 ****
throw new SocketException(
"Cannot handle non-Inet socket addresses.");
}
super.connect(endpoint, timeout);
-
- if (host == null || host.length() == 0) {
- useImplicitHost(false);
- }
-
doneConnect();
}
! /**
! * Initialize the handshaker and socket streams.
! *
! * Called by connect, the layered constructor, and SSLServerSocket.
! */
! void doneConnect() throws IOException {
! /*
! * Save the input and output streams. May be done only after
! * java.net actually connects using the socket "self", else
! * we get some pretty bizarre failure modes.
! */
! sockInput = super.getInputStream();
! sockOutput = super.getOutputStream();
!
! inputRecord.setDeliverStream(sockOutput);
! outputRecord.setDeliverStream(sockOutput);
!
! /*
! * Move to handshaking state, with pending session initialized
! * to defaults and the appropriate kind of handshaker set up.
! */
! initHandshaker();
}
! private synchronized int getConnectionState() {
! return connectionState;
}
! private synchronized void setConnectionState(int state) {
! connectionState = state;
}
! AccessControlContext getAcc() {
! return acc;
}
! //
! // READING AND WRITING RECORDS
! //
!
! /*
! * Application data record output.
! *
! * Application data can't be sent until the first handshake establishes
! * a session.
! */
! void writeRecord(byte[] source, int offset, int length) throws IOException {
! /*
! * The loop is in case of HANDSHAKE --> ERROR transitions, etc
! */
! // Don't bother to check the emptiness of source applicatoin data
! // before the security connection established.
! for (boolean readyForApp = false; !readyForApp;) {
! /*
! * Not all states support passing application data. We
! * synchronize access to the connection state, so that
! * synchronous handshakes can complete cleanly.
! */
! switch (getConnectionState()) {
!
! /*
! * We've deferred the initial handshaking till just now,
! * when presumably a thread's decided it's OK to block for
! * longish periods of time for I/O purposes (as well as
! * configured the cipher suites it wants to use).
! */
! case cs_HANDSHAKE:
! performInitialHandshake();
! break;
!
! case cs_DATA:
! case cs_RENEGOTIATE:
! readyForApp = true;
! break;
! case cs_ERROR:
! fatal(Alerts.alert_close_notify,
! "error while writing to socket");
! break; // dummy
!
! case cs_SENT_CLOSE:
! case cs_CLOSED:
! case cs_APP_CLOSED:
! // we should never get here (check in AppOutputStream)
! // this is just a fallback
! if (closeReason != null) {
! throw closeReason;
! } else {
! throw new SocketException("Socket closed");
}
! /*
! * Else something's goofy in this state machine's use.
! */
! default:
! throw new SSLProtocolException(
! "State error, send app data");
}
}
! //
! // Don't bother to really write empty records. We went this
! // far to drive the handshake machinery, for correctness; not
! // writing empty records improves performance by cutting CPU
! // time and network resource usage. However, some protocol
! // implementations are fragile and don't like to see empty
! // records, so this also increases robustness.
! //
! if (length > 0) {
! IOException ioe = null;
! byte description = 0; // 0: never used, make the compiler happy
! writeLock.lock();
try {
! outputRecord.deliver(source, offset, length);
! } catch (SSLHandshakeException she) {
! // may be record sequence number overflow
! description = Alerts.alert_handshake_failure;
! ioe = she;
! } catch (IOException e) {
! description = Alerts.alert_unexpected_message;
! ioe = e;
! } finally {
! writeLock.unlock();
}
! // Be care of deadlock. Please don't place the call to fatal()
! // into the writeLock locked block.
! if (ioe != null) {
! fatal(description, ioe);
! }
}
! /*
! * Check the sequence number state
! *
! * Note that in order to maintain the connection I/O
! * 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.
! *
! * Don't bother to kickstart the renegotiation when the
! * local is asking for it.
! */
! if ((connectionState == cs_DATA) && 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");
}
! startHandshake();
! }
}
! /*
! * Alert record output.
! */
! void writeAlert(byte level, byte description) throws IOException {
!
! // If the record is a close notify alert, we need to honor
! // socket option SO_LINGER. Note that we will try to send
! // the close notify even if the SO_LINGER set to zero.
! if ((description == Alerts.alert_close_notify) && getSoLinger() >= 0) {
!
! // keep and clear the current thread interruption status.
! boolean interrupted = Thread.interrupted();
! try {
! if (writeLock.tryLock(getSoLinger(), TimeUnit.SECONDS)) {
! try {
! outputRecord.encodeAlert(level, description);
! } finally {
! writeLock.unlock();
! }
! } else {
! SSLException ssle = new SSLException(
! "SO_LINGER timeout," +
! " close_notify message cannot be sent.");
!
!
! // For layered, non-autoclose sockets, we are not
! // able to bring them into a usable state, so we
! // treat it as fatal error.
! if (isLayered() && !autoClose) {
! // Note that the alert description is
! // specified as -1, so no message will be send
! // to peer anymore.
! fatal((byte)(-1), ssle);
! } else if ((debug != null) && Debug.isOn("ssl")) {
! System.out.println(
! Thread.currentThread().getName() +
! ", received Exception: " + ssle);
! }
!
! // RFC2246 requires that the session becomes
! // unresumable if any connection is terminated
! // without proper close_notify messages with
! // level equal to warning.
! //
! // RFC4346 no longer requires that a session not be
! // resumed if failure to properly close a connection.
! //
! // We choose to make the session unresumable if
! // failed to send the close_notify message.
! //
! sess.invalidate();
! }
! } catch (InterruptedException ie) {
! // keep interrupted status
! interrupted = true;
}
! // restore the interrupted status
! if (interrupted) {
! Thread.currentThread().interrupt();
! }
! } else {
! writeLock.lock();
! try {
! outputRecord.encodeAlert(level, description);
! } finally {
! writeLock.unlock();
! }
}
! // Don't bother to check sequence number overlap here. If sequence
! // number is huge, there should be enough sequence number space to
! // request renegotiation in next application data read and write.
}
!
! int bytesInCompletePacket() throws IOException {
! if (getConnectionState() == cs_HANDSHAKE) {
! performInitialHandshake();
}
! synchronized (readLock) {
! int state = getConnectionState();
! if ((state == cs_CLOSED) ||
! (state == cs_ERROR) || (state == cs_APP_CLOSED)) {
! return -1;
}
try {
! return inputRecord.bytesInCompletePacket(sockInput);
! } catch (EOFException eofe) {
! boolean handshaking = (connectionState <= cs_HANDSHAKE);
! boolean rethrow = requireCloseNotify || handshaking;
! if ((debug != null) && Debug.isOn("ssl")) {
! System.out.println(Thread.currentThread().getName() +
! ", received EOFException: "
! + (rethrow ? "error" : "ignored"));
! }
!
! if (!rethrow) {
! // treat as if we had received a close_notify
! closeInternal(false);
! } else {
! SSLException e;
! if (handshaking) {
! e = new SSLHandshakeException(
! "Remote host terminated the handshake");
! } else {
! e = new SSLProtocolException(
! "Remote host terminated the handshake");
! }
! e.initCause(eofe);
! throw e;
! }
! }
!
! return -1;
! }
! }
!
! // the caller have synchronized readLock
! void expectingFinishFlight() {
! inputRecord.expectingFinishFlight();
! }
!
! /*
! * Read an application data record.
! *
! * Alerts and handshake messages are internally handled directly.
! */
! int readRecord(ByteBuffer buffer) throws IOException {
! if (getConnectionState() == cs_HANDSHAKE) {
! performInitialHandshake();
! }
!
! return readRecord(buffer, true);
! }
!
! /*
! * Read a record, no application data input required.
! *
! * Alerts and handshake messages are internally handled directly.
! */
! int readRecord(boolean needAppData) throws IOException {
! return readRecord(null, needAppData);
! }
!
! /*
! * Clear the pipeline of records from the peer, optionally returning
! * application data. Caller is responsible for knowing that it's
! * possible to do this kind of clearing, if they don't want app
! * data -- e.g. since it's the initial SSL handshake.
! *
! * Don't synchronize (this) during a blocking read() since it
! * protects data which is accessed on the write side as well.
! */
! private int readRecord(ByteBuffer buffer, boolean needAppData)
! throws IOException {
! int state;
!
! // readLock protects reading and processing of an SSLInputRecord.
! // It keeps the reading from sockInput and processing of the record
! // atomic so that no two threads can be blocked on the
! // read from the same input stream at the same time.
! // This is required for example when a reader thread is
! // blocked on the read and another thread is trying to
! // close the socket. For a non-autoclose, layered socket,
! // the thread performing the close needs to read the close_notify.
! //
! // Use readLock instead of 'this' for locking because
! // 'this' also protects data accessed during writing.
! synchronized (readLock) {
! /*
! * Read and handle records ... return application data
! * ONLY if it's needed.
! */
! Plaintext plainText = null;
! while (((state = getConnectionState()) != cs_CLOSED) &&
! (state != cs_ERROR) && (state != cs_APP_CLOSED)) {
!
! /*
! * clean the buffer and check if it is too small, e.g. because
! * the AppInputStream did not have the chance to see the
! * current packet length but rather something like that of the
! * handshake before. In that case we return 0 at this point to
! * give the caller the chance to adjust the buffer.
! */
! if (buffer != null) {
! buffer.clear();
!
! if (buffer.remaining() <
! inputRecord.bytesInCompletePacket(sockInput)) {
! return 0;
! }
! }
!
! /*
! * Read a record ... maybe emitting an alert if we get a
! * comprehensible but unsupported "hello" message during
! * format checking (e.g. V2).
! */
! try {
! plainText = inputRecord.decode(sockInput, buffer);
! } catch (BadPaddingException bpe) {
! byte alertType = (state != cs_DATA) ?
! Alerts.alert_handshake_failure :
! Alerts.alert_bad_record_mac;
! fatal(alertType, bpe.getMessage(), bpe);
! } catch (SSLProtocolException spe) {
! try {
! fatal(Alerts.alert_unexpected_message, spe);
! } catch (IOException x) {
! // discard this exception, throw the original exception
! }
! throw spe;
! } catch (SSLHandshakeException she) {
! // may be record sequence number overflow
! fatal(Alerts.alert_handshake_failure, she);
! } catch (EOFException eof) {
! boolean handshaking = (connectionState <= cs_HANDSHAKE);
! boolean rethrow = requireCloseNotify || handshaking;
! if ((debug != null) && Debug.isOn("ssl")) {
! System.out.println(Thread.currentThread().getName() +
! ", received EOFException: "
! + (rethrow ? "error" : "ignored"));
! }
! if (rethrow) {
! SSLException e;
! if (handshaking) {
! e = new SSLHandshakeException(
! "Remote host terminated the handshake");
! } else {
! e = new SSLProtocolException(
! "Remote host terminated the connection");
! }
! e.initCause(eof);
! throw e;
! } else {
! // treat as if we had received a close_notify
! closeInternal(false);
! continue;
! }
! }
!
! // PlainText should never be null. Process input record.
! int volume = processInputRecord(plainText, needAppData);
!
! if (plainText.contentType == Record.ct_application_data) {
! return volume;
! }
!
! if (plainText.contentType == Record.ct_handshake) {
! if (!needAppData && connectionState == cs_DATA) {
! return volume;
! } // otherwise, need to read more for app data.
! }
!
! // continue to read more net data
! } // while
!
! //
! // couldn't read, due to some kind of error
! //
! return -1;
! } // readLock synchronization
! }
!
! /*
! * Process the plainText input record.
! */
! private synchronized int processInputRecord(
! Plaintext plainText, boolean needAppData) throws IOException {
!
! /*
! * Process the record.
! */
! int volume = 0; // no application data
! 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) {
! handshaker = null;
! inputRecord.setHandshakeHash(null);
! outputRecord.setHandshakeHash(null);
!
! // 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;
! handshaker = null;
! inputRecord.setHandshakeHash(null);
! outputRecord.setHandshakeHash(null);
! connectionState = cs_DATA;
!
! //
! // Tell folk about handshake completion, but do
! // it in a separate thread.
! //
! if (handshakeListeners != null) {
! HandshakeCompletedEvent event =
! new HandshakeCompletedEvent(this, sess);
!
! Thread thread = new Thread(
! null,
! new NotifyHandshake(
! handshakeListeners.entrySet(), event),
! "HandshakeCompletedNotify-Thread",
! 0,
! false);
! thread.start();
! }
! }
!
! break;
! case Record.ct_application_data:
! if (connectionState != cs_DATA
! && connectionState != cs_RENEGOTIATE
! && connectionState != cs_SENT_CLOSE) {
! throw new SSLProtocolException(
! "Data received in non-data state: " +
! connectionState);
! }
! if (expectingFinished) {
! throw new SSLProtocolException
! ("Expecting finished message, received data");
! }
! if (!needAppData) {
! throw new SSLException("Discarding app data");
! }
!
! volume = plainText.fragment.remaining();
! 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;
}
! /*
! * Check the sequence number state
! *
! * Note that in order to maintain the connection I/O
! * 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.
! *
! * Don't bother to kickstart the renegotiation when the
! * local is asking for it.
! */
! if ((connectionState == cs_DATA) && 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");
}
! startHandshake();
}
! return volume;
}
!
! //
! // HANDSHAKE RELATED CODE
! //
!
! /**
! * Return the AppInputStream. For use by Handshaker only.
! */
! AppInputStream getAppInputStream() {
! return input;
}
! /**
! * Return the AppOutputStream. For use by Handshaker only.
! */
! AppOutputStream getAppOutputStream() {
! return output;
}
! /**
! * 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 socket 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 socket is created, when
! * starting renegotiation, or when changing client/ server mode of the
! * socket.
! */
! 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);
! handshaker.setSNIMatchers(sniMatchers);
! handshaker.setUseCipherSuitesOrder(preferLocalCipherSuites);
! } else {
! handshaker = new ClientHandshaker(this, sslContext,
! enabledProtocols,
! protocolVersion, connectionState == cs_HANDSHAKE,
! secureRenegotiation, clientVerifyData, serverVerifyData);
! handshaker.setSNIServerNames(serverNames);
! }
! handshaker.setMaximumPacketSize(maximumPacketSize);
! handshaker.setEnabledCipherSuites(enabledCipherSuites);
! handshaker.setEnableSessionCreation(enableSessionCreation);
! handshaker.setApplicationProtocols(applicationProtocols);
! handshaker.setApplicationProtocolSelectorSSLSocket(
! applicationProtocolSelector);
}
! /**
! * Synchronously perform the initial handshake.
! *
! * If the handshake is already in progress, this method blocks until it
! * is completed. If the initial handshake has already been completed,
! * it returns immediately.
! */
! private void performInitialHandshake() throws IOException {
! // use handshakeLock and the state check to make sure only
! // one thread performs the handshake
! synchronized (handshakeLock) {
! if (getConnectionState() == cs_HANDSHAKE) {
! kickstartHandshake();
!
! /*
! * All initial handshaking goes through this operation
! * until we have a valid SSL connection.
! *
! * Handle handshake messages only, need no application data.
! */
! readRecord(false);
! }
! }
}
- /**
- * Starts an SSL handshake on this connection.
- */
@Override
! public void startHandshake() throws IOException {
! // start an ssl handshake that could be resumed from timeout exception
! startHandshake(true);
}
! /**
! * Starts an ssl handshake on this connection.
! *
! * @param resumable indicates the handshake process is resumable from a
! * certain exception. If <code>resumable</code>, the socket will
! * be reserved for exceptions like timeout; otherwise, the socket
! * will be closed, no further communications could be done.
! */
! private void startHandshake(boolean resumable) throws IOException {
! checkWrite();
try {
! if (getConnectionState() == cs_HANDSHAKE) {
! // do initial handshake
! performInitialHandshake();
! } else {
! // start renegotiation
! kickstartHandshake();
! }
! } catch (Exception e) {
! // shutdown and rethrow (wrapped) exception as appropriate
! handleException(e, resumable);
! }
! }
!
! /**
! * Kickstart the handshake if it is not already in progress.
! * This means:
! *
! * . if handshaking is already underway, do nothing and return
! *
! * . if the socket 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) {
!
! 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");
! }
! }
!
! // initialize the handshaker, move to cs_RENEGOTIATE
! initHandshaker();
! break;
!
! case cs_RENEGOTIATE:
! // handshaking already in progress, return
! return;
!
! /*
! * The only way to get a socket in the state is when
! * you have an unconnected socket.
! */
! case cs_START:
! throw new SocketException(
! "handshaking attempted on unconnected socket");
!
! default:
! throw new SocketException("connection is closed");
! }
!
! //
! // Kickstart handshake state machine if we need to ...
! //
! // Note that handshaker.kickstart() writes the message
! // to its HandshakeOutStream, which calls back into
! // SSLSocketImpl.writeRecord() to send it.
! //
! 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);
! }
!
! if (handshaker instanceof ClientHandshaker) {
! // send client hello
! handshaker.kickstart();
! } else {
! if (connectionState == cs_HANDSHAKE) {
! // initial handshake, no kickstart message to send
! } else {
! // we want to renegotiate, send hello request
! handshaker.kickstart();
! }
}
}
}
- //
- // CLOSURE RELATED CALLS
- //
-
- /**
- * Return whether the socket has been explicitly closed by the application.
- */
@Override
! public boolean isClosed() {
! return connectionState == cs_APP_CLOSED;
}
! /**
! * Return whether we have reached end-of-file.
! *
! * If the socket is not connected, has been shutdown because of an error
! * or has been closed, throw an Exception.
! */
! boolean checkEOF() throws IOException {
! switch (getConnectionState()) {
! case cs_START:
throw new SocketException("Socket is not connected");
-
- case cs_HANDSHAKE:
- case cs_DATA:
- case cs_RENEGOTIATE:
- case cs_SENT_CLOSE:
- return false;
-
- case cs_APP_CLOSED:
- throw new SocketException("Socket is closed");
-
- case cs_ERROR:
- case cs_CLOSED:
- default:
- // either closed because of error, or normal EOF
- if (closeReason == null) {
- return true;
}
- IOException e = new SSLException
- ("Connection has been shutdown: " + closeReason);
- e.initCause(closeReason);
- throw e;
! }
}
! /**
! * Check if we can write data to this socket. If not, throw an IOException.
! */
! void checkWrite() throws IOException {
! if (checkEOF() || (getConnectionState() == cs_SENT_CLOSE)) {
! // we are at EOF, write must throw Exception
! throw new SocketException("Connection closed by remote host");
}
}
! private void closeSocket() throws IOException {
! if ((debug != null) && Debug.isOn("ssl")) {
! System.out.println(Thread.currentThread().getName() +
! ", called closeSocket()");
! }
! super.close();
! }
! private void closeSocket(boolean selfInitiated) throws IOException {
! if ((debug != null) && Debug.isOn("ssl")) {
! System.out.println(Thread.currentThread().getName() +
! ", called closeSocket(" + selfInitiated + ")");
! }
! if (!isLayered() || autoClose) {
! super.close();
! } else if (selfInitiated) {
! // layered && non-autoclose
! // read close_notify alert to clear input stream
! waitForClose(false);
}
- }
-
- /*
- * Closing the connection is tricky ... we can't officially close the
- * connection until we know the other end is ready to go away too,
- * and if ever the connection gets aborted we must forget session
- * state (it becomes invalid).
- */
/**
! * Closes the SSL connection. SSL includes an application level
! * shutdown handshake; you should close SSL sockets explicitly
! * rather than leaving it for finalization, so that your remote
! * peer does not experience a protocol error.
*/
@Override
! public void close() throws IOException {
! if ((debug != null) && Debug.isOn("ssl")) {
! System.out.println(Thread.currentThread().getName() +
! ", called close()");
! }
! closeInternal(true); // caller is initiating close
!
! // Clearup the resources.
! try {
! synchronized (readLock) {
! inputRecord.close();
! }
!
! writeLock.lock();
! try {
! outputRecord.close();
! } finally {
! writeLock.unlock();
! }
! } catch (IOException ioe) {
! // ignore
}
! setConnectionState(cs_APP_CLOSED);
}
/**
! * Don't synchronize the whole method because waitForClose()
! * (which calls readRecord()) might be called.
! *
! * @param selfInitiated Indicates which party initiated the close.
! * If selfInitiated, this side is initiating a close; for layered and
! * non-autoclose socket, wait for close_notify response.
! * If !selfInitiated, peer sent close_notify; we reciprocate but
! * no need to wait for response.
! */
! private void closeInternal(boolean selfInitiated) throws IOException {
! if ((debug != null) && Debug.isOn("ssl")) {
! System.out.println(Thread.currentThread().getName() +
! ", called closeInternal(" + selfInitiated + ")");
! }
!
! int state = getConnectionState();
! boolean closeSocketCalled = false;
! Throwable cachedThrowable = null;
! try {
! switch (state) {
! case cs_START:
! // unconnected socket or handshaking has not been initialized
! closeSocket(selfInitiated);
! break;
!
! /*
! * If we're closing down due to error, we already sent (or else
! * received) the fatal alert ... no niceties, blow the connection
! * away as quickly as possible (even if we didn't allocate the
! * socket ourselves; it's unusable, regardless).
! */
! case cs_ERROR:
! closeSocket();
! break;
!
! /*
! * Sometimes close() gets called more than once.
! */
! case cs_CLOSED:
! case cs_APP_CLOSED:
! break;
!
! /*
! * Otherwise we indicate clean termination.
! */
! // case cs_HANDSHAKE:
! // case cs_DATA:
! // case cs_RENEGOTIATE:
! // case cs_SENT_CLOSE:
! default:
! synchronized (this) {
! if (((state = getConnectionState()) == cs_CLOSED) ||
! (state == cs_ERROR) || (state == cs_APP_CLOSED)) {
! return; // connection was closed while we waited
! }
! if (state != cs_SENT_CLOSE) {
! try {
! warning(Alerts.alert_close_notify);
! connectionState = cs_SENT_CLOSE;
! } catch (Throwable th) {
! // we need to ensure socket is closed out
! // if we encounter any errors.
! connectionState = cs_ERROR;
! // cache this for later use
! cachedThrowable = th;
! closeSocketCalled = true;
! closeSocket(selfInitiated);
! }
! }
! }
! // If state was cs_SENT_CLOSE before, we don't do the actual
! // closing since it is already in progress.
! if (state == cs_SENT_CLOSE) {
! if (debug != null && Debug.isOn("ssl")) {
! System.out.println(Thread.currentThread().getName() +
! ", close invoked again; state = " +
! getConnectionState());
! }
! if (selfInitiated == false) {
! // We were called because a close_notify message was
! // received. This may be due to another thread calling
! // read() or due to our call to waitForClose() below.
! // In either case, just return.
! return;
! }
! // Another thread explicitly called close(). We need to
! // wait for the closing to complete before returning.
! synchronized (this) {
! while (connectionState < cs_CLOSED) {
! try {
! this.wait();
! } catch (InterruptedException e) {
! // ignore
! }
! }
! }
! if ((debug != null) && Debug.isOn("ssl")) {
! System.out.println(Thread.currentThread().getName() +
! ", after primary close; state = " +
! getConnectionState());
! }
! return;
! }
!
! if (!closeSocketCalled) {
! closeSocketCalled = true;
! closeSocket(selfInitiated);
! }
!
! break;
! }
! } finally {
! synchronized (this) {
! // Upon exit from this method, the state is always >= cs_CLOSED
! connectionState = (connectionState == cs_APP_CLOSED)
! ? cs_APP_CLOSED : cs_CLOSED;
! // notify any threads waiting for the closing to finish
! this.notifyAll();
! }
!
! if (cachedThrowable != null) {
! /*
! * Rethrow the error to the calling method
! * The Throwable caught can only be an Error or RuntimeException
*/
! if (cachedThrowable instanceof Error) {
! throw (Error)cachedThrowable;
! } else if (cachedThrowable instanceof RuntimeException) {
! throw (RuntimeException)cachedThrowable;
! } // Otherwise, unlikely
! }
! }
! }
!
! /**
! * Reads a close_notify or a fatal alert from the input stream.
! * Keep reading records until we get a close_notify or until
! * the connection is otherwise closed. The close_notify or alert
! * might be read by another reader,
! * which will then process the close and set the connection state.
! */
! void waitForClose(boolean rethrow) throws IOException {
! if (debug != null && Debug.isOn("ssl")) {
! System.out.println(Thread.currentThread().getName() +
! ", waiting for close_notify or alert: state "
! + getConnectionState());
! }
!
! try {
! int state;
!
! while (((state = getConnectionState()) != cs_CLOSED) &&
! (state != cs_ERROR) && (state != cs_APP_CLOSED)) {
!
! // Ask for app data and then throw it away
! try {
! readRecord(true);
! } catch (SocketTimeoutException e) {
! if ((debug != null) && Debug.isOn("ssl")) {
! System.out.println(
! Thread.currentThread().getName() +
! ", received Exception: " + e);
! }
! fatal((byte)(-1), "Did not receive close_notify from peer", e);
! }
! }
! } catch (IOException e) {
! if (debug != null && Debug.isOn("ssl")) {
! System.out.println(Thread.currentThread().getName() +
! ", Exception while waiting for close " +e);
! }
! if (rethrow) {
! throw e; // pass exception up
! }
! }
}
! //
! // EXCEPTION AND ALERT HANDLING
! //
!
! /**
! * Handle an exception. This method is called by top level exception
! * handlers (in read(), write()) to make sure we always shutdown the
! * connection correctly and do not pass runtime exception to the
! * application.
! */
! void handleException(Exception e) throws IOException {
! handleException(e, true);
}
/**
! * Handle an exception. This method is called by top level exception
! * handlers (in read(), write(), startHandshake()) to make sure we
! * always shutdown the connection correctly and do not pass runtime
! * exception to the application.
*
! * This method never returns normally, it always throws an IOException.
*
! * We first check if the socket has already been shutdown because of an
! * error. If so, we just rethrow the exception. If the socket has not
! * been shutdown, we sent a fatal alert and remember the exception.
! *
! * @param e the Exception
! * @param resumable indicates the caller process is resumable from the
! * exception. If <code>resumable</code>, the socket will be
! * reserved for exceptions like timeout; otherwise, the socket
! * will be closed, no further communications could be done.
! */
! private synchronized void handleException(Exception e, boolean resumable)
! throws IOException {
! if ((debug != null) && Debug.isOn("ssl")) {
! System.out.println(Thread.currentThread().getName() +
! ", handling exception: " + e.toString());
! }
!
! // don't close the Socket in case of timeouts or interrupts if
! // the process is resumable.
! if (e instanceof InterruptedIOException && resumable) {
! throw (IOException)e;
! }
!
! // if we've already shutdown because of an error,
! // there is nothing to do except rethrow the exception
! if (closeReason != null) {
! if (e instanceof IOException) { // includes SSLException
! throw (IOException)e;
! } else {
! // this is odd, not an IOException.
! // normally, this should not happen
! // if closeReason has been already been set
! throw Alerts.getSSLException(Alerts.alert_internal_error, e,
! "Unexpected exception");
! }
! }
!
! // need to perform error shutdown
! boolean isSSLException = (e instanceof SSLException);
! if ((!isSSLException) && (e instanceof IOException)) {
! // IOException from the socket
! // this means the TCP connection is already dead
! // we call fatal just to set the error status
! try {
! fatal(Alerts.alert_unexpected_message, e);
! } catch (IOException ee) {
! // ignore (IOException wrapped in SSLException)
! }
! // rethrow original IOException
! throw (IOException)e;
! }
!
! // must be SSLException or RuntimeException
! byte alertType;
! if (isSSLException) {
! if (e instanceof SSLHandshakeException) {
! alertType = Alerts.alert_handshake_failure;
! } else {
! alertType = Alerts.alert_unexpected_message;
! }
! } else {
! alertType = Alerts.alert_internal_error;
! }
! fatal(alertType, e);
! }
!
! /*
! * Send a warning alert.
*/
! void warning(byte description) {
! sendAlert(Alerts.alert_warning, description);
! }
!
! synchronized void fatal(byte description, String diagnostic)
! throws IOException {
! fatal(description, diagnostic, null);
! }
!
! synchronized void fatal(byte description, Throwable cause)
throws IOException {
! fatal(description, null, cause);
! }
!
! /*
! * Send a fatal alert, and throw an exception so that callers will
! * need to stand on their heads to accidentally continue processing.
! */
! synchronized void fatal(byte description, String diagnostic,
! Throwable cause) throws IOException {
!
! // Be care of deadlock. Please don't synchronize readLock.
! try {
! inputRecord.close();
! } catch (IOException ioe) {
! // ignore
! }
!
! sess.invalidate();
! if (handshakeSession != null) {
! handshakeSession.invalidate();
! }
!
! int oldState = connectionState;
! if (connectionState < cs_ERROR) {
! connectionState = cs_ERROR;
! }
!
! /*
! * Has there been an error received yet? If not, remember it.
! * By RFC 2246, we don't bother waiting for a response.
! * Fatal errors require immediate shutdown.
! */
! if (closeReason == null) {
! /*
! * Try to clear the kernel buffer to avoid TCP connection resets.
! */
! if (oldState == cs_HANDSHAKE) {
! sockInput.skip(sockInput.available());
! }
!
! // If the description equals -1, the alert won't be sent to peer.
! if (description != -1) {
! sendAlert(Alerts.alert_fatal, description);
! }
! if (cause instanceof SSLException) { // only true if != null
! closeReason = (SSLException)cause;
! } else {
! closeReason =
! Alerts.getSSLException(description, cause, diagnostic);
! }
! }
!
! /*
! * Clean up our side.
! */
! closeSocket();
!
! // Be care of deadlock. Please don't synchronize writeLock.
! try {
! outputRecord.close();
! } catch (IOException ioe) {
! // ignore
! }
!
! throw closeReason;
! }
!
!
! /*
! * 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 (description == -1) { // check for short message
! fatal(Alerts.alert_illegal_parameter, "Short alert message");
! }
!
! 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("<level " + (0x0ff & level) + ">, ");
! }
! System.out.println(Alerts.alertDescription(description));
! }
! }
!
! if (level == Alerts.alert_warning) {
! if (description == Alerts.alert_close_notify) {
! if (connectionState == cs_HANDSHAKE) {
! fatal(Alerts.alert_unexpected_message,
! "Received close_notify during handshake");
! } else {
! closeInternal(false); // 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);
! if (closeReason == null) {
! closeReason = Alerts.getSSLException(description, reason);
! }
! fatal(Alerts.alert_unexpected_message, reason);
! }
}
!
! /*
! * Emit alerts. Caller must have synchronized with "this".
! */
! private void sendAlert(byte level, byte description) {
! // the connectionState cannot be cs_START
! if (connectionState >= cs_SENT_CLOSE) {
! 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;
}
! boolean useDebug = debug != null && Debug.isOn("ssl");
! if (useDebug) {
! synchronized (System.out) {
! System.out.print(Thread.currentThread().getName());
! System.out.print(", SEND " + 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("<level = " + (0x0ff & level) + ">, ");
! }
! System.out.println("description = "
! + Alerts.alertDescription(description));
! }
! }
! try {
! writeAlert(level, description);
! } catch (IOException e) {
! if (useDebug) {
! System.out.println(Thread.currentThread().getName() +
! ", Exception sending alert: " + e);
}
- }
- }
-
- //
- // VARIOUS OTHER METHODS
- //
! // used by Handshaker
! 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);
! }
! outputRecord.changeWriteCiphers(writeAuthenticator, writeCipher);
! }
!
! /*
! * 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);
! }
!
! //
! // ONLY used by ClientHandshaker for the server hostname during handshaking
! //
! synchronized String getHost() {
! // Note that the host may be null or empty for localhost.
! if (host == null || host.length() == 0) {
! useImplicitHost(true);
! }
!
! return host;
! }
!
! /*
! * Try to set and use the implicit specified hostname
! */
! private synchronized void useImplicitHost(boolean noSniUpdate) {
!
! // Note: If the local name service is not trustworthy, reverse
! // host name resolution should not be performed for endpoint
! // identification. Use the application original specified
! // hostname or IP address instead.
!
! // Get the original hostname via jdk.internal.misc.SharedSecrets
! InetAddress inetAddress = getInetAddress();
! if (inetAddress == null) { // not connected
! return;
! }
!
! JavaNetInetAddressAccess jna =
! SharedSecrets.getJavaNetInetAddressAccess();
! String originalHostname = jna.getOriginalHostName(inetAddress);
! if ((originalHostname != null) &&
! (originalHostname.length() != 0)) {
! host = originalHostname;
! if (!noSniUpdate && serverNames.isEmpty() && !noSniExtension) {
! serverNames =
! Utilities.addToSNIServerNameList(serverNames, host);
!
! if (!roleIsServer &&
! (handshaker != null) && !handshaker.activated()) {
! handshaker.setSNIServerNames(serverNames);
! }
}
! return;
}
! // No explicitly specified hostname, no server name indication.
! if (!trustNameService) {
! // The local name service is not trustworthy, use IP address.
! host = inetAddress.getHostAddress();
! } else {
! // Use the underlying reverse host name resolution service.
! host = getInetAddress().getHostName();
}
- }
-
- // ONLY used by HttpsClient to setup the URI specified hostname
- //
- // Please NOTE that this method MUST be called before calling to
- // SSLSocket.setSSLParameters(). Otherwise, the {@code host} parameter
- // may override SNIHostName in the customized server name indication.
- public synchronized void setHost(String host) {
- this.host = host;
- this.serverNames =
- Utilities.addToSNIServerNameList(this.serverNames, this.host);
! if (!roleIsServer && (handshaker != null) && !handshaker.activated()) {
! handshaker.setSNIServerNames(serverNames);
}
}
! /**
! * Gets an input stream to read from the peer on the other side.
! * Data read from this stream was always integrity protected in
! * transit, and will usually have been confidentiality protected.
! */
! @Override
! public synchronized InputStream getInputStream() throws IOException {
! if (isClosed()) {
! throw new SocketException("Socket is closed");
! }
! /*
! * Can't call isConnected() here, because the Handshakers
! * do some initialization before we actually connect.
! */
! if (connectionState == cs_START) {
! throw new SocketException("Socket is not connected");
}
-
- return input;
}
/**
! * Gets an output stream to write to the peer on the other side.
! * Data written on this stream is always integrity protected, and
! * will usually be confidentiality protected.
! */
! @Override
! public synchronized OutputStream getOutputStream() throws IOException {
! if (isClosed()) {
! throw new SocketException("Socket is closed");
}
!
! /*
! * Can't call isConnected() here, because the Handshakers
! * do some initialization before we actually connect.
! */
! if (connectionState == cs_START) {
! throw new SocketException("Socket is not connected");
}
! return output;
}
- /**
- * Returns the SSL Session in use by this connection. These can
- * be long lived, and frequently correspond to an entire login session
- * for some user.
- */
@Override
! public SSLSession getSession() {
! /*
! * Force a synchronous handshake, if appropriate.
! */
! if (getConnectionState() == cs_HANDSHAKE) {
! try {
! // start handshaking, if failed, the connection will be closed.
! startHandshake(false);
! } catch (IOException e) {
! // handshake failed. log and return a nullSession
! if (debug != null && Debug.isOn("handshake")) {
! System.out.println(Thread.currentThread().getName() +
! ", IOException in getSession(): " + e);
! }
! }
}
! synchronized (this) {
! return sess;
}
}
@Override
! public synchronized SSLSession getHandshakeSession() {
! return handshakeSession;
}
! synchronized void setHandshakeSession(SSLSessionImpl session) {
! // update the fragment size, which may be negotiated during handshaking
! inputRecord.changeFragmentSize(session.getNegotiatedMaxFragSize());
! outputRecord.changeFragmentSize(session.getNegotiatedMaxFragSize());
! handshakeSession = session;
}
! /**
! * 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);
! }
}
- /**
- * Returns true if new connections may cause creation of new SSL
- * sessions.
- */
@Override
! public synchronized boolean getEnableSessionCreation() {
! return enableSessionCreation;
}
- /**
- * Sets the flag controlling whether a server mode socket
- * *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);
}
}
@Override
! public synchronized boolean getNeedClientAuth() {
! return (doClientAuth == ClientAuthType.CLIENT_AUTH_REQUIRED);
}
- /**
- * Sets the flag controlling whether a server mode socket
- * *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);
}
}
@Override
! public synchronized boolean getWantClientAuth() {
! return (doClientAuth == ClientAuthType.CLIENT_AUTH_REQUESTED);
}
-
- /**
- * Sets the flag controlling whether the socket 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;
! 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);
}
! throw new IllegalArgumentException(
! "Cannot change mode after SSL traffic has started");
}
}
! @Override
! public synchronized boolean getUseClientMode() {
! return !roleIsServer;
}
!
! /**
! * 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();
}
! /**
! * 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);
! }
}
! /**
! * Returns the names of the SSL cipher suites which are currently enabled
! * for use on this connection. When an SSL socket is first created,
! * all enabled cipher suites <em>(a)</em> protect data confidentiality,
! * by traffic encryption, and <em>(b)</em> 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();
}
!
!
! /**
! * 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();
}
-
- /**
- * 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);
}
}
! @Override
! public synchronized String[] getEnabledProtocols() {
! return enabledProtocols.toStringArray();
}
! /**
! * Assigns the socket timeout.
! * @see java.net.Socket#setSoTimeout
! */
! @Override
! public void setSoTimeout(int timeout) throws SocketException {
! if ((debug != null) && Debug.isOn("ssl")) {
! System.out.println(Thread.currentThread().getName() +
! ", setSoTimeout(" + timeout + ") called");
}
! super.setSoTimeout(timeout);
}
/**
! * Registers an event listener to receive notifications that an
! * SSL handshake has completed on this connection.
*/
! @Override
! public synchronized void addHandshakeCompletedListener(
! HandshakeCompletedListener listener) {
! if (listener == null) {
! throw new IllegalArgumentException("listener is null");
}
! if (handshakeListeners == null) {
! handshakeListeners = new
! HashMap<HandshakeCompletedListener, AccessControlContext>(4);
}
- handshakeListeners.put(listener, AccessController.getContext());
}
/**
! * Removes a previously registered handshake completion listener.
*/
! @Override
! public synchronized void removeHandshakeCompletedListener(
! HandshakeCompletedListener listener) {
! if (handshakeListeners == null) {
! throw new IllegalArgumentException("no listeners");
}
! if (handshakeListeners.remove(listener) == null) {
! throw new IllegalArgumentException("listener not registered");
}
- if (handshakeListeners.isEmpty()) {
- handshakeListeners = null;
}
}
/**
! * Returns the SSLParameters in effect for this SSLSocket.
*/
! @Override
! public synchronized SSLParameters getSSLParameters() {
! SSLParameters params = super.getSSLParameters();
!
! // the super implementation does not handle the following parameters
! params.setEndpointIdentificationAlgorithm(identificationProtocol);
! params.setAlgorithmConstraints(algorithmConstraints);
!
! if (sniMatchers.isEmpty() && !noSniMatcher) {
! // 'null' indicates none has been set
! params.setSNIMatchers(null);
! } else {
! params.setSNIMatchers(sniMatchers);
! }
!
! if (serverNames.isEmpty() && !noSniExtension) {
! // 'null' indicates none has been set
! params.setServerNames(null);
} else {
! params.setServerNames(serverNames);
}
! params.setUseCipherSuitesOrder(preferLocalCipherSuites);
! params.setMaximumPacketSize(maximumPacketSize);
! params.setApplicationProtocols(applicationProtocols);
! // DTLS handshake retransmissions parameter does not apply here.
! return params;
}
! /**
! * Applies SSLParameters to this socket.
! */
! @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();
! maximumPacketSize = params.getMaximumPacketSize();
! // DTLS handshake retransmissions parameter does not apply here.
! if (maximumPacketSize != 0) {
! outputRecord.changePacketSize(maximumPacketSize);
! } else {
! // use the implicit maximum packet size.
! maximumPacketSize = outputRecord.getMaxPacketSize();
}
! List<SNIServerName> sniNames = params.getServerNames();
! if (sniNames != null) {
! noSniExtension = sniNames.isEmpty();
! serverNames = sniNames;
}
! Collection<SNIMatcher> matchers = params.getSNIMatchers();
! if (matchers != null) {
! noSniMatcher = matchers.isEmpty();
! 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);
}
}
}
! @Override
! public synchronized String getApplicationProtocol() {
! return applicationProtocol;
}
! @Override
! public synchronized String getHandshakeApplicationProtocol() {
! if ((handshaker != null) && handshaker.started()) {
! return handshaker.getHandshakeApplicationProtocol();
}
! return null;
}
! @Override
! public synchronized void setHandshakeApplicationProtocolSelector(
! BiFunction<SSLSocket, List<String>, String> selector) {
! applicationProtocolSelector = selector;
! if ((handshaker != null) && !handshaker.activated()) {
! handshaker.setApplicationProtocolSelectorSSLSocket(selector);
}
}
@Override
! public synchronized BiFunction<SSLSocket, List<String>, String>
! getHandshakeApplicationProtocolSelector() {
! return this.applicationProtocolSelector;
}
! //
! // We allocate a separate thread to deliver handshake completion
! // events. This ensures that the notifications don't block the
! // protocol state machine.
! //
! private static class NotifyHandshake implements Runnable {
!
! private Set<Map.Entry<HandshakeCompletedListener,AccessControlContext>>
! targets; // who gets notified
! private HandshakeCompletedEvent event; // the notification
!
! NotifyHandshake(
! Set<Map.Entry<HandshakeCompletedListener,AccessControlContext>>
! entrySet, HandshakeCompletedEvent e) {
!
! targets = new HashSet<>(entrySet); // clone the entry set
! event = e;
}
@Override
! public void run() {
! // Don't need to synchronize, as it only runs in one thread.
! for (Map.Entry<HandshakeCompletedListener,AccessControlContext>
! entry : targets) {
- final HandshakeCompletedListener l = entry.getKey();
- AccessControlContext acc = entry.getValue();
- AccessController.doPrivileged(new PrivilegedAction<Void>() {
@Override
! public Void run() {
! l.handshakeCompleted(event);
! return null;
}
! }, acc);
}
}
}
-
- /**
- * Returns a printable representation of this end of the connection.
- */
- @Override
- public String toString() {
- StringBuilder retval = new StringBuilder(80);
-
- retval.append(Integer.toHexString(hashCode()));
- retval.append("[");
- retval.append(sess.getCipherSuite());
- retval.append(": ");
-
- retval.append(super.toString());
- retval.append("]");
-
- return retval.toString();
}
}
--- 281,1122 ----
throw new SocketException(
"Cannot handle non-Inet socket addresses.");
}
super.connect(endpoint, timeout);
doneConnect();
}
! @Override
! public String[] getSupportedCipherSuites() {
! return CipherSuite.namesOf(sslContext.getSupportedCipherSuites());
}
! @Override
! public synchronized String[] getEnabledCipherSuites() {
! return CipherSuite.namesOf(conContext.sslConfig.enabledCipherSuites);
}
! @Override
! public synchronized void setEnabledCipherSuites(String[] suites) {
! if (suites == null) {
! throw new IllegalArgumentException("CipherSuites cannot be null");
}
! conContext.sslConfig.enabledCipherSuites =
! CipherSuite.validValuesOf(suites);
}
! @Override
! public String[] getSupportedProtocols() {
! return ProtocolVersion.toStringArray(
! sslContext.getSuportedProtocolVersions());
! }
! @Override
! public synchronized String[] getEnabledProtocols() {
! return ProtocolVersion.toStringArray(
! conContext.sslConfig.enabledProtocols);
}
! @Override
! 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 SSLSession getSession() {
try {
! // start handshaking, if failed, the connection will be closed.
! ensureNegotiated();
! } catch (IOException ioe) {
! if (SSLLogger.isOn && SSLLogger.isOn("handshake")) {
! SSLLogger.severe("handshake failed", ioe);
}
! return SSLSessionImpl.nullSession;
}
! return conContext.conSession;
}
! @Override
! public synchronized SSLSession getHandshakeSession() {
! if (conContext.handshakeContext != null) {
! return conContext.handshakeContext.handshakeSession;
}
! return null;
}
! @Override
! public synchronized void addHandshakeCompletedListener(
! HandshakeCompletedListener listener) {
! if (listener == null) {
! throw new IllegalArgumentException("listener is null");
}
! conContext.sslConfig.addHandshakeCompletedListener(listener);
}
! @Override
! public synchronized void removeHandshakeCompletedListener(
! HandshakeCompletedListener listener) {
! if (listener == null) {
! throw new IllegalArgumentException("listener is null");
}
! conContext.sslConfig.removeHandshakeCompletedListener(listener);
}
+ @Override
+ public synchronized void startHandshake() throws IOException {
+ checkWrite();
try {
! conContext.kickstart();
! // All initial handshaking goes through this operation until we
! // have a valid SSL connection.
//
! // Handle handshake messages only, need no application data.
! if (!conContext.isNegotiated) {
! readRecord();
}
! } catch (IOException ioe) {
! conContext.fatal(Alert.HANDSHAKE_FAILURE,
! "Couldn't kickstart handshaking", ioe);
! } catch (Exception oe) { // including RuntimeException
! handleException(oe);
}
}
! @Override
! public synchronized void setUseClientMode(boolean mode) {
! conContext.setUseClientMode(mode);
}
! @Override
! public synchronized boolean getUseClientMode() {
! return conContext.sslConfig.isClientMode;
}
! @Override
! public synchronized void setNeedClientAuth(boolean need) {
! conContext.sslConfig.clientAuthType =
! (need ? ClientAuthType.CLIENT_AUTH_REQUIRED :
! ClientAuthType.CLIENT_AUTH_NONE);
}
! @Override
! public synchronized boolean getNeedClientAuth() {
! return (conContext.sslConfig.clientAuthType ==
! ClientAuthType.CLIENT_AUTH_REQUIRED);
}
! @Override
! public synchronized void setWantClientAuth(boolean want) {
! conContext.sslConfig.clientAuthType =
! (want ? ClientAuthType.CLIENT_AUTH_REQUESTED :
! ClientAuthType.CLIENT_AUTH_NONE);
}
! @Override
! public synchronized boolean getWantClientAuth() {
! return (conContext.sslConfig.clientAuthType ==
! ClientAuthType.CLIENT_AUTH_REQUESTED);
}
! @Override
! public synchronized void setEnableSessionCreation(boolean flag) {
! conContext.sslConfig.enableSessionCreation = flag;
}
! @Override
! public synchronized boolean getEnableSessionCreation() {
! return conContext.sslConfig.enableSessionCreation;
}
@Override
! public synchronized boolean isClosed() {
! return tlsIsClosed && conContext.isClosed();
}
! @Override
! public synchronized void close() throws IOException {
try {
! conContext.close();
! } catch (IOException ioe) {
! // ignore the exception
! if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
! SSLLogger.warning("connection context closure failed", ioe);
}
+ } finally {
+ tlsIsClosed = true;
}
}
@Override
! public synchronized InputStream getInputStream() throws IOException {
! if (isClosed() || conContext.isInboundDone()) {
! throw new SocketException("Socket or inbound is closed");
}
! if (!isConnected) {
throw new SocketException("Socket is not connected");
}
! return appInput;
}
! private synchronized void ensureNegotiated() throws IOException {
! if (conContext.isNegotiated || conContext.isClosed()) {
! return;
}
+
+ startHandshake();
}
! private class AppInputStream extends InputStream {
! // One element array used to implement the single byte read() method
! private final byte[] oneByte = new byte[1];
! // the temporary buffer used to read network
! private ByteBuffer buffer;
! // Is application data available in the stream?
! private boolean appDataIsAvailable;
! AppInputStream() {
! this.appDataIsAvailable = false;
! this.buffer = ByteBuffer.allocate(4096);
}
/**
! * Return the minimum number of bytes that can be read
! * without blocking.
*/
@Override
! public int available() throws IOException {
! // Currently not synchronized.
! if ((!appDataIsAvailable) || checkEOF()) {
! return 0;
}
! return buffer.remaining();
}
/**
! * Read a single byte, returning -1 on non-fault EOF status.
*/
! @Override
! public synchronized int read() throws IOException {
! int n = read(oneByte, 0, 1);
! if (n <= 0) { // EOF
! return -1;
}
! return oneByte[0] & 0xFF;
}
/**
! * Reads up to {@code len} bytes of data from the input stream
! * into an array of bytes.
*
! * An attempt is made to read as many as {@code len} bytes, but a
! * smaller number may be read. The number of bytes actually read
! * is returned as an integer.
*
! * If the layer above needs more data, it asks for more, so we
! * are responsible only for blocking to fill at most one buffer,
! * and returning "-1" on non-fault EOF status.
*/
! @Override
! public synchronized int read(byte[] b, int off, int len)
throws IOException {
! if (b == null) {
! throw new NullPointerException("the target buffer is null");
! } else if (off < 0 || len < 0 || len > b.length - off) {
! throw new IndexOutOfBoundsException(
! "buffer length: " + b.length + ", offset; " + off +
! ", bytes to read:" + len);
! } else if (len == 0) {
! return 0;
}
! if (checkEOF()) {
! return -1;
}
! // start handshaking if the connection has not been negotiated.
! if (!conContext.isNegotiated && !conContext.isClosed()) {
! ensureNegotiated();
}
! // Read the available bytes at first.
! int remains = available();
! if (remains > 0) {
! int howmany = Math.min(remains, len);
! buffer.get(b, off, howmany);
! return howmany;
}
! appDataIsAvailable = false;
! int volume = 0;
try {
! while (volume == 0) {
! // Clear the buffer for a new record reading.
! buffer.clear();
! // grow the buffer if needed
! int inLen = conContext.inputRecord.bytesInCompletePacket();
! if (inLen < 0) { // EOF
! // treat like receiving a close_notify warning message.
! conContext.isInputCloseNotified = true;
! conContext.closeInbound();
! return -1;
}
! // Is this packet bigger than SSL/TLS normally allows?
! if (inLen > SSLRecord.maxLargeRecordSize) {
! throw new SSLProtocolException(
! "Illegal packet size: " + inLen);
}
! if (inLen > buffer.remaining()) {
! buffer = ByteBuffer.allocate(inLen);
}
! volume = readRecord(buffer);
! buffer.flip();
! if (volume < 0) { // EOF
! // treat like receiving a close_notify warning message.
! conContext.isInputCloseNotified = true;
! conContext.closeInbound();
! return -1;
! } else if (volume > 0) {
! appDataIsAvailable = true;
! break;
}
}
! // file the destination buffer
! int howmany = Math.min(len, volume);
! buffer.get(b, off, howmany);
! return howmany;
! } catch (Exception e) { // including RuntimeException
! // shutdown and rethrow (wrapped) exception as appropriate
! handleException(e);
! // dummy for compiler
! return -1;
}
}
/**
! * Skip n bytes.
! *
! * This implementation is somewhat less efficient than possible, but
! * not badly so (redundant copy). We reuse the read() code to keep
! * things simpler. Note that SKIP_ARRAY is static and may garbled by
! * concurrent use, but we are not interested in the data anyway.
! */
! @Override
! public synchronized long skip(long n) throws IOException {
! // dummy array used to implement skip()
! byte[] skipArray = new byte[256];
!
! long skipped = 0;
! while (n > 0) {
! int len = (int)Math.min(n, skipArray.length);
! int r = read(skipArray, 0, len);
! if (r <= 0) {
! break;
}
! n -= r;
! skipped += r;
}
! return skipped;
}
@Override
! public void close() throws IOException {
! if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
! SSLLogger.finest("Closing input stream");
}
!
! conContext.closeInbound();
}
}
@Override
! public synchronized OutputStream getOutputStream() throws IOException {
! if (isClosed() || conContext.isOutboundDone()) {
! throw new SocketException("Socket or outbound is closed");
}
! if (!isConnected) {
! throw new SocketException("Socket is not connected");
! }
! return appOutput;
}
! private class AppOutputStream extends OutputStream {
! // One element array used to implement the write(byte) method
! private final byte[] oneByte = new byte[1];
! @Override
! public void write(int i) throws IOException {
! oneByte[0] = (byte)i;
! write(oneByte, 0, 1);
}
@Override
! public synchronized void write(byte[] b,
! int off, int len) throws IOException {
! if (b == null) {
! throw new NullPointerException("the source buffer is null");
! } else if (off < 0 || len < 0 || len > b.length - off) {
! throw new IndexOutOfBoundsException(
! "buffer length: " + b.length + ", offset; " + off +
! ", bytes to read:" + len);
! } else if (len == 0) {
! return;
}
+ // start handshaking if the connection has not been negotiated.
+ if (!conContext.isNegotiated && !conContext.isClosed()) {
+ ensureNegotiated();
+ }
+
+ // check if the Socket is invalid (error or closed)
+ checkWrite();
+
+ // Delegate the writing to the underlying socket.
+ try {
+ writeRecord(b, off, len);
+ checkWrite();
+ } catch (IOException ioe) {
+ // shutdown and rethrow (wrapped) exception as appropriate
+ handleException(ioe);
+ }
+ }
@Override
! public void close() throws IOException {
! if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
! SSLLogger.finest("Closing output stream");
! }
! conContext.closeOutbound();
}
}
@Override
! public synchronized SSLParameters getSSLParameters() {
! return conContext.sslConfig.getSSLParameters();
}
@Override
! public synchronized void setSSLParameters(SSLParameters params) {
! conContext.sslConfig.setSSLParameters(params);
! if (conContext.sslConfig.maximumPacketSize != 0) {
! conContext.outputRecord.changePacketSize(
! conContext.sslConfig.maximumPacketSize);
}
}
@Override
! public synchronized String getApplicationProtocol() {
! return conContext.applicationProtocol;
}
@Override
! public synchronized String getHandshakeApplicationProtocol() {
! if (conContext.handshakeContext != null) {
! return conContext.handshakeContext.applicationProtocol;
}
! return null;
}
+
+ @Override
+ public synchronized void setHandshakeApplicationProtocolSelector(
+ BiFunction<SSLSocket, List<String>, String> selector) {
+ conContext.sslConfig.socketAPSelector = selector;
}
! @Override
! public synchronized BiFunction<SSLSocket, List<String>, String>
! getHandshakeApplicationProtocolSelector() {
! return conContext.sslConfig.socketAPSelector;
! }
! private synchronized void writeRecord(byte[] source,
! int offset, int length) throws IOException {
! if (conContext.isOutboundDone()) {
! throw new SocketException("Socket or outbound closed");
}
! //
! // Don't bother to really write empty records. We went this
! // far to drive the handshake machinery, for correctness; not
! // writing empty records improves performance by cutting CPU
! // time and network resource usage. However, some protocol
! // implementations are fragile and don't like to see empty
! // records, so this also increases robustness.
! //
! if (length > 0) {
! try {
! conContext.outputRecord.deliver(source, offset, length);
! } catch (SSLHandshakeException she) {
! // may be record sequence number overflow
! conContext.fatal(Alert.HANDSHAKE_FAILURE, she);
! } catch (IOException e) {
! conContext.fatal(Alert.UNEXPECTED_MESSAGE, e);
}
}
! // Is the sequence number is nearly overflow?
! if (conContext.outputRecord.seqNumIsHuge()) {
! tryKeyUpdate();
! }
}
! private synchronized int readRecord() throws IOException {
! while (!conContext.isInboundDone()) {
! try {
! Plaintext plainText = decode(null);
! if ((plainText.contentType == ContentType.HANDSHAKE.id) &&
! conContext.isNegotiated) {
! return 0;
}
! } catch (SSLException ssle) {
! throw ssle;
! } catch (IOException ioe) {
! throw new SSLException("readRecord", ioe);
}
}
! return -1;
}
! private synchronized int readRecord(ByteBuffer buffer) throws IOException {
! while (!conContext.isInboundDone()) {
! /*
! * clean the buffer and check if it is too small, e.g. because
! * the AppInputStream did not have the chance to see the
! * current packet length but rather something like that of the
! * handshake before. In that case we return 0 at this point to
! * give the caller the chance to adjust the buffer.
*/
! buffer.clear();
! int inLen = conContext.inputRecord.bytesInCompletePacket();
! if (inLen < 0) { // EOF
! return -1;
}
! if (buffer.remaining() < inLen) {
! return 0;
}
! try {
! Plaintext plainText = decode(buffer);
! if (plainText.contentType == ContentType.APPLICATION_DATA.id) {
! return buffer.position();
}
! } catch (SSLException ssle) {
! throw ssle;
! } catch (IOException ioe) {
! throw new SSLException("readRecord", ioe);
}
}
+
+ //
+ // couldn't read, due to some kind of error
+ //
+ return -1;
}
! private Plaintext decode(ByteBuffer destination) throws IOException {
! Plaintext plainText;
! if (destination == null) {
! plainText = SSLTransport.decode(conContext,
! null, 0, 0, null, 0, 0);
! } else {
! plainText = SSLTransport.decode(conContext,
! null, 0, 0, new ByteBuffer[]{destination}, 0, 1);
}
! // Is the sequence number is nearly overflow?
! if (plainText != Plaintext.PLAINTEXT_NULL &&
! conContext.inputRecord.seqNumIsHuge()) {
! tryKeyUpdate();
}
! return plainText;
}
/**
! * Try renegotiation or key update for sequence number wrap.
! *
! * 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.
*/
! private void tryKeyUpdate() 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();
}
}
+ private void closeSocket(boolean selfInitiated) throws IOException {
+ if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
+ SSLLogger.fine("close the ssl connection " +
+ (selfInitiated ? "(initiative)" : "(passive)"));
+ }
+
+ if (autoClose || !isLayered()) {
+ super.close();
+ } else if (selfInitiated) {
+ // wait for close_notify alert to clear input stream.
+ waitForClose();
+ }
+ }
/**
! * Wait for close_notify alert for a graceful closure.
! *
! * [RFC 5246] If the application protocol using TLS provides that any
! * data may be carried over the underlying transport after the TLS
! * connection is closed, the TLS implementation must receive the responding
! * close_notify alert before indicating to the application layer that
! * the TLS connection has ended. If the application protocol will not
! * transfer any additional data, but will only close the underlying
! * transport connection, then the implementation MAY choose to close the
! * transport without waiting for the responding close_notify.
*/
! private void waitForClose() throws IOException {
! if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
! SSLLogger.fine("wait for close_notify or alert");
! }
!
! while (!conContext.isInboundDone()) {
! try {
! Plaintext plainText = decode(null);
! // discard and continue
! if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
! SSLLogger.finest(
! "discard plaintext while waiting for close", plainText);
}
! } catch (Exception e) { // including RuntimeException
! handleException(e);
}
}
}
/**
! * Initialize the handshaker and socket streams.
! *
! * Called by connect, the layered constructor, and SSLServerSocket.
*/
! synchronized void doneConnect() throws IOException {
! // In server mode, it is not necessary to set host and serverNames.
! // Otherwise, would require a reverse DNS lookup to get the hostname.
! if ((peerHost == null) || (peerHost.length() == 0)) {
! boolean useNameService =
! trustNameService && conContext.sslConfig.isClientMode;
! useImplicitHost(useNameService);
} else {
! conContext.sslConfig.serverNames =
! Utilities.addToSNIServerNameList(
! conContext.sslConfig.serverNames, peerHost);
}
! InputStream sockInput = super.getInputStream();
! conContext.inputRecord.setReceiverStream(sockInput);
! OutputStream sockOutput = super.getOutputStream();
! conContext.inputRecord.setDeliverStream(sockOutput);
! conContext.outputRecord.setDeliverStream(sockOutput);
! this.isConnected = true;
}
! private void useImplicitHost(boolean useNameService) {
! // Note: If the local name service is not trustworthy, reverse
! // host name resolution should not be performed for endpoint
! // identification. Use the application original specified
! // hostname or IP address instead.
! // Get the original hostname via jdk.internal.misc.SharedSecrets
! InetAddress inetAddress = getInetAddress();
! if (inetAddress == null) { // not connected
! return;
! }
! JavaNetInetAddressAccess jna =
! SharedSecrets.getJavaNetInetAddressAccess();
! String originalHostname = jna.getOriginalHostName(inetAddress);
! if ((originalHostname != null) &&
! (originalHostname.length() != 0)) {
! this.peerHost = originalHostname;
! if (conContext.sslConfig.serverNames.isEmpty() &&
! !conContext.sslConfig.noSniExtension) {
! conContext.sslConfig.serverNames =
! Utilities.addToSNIServerNameList(
! conContext.sslConfig.serverNames, peerHost);
}
! return;
}
! // No explicitly specified hostname, no server name indication.
! if (!useNameService) {
! // The local name service is not trustworthy, use IP address.
! this.peerHost = inetAddress.getHostAddress();
! } else {
! // Use the underlying reverse host name resolution service.
! this.peerHost = getInetAddress().getHostName();
! }
}
! // ONLY used by HttpsClient to setup the URI specified hostname
! //
! // Please NOTE that this method MUST be called before calling to
! // SSLSocket.setSSLParameters(). Otherwise, the {@code host} parameter
! // may override SNIHostName in the customized server name indication.
! public synchronized void setHost(String host) {
! this.peerHost = host;
! this.conContext.sslConfig.serverNames =
! Utilities.addToSNIServerNameList(
! conContext.sslConfig.serverNames, host);
! }
! /**
! * Return whether we have reached end-of-file.
! *
! * If the socket is not connected, has been shutdown because of an error
! * or has been closed, throw an Exception.
! */
! synchronized boolean checkEOF() throws IOException {
! if (conContext.isClosed()) {
! throw new SocketException("Socket is closed");
! } else if (conContext.isInputCloseNotified || conContext.isBroken) {
! if (conContext.closeReason == null) {
! return true;
} else {
! throw new SSLException(
! "Connection has been shutdown: " + conContext.closeReason,
! conContext.closeReason);
}
}
+
+ return false;
}
! /**
! * Check if we can write data to this socket.
! */
! synchronized void checkWrite() throws IOException {
! if (checkEOF() || conContext.isOutboundDone()) {
! // we are at EOF, write must throw Exception
! throw new SocketException("Connection closed");
! }
! if (!isConnected) {
! throw new SocketException("Socket is not connected");
! }
}
! /**
! * Handle an exception.
! *
! * This method is called by top level exception handlers (in read(),
! * write()) to make sure we always shutdown the connection correctly
! * and do not pass runtime exception to the application.
! *
! * This method never returns normally, it always throws an IOException.
! */
! private void handleException(Exception cause) throws IOException {
! if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
! SSLLogger.warning("handling exception", cause);
}
!
! // Don't close the Socket in case of timeouts or interrupts.
! if (cause instanceof InterruptedIOException) {
! throw (IOException)cause;
}
! // need to perform error shutdown
! boolean isSSLException = (cause instanceof SSLException);
! Alert alert;
! if (isSSLException) {
! if (cause instanceof SSLHandshakeException) {
! alert = Alert.HANDSHAKE_FAILURE;
! } else {
! alert = Alert.UNEXPECTED_MESSAGE;
! }
! } else {
! if (cause instanceof IOException) {
! alert = Alert.UNEXPECTED_MESSAGE;
! } else {
! // RuntimeException
! alert = Alert.INTERNAL_ERROR;
! }
}
+ conContext.fatal(alert, cause);
}
@Override
! public String getPeerHost() {
! return peerHost;
}
! @Override
! public int getPeerPort() {
! return getPort();
}
@Override
! public boolean useDelegatedTask() {
! return false;
! }
@Override
! public void shutdown() throws IOException {
! if (!isClosed()) {
! if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
! SSLLogger.fine("close the underlying socket");
}
!
! try {
! if (conContext.isInputCloseNotified) {
! // Close the connection, no wait for more peer response.
! closeSocket(false);
! } else {
! // Close the connection, may wait for peer close_notify.
! closeSocket(true);
}
+ } finally {
+ tlsIsClosed = true;
}
}
}
}
< prev index next >