| /* |
| * 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. |
| */ |
| |
| package 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.TimeUnit; |
| 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; |
| import javax.net.ssl.SSLSocket; |
| import jdk.internal.access.JavaNetInetAddressAccess; |
| import jdk.internal.access.SharedSecrets; |
| |
| /** |
| * Implementation of an SSL socket. |
| * <P> |
| * This is a normal connection type socket, implementing SSL over some lower |
| * level socket, such as TCP. Because it is layered over some lower level |
| * socket, it MUST override all default socket methods. |
| * <P> |
| * This API offers a non-traditional option for establishing SSL |
| * connections. You may first establish the connection directly, then pass |
| * that connection to the SSL socket constructor with a flag saying which |
| * role should be taken in the handshake protocol. (The two ends of the |
| * connection must not choose the same role!) This allows setup of SSL |
| * proxying or tunneling, and also allows the kind of "role reversal" |
| * 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. |
| * <p> |
| * This particular constructor always uses the socket in the |
| * role of an SSL client. It may be useful in cases which start |
| * using SSL after some initial data transfers, for example in some |
| * SSL tunneling applications or as part of some kinds of application |
| * protocols which negotiate use of a SSL based security. |
| */ |
| SSLSocketImpl(SSLContextImpl sslContext, Socket sock, |
| String peerHost, int port, boolean autoClose) throws IOException { |
| super(sock); |
| // We always layer over a connected socket |
| if (!sock.isConnected()) { |
| throw new SocketException("Underlying socket is not connected"); |
| } |
| |
| this.sslContext = sslContext; |
| HandshakeHash handshakeHash = new HandshakeHash(); |
| this.conContext = new TransportContext(sslContext, this, |
| new SSLSocketInputRecord(handshakeHash), |
| new SSLSocketOutputRecord(handshakeHash), true); |
| this.peerHost = peerHost; |
| this.autoClose = autoClose; |
| doneConnect(); |
| } |
| |
| @Override |
| public void connect(SocketAddress endpoint, |
| int timeout) throws IOException { |
| |
| if (isLayered()) { |
| throw new SocketException("Already connected"); |
| } |
| |
| 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; |
| } |
| } |
| |
| // Deliver the user_canceled alert and the close notify alert. |
| closeNotify(useUserCanceled); |
| |
| if (!isInputShutdown()) { |
| bruteForceCloseInput(hasCloseReceipt); |
| } |
| } |
| |
| void closeNotify(boolean useUserCanceled) throws IOException { |
| // Need a lock here so that the user_canceled alert and the |
| // close_notify alert can be delivered together. |
| int linger = getSoLinger(); |
| if (linger >= 0) { |
| // don't wait more than SO_LINGER for obtaining the |
| // the lock. |
| // |
| // keep and clear the current thread interruption status. |
| boolean interrupted = Thread.interrupted(); |
| try { |
| if (conContext.outputRecord.recordLock.tryLock() || |
| conContext.outputRecord.recordLock.tryLock( |
| linger, TimeUnit.SECONDS)) { |
| try { |
| deliverClosedNotify(useUserCanceled); |
| } finally { |
| conContext.outputRecord.recordLock.unlock(); |
| } |
| } else { |
| // For layered, non-autoclose sockets, we are not |
| // able to bring them into a usable state, so we |
| // treat it as fatal error. |
| if (!super.isOutputShutdown()) { |
| if (isLayered() && !autoClose) { |
| throw new SSLException( |
| "SO_LINGER timeout, " + |
| "close_notify message cannot be sent."); |
| } else { |
| super.shutdownOutput(); |
| if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { |
| SSLLogger.warning( |
| "SSLSocket output duplex close failed: " + |
| "SO_LINGER timeout, " + |
| "close_notify message cannot be sent."); |
| } |
| } |
| } |
| |
| // 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. |
| // |
| conContext.conSession.invalidate(); |
| if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { |
| SSLLogger.warning( |
| "Invalidate the session: SO_LINGER timeout, " + |
| "close_notify message cannot be sent."); |
| } |
| } |
| } catch (InterruptedException ex) { |
| // keep interrupted status |
| interrupted = true; |
| } |
| |
| // restore the interrupted status |
| if (interrupted) { |
| Thread.currentThread().interrupt(); |
| } |
| } else { |
| conContext.outputRecord.recordLock.lock(); |
| try { |
| deliverClosedNotify(useUserCanceled); |
| } finally { |
| conContext.outputRecord.recordLock.unlock(); |
| } |
| } |
| } |
| |
| private void deliverClosedNotify( |
| boolean useUserCanceled) throws IOException { |
| try { |
| // 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 (!super.isOutputShutdown() && |
| (autoClose || !isLayered())) { |
| super.shutdownOutput(); |
| } |
| } |
| } |
| |
| /** |
| * 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 (conContext.inputRecord) { |
| // Try the best to use up the input records and close the |
| // socket gracefully, without impact the performance too |
| // much. |
| appInput.deplete(); |
| } |
| } |
| |
| 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. |
| */ |
| @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(); |
| } |
| // Check if NewSessionTicket PostHandshake message needs to be sent |
| if (conContext.conSession.updateNST) { |
| conContext.conSession.updateNST = false; |
| tryNewSessionTicket(); |
| } |
| } |
| |
| @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<SSLSocket, List<String>, String> selector) { |
| socketLock.lock(); |
| try { |
| conContext.sslConfig.socketAPSelector = selector; |
| } finally { |
| socketLock.unlock(); |
| } |
| } |
| |
| @Override |
| public BiFunction<SSLSocket, List<String>, 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(); |
| } |
| } |
| |
| // Try to generate a PostHandshake NewSessionTicket message. This is |
| // TLS 1.3 only. |
| private void tryNewSessionTicket() throws IOException { |
| // Don't bother to kickstart if handshaking is in progress, or if the |
| // connection is not duplex-open. |
| if (!conContext.sslConfig.isClientMode && |
| conContext.protocolVersion.useTLS13PlusSpec() && |
| conContext.handshakeContext == null && |
| !conContext.isOutboundClosed() && |
| !conContext.isInboundClosed() && |
| !conContext.isBroken) { |
| if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { |
| SSLLogger.finest("trigger new session ticket"); |
| } |
| NewSessionTicket.kickstartProducer.produce( |
| new PostHandshakeContext(conContext)); |
| } |
| } |
| |
| /** |
| * 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.access.SharedSecrets |
| InetAddress inetAddress = getInetAddress(); |
| if (inetAddress == null) { // not connected |
| return; |
| } |
| |
| JavaNetInetAddressAccess jna = |
| SharedSecrets.getJavaNetInetAddressAccess(); |
| String originalHostname = jna.getOriginalHostName(inetAddress); |
| if (originalHostname != null && !originalHostname.isEmpty()) { |
| |
| 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); |
| } |
| } |
| } |
| } |