| /* |
| * 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 |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| |
| package sun.security.ssl; |
| |
| import java.io.*; |
| import java.nio.*; |
| import java.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 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" |
| * that is required for most FTP data transfers. |
| * |
| * @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; |
| |
| /* |
| * 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) throws IOException { |
| |
| super(); |
| doClientAuth = clientAuth; |
| enableSessionCreation = sessionCreation; |
| this.identificationProtocol = identificationProtocol; |
| this.algorithmConstraints = algorithmConstraints; |
| this.sniMatchers = sniMatchers; |
| this.preferLocalCipherSuites = preferLocalCipherSuites; |
| 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"); |
| } |
| |
| if (!(endpoint instanceof InetSocketAddress)) { |
| 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); |
| } |
| |
| /** |
| * 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 time out, ignore the exception and continue |
| } |
| } |
| } 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.started()) { |
| 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.started()) { |
| 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.started()) { |
| 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; |
| } |
| |
| // |
| // 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(); |
| } |
| } |