All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openjsse.sun.security.ssl.SSLSocketImpl Maven / Gradle / Ivy

There is a newer version: 1.1.14
Show newest version
/*
 * Copyright (c) 1996, 2020, 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.
 */

/*
 * Copyright 2019 Azul Systems, Inc.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package org.openjsse.sun.security.ssl;

import java.io.EOFException;
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.concurrent.locks.ReentrantLock;
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;

/**
 * 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. *

* 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 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 volatile boolean tlsIsClosed = false; private final ReentrantLock socketLock = new ReentrantLock(); private final ReentrantLock handshakeLock = new ReentrantLock(); /* * 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. */ private 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. *

* 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"); } if (!(endpoint instanceof InetSocketAddress)) { 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 String[] getEnabledCipherSuites() { socketLock.lock(); try { return CipherSuite.namesOf( conContext.sslConfig.enabledCipherSuites); } finally { socketLock.unlock(); } } @Override public void setEnabledCipherSuites(String[] suites) { socketLock.lock(); try { conContext.sslConfig.enabledCipherSuites = CipherSuite.validValuesOf(suites); } finally { socketLock.unlock(); } } @Override public String[] getSupportedProtocols() { return ProtocolVersion.toStringArray( sslContext.getSupportedProtocolVersions()); } @Override public String[] getEnabledProtocols() { socketLock.lock(); try { return ProtocolVersion.toStringArray( conContext.sslConfig.enabledProtocols); } finally { socketLock.unlock(); } } @Override public void setEnabledProtocols(String[] protocols) { if (protocols == null) { throw new IllegalArgumentException("Protocols cannot be null"); } socketLock.lock(); try { conContext.sslConfig.enabledProtocols = ProtocolVersion.namesOf(protocols); } finally { socketLock.unlock(); } } @Override public 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 new SSLSessionImpl(); } return conContext.conSession; } @Override public SSLSession getHandshakeSession() { socketLock.lock(); try { return conContext.handshakeContext == null ? null : conContext.handshakeContext.handshakeSession; } finally { socketLock.unlock(); } } @Override public void addHandshakeCompletedListener( HandshakeCompletedListener listener) { if (listener == null) { throw new IllegalArgumentException("listener is null"); } socketLock.lock(); try { conContext.sslConfig.addHandshakeCompletedListener(listener); } finally { socketLock.unlock(); } } @Override public void removeHandshakeCompletedListener( HandshakeCompletedListener listener) { if (listener == null) { throw new IllegalArgumentException("listener is null"); } socketLock.lock(); try { conContext.sslConfig.removeHandshakeCompletedListener(listener); } finally { socketLock.unlock(); } } @Override public void startHandshake() throws IOException { if (!isConnected) { throw new SocketException("Socket is not connected"); } if (conContext.isBroken || conContext.isInboundClosed() || conContext.isOutboundClosed()) { throw new SocketException("Socket has been closed or broken"); } handshakeLock.lock(); try { // double check the context status if (conContext.isBroken || conContext.isInboundClosed() || conContext.isOutboundClosed()) { throw new SocketException("Socket has been closed or broken"); } 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) { readHandshakeRecord(); } } catch (InterruptedIOException iioe) { handleException(iioe); } catch (IOException ioe) { throw conContext.fatal(Alert.HANDSHAKE_FAILURE, "Couldn't kickstart handshaking", ioe); } catch (Exception oe) { // including RuntimeException handleException(oe); } } finally { handshakeLock.unlock(); } } @Override public void setUseClientMode(boolean mode) { socketLock.lock(); try { conContext.setUseClientMode(mode); } finally { socketLock.unlock(); } } @Override public boolean getUseClientMode() { socketLock.lock(); try { return conContext.sslConfig.isClientMode; } finally { socketLock.unlock(); } } @Override public void setNeedClientAuth(boolean need) { socketLock.lock(); try { conContext.sslConfig.clientAuthType = (need ? ClientAuthType.CLIENT_AUTH_REQUIRED : ClientAuthType.CLIENT_AUTH_NONE); } finally { socketLock.unlock(); } } @Override public boolean getNeedClientAuth() { socketLock.lock(); try { return (conContext.sslConfig.clientAuthType == ClientAuthType.CLIENT_AUTH_REQUIRED); } finally { socketLock.unlock(); } } @Override public void setWantClientAuth(boolean want) { socketLock.lock(); try { conContext.sslConfig.clientAuthType = (want ? ClientAuthType.CLIENT_AUTH_REQUESTED : ClientAuthType.CLIENT_AUTH_NONE); } finally { socketLock.unlock(); } } @Override public boolean getWantClientAuth() { socketLock.lock(); try { return (conContext.sslConfig.clientAuthType == ClientAuthType.CLIENT_AUTH_REQUESTED); } finally { socketLock.unlock(); } } @Override public void setEnableSessionCreation(boolean flag) { socketLock.lock(); try { conContext.sslConfig.enableSessionCreation = flag; } finally { socketLock.unlock(); } } @Override public boolean getEnableSessionCreation() { socketLock.lock(); try { return conContext.sslConfig.enableSessionCreation; } finally { socketLock.unlock(); } } @Override public boolean isClosed() { return tlsIsClosed; } // Please don't synchronized this method. Otherwise, the read and close // locks may be deadlocked. @Override public void close() throws IOException { if (tlsIsClosed) { return; } if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { SSLLogger.fine("duplex close of SSLSocket"); } try { // shutdown output bound, which may have been closed previously. if (!isOutputShutdown()) { duplexCloseOutput(); } // shutdown input bound, which may have been closed previously. if (!isInputShutdown()) { duplexCloseInput(); } if (!isClosed()) { // close the connection directly closeSocket(false); } } catch (IOException ioe) { // ignore the exception if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { SSLLogger.warning("SSLSocket duplex close failed", ioe); } } finally { tlsIsClosed = true; } } /** * Duplex close, start from closing outbound. * * For TLS 1.2 [RFC 5246], unless some other fatal alert has been * transmitted, each party is required to send a close_notify alert * before closing the write side of the connection. The other party * MUST respond with a close_notify alert of its own and close down * the connection immediately, discarding any pending writes. It is * not required for the initiator of the close to wait for the responding * close_notify alert before closing the read side of the connection. * * For TLS 1.3, Each party MUST send a close_notify alert before * closing its write side of the connection, unless it has already sent * some error alert. This does not have any effect on its read side of * the connection. Both parties need not wait to receive a close_notify * alert before closing their read side of the connection, though doing * so would introduce the possibility of truncation. * * In order to support user initiated duplex-close for TLS 1.3 connections, * the user_canceled alert is used together with the close_notify alert. */ private void duplexCloseOutput() throws IOException { boolean useUserCanceled = false; boolean hasCloseReceipt = false; if (conContext.isNegotiated) { if (!conContext.protocolVersion.useTLS13PlusSpec()) { hasCloseReceipt = true; } else { // Use a user_canceled alert for TLS 1.3 duplex close. useUserCanceled = true; } } else if (conContext.handshakeContext != null) { // initial handshake // Use user_canceled alert regardless the protocol versions. useUserCanceled = true; // The protocol version may have been negotiated. ProtocolVersion pv = conContext.handshakeContext.negotiatedProtocol; if (pv == null || (!pv.useTLS13PlusSpec())) { hasCloseReceipt = true; } } // Need a lock here so that the user_canceled alert and the // close_notify alert can be delivered together. try { synchronized (conContext.outputRecord) { // send a user_canceled alert if needed. if (useUserCanceled) { conContext.warning(Alert.USER_CANCELED); } // send a close_notify alert conContext.warning(Alert.CLOSE_NOTIFY); } } finally { if (!conContext.isOutboundClosed()) { conContext.outputRecord.close(); } if ((autoClose || !isLayered()) && !super.isOutputShutdown()) { super.shutdownOutput(); } } if (!isInputShutdown()) { bruteForceCloseInput(hasCloseReceipt); } } /** * Duplex close, start from closing inbound. * * This method should only be called when the outbound has been closed, * but the inbound is still open. */ private void duplexCloseInput() throws IOException { boolean hasCloseReceipt = false; if (conContext.isNegotiated && !conContext.protocolVersion.useTLS13PlusSpec()) { hasCloseReceipt = true; } // No close receipt if handshake has no completed. bruteForceCloseInput(hasCloseReceipt); } /** * Brute force close the input bound. * * This method should only be called when the outbound has been closed, * but the inbound is still open. */ private void bruteForceCloseInput( boolean hasCloseReceipt) throws IOException { if (hasCloseReceipt) { // It is not required for the initiator of the close to wait for // the responding close_notify alert before closing the read side // of the connection. However, 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 a "close_notify" alert before // indicating end-of-data to the application-layer. try { this.shutdown(); } finally { if (!isInputShutdown()) { shutdownInput(false); } } } else { if (!conContext.isInboundClosed()) { try { // Try the best to use up the input records and close the // socket gracefully, without impact the performance too // much. appInput.deplete(); } finally { conContext.inputRecord.close(); } } if ((autoClose || !isLayered()) && !super.isInputShutdown()) { super.shutdownInput(); } } } // Please don't synchronized this method. Otherwise, the read and close // locks may be deadlocked. @Override public void shutdownInput() throws IOException { shutdownInput(true); } // It is not required to check the close_notify receipt unless an // application call shutdownInput() explicitly. private void shutdownInput( boolean checkCloseNotify) throws IOException { if (isInputShutdown()) { return; } if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { SSLLogger.fine("close inbound of SSLSocket"); } // Is it ready to close inbound? // // No need to throw exception if the initial handshake is not started. if (checkCloseNotify && !conContext.isInputCloseNotified && (conContext.isNegotiated || conContext.handshakeContext != null)) { throw conContext.fatal(Alert.INTERNAL_ERROR, "closing inbound before receiving peer's close_notify"); } conContext.closeInbound(); if ((autoClose || !isLayered()) && !super.isInputShutdown()) { super.shutdownInput(); } } @Override public boolean isInputShutdown() { return conContext.isInboundClosed() && ((autoClose || !isLayered()) ? super.isInputShutdown(): true); } // Please don't synchronized this method. Otherwise, the read and close // locks may be deadlocked. @Override public void shutdownOutput() throws IOException { if (isOutputShutdown()) { return; } if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { SSLLogger.fine("close outbound of SSLSocket"); } conContext.closeOutbound(); if ((autoClose || !isLayered()) && !super.isOutputShutdown()) { super.shutdownOutput(); } } @Override public boolean isOutputShutdown() { return conContext.isOutboundClosed() && ((autoClose || !isLayered()) ? super.isOutputShutdown(): true); } @Override public InputStream getInputStream() throws IOException { socketLock.lock(); try { if (isClosed()) { throw new SocketException("Socket is closed"); } if (!isConnected) { throw new SocketException("Socket is not connected"); } if (conContext.isInboundClosed() || isInputShutdown()) { throw new SocketException("Socket input is already shutdown"); } return appInput; } finally { socketLock.unlock(); } } private void ensureNegotiated() throws IOException { if (conContext.isNegotiated || conContext.isBroken || conContext.isInboundClosed() || conContext.isOutboundClosed()) { return; } handshakeLock.lock(); try { // double check the context status if (conContext.isNegotiated || conContext.isBroken || conContext.isInboundClosed() || conContext.isOutboundClosed()) { return; } startHandshake(); } finally { handshakeLock.unlock(); } } /** * InputStream for application data as returned by * SSLSocket.getInputStream(). */ 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 volatile boolean appDataIsAvailable; // reading lock private final ReentrantLock readLock = new ReentrantLock(); // closing status private volatile boolean isClosing; private volatile boolean hasDepleted; 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 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 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.isBroken && !conContext.isInboundClosed() && !conContext.isOutboundClosed()) { ensureNegotiated(); } // Check if the Socket is invalid (error or closed). if (!conContext.isNegotiated || conContext.isBroken || conContext.isInboundClosed()) { throw new SocketException("Connection or inbound has closed"); } // Check if the input stream has been depleted. // // Note that the "hasDepleted" rather than the isClosing // filed is checked here, in case the closing process is // still in progress. if (hasDepleted) { if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { SSLLogger.fine("The input stream has been depleted"); } return -1; } // Read the available bytes at first. // // Note that the receiving and processing of post-handshake message // are also synchronized with the read lock. readLock.lock(); try { // Double check if the Socket is invalid (error or closed). if (conContext.isBroken || conContext.isInboundClosed()) { throw new SocketException( "Connection or inbound has closed"); } // Double check if the input stream has been depleted. if (hasDepleted) { if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { SSLLogger.fine("The input stream is closing"); } return -1; } int remains = available(); if (remains > 0) { int howmany = Math.min(remains, len); buffer.get(b, off, howmany); return howmany; } appDataIsAvailable = false; try { ByteBuffer bb = readApplicationRecord(buffer); if (bb == null) { // EOF return -1; } else { // The buffer may be reallocated for bigger capacity. buffer = bb; } bb.flip(); int volume = Math.min(len, bb.remaining()); buffer.get(b, off, volume); appDataIsAvailable = true; return volume; } catch (Exception e) { // including RuntimeException // shutdown and rethrow (wrapped) exception as appropriate handleException(e); // dummy for compiler return -1; } } finally { // Check if the input stream is closing. // // If the deplete() did not hold the lock, clean up the // input stream here. try { if (isClosing) { readLockedDeplete(); } } finally { readLock.unlock(); } } } /** * 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 long skip(long n) throws IOException { // dummy array used to implement skip() byte[] skipArray = new byte[256]; long skipped = 0; readLock.lock(); try { 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; } } finally { readLock.unlock(); } return skipped; } @Override public void close() throws IOException { if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { SSLLogger.finest("Closing input stream"); } try { SSLSocketImpl.this.close(); } catch (IOException ioe) { // ignore the exception if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { SSLLogger.warning("input stream close failed", ioe); } } } /** * 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. */ private boolean checkEOF() throws IOException { if (conContext.isBroken) { if (conContext.closeReason == null) { return true; } else { throw new SSLException( "Connection has closed: " + conContext.closeReason, conContext.closeReason); } } else if (conContext.isInboundClosed()) { return true; } else if (conContext.isInputCloseNotified) { if (conContext.closeReason == null) { return true; } else { throw new SSLException( "Connection has closed: " + conContext.closeReason, conContext.closeReason); } } return false; } /** * Try the best to use up the input records so as to close the * socket gracefully, without impact the performance too much. */ private void deplete() { if (conContext.isInboundClosed() || isClosing) { return; } isClosing = true; if (readLock.tryLock()) { try { readLockedDeplete(); } finally { readLock.unlock(); } } } /** * Try to use up the input records. * * Please don't call this method unless the readLock is held by * the current thread. */ private void readLockedDeplete() { // double check if (hasDepleted || conContext.isInboundClosed()) { return; } if (!(conContext.inputRecord instanceof SSLSocketInputRecord)) { return; } SSLSocketInputRecord socketInputRecord = (SSLSocketInputRecord)conContext.inputRecord; try { socketInputRecord.deplete( conContext.isNegotiated && (getSoTimeout() > 0)); } catch (Exception ex) { if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { SSLLogger.warning( "input stream close depletion failed", ex); } } finally { hasDepleted = true; } } } @Override public OutputStream getOutputStream() throws IOException { socketLock.lock(); try { if (isClosed()) { throw new SocketException("Socket is closed"); } if (!isConnected) { throw new SocketException("Socket is not connected"); } if (conContext.isOutboundDone() || isOutputShutdown()) { throw new SocketException("Socket output is already shutdown"); } return appOutput; } finally { socketLock.unlock(); } } /** * OutputStream for application data as returned by * SSLSocket.getOutputStream(). */ 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 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) { // // 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. // return; } // Start handshaking if the connection has not been negotiated. if (!conContext.isNegotiated && !conContext.isBroken && !conContext.isInboundClosed() && !conContext.isOutboundClosed()) { ensureNegotiated(); } // Check if the Socket is invalid (error or closed). if (!conContext.isNegotiated || conContext.isBroken || conContext.isOutboundClosed()) { throw new SocketException("Connection or outbound has closed"); } // // Delegate the writing to the underlying socket. try { conContext.outputRecord.deliver(b, off, len); } catch (SSLHandshakeException she) { // may be record sequence number overflow throw conContext.fatal(Alert.HANDSHAKE_FAILURE, she); } catch (SSLException ssle) { throw conContext.fatal(Alert.UNEXPECTED_MESSAGE, ssle); } // re-throw other IOException, which should be caused by // the underlying plain socket and could be handled by // applications (for example, re-try the connection). // Is the sequence number is nearly overflow, or has the key usage // limit been reached? if (conContext.outputRecord.seqNumIsHuge() || conContext.outputRecord.writeCipher.atKeyLimit()) { tryKeyUpdate(); } } @Override public void close() throws IOException { if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { SSLLogger.finest("Closing output stream"); } try { SSLSocketImpl.this.close(); } catch (IOException ioe) { // ignore the exception if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { SSLLogger.warning("output stream close failed", ioe); } } } } @Override public SSLParameters getSSLParameters() { socketLock.lock(); try { return conContext.sslConfig.getSSLParameters(); } finally { socketLock.unlock(); } } @Override public void setSSLParameters(SSLParameters params) { socketLock.lock(); try { conContext.sslConfig.setSSLParameters(params); if (conContext.sslConfig.maximumPacketSize != 0) { conContext.outputRecord.changePacketSize( conContext.sslConfig.maximumPacketSize); } } finally { socketLock.unlock(); } } @Override public String getApplicationProtocol() { socketLock.lock(); try { return conContext.applicationProtocol; } finally { socketLock.unlock(); } } @Override public String getHandshakeApplicationProtocol() { socketLock.lock(); try { if (conContext.handshakeContext != null) { return conContext.handshakeContext.applicationProtocol; } } finally { socketLock.unlock(); } return null; } @Override public void setHandshakeApplicationProtocolSelector( BiFunction, String> selector) { socketLock.lock(); try { conContext.sslConfig.socketAPSelector = selector; } finally { socketLock.unlock(); } } @Override public BiFunction, String> getHandshakeApplicationProtocolSelector() { socketLock.lock(); try { return conContext.sslConfig.socketAPSelector; } finally { socketLock.unlock(); } } /** * Read the initial handshake records. */ private int readHandshakeRecord() throws IOException { while (!conContext.isInboundClosed()) { try { Plaintext plainText = decode(null); if ((plainText.contentType == ContentType.HANDSHAKE.id) && conContext.isNegotiated) { return 0; } } catch (SSLException ssle) { throw ssle; } catch (InterruptedIOException iioe) { // don't change exception in case of timeouts or interrupts throw iioe; } catch (IOException ioe) { throw new SSLException("readHandshakeRecord", ioe); } } return -1; } /** * Read application data record. Used by AppInputStream only, but defined * here so as to use the socket level synchronization. * * Note that the connection guarantees that handshake, alert, and change * cipher spec data streams are handled as they arrive, so we never see * them here. * * Note: Please be careful about the synchronization, and don't use this * method other than in the AppInputStream class! */ private ByteBuffer readApplicationRecord( ByteBuffer buffer) throws IOException { while (!conContext.isInboundClosed()) { /* * 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 handleEOF(null); // if no exception thrown return null; } // 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); } try { Plaintext plainText; socketLock.lock(); try { plainText = decode(buffer); } finally { socketLock.unlock(); } if (plainText.contentType == ContentType.APPLICATION_DATA.id && buffer.position() > 0) { return buffer; } } catch (SSLException ssle) { throw ssle; } catch (InterruptedIOException iioe) { // don't change exception in case of timeouts or interrupts throw iioe; } catch (IOException ioe) { if (!(ioe instanceof SSLException)) { throw new SSLException("readApplicationRecord", ioe); } else { throw ioe; } } } // // couldn't read, due to some kind of error // return null; } private Plaintext decode(ByteBuffer destination) throws IOException { Plaintext plainText; try { 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); } } catch (EOFException eofe) { // EOFException is special as it is related to close_notify. plainText = handleEOF(eofe); } // Is the sequence number is nearly overflow? if (plainText != Plaintext.PLAINTEXT_NULL && (conContext.inputRecord.seqNumIsHuge() || conContext.inputRecord.readCipher.atKeyLimit())) { tryKeyUpdate(); } return plainText; } /** * Try key update for sequence number wrap or key usage limit. * * Note that in order to maintain the handshake status properly, we check * the sequence number and key usage limit 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 if handshaking is in progress, or if the // connection is not duplex-open. if ((conContext.handshakeContext == null) && !conContext.isOutboundClosed() && !conContext.isInboundClosed() && !conContext.isBroken) { if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { SSLLogger.finest("trigger key update"); } startHandshake(); } } /** * Initialize the handshaker and socket streams. * * Called by connect, the layered constructor, and SSLServerSocket. */ void doneConnect() throws IOException { socketLock.lock(); try { // 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.isEmpty()) { 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; } finally { socketLock.unlock(); } } 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; } //JDK8 String originalHostname = HostNameAccessor.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 void setHost(String host) { socketLock.lock(); try { this.peerHost = host; this.conContext.sslConfig.serverNames = Utilities.addToSNIServerNameList( conContext.sslConfig.serverNames, host); } finally { socketLock.unlock(); } } /** * 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; } } throw conContext.fatal(alert, cause); } private Plaintext handleEOF(EOFException eofe) throws IOException { if (requireCloseNotify || conContext.handshakeContext != null) { SSLException ssle; if (conContext.handshakeContext != null) { ssle = new SSLHandshakeException( "Remote host terminated the handshake"); } else { ssle = new SSLProtocolException( "Remote host terminated the connection"); } if (eofe != null) { ssle.initCause(eofe); } throw ssle; } else { // treat as if we had received a close_notify conContext.isInputCloseNotified = true; shutdownInput(); return Plaintext.PLAINTEXT_NULL; } } @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; } } } 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) { if (!conContext.isInboundClosed() && !isInputShutdown()) { // 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.isInboundClosed()) { 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); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy