/* * Copyright (c) 1996, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package sun.security.ssl; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; import java.io.OutputStream; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketAddress; import java.net.SocketException; import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.util.List; import java.util.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. *
* 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; /* * 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 synchronized String[] getEnabledCipherSuites() {
return CipherSuite.namesOf(conContext.sslConfig.enabledCipherSuites);
}
@Override
public synchronized void setEnabledCipherSuites(String[] suites) {
conContext.sslConfig.enabledCipherSuites =
CipherSuite.validValuesOf(suites);
}
@Override
public String[] getSupportedProtocols() {
return ProtocolVersion.toStringArray(
sslContext.getSupportedProtocolVersions());
}
@Override
public synchronized String[] getEnabledProtocols() {
return ProtocolVersion.toStringArray(
conContext.sslConfig.enabledProtocols);
}
@Override
public synchronized void setEnabledProtocols(String[] protocols) {
if (protocols == null) {
throw new IllegalArgumentException("Protocols cannot be null");
}
conContext.sslConfig.enabledProtocols =
ProtocolVersion.namesOf(protocols);
}
@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 SSLSessionImpl.nullSession;
}
return conContext.conSession;
}
@Override
public synchronized SSLSession getHandshakeSession() {
if (conContext.handshakeContext != null) {
synchronized (this) {
if (conContext.handshakeContext != null) {
return conContext.handshakeContext.handshakeSession;
}
}
}
return null;
}
@Override
public synchronized void addHandshakeCompletedListener(
HandshakeCompletedListener listener) {
if (listener == null) {
throw new IllegalArgumentException("listener is null");
}
conContext.sslConfig.addHandshakeCompletedListener(listener);
}
@Override
public synchronized void removeHandshakeCompletedListener(
HandshakeCompletedListener listener) {
if (listener == null) {
throw new IllegalArgumentException("listener is null");
}
conContext.sslConfig.removeHandshakeCompletedListener(listener);
}
@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");
}
synchronized (conContext) { // handshake lock
// 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 (IOException ioe) {
throw conContext.fatal(Alert.HANDSHAKE_FAILURE,
"Couldn't kickstart handshaking", ioe);
} catch (Exception oe) { // including RuntimeException
handleException(oe);
}
}
}
@Override
public synchronized void setUseClientMode(boolean mode) {
conContext.setUseClientMode(mode);
}
@Override
public synchronized boolean getUseClientMode() {
return conContext.sslConfig.isClientMode;
}
@Override
public synchronized void setNeedClientAuth(boolean need) {
conContext.sslConfig.clientAuthType =
(need ? ClientAuthType.CLIENT_AUTH_REQUIRED :
ClientAuthType.CLIENT_AUTH_NONE);
}
@Override
public synchronized boolean getNeedClientAuth() {
return (conContext.sslConfig.clientAuthType ==
ClientAuthType.CLIENT_AUTH_REQUIRED);
}
@Override
public synchronized void setWantClientAuth(boolean want) {
conContext.sslConfig.clientAuthType =
(want ? ClientAuthType.CLIENT_AUTH_REQUESTED :
ClientAuthType.CLIENT_AUTH_NONE);
}
@Override
public synchronized boolean getWantClientAuth() {
return (conContext.sslConfig.clientAuthType ==
ClientAuthType.CLIENT_AUTH_REQUESTED);
}
@Override
public synchronized void setEnableSessionCreation(boolean flag) {
conContext.sslConfig.enableSessionCreation = flag;
}
@Override
public synchronized boolean getEnableSessionCreation() {
return conContext.sslConfig.enableSessionCreation;
}
@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()) {
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 synchronized InputStream getInputStream() throws IOException {
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;
}
private void ensureNegotiated() throws IOException {
if (conContext.isNegotiated || conContext.isBroken ||
conContext.isInboundClosed() || conContext.isOutboundClosed()) {
return;
}
synchronized (conContext) { // handshake lock
// double check the context status
if (conContext.isNegotiated || conContext.isBroken ||
conContext.isInboundClosed() ||
conContext.isOutboundClosed()) {
return;
}
startHandshake();
}
}
/**
* 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;
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");
}
// Read the available bytes at first.
//
// Note that the receiving and processing of post-handshake message
// are also synchronized with the read lock.
synchronized (this) {
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;
}
}
}
/**
* 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 synchronized long skip(long n) throws IOException {
// dummy array used to implement skip()
byte[] skipArray = new byte[256];
long skipped = 0;
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;
}
return skipped;
}
@Override
public void close() throws IOException {
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.finest("Closing input stream");
}
try {
shutdownInput(false);
} 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.isInboundClosed()) {
return true;
} else if (conContext.isInputCloseNotified || conContext.isBroken) {
if (conContext.closeReason == null) {
return true;
} else {
throw new SSLException(
"Connection has closed: " + conContext.closeReason,
conContext.closeReason);
}
}
return false;
}
}
@Override
public synchronized OutputStream getOutputStream() throws IOException {
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;
}
/**
* 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 (IOException e) {
throw conContext.fatal(Alert.UNEXPECTED_MESSAGE, e);
}
// 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 {
shutdownOutput();
} catch (IOException ioe) {
// ignore the exception
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
SSLLogger.warning("output stream close failed", ioe);
}
}
}
}
@Override
public synchronized SSLParameters getSSLParameters() {
return conContext.sslConfig.getSSLParameters();
}
@Override
public synchronized void setSSLParameters(SSLParameters params) {
conContext.sslConfig.setSSLParameters(params);
if (conContext.sslConfig.maximumPacketSize != 0) {
conContext.outputRecord.changePacketSize(
conContext.sslConfig.maximumPacketSize);
}
}
@Override
public synchronized String getApplicationProtocol() {
return conContext.applicationProtocol;
}
@Override
public synchronized String getHandshakeApplicationProtocol() {
if (conContext.handshakeContext != null) {
return conContext.handshakeContext.applicationProtocol;
}
return null;
}
@Override
public synchronized void setHandshakeApplicationProtocolSelector(
BiFunction