| /* | |
| * Copyright (c) 1996, 2024, 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.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 { | |
| /** | |
| * ERROR HANDLING GUIDELINES | |
| * (which exceptions to throw and catch and which not to throw and catch) | |
| * | |
| * - if there is an IOException (SocketException) when accessing the | |
| * underlying Socket, pass it through | |
| * | |
| * - do not throw IOExceptions, throw SSLExceptions (or a subclass) | |
| */ | |
| 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; | |
| private volatile boolean tlsIsClosed; | |
| 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); | |
| /* | |
| * Default timeout to skip bytes from the open socket | |
| */ | |
| private static final int DEFAULT_SKIP_TIMEOUT = 1; | |
| /** | |
| * 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 { | |
| 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 { | |
| 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 an 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(false); | |
| } 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 { | |
| startHandshake(true); | |
| } | |
| private void startHandshake(boolean resumable) 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) { | |
| if(resumable){ | |
| handleException(iioe); | |
| } else{ | |
| throw conContext.fatal(Alert.HANDSHAKE_FAILURE, | |
| "Couldn't kickstart handshaking", iioe); | |
| } | |
| } catch (SocketException se) { | |
| handleException(se); | |
| } 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 synchronize this method. Otherwise, the read and close | |
| // locks may be deadlocked. | |
| @Override | |
| public void close() throws IOException { | |
| if (isClosed()) { | |
| return; | |
| } | |
| if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { | |
| SSLLogger.fine("duplex close of SSLSocket"); | |
| } | |
| try { | |
| if (isConnected()) { | |
| // shutdown output bound, which may have been closed previously. | |
| if (!isOutputShutdown()) { | |
| duplexCloseOutput(); | |
| } | |
| // shutdown input bound, which may have been closed previously. | |
| if (!isInputShutdown()) { | |
| duplexCloseInput(); | |
| } | |
| } | |
| } catch (IOException ioe) { | |
| // ignore the exception | |
| if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { | |
| SSLLogger.warning("SSLSocket duplex close failed. Debug info only. Exception details:", ioe); | |
| } | |
| } finally { | |
| if (!isClosed()) { | |
| // close the connection directly | |
| try { | |
| closeSocket(false); | |
| } catch (IOException ioe) { | |
| // ignore the exception | |
| if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { | |
| SSLLogger.warning("SSLSocket close failed. Debug info only. Exception details:", 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 { | |
| // Do not use user_canceled workaround if the other side has | |
| // already half-closed the connection | |
| if (!conContext.isInboundClosed()) { | |
| // 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. The | |
| // conContext.handshakeContext.negotiatedProtocol is not used as there | |
| // may be a race to set it to null. | |
| ProtocolVersion pv = conContext.protocolVersion; | |
| 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 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 = conContext.isNegotiated && | |
| !conContext.protocolVersion.useTLS13PlusSpec(); | |
| // No close receipt if handshake has not 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 synchronize 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. | |
| try { | |
| if (checkCloseNotify && !conContext.isInputCloseNotified && | |
| (conContext.isNegotiated || | |
| conContext.handshakeContext != null)) { | |
| throw new SSLException( | |
| "closing inbound before receiving peer's close_notify"); | |
| } | |
| } finally { | |
| conContext.closeInbound(); | |
| if ((autoClose || !isLayered()) && !super.isInputShutdown()) { | |
| super.shutdownInput(); | |
| } | |
| } | |
| } | |
| @Override | |
| public boolean isInputShutdown() { | |
| return conContext.isInboundClosed() && | |
| (!autoClose && isLayered() || super.isInputShutdown()); | |
| } | |
| // Please don't synchronize 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()); | |
| } | |
| @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(boolean resumable) 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(resumable); | |
| } 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(true); | |
| } | |
| // 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 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. Debug info only. Exception details:", 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 to use up input records 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 | |
| socketInputRecord)) { | |
| return; | |
| } | |
| 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(true); | |
| } | |
| // 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) { | |
| 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. Debug info only. Exception details:", 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 | | |
| InterruptedIOException | SocketException se) { | |
| // Don't change exception in case of timeouts or interrupts | |
| // or SocketException. | |
| throw se; | |
| } catch (IOException ioe) { | |
| throw new SSLException("readHandshakeRecord", ioe); | |
| } | |
| } | |
| return -1; | |
| } | |
| /** | |
| * Read application data record. Used by AppInputStream only, but defined | |
| * here 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 = decode(buffer); | |
| if (plainText.contentType == ContentType.APPLICATION_DATA.id && | |
| buffer.position() > 0) { | |
| return buffer; | |
| } | |
| } catch (SSLException | | |
| InterruptedIOException | SocketException se) { | |
| // Don't change exception in case of timeouts or interrupts | |
| // or SocketException. | |
| throw se; | |
| } catch (IOException ioe) { | |
| throw new SSLException("readApplicationRecord", ioe); | |
| } | |
| } | |
| // | |
| // Couldn't read, due to some kind of error or inbound | |
| // has been closed. | |
| // | |
| 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 write side of the connection is not open. We allow a half- | |
| // duplex write-only connection for key updates. | |
| if ((conContext.handshakeContext == null) && | |
| !conContext.isOutboundClosed() && | |
| !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 (SSLConfiguration.serverNewSessionTicketCount > 0 && | |
| !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"); | |
| } | |
| conContext.conSession.updateNST = false; | |
| NewSessionTicket.t13PosthandshakeProducer.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; | |
| } | |
| } | |
| if (cause instanceof SocketException) { | |
| try { | |
| throw conContext.fatal(alert, cause); | |
| } catch (Exception e) { | |
| // Just delivering the fatal alert, re-throw the socket exception instead. | |
| } | |
| throw (SocketException)cause; | |
| } | |
| throw conContext.fatal(alert, cause); | |
| } | |
| private Plaintext handleEOF(EOFException eofe) throws IOException { | |
| if (requireCloseNotify || conContext.handshakeContext != null) { | |
| if (conContext.handshakeContext != null) { | |
| throw new SSLHandshakeException( | |
| "Remote host terminated the handshake", eofe); | |
| } else { | |
| throw new SSLProtocolException( | |
| "Remote host terminated the connection", eofe); | |
| } | |
| } 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 is false, close the | |
| // connection, no wait for more peer response. Otherwise, | |
| // may wait for peer close_notify. | |
| closeSocket(conContext.isNegotiated && | |
| !conContext.isInputCloseNotified); | |
| } finally { | |
| tlsIsClosed = true; | |
| } | |
| } | |
| } | |
| @Override | |
| public String toString() { | |
| return "SSLSocket[" + | |
| "hostname=" + getPeerHost() + | |
| ", port=" + getPeerPort() + | |
| ", " + conContext.conSession + // SSLSessionImpl.toString() | |
| "]"; | |
| } | |
| 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()) { | |
| // Try to clear the kernel buffer to avoid TCP connection resets. | |
| if (conContext.inputRecord instanceof | |
| SSLSocketInputRecord inputRecord && isConnected) { | |
| if (appInput.readLock.tryLock()) { | |
| try { | |
| int soTimeout = getSoTimeout(); | |
| try { | |
| // deplete could hang on the skip operation | |
| // in case of infinite socket read timeout. | |
| // Change read timeout to avoid deadlock. | |
| // This workaround could be replaced later | |
| // with the right synchronization | |
| if (soTimeout == 0) | |
| setSoTimeout(DEFAULT_SKIP_TIMEOUT); | |
| inputRecord.deplete(false); | |
| } catch (java.net.SocketTimeoutException stEx) { | |
| // skip timeout exception during deplete | |
| } finally { | |
| if (soTimeout == 0) | |
| setSoTimeout(soTimeout); | |
| } | |
| } finally { | |
| appInput.readLock.unlock(); | |
| } | |
| } | |
| } | |
| 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"); | |
| } | |
| appInput.readLock.lock(); | |
| try { | |
| 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); | |
| } | |
| } | |
| } finally { | |
| appInput.readLock.unlock(); | |
| } | |
| } | |
| } |