org.openjsse.sun.security.ssl.SSLSocketImpl Maven / Gradle / Ivy
Show all versions of openjsse Show documentation
/*
* Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* Copyright 2019 Azul Systems, Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package org.openjsse.sun.security.ssl;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.BiFunction;
import javax.net.ssl.HandshakeCompletedListener;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLProtocolException;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLSession;
/**
* Implementation of an SSL socket.
*
* This is a normal connection type socket, implementing SSL over some lower
* level socket, such as TCP. Because it is layered over some lower level
* socket, it MUST override all default socket methods.
*
* This API offers a non-traditional option for establishing SSL
* connections. You may first establish the connection directly, then pass
* that connection to the SSL socket constructor with a flag saying which
* role should be taken in the handshake protocol. (The two ends of the
* connection must not choose the same role!) This allows setup of SSL
* proxying or tunneling, and also allows the kind of "role reversal"
* that is required for most FTP data transfers.
*
* @see javax.net.ssl.SSLSocket
* @see SSLServerSocket
*
* @author David Brownell
*/
public final class SSLSocketImpl
extends BaseSSLSocketImpl implements SSLTransport {
final SSLContextImpl sslContext;
final TransportContext conContext;
private final AppInputStream appInput = new AppInputStream();
private final AppOutputStream appOutput = new AppOutputStream();
private String peerHost;
private boolean autoClose;
private boolean isConnected = false;
private volatile boolean tlsIsClosed = false;
private final ReentrantLock socketLock = new ReentrantLock();
private final ReentrantLock handshakeLock = new ReentrantLock();
/*
* Is the local name service trustworthy?
*
* If the local name service is not trustworthy, reverse host name
* resolution should not be performed for endpoint identification.
*/
private static final boolean trustNameService =
Utilities.getBooleanProperty("jdk.tls.trustNameService", false);
/**
* Package-private constructor used to instantiate an unconnected
* socket.
*
* This instance is meant to set handshake state to use "client mode".
*/
SSLSocketImpl(SSLContextImpl sslContext) {
super();
this.sslContext = sslContext;
HandshakeHash handshakeHash = new HandshakeHash();
this.conContext = new TransportContext(sslContext, this,
new SSLSocketInputRecord(handshakeHash),
new SSLSocketOutputRecord(handshakeHash), true);
}
/**
* Package-private constructor used to instantiate a server socket.
*
* This instance is meant to set handshake state to use "server mode".
*/
SSLSocketImpl(SSLContextImpl sslContext, SSLConfiguration sslConfig) {
super();
this.sslContext = sslContext;
HandshakeHash handshakeHash = new HandshakeHash();
this.conContext = new TransportContext(sslContext, this, sslConfig,
new SSLSocketInputRecord(handshakeHash),
new SSLSocketOutputRecord(handshakeHash));
}
/**
* Constructs an SSL connection to a named host at a specified
* port, using the authentication context provided.
*
* This endpoint acts as the client, and may rejoin an existing SSL session
* if appropriate.
*/
SSLSocketImpl(SSLContextImpl sslContext, String peerHost,
int peerPort) throws IOException, UnknownHostException {
super();
this.sslContext = sslContext;
HandshakeHash handshakeHash = new HandshakeHash();
this.conContext = new TransportContext(sslContext, this,
new SSLSocketInputRecord(handshakeHash),
new SSLSocketOutputRecord(handshakeHash), true);
this.peerHost = peerHost;
SocketAddress socketAddress =
peerHost != null ? new InetSocketAddress(peerHost, peerPort) :
new InetSocketAddress(InetAddress.getByName(null), peerPort);
connect(socketAddress, 0);
}
/**
* Constructs an SSL connection to a server at a specified
* address, and TCP port, using the authentication context
* provided.
*
* This endpoint acts as the client, and may rejoin an existing SSL
* session if appropriate.
*/
SSLSocketImpl(SSLContextImpl sslContext,
InetAddress address, int peerPort) throws IOException {
super();
this.sslContext = sslContext;
HandshakeHash handshakeHash = new HandshakeHash();
this.conContext = new TransportContext(sslContext, this,
new SSLSocketInputRecord(handshakeHash),
new SSLSocketOutputRecord(handshakeHash), true);
SocketAddress socketAddress = new InetSocketAddress(address, peerPort);
connect(socketAddress, 0);
}
/**
* Constructs an SSL connection to a named host at a specified
* port, using the authentication context provided.
*
* This endpoint acts as the client, and may rejoin an existing SSL
* session if appropriate.
*/
SSLSocketImpl(SSLContextImpl sslContext,
String peerHost, int peerPort, InetAddress localAddr,
int localPort) throws IOException, UnknownHostException {
super();
this.sslContext = sslContext;
HandshakeHash handshakeHash = new HandshakeHash();
this.conContext = new TransportContext(sslContext, this,
new SSLSocketInputRecord(handshakeHash),
new SSLSocketOutputRecord(handshakeHash), true);
this.peerHost = peerHost;
bind(new InetSocketAddress(localAddr, localPort));
SocketAddress socketAddress =
peerHost != null ? new InetSocketAddress(peerHost, peerPort) :
new InetSocketAddress(InetAddress.getByName(null), peerPort);
connect(socketAddress, 0);
}
/**
* Constructs an SSL connection to a server at a specified
* address, and TCP port, using the authentication context
* provided.
*
* This endpoint acts as the client, and may rejoin an existing SSL
* session if appropriate.
*/
SSLSocketImpl(SSLContextImpl sslContext,
InetAddress peerAddr, int peerPort,
InetAddress localAddr, int localPort) throws IOException {
super();
this.sslContext = sslContext;
HandshakeHash handshakeHash = new HandshakeHash();
this.conContext = new TransportContext(sslContext, this,
new SSLSocketInputRecord(handshakeHash),
new SSLSocketOutputRecord(handshakeHash), true);
bind(new InetSocketAddress(localAddr, localPort));
SocketAddress socketAddress = new InetSocketAddress(peerAddr, peerPort);
connect(socketAddress, 0);
}
/**
* Creates a server mode {@link Socket} layered over an
* existing connected socket, and is able to read data which has
* already been consumed/removed from the {@link Socket}'s
* underlying {@link InputStream}.
*/
SSLSocketImpl(SSLContextImpl sslContext, Socket sock,
InputStream consumed, boolean autoClose) throws IOException {
super(sock, consumed);
// We always layer over a connected socket
if (!sock.isConnected()) {
throw new SocketException("Underlying socket is not connected");
}
this.sslContext = sslContext;
HandshakeHash handshakeHash = new HandshakeHash();
this.conContext = new TransportContext(sslContext, this,
new SSLSocketInputRecord(handshakeHash),
new SSLSocketOutputRecord(handshakeHash), false);
this.autoClose = autoClose;
doneConnect();
}
/**
* Layer SSL traffic over an existing connection, rather than
* creating a new connection.
*
* The existing connection may be used only for SSL traffic (using this
* SSLSocket) until the SSLSocket.close() call returns. However, if a
* protocol error is detected, that existing connection is automatically
* closed.
*
* This particular constructor always uses the socket in the
* role of an SSL client. It may be useful in cases which start
* using SSL after some initial data transfers, for example in some
* SSL tunneling applications or as part of some kinds of application
* protocols which negotiate use of a SSL based security.
*/
SSLSocketImpl(SSLContextImpl sslContext, Socket sock,
String peerHost, int port, boolean autoClose) throws IOException {
super(sock);
// We always layer over a connected socket
if (!sock.isConnected()) {
throw new SocketException("Underlying socket is not connected");
}
this.sslContext = sslContext;
HandshakeHash handshakeHash = new HandshakeHash();
this.conContext = new TransportContext(sslContext, this,
new SSLSocketInputRecord(handshakeHash),
new SSLSocketOutputRecord(handshakeHash), true);
this.peerHost = peerHost;
this.autoClose = autoClose;
doneConnect();
}
@Override
public void connect(SocketAddress endpoint,
int timeout) throws IOException {
if (isLayered()) {
throw new SocketException("Already connected");
}
if (!(endpoint instanceof InetSocketAddress)) {
throw new SocketException(
"Cannot handle non-Inet socket addresses.");
}
super.connect(endpoint, timeout);
doneConnect();
}
@Override
public String[] getSupportedCipherSuites() {
return CipherSuite.namesOf(sslContext.getSupportedCipherSuites());
}
@Override
public String[] getEnabledCipherSuites() {
socketLock.lock();
try {
return CipherSuite.namesOf(
conContext.sslConfig.enabledCipherSuites);
} finally {
socketLock.unlock();
}
}
@Override
public void setEnabledCipherSuites(String[] suites) {
socketLock.lock();
try {
conContext.sslConfig.enabledCipherSuites =
CipherSuite.validValuesOf(suites);
} finally {
socketLock.unlock();
}
}
@Override
public String[] getSupportedProtocols() {
return ProtocolVersion.toStringArray(
sslContext.getSupportedProtocolVersions());
}
@Override
public String[] getEnabledProtocols() {
socketLock.lock();
try {
return ProtocolVersion.toStringArray(
conContext.sslConfig.enabledProtocols);
} finally {
socketLock.unlock();
}
}
@Override
public void setEnabledProtocols(String[] protocols) {
if (protocols == null) {
throw new IllegalArgumentException("Protocols cannot be null");
}
socketLock.lock();
try {
conContext.sslConfig.enabledProtocols =
ProtocolVersion.namesOf(protocols);
} finally {
socketLock.unlock();
}
}
@Override
public SSLSession getSession() {
try {
// start handshaking, if failed, the connection will be closed.
ensureNegotiated();
} catch (IOException ioe) {
if (SSLLogger.isOn && SSLLogger.isOn("handshake")) {
SSLLogger.severe("handshake failed", ioe);
}
return new SSLSessionImpl();
}
return conContext.conSession;
}
@Override
public SSLSession getHandshakeSession() {
socketLock.lock();
try {
return conContext.handshakeContext == null ?
null : conContext.handshakeContext.handshakeSession;
} finally {
socketLock.unlock();
}
}
@Override
public void addHandshakeCompletedListener(
HandshakeCompletedListener listener) {
if (listener == null) {
throw new IllegalArgumentException("listener is null");
}
socketLock.lock();
try {
conContext.sslConfig.addHandshakeCompletedListener(listener);
} finally {
socketLock.unlock();
}
}
@Override
public void removeHandshakeCompletedListener(
HandshakeCompletedListener listener) {
if (listener == null) {
throw new IllegalArgumentException("listener is null");
}
socketLock.lock();
try {
conContext.sslConfig.removeHandshakeCompletedListener(listener);
} finally {
socketLock.unlock();
}
}
@Override
public void startHandshake() throws IOException {
if (!isConnected) {
throw new SocketException("Socket is not connected");
}
if (conContext.isBroken || conContext.isInboundClosed() ||
conContext.isOutboundClosed()) {
throw new SocketException("Socket has been closed or broken");
}
handshakeLock.lock();
try {
// double check the context status
if (conContext.isBroken || conContext.isInboundClosed() ||
conContext.isOutboundClosed()) {
throw new SocketException("Socket has been closed or broken");
}
try {
conContext.kickstart();
// All initial handshaking goes through this operation until we
// have a valid SSL connection.
//
// Handle handshake messages only, need no application data.
if (!conContext.isNegotiated) {
readHandshakeRecord();
}
} catch (InterruptedIOException iioe) {
handleException(iioe);
} catch (IOException ioe) {
throw conContext.fatal(Alert.HANDSHAKE_FAILURE,
"Couldn't kickstart handshaking", ioe);
} catch (Exception oe) { // including RuntimeException
handleException(oe);
}
} finally {
handshakeLock.unlock();
}
}
@Override
public void setUseClientMode(boolean mode) {
socketLock.lock();
try {
conContext.setUseClientMode(mode);
} finally {
socketLock.unlock();
}
}
@Override
public boolean getUseClientMode() {
socketLock.lock();
try {
return conContext.sslConfig.isClientMode;
} finally {
socketLock.unlock();
}
}
@Override
public void setNeedClientAuth(boolean need) {
socketLock.lock();
try {
conContext.sslConfig.clientAuthType =
(need ? ClientAuthType.CLIENT_AUTH_REQUIRED :
ClientAuthType.CLIENT_AUTH_NONE);
} finally {
socketLock.unlock();
}
}
@Override
public boolean getNeedClientAuth() {
socketLock.lock();
try {
return (conContext.sslConfig.clientAuthType ==
ClientAuthType.CLIENT_AUTH_REQUIRED);
} finally {
socketLock.unlock();
}
}
@Override
public void setWantClientAuth(boolean want) {
socketLock.lock();
try {
conContext.sslConfig.clientAuthType =
(want ? ClientAuthType.CLIENT_AUTH_REQUESTED :
ClientAuthType.CLIENT_AUTH_NONE);
} finally {
socketLock.unlock();
}
}
@Override
public boolean getWantClientAuth() {
socketLock.lock();
try {
return (conContext.sslConfig.clientAuthType ==
ClientAuthType.CLIENT_AUTH_REQUESTED);
} finally {
socketLock.unlock();
}
}
@Override
public void setEnableSessionCreation(boolean flag) {
socketLock.lock();
try {
conContext.sslConfig.enableSessionCreation = flag;
} finally {
socketLock.unlock();
}
}
@Override
public boolean getEnableSessionCreation() {
socketLock.lock();
try {
return conContext.sslConfig.enableSessionCreation;
} finally {
socketLock.unlock();
}
}
@Override
public boolean isClosed() {
return tlsIsClosed;
}
// Please don't synchronized this method. Otherwise, the read and close
// locks may be deadlocked.
@Override
public void close() throws IOException {
if (tlsIsClosed) {
return;
}
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.fine("duplex close of SSLSocket");
}
try {
// shutdown output bound, which may have been closed previously.
if (!isOutputShutdown()) {
duplexCloseOutput();
}
// shutdown input bound, which may have been closed previously.
if (!isInputShutdown()) {
duplexCloseInput();
}
if (!isClosed()) {
// close the connection directly
closeSocket(false);
}
} catch (IOException ioe) {
// ignore the exception
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.warning("SSLSocket duplex close failed", ioe);
}
} finally {
tlsIsClosed = true;
}
}
/**
* Duplex close, start from closing outbound.
*
* For TLS 1.2 [RFC 5246], unless some other fatal alert has been
* transmitted, each party is required to send a close_notify alert
* before closing the write side of the connection. The other party
* MUST respond with a close_notify alert of its own and close down
* the connection immediately, discarding any pending writes. It is
* not required for the initiator of the close to wait for the responding
* close_notify alert before closing the read side of the connection.
*
* For TLS 1.3, Each party MUST send a close_notify alert before
* closing its write side of the connection, unless it has already sent
* some error alert. This does not have any effect on its read side of
* the connection. Both parties need not wait to receive a close_notify
* alert before closing their read side of the connection, though doing
* so would introduce the possibility of truncation.
*
* In order to support user initiated duplex-close for TLS 1.3 connections,
* the user_canceled alert is used together with the close_notify alert.
*/
private void duplexCloseOutput() throws IOException {
boolean useUserCanceled = false;
boolean hasCloseReceipt = false;
if (conContext.isNegotiated) {
if (!conContext.protocolVersion.useTLS13PlusSpec()) {
hasCloseReceipt = true;
} else {
// Use a user_canceled alert for TLS 1.3 duplex close.
useUserCanceled = true;
}
} else if (conContext.handshakeContext != null) { // initial handshake
// Use user_canceled alert regardless the protocol versions.
useUserCanceled = true;
// The protocol version may have been negotiated.
ProtocolVersion pv = conContext.handshakeContext.negotiatedProtocol;
if (pv == null || (!pv.useTLS13PlusSpec())) {
hasCloseReceipt = true;
}
}
// Need a lock here so that the user_canceled alert and the
// close_notify alert can be delivered together.
try {
synchronized (conContext.outputRecord) {
// send a user_canceled alert if needed.
if (useUserCanceled) {
conContext.warning(Alert.USER_CANCELED);
}
// send a close_notify alert
conContext.warning(Alert.CLOSE_NOTIFY);
}
} finally {
if (!conContext.isOutboundClosed()) {
conContext.outputRecord.close();
}
if ((autoClose || !isLayered()) && !super.isOutputShutdown()) {
super.shutdownOutput();
}
}
if (!isInputShutdown()) {
bruteForceCloseInput(hasCloseReceipt);
}
}
/**
* Duplex close, start from closing inbound.
*
* This method should only be called when the outbound has been closed,
* but the inbound is still open.
*/
private void duplexCloseInput() throws IOException {
boolean hasCloseReceipt = false;
if (conContext.isNegotiated &&
!conContext.protocolVersion.useTLS13PlusSpec()) {
hasCloseReceipt = true;
} // No close receipt if handshake has no completed.
bruteForceCloseInput(hasCloseReceipt);
}
/**
* Brute force close the input bound.
*
* This method should only be called when the outbound has been closed,
* but the inbound is still open.
*/
private void bruteForceCloseInput(
boolean hasCloseReceipt) throws IOException {
if (hasCloseReceipt) {
// It is not required for the initiator of the close to wait for
// the responding close_notify alert before closing the read side
// of the connection. However, if the application protocol using
// TLS provides that any data may be carried over the underlying
// transport after the TLS connection is closed, the TLS
// implementation MUST receive a "close_notify" alert before
// indicating end-of-data to the application-layer.
try {
this.shutdown();
} finally {
if (!isInputShutdown()) {
shutdownInput(false);
}
}
} else {
if (!conContext.isInboundClosed()) {
try {
// Try the best to use up the input records and close the
// socket gracefully, without impact the performance too
// much.
appInput.deplete();
} finally {
conContext.inputRecord.close();
}
}
if ((autoClose || !isLayered()) && !super.isInputShutdown()) {
super.shutdownInput();
}
}
}
// Please don't synchronized this method. Otherwise, the read and close
// locks may be deadlocked.
@Override
public void shutdownInput() throws IOException {
shutdownInput(true);
}
// It is not required to check the close_notify receipt unless an
// application call shutdownInput() explicitly.
private void shutdownInput(
boolean checkCloseNotify) throws IOException {
if (isInputShutdown()) {
return;
}
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.fine("close inbound of SSLSocket");
}
// Is it ready to close inbound?
//
// No need to throw exception if the initial handshake is not started.
if (checkCloseNotify && !conContext.isInputCloseNotified &&
(conContext.isNegotiated || conContext.handshakeContext != null)) {
throw conContext.fatal(Alert.INTERNAL_ERROR,
"closing inbound before receiving peer's close_notify");
}
conContext.closeInbound();
if ((autoClose || !isLayered()) && !super.isInputShutdown()) {
super.shutdownInput();
}
}
@Override
public boolean isInputShutdown() {
return conContext.isInboundClosed() &&
((autoClose || !isLayered()) ? super.isInputShutdown(): true);
}
// Please don't synchronized this method. Otherwise, the read and close
// locks may be deadlocked.
@Override
public void shutdownOutput() throws IOException {
if (isOutputShutdown()) {
return;
}
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.fine("close outbound of SSLSocket");
}
conContext.closeOutbound();
if ((autoClose || !isLayered()) && !super.isOutputShutdown()) {
super.shutdownOutput();
}
}
@Override
public boolean isOutputShutdown() {
return conContext.isOutboundClosed() &&
((autoClose || !isLayered()) ? super.isOutputShutdown(): true);
}
@Override
public InputStream getInputStream() throws IOException {
socketLock.lock();
try {
if (isClosed()) {
throw new SocketException("Socket is closed");
}
if (!isConnected) {
throw new SocketException("Socket is not connected");
}
if (conContext.isInboundClosed() || isInputShutdown()) {
throw new SocketException("Socket input is already shutdown");
}
return appInput;
} finally {
socketLock.unlock();
}
}
private void ensureNegotiated() throws IOException {
if (conContext.isNegotiated || conContext.isBroken ||
conContext.isInboundClosed() || conContext.isOutboundClosed()) {
return;
}
handshakeLock.lock();
try {
// double check the context status
if (conContext.isNegotiated || conContext.isBroken ||
conContext.isInboundClosed() ||
conContext.isOutboundClosed()) {
return;
}
startHandshake();
} finally {
handshakeLock.unlock();
}
}
/**
* InputStream for application data as returned by
* SSLSocket.getInputStream().
*/
private class AppInputStream extends InputStream {
// One element array used to implement the single byte read() method
private final byte[] oneByte = new byte[1];
// the temporary buffer used to read network
private ByteBuffer buffer;
// Is application data available in the stream?
private volatile boolean appDataIsAvailable;
// reading lock
private final ReentrantLock readLock = new ReentrantLock();
// closing status
private volatile boolean isClosing;
private volatile boolean hasDepleted;
AppInputStream() {
this.appDataIsAvailable = false;
this.buffer = ByteBuffer.allocate(4096);
}
/**
* Return the minimum number of bytes that can be read
* without blocking.
*/
@Override
public int available() throws IOException {
// Currently not synchronized.
if ((!appDataIsAvailable) || checkEOF()) {
return 0;
}
return buffer.remaining();
}
/**
* Read a single byte, returning -1 on non-fault EOF status.
*/
@Override
public int read() throws IOException {
int n = read(oneByte, 0, 1);
if (n <= 0) { // EOF
return -1;
}
return oneByte[0] & 0xFF;
}
/**
* Reads up to {@code len} bytes of data from the input stream
* into an array of bytes.
*
* An attempt is made to read as many as {@code len} bytes, but a
* smaller number may be read. The number of bytes actually read
* is returned as an integer.
*
* If the layer above needs more data, it asks for more, so we
* are responsible only for blocking to fill at most one buffer,
* and returning "-1" on non-fault EOF status.
*/
@Override
public int read(byte[] b, int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException("the target buffer is null");
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException(
"buffer length: " + b.length + ", offset; " + off +
", bytes to read:" + len);
} else if (len == 0) {
return 0;
}
if (checkEOF()) {
return -1;
}
// start handshaking if the connection has not been negotiated.
if (!conContext.isNegotiated && !conContext.isBroken &&
!conContext.isInboundClosed() &&
!conContext.isOutboundClosed()) {
ensureNegotiated();
}
// Check if the Socket is invalid (error or closed).
if (!conContext.isNegotiated ||
conContext.isBroken || conContext.isInboundClosed()) {
throw new SocketException("Connection or inbound has closed");
}
// Check if the input stream has been depleted.
//
// Note that the "hasDepleted" rather than the isClosing
// filed is checked here, in case the closing process is
// still in progress.
if (hasDepleted) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.fine("The input stream has been depleted");
}
return -1;
}
// Read the available bytes at first.
//
// Note that the receiving and processing of post-handshake message
// are also synchronized with the read lock.
readLock.lock();
try {
// Double check if the Socket is invalid (error or closed).
if (conContext.isBroken || conContext.isInboundClosed()) {
throw new SocketException(
"Connection or inbound has closed");
}
// Double check if the input stream has been depleted.
if (hasDepleted) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.fine("The input stream is closing");
}
return -1;
}
int remains = available();
if (remains > 0) {
int howmany = Math.min(remains, len);
buffer.get(b, off, howmany);
return howmany;
}
appDataIsAvailable = false;
try {
ByteBuffer bb = readApplicationRecord(buffer);
if (bb == null) { // EOF
return -1;
} else {
// The buffer may be reallocated for bigger capacity.
buffer = bb;
}
bb.flip();
int volume = Math.min(len, bb.remaining());
buffer.get(b, off, volume);
appDataIsAvailable = true;
return volume;
} catch (Exception e) { // including RuntimeException
// shutdown and rethrow (wrapped) exception as appropriate
handleException(e);
// dummy for compiler
return -1;
}
} finally {
// Check if the input stream is closing.
//
// If the deplete() did not hold the lock, clean up the
// input stream here.
try {
if (isClosing) {
readLockedDeplete();
}
} finally {
readLock.unlock();
}
}
}
/**
* Skip n bytes.
*
* This implementation is somewhat less efficient than possible, but
* not badly so (redundant copy). We reuse the read() code to keep
* things simpler. Note that SKIP_ARRAY is static and may garbled by
* concurrent use, but we are not interested in the data anyway.
*/
@Override
public long skip(long n) throws IOException {
// dummy array used to implement skip()
byte[] skipArray = new byte[256];
long skipped = 0;
readLock.lock();
try {
while (n > 0) {
int len = (int)Math.min(n, skipArray.length);
int r = read(skipArray, 0, len);
if (r <= 0) {
break;
}
n -= r;
skipped += r;
}
} finally {
readLock.unlock();
}
return skipped;
}
@Override
public void close() throws IOException {
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.finest("Closing input stream");
}
try {
SSLSocketImpl.this.close();
} catch (IOException ioe) {
// ignore the exception
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.warning("input stream close failed", ioe);
}
}
}
/**
* Return whether we have reached end-of-file.
*
* If the socket is not connected, has been shutdown because of an error
* or has been closed, throw an Exception.
*/
private boolean checkEOF() throws IOException {
if (conContext.isBroken) {
if (conContext.closeReason == null) {
return true;
} else {
throw new SSLException(
"Connection has closed: " + conContext.closeReason,
conContext.closeReason);
}
} else if (conContext.isInboundClosed()) {
return true;
} else if (conContext.isInputCloseNotified) {
if (conContext.closeReason == null) {
return true;
} else {
throw new SSLException(
"Connection has closed: " + conContext.closeReason,
conContext.closeReason);
}
}
return false;
}
/**
* Try the best to use up the input records so as to close the
* socket gracefully, without impact the performance too much.
*/
private void deplete() {
if (conContext.isInboundClosed() || isClosing) {
return;
}
isClosing = true;
if (readLock.tryLock()) {
try {
readLockedDeplete();
} finally {
readLock.unlock();
}
}
}
/**
* Try to use up the input records.
*
* Please don't call this method unless the readLock is held by
* the current thread.
*/
private void readLockedDeplete() {
// double check
if (hasDepleted || conContext.isInboundClosed()) {
return;
}
if (!(conContext.inputRecord instanceof SSLSocketInputRecord)) {
return;
}
SSLSocketInputRecord socketInputRecord =
(SSLSocketInputRecord)conContext.inputRecord;
try {
socketInputRecord.deplete(
conContext.isNegotiated && (getSoTimeout() > 0));
} catch (Exception ex) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.warning(
"input stream close depletion failed", ex);
}
} finally {
hasDepleted = true;
}
}
}
@Override
public OutputStream getOutputStream() throws IOException {
socketLock.lock();
try {
if (isClosed()) {
throw new SocketException("Socket is closed");
}
if (!isConnected) {
throw new SocketException("Socket is not connected");
}
if (conContext.isOutboundDone() || isOutputShutdown()) {
throw new SocketException("Socket output is already shutdown");
}
return appOutput;
} finally {
socketLock.unlock();
}
}
/**
* OutputStream for application data as returned by
* SSLSocket.getOutputStream().
*/
private class AppOutputStream extends OutputStream {
// One element array used to implement the write(byte) method
private final byte[] oneByte = new byte[1];
@Override
public void write(int i) throws IOException {
oneByte[0] = (byte)i;
write(oneByte, 0, 1);
}
@Override
public void write(byte[] b,
int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException("the source buffer is null");
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException(
"buffer length: " + b.length + ", offset; " + off +
", bytes to read:" + len);
} else if (len == 0) {
//
// Don't bother to really write empty records. We went this
// far to drive the handshake machinery, for correctness; not
// writing empty records improves performance by cutting CPU
// time and network resource usage. However, some protocol
// implementations are fragile and don't like to see empty
// records, so this also increases robustness.
//
return;
}
// Start handshaking if the connection has not been negotiated.
if (!conContext.isNegotiated && !conContext.isBroken &&
!conContext.isInboundClosed() &&
!conContext.isOutboundClosed()) {
ensureNegotiated();
}
// Check if the Socket is invalid (error or closed).
if (!conContext.isNegotiated ||
conContext.isBroken || conContext.isOutboundClosed()) {
throw new SocketException("Connection or outbound has closed");
}
//
// Delegate the writing to the underlying socket.
try {
conContext.outputRecord.deliver(b, off, len);
} catch (SSLHandshakeException she) {
// may be record sequence number overflow
throw conContext.fatal(Alert.HANDSHAKE_FAILURE, she);
} catch (SSLException ssle) {
throw conContext.fatal(Alert.UNEXPECTED_MESSAGE, ssle);
} // re-throw other IOException, which should be caused by
// the underlying plain socket and could be handled by
// applications (for example, re-try the connection).
// Is the sequence number is nearly overflow, or has the key usage
// limit been reached?
if (conContext.outputRecord.seqNumIsHuge() ||
conContext.outputRecord.writeCipher.atKeyLimit()) {
tryKeyUpdate();
}
}
@Override
public void close() throws IOException {
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.finest("Closing output stream");
}
try {
SSLSocketImpl.this.close();
} catch (IOException ioe) {
// ignore the exception
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.warning("output stream close failed", ioe);
}
}
}
}
@Override
public SSLParameters getSSLParameters() {
socketLock.lock();
try {
return conContext.sslConfig.getSSLParameters();
} finally {
socketLock.unlock();
}
}
@Override
public void setSSLParameters(SSLParameters params) {
socketLock.lock();
try {
conContext.sslConfig.setSSLParameters(params);
if (conContext.sslConfig.maximumPacketSize != 0) {
conContext.outputRecord.changePacketSize(
conContext.sslConfig.maximumPacketSize);
}
} finally {
socketLock.unlock();
}
}
@Override
public String getApplicationProtocol() {
socketLock.lock();
try {
return conContext.applicationProtocol;
} finally {
socketLock.unlock();
}
}
@Override
public String getHandshakeApplicationProtocol() {
socketLock.lock();
try {
if (conContext.handshakeContext != null) {
return conContext.handshakeContext.applicationProtocol;
}
} finally {
socketLock.unlock();
}
return null;
}
@Override
public void setHandshakeApplicationProtocolSelector(
BiFunction, String> selector) {
socketLock.lock();
try {
conContext.sslConfig.socketAPSelector = selector;
} finally {
socketLock.unlock();
}
}
@Override
public BiFunction, String>
getHandshakeApplicationProtocolSelector() {
socketLock.lock();
try {
return conContext.sslConfig.socketAPSelector;
} finally {
socketLock.unlock();
}
}
/**
* Read the initial handshake records.
*/
private int readHandshakeRecord() throws IOException {
while (!conContext.isInboundClosed()) {
try {
Plaintext plainText = decode(null);
if ((plainText.contentType == ContentType.HANDSHAKE.id) &&
conContext.isNegotiated) {
return 0;
}
} catch (SSLException ssle) {
throw ssle;
} catch (InterruptedIOException iioe) {
// don't change exception in case of timeouts or interrupts
throw iioe;
} catch (IOException ioe) {
throw new SSLException("readHandshakeRecord", ioe);
}
}
return -1;
}
/**
* Read application data record. Used by AppInputStream only, but defined
* here so as to use the socket level synchronization.
*
* Note that the connection guarantees that handshake, alert, and change
* cipher spec data streams are handled as they arrive, so we never see
* them here.
*
* Note: Please be careful about the synchronization, and don't use this
* method other than in the AppInputStream class!
*/
private ByteBuffer readApplicationRecord(
ByteBuffer buffer) throws IOException {
while (!conContext.isInboundClosed()) {
/*
* clean the buffer and check if it is too small, e.g. because
* the AppInputStream did not have the chance to see the
* current packet length but rather something like that of the
* handshake before. In that case we return 0 at this point to
* give the caller the chance to adjust the buffer.
*/
buffer.clear();
int inLen = conContext.inputRecord.bytesInCompletePacket();
if (inLen < 0) { // EOF
handleEOF(null);
// if no exception thrown
return null;
}
// Is this packet bigger than SSL/TLS normally allows?
if (inLen > SSLRecord.maxLargeRecordSize) {
throw new SSLProtocolException(
"Illegal packet size: " + inLen);
}
if (inLen > buffer.remaining()) {
buffer = ByteBuffer.allocate(inLen);
}
try {
Plaintext plainText;
socketLock.lock();
try {
plainText = decode(buffer);
} finally {
socketLock.unlock();
}
if (plainText.contentType == ContentType.APPLICATION_DATA.id &&
buffer.position() > 0) {
return buffer;
}
} catch (SSLException ssle) {
throw ssle;
} catch (InterruptedIOException iioe) {
// don't change exception in case of timeouts or interrupts
throw iioe;
} catch (IOException ioe) {
if (!(ioe instanceof SSLException)) {
throw new SSLException("readApplicationRecord", ioe);
} else {
throw ioe;
}
}
}
//
// couldn't read, due to some kind of error
//
return null;
}
private Plaintext decode(ByteBuffer destination) throws IOException {
Plaintext plainText;
try {
if (destination == null) {
plainText = SSLTransport.decode(conContext,
null, 0, 0, null, 0, 0);
} else {
plainText = SSLTransport.decode(conContext,
null, 0, 0, new ByteBuffer[]{destination}, 0, 1);
}
} catch (EOFException eofe) {
// EOFException is special as it is related to close_notify.
plainText = handleEOF(eofe);
}
// Is the sequence number is nearly overflow?
if (plainText != Plaintext.PLAINTEXT_NULL &&
(conContext.inputRecord.seqNumIsHuge() ||
conContext.inputRecord.readCipher.atKeyLimit())) {
tryKeyUpdate();
}
return plainText;
}
/**
* Try key update for sequence number wrap or key usage limit.
*
* Note that in order to maintain the handshake status properly, we check
* the sequence number and key usage limit after the last record
* reading/writing process.
*
* As we request renegotiation or close the connection for wrapped sequence
* number when there is enough sequence number space left to handle a few
* more records, so the sequence number of the last record cannot be
* wrapped.
*/
private void tryKeyUpdate() throws IOException {
// Don't bother to kickstart if handshaking is in progress, or if the
// connection is not duplex-open.
if ((conContext.handshakeContext == null) &&
!conContext.isOutboundClosed() &&
!conContext.isInboundClosed() &&
!conContext.isBroken) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.finest("trigger key update");
}
startHandshake();
}
}
/**
* Initialize the handshaker and socket streams.
*
* Called by connect, the layered constructor, and SSLServerSocket.
*/
void doneConnect() throws IOException {
socketLock.lock();
try {
// In server mode, it is not necessary to set host and serverNames.
// Otherwise, would require a reverse DNS lookup to get
// the hostname.
if (peerHost == null || peerHost.isEmpty()) {
boolean useNameService =
trustNameService && conContext.sslConfig.isClientMode;
useImplicitHost(useNameService);
} else {
conContext.sslConfig.serverNames =
Utilities.addToSNIServerNameList(
conContext.sslConfig.serverNames, peerHost);
}
InputStream sockInput = super.getInputStream();
conContext.inputRecord.setReceiverStream(sockInput);
OutputStream sockOutput = super.getOutputStream();
conContext.inputRecord.setDeliverStream(sockOutput);
conContext.outputRecord.setDeliverStream(sockOutput);
this.isConnected = true;
} finally {
socketLock.unlock();
}
}
private void useImplicitHost(boolean useNameService) {
// Note: If the local name service is not trustworthy, reverse
// host name resolution should not be performed for endpoint
// identification. Use the application original specified
// hostname or IP address instead.
// Get the original hostname via jdk.internal.misc.SharedSecrets
InetAddress inetAddress = getInetAddress();
if (inetAddress == null) { // not connected
return;
}
//JDK8
String originalHostname = HostNameAccessor.getOriginalHostName(inetAddress);
if ((originalHostname != null) &&
(originalHostname.length() != 0)) {
this.peerHost = originalHostname;
if (conContext.sslConfig.serverNames.isEmpty() &&
!conContext.sslConfig.noSniExtension) {
conContext.sslConfig.serverNames =
Utilities.addToSNIServerNameList(
conContext.sslConfig.serverNames, peerHost);
}
return;
}
// No explicitly specified hostname, no server name indication.
if (!useNameService) {
// The local name service is not trustworthy, use IP address.
this.peerHost = inetAddress.getHostAddress();
} else {
// Use the underlying reverse host name resolution service.
this.peerHost = getInetAddress().getHostName();
}
}
// ONLY used by HttpsClient to setup the URI specified hostname
//
// Please NOTE that this method MUST be called before calling to
// SSLSocket.setSSLParameters(). Otherwise, the {@code host} parameter
// may override SNIHostName in the customized server name indication.
public void setHost(String host) {
socketLock.lock();
try {
this.peerHost = host;
this.conContext.sslConfig.serverNames =
Utilities.addToSNIServerNameList(
conContext.sslConfig.serverNames, host);
} finally {
socketLock.unlock();
}
}
/**
* Handle an exception.
*
* This method is called by top level exception handlers (in read(),
* write()) to make sure we always shutdown the connection correctly
* and do not pass runtime exception to the application.
*
* This method never returns normally, it always throws an IOException.
*/
private void handleException(Exception cause) throws IOException {
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.warning("handling exception", cause);
}
// Don't close the Socket in case of timeouts or interrupts.
if (cause instanceof InterruptedIOException) {
throw (IOException)cause;
}
// need to perform error shutdown
boolean isSSLException = (cause instanceof SSLException);
Alert alert;
if (isSSLException) {
if (cause instanceof SSLHandshakeException) {
alert = Alert.HANDSHAKE_FAILURE;
} else {
alert = Alert.UNEXPECTED_MESSAGE;
}
} else {
if (cause instanceof IOException) {
alert = Alert.UNEXPECTED_MESSAGE;
} else {
// RuntimeException
alert = Alert.INTERNAL_ERROR;
}
}
throw conContext.fatal(alert, cause);
}
private Plaintext handleEOF(EOFException eofe) throws IOException {
if (requireCloseNotify || conContext.handshakeContext != null) {
SSLException ssle;
if (conContext.handshakeContext != null) {
ssle = new SSLHandshakeException(
"Remote host terminated the handshake");
} else {
ssle = new SSLProtocolException(
"Remote host terminated the connection");
}
if (eofe != null) {
ssle.initCause(eofe);
}
throw ssle;
} else {
// treat as if we had received a close_notify
conContext.isInputCloseNotified = true;
shutdownInput();
return Plaintext.PLAINTEXT_NULL;
}
}
@Override
public String getPeerHost() {
return peerHost;
}
@Override
public int getPeerPort() {
return getPort();
}
@Override
public boolean useDelegatedTask() {
return false;
}
@Override
public void shutdown() throws IOException {
if (!isClosed()) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.fine("close the underlying socket");
}
try {
if (conContext.isInputCloseNotified) {
// Close the connection, no wait for more peer response.
closeSocket(false);
} else {
// Close the connection, may wait for peer close_notify.
closeSocket(true);
}
} finally {
tlsIsClosed = true;
}
}
}
private void closeSocket(boolean selfInitiated) throws IOException {
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.fine("close the SSL connection " +
(selfInitiated ? "(initiative)" : "(passive)"));
}
if (autoClose || !isLayered()) {
super.close();
} else if (selfInitiated) {
if (!conContext.isInboundClosed() && !isInputShutdown()) {
// wait for close_notify alert to clear input stream.
waitForClose();
}
}
}
/**
* Wait for close_notify alert for a graceful closure.
*
* [RFC 5246] If the application protocol using TLS provides that any
* data may be carried over the underlying transport after the TLS
* connection is closed, the TLS implementation must receive the responding
* close_notify alert before indicating to the application layer that
* the TLS connection has ended. If the application protocol will not
* transfer any additional data, but will only close the underlying
* transport connection, then the implementation MAY choose to close the
* transport without waiting for the responding close_notify.
*/
private void waitForClose() throws IOException {
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.fine("wait for close_notify or alert");
}
while (!conContext.isInboundClosed()) {
try {
Plaintext plainText = decode(null);
// discard and continue
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.finest(
"discard plaintext while waiting for close", plainText);
}
} catch (Exception e) { // including RuntimeException
handleException(e);
}
}
}
}