|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
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; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static final boolean trustNameService = |
|
Utilities.getBooleanProperty("jdk.tls.trustNameService", false); |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
SSLSocketImpl(SSLContextImpl sslContext) { |
|
super(); |
|
this.sslContext = sslContext; |
|
HandshakeHash handshakeHash = new HandshakeHash(); |
|
this.conContext = new TransportContext(sslContext, this, |
|
new SSLSocketInputRecord(handshakeHash), |
|
new SSLSocketOutputRecord(handshakeHash), true); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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)); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
SSLSocketImpl(SSLContextImpl sslContext, Socket sock, |
|
InputStream consumed, boolean autoClose) throws IOException { |
|
super(sock, consumed); |
|
|
|
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(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
SSLSocketImpl(SSLContextImpl sslContext, Socket sock, |
|
String peerHost, int port, boolean autoClose) throws IOException { |
|
super(sock); |
|
|
|
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 { |
|
|
|
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 { |
|
|
|
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. |
|
// |
|
|
|
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) { |
|
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 |
|
|
|
@Override |
|
public void close() throws IOException { |
|
if (isClosed()) { |
|
return; |
|
} |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { |
|
SSLLogger.fine("duplex close of SSLSocket"); |
|
} |
|
|
|
try { |
|
if (isConnected()) { |
|
|
|
if (!isOutputShutdown()) { |
|
duplexCloseOutput(); |
|
} |
|
|
|
|
|
if (!isInputShutdown()) { |
|
duplexCloseInput(); |
|
} |
|
} |
|
} catch (IOException ioe) { |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { |
|
SSLLogger.warning("SSLSocket duplex close failed. Debug info only. Exception details:", ioe); |
|
} |
|
} finally { |
|
if (!isClosed()) { |
|
|
|
try { |
|
closeSocket(false); |
|
} catch (IOException ioe) { |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { |
|
SSLLogger.warning("SSLSocket close failed. Debug info only. Exception details:", ioe); |
|
} |
|
} finally { |
|
tlsIsClosed = true; |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private void duplexCloseOutput() throws IOException { |
|
boolean useUserCanceled = false; |
|
boolean hasCloseReceipt = false; |
|
if (conContext.isNegotiated) { |
|
if (!conContext.protocolVersion.useTLS13PlusSpec()) { |
|
hasCloseReceipt = true; |
|
} else { |
|
|
|
useUserCanceled = true; |
|
} |
|
} else if (conContext.handshakeContext != null) { // initial handshake |
|
|
|
useUserCanceled = true; |
|
|
|
|
|
ProtocolVersion pv = conContext.handshakeContext.negotiatedProtocol; |
|
if (pv == null || (!pv.useTLS13PlusSpec())) { |
|
hasCloseReceipt = true; |
|
} |
|
} |
|
|
|
|
|
closeNotify(useUserCanceled); |
|
|
|
if (!isInputShutdown()) { |
|
bruteForceCloseInput(hasCloseReceipt); |
|
} |
|
} |
|
|
|
void closeNotify(boolean useUserCanceled) throws IOException { |
|
// Need a lock here so that the user_canceled alert and the |
|
|
|
int linger = getSoLinger(); |
|
if (linger >= 0) { |
|
// don't wait more than SO_LINGER for obtaining the |
|
// the lock. |
|
// |
|
|
|
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 |
|
|
|
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) { |
|
|
|
interrupted = true; |
|
} |
|
|
|
|
|
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 { |
|
|
|
if (useUserCanceled) { |
|
conContext.warning(Alert.USER_CANCELED); |
|
} |
|
|
|
|
|
conContext.warning(Alert.CLOSE_NOTIFY); |
|
} finally { |
|
if (!conContext.isOutboundClosed()) { |
|
conContext.outputRecord.close(); |
|
} |
|
|
|
if (!super.isOutputShutdown() && |
|
(autoClose || !isLayered())) { |
|
super.shutdownOutput(); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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 |
|
|
|
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 |
|
|
|
appInput.deplete(); |
|
} |
|
} |
|
|
|
if ((autoClose || !isLayered()) && !super.isInputShutdown()) { |
|
super.shutdownInput(); |
|
} |
|
} |
|
} |
|
|
|
// Please don't synchronized this method. Otherwise, the read and close |
|
|
|
@Override |
|
public void shutdownInput() throws IOException { |
|
shutdownInput(true); |
|
} |
|
|
|
// It is not required to check the close_notify receipt unless an |
|
|
|
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? |
|
// |
|
|
|
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 synchronized this method. Otherwise, the read and close |
|
|
|
@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 { |
|
|
|
if (conContext.isNegotiated || conContext.isBroken || |
|
conContext.isInboundClosed() || |
|
conContext.isOutboundClosed()) { |
|
return; |
|
} |
|
|
|
startHandshake(resumable); |
|
} finally { |
|
handshakeLock.unlock(); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private class AppInputStream extends InputStream { |
|
|
|
private final byte[] oneByte = new byte[1]; |
|
|
|
|
|
private ByteBuffer buffer; |
|
|
|
|
|
private volatile boolean appDataIsAvailable; |
|
|
|
|
|
private final ReentrantLock readLock = new ReentrantLock(); |
|
|
|
|
|
private volatile boolean isClosing; |
|
private volatile boolean hasDepleted; |
|
|
|
AppInputStream() { |
|
this.appDataIsAvailable = false; |
|
this.buffer = ByteBuffer.allocate(4096); |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public int available() throws IOException { |
|
|
|
if ((!appDataIsAvailable) || checkEOF()) { |
|
return 0; |
|
} |
|
|
|
return buffer.remaining(); |
|
} |
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public int read() throws IOException { |
|
int n = read(oneByte, 0, 1); |
|
if (n <= 0) { |
|
return -1; |
|
} |
|
|
|
return oneByte[0] & 0xFF; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@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; |
|
} |
|
|
|
|
|
if (!conContext.isNegotiated && !conContext.isBroken && |
|
!conContext.isInboundClosed() && |
|
!conContext.isOutboundClosed()) { |
|
ensureNegotiated(true); |
|
} |
|
|
|
|
|
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 |
|
|
|
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 |
|
|
|
readLock.lock(); |
|
try { |
|
|
|
if (conContext.isBroken || conContext.isInboundClosed()) { |
|
throw new SocketException( |
|
"Connection or inbound has closed"); |
|
} |
|
|
|
|
|
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) { |
|
return -1; |
|
} else { |
|
|
|
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 |
|
|
|
handleException(e); |
|
|
|
|
|
return -1; |
|
} |
|
} finally { |
|
// Check if the input stream is closing. |
|
// |
|
// If the deplete() did not hold the lock, clean up the |
|
|
|
try { |
|
if (isClosing) { |
|
readLockedDeplete(); |
|
} |
|
} finally { |
|
readLock.unlock(); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public long skip(long n) throws IOException { |
|
|
|
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) { |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { |
|
SSLLogger.warning("input stream close failed. Debug info only. Exception details:", ioe); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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; |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private void deplete() { |
|
if (conContext.isInboundClosed() || isClosing) { |
|
return; |
|
} |
|
|
|
isClosing = true; |
|
if (readLock.tryLock()) { |
|
try { |
|
readLockedDeplete(); |
|
} finally { |
|
readLock.unlock(); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private void readLockedDeplete() { |
|
|
|
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(); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private class AppOutputStream extends OutputStream { |
|
|
|
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; |
|
} |
|
|
|
|
|
if (!conContext.isNegotiated && !conContext.isBroken && |
|
!conContext.isInboundClosed() && |
|
!conContext.isOutboundClosed()) { |
|
ensureNegotiated(true); |
|
} |
|
|
|
|
|
if (!conContext.isNegotiated || |
|
conContext.isBroken || conContext.isOutboundClosed()) { |
|
throw new SocketException("Connection or outbound has closed"); |
|
} |
|
|
|
// |
|
|
|
|
|
try { |
|
conContext.outputRecord.deliver(b, off, len); |
|
} catch (SSLHandshakeException she) { |
|
|
|
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 |
|
|
|
if (conContext.outputRecord.seqNumIsHuge() || |
|
conContext.outputRecord.writeCipher.atKeyLimit()) { |
|
tryKeyUpdate(); |
|
} |
|
|
|
if (conContext.conSession.updateNST) { |
|
conContext.conSession.updateNST = false; |
|
tryNewSessionTicket(); |
|
} |
|
} |
|
|
|
@Override |
|
public void close() throws IOException { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { |
|
SSLLogger.finest("Closing output stream"); |
|
} |
|
|
|
try { |
|
SSLSocketImpl.this.close(); |
|
} catch (IOException ioe) { |
|
|
|
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(); |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
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 |
|
|
|
throw se; |
|
} catch (IOException ioe) { |
|
throw new SSLException("readHandshakeRecord", ioe); |
|
} |
|
} |
|
|
|
return -1; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private ByteBuffer readApplicationRecord( |
|
ByteBuffer buffer) throws IOException { |
|
while (!conContext.isInboundClosed()) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
buffer.clear(); |
|
int inLen = conContext.inputRecord.bytesInCompletePacket(); |
|
if (inLen < 0) { |
|
handleEOF(null); |
|
|
|
|
|
return null; |
|
} |
|
|
|
|
|
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 |
|
|
|
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) { |
|
|
|
plainText = handleEOF(eofe); |
|
} |
|
|
|
|
|
if (plainText != Plaintext.PLAINTEXT_NULL && |
|
(conContext.inputRecord.seqNumIsHuge() || |
|
conContext.inputRecord.readCipher.atKeyLimit())) { |
|
tryKeyUpdate(); |
|
} |
|
|
|
return plainText; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private void tryKeyUpdate() throws IOException { |
|
// Don't bother to kickstart if handshaking is in progress, or if the |
|
|
|
if ((conContext.handshakeContext == null) && |
|
!conContext.isOutboundClosed() && |
|
!conContext.isInboundClosed() && |
|
!conContext.isBroken) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { |
|
SSLLogger.finest("trigger key update"); |
|
} |
|
startHandshake(); |
|
} |
|
} |
|
|
|
// Try to generate a PostHandshake NewSessionTicket message. This is |
|
|
|
private void tryNewSessionTicket() throws IOException { |
|
// Don't bother to kickstart if handshaking is in progress, or if the |
|
|
|
if (!conContext.sslConfig.isClientMode && |
|
conContext.protocolVersion.useTLS13PlusSpec() && |
|
conContext.handshakeContext == null && |
|
!conContext.isOutboundClosed() && |
|
!conContext.isInboundClosed() && |
|
!conContext.isBroken) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { |
|
SSLLogger.finest("trigger new session ticket"); |
|
} |
|
NewSessionTicket.t13PosthandshakeProducer.produce( |
|
new PostHandshakeContext(conContext)); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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 |
|
|
|
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. |
|
|
|
|
|
InetAddress inetAddress = getInetAddress(); |
|
if (inetAddress == null) { |
|
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; |
|
} |
|
|
|
|
|
if (!useNameService) { |
|
|
|
this.peerHost = inetAddress.getHostAddress(); |
|
} else { |
|
|
|
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 |
|
|
|
public void setHost(String host) { |
|
socketLock.lock(); |
|
try { |
|
this.peerHost = host; |
|
this.conContext.sslConfig.serverNames = |
|
Utilities.addToSNIServerNameList( |
|
conContext.sslConfig.serverNames, host); |
|
} finally { |
|
socketLock.unlock(); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private void handleException(Exception cause) throws IOException { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { |
|
SSLLogger.warning("handling exception", cause); |
|
} |
|
|
|
|
|
if (cause instanceof InterruptedIOException) { |
|
throw (IOException)cause; |
|
} |
|
|
|
|
|
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 { |
|
|
|
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) { |
|
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 { |
|
|
|
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, |
|
|
|
closeSocket(conContext.isNegotiated && |
|
!conContext.isInputCloseNotified); |
|
} finally { |
|
tlsIsClosed = true; |
|
} |
|
} |
|
} |
|
|
|
@Override |
|
public String toString() { |
|
return "SSLSocket[" + |
|
"hostname=" + getPeerHost() + |
|
", port=" + getPeerPort() + |
|
", " + conContext.conSession + |
|
"]"; |
|
} |
|
|
|
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()) { |
|
|
|
if (conContext.inputRecord instanceof |
|
SSLSocketInputRecord inputRecord && isConnected) { |
|
if (appInput.readLock.tryLock()) { |
|
try { |
|
inputRecord.deplete(false); |
|
} finally { |
|
appInput.readLock.unlock(); |
|
} |
|
} |
|
} |
|
|
|
super.close(); |
|
} else if (selfInitiated) { |
|
if (!conContext.isInboundClosed() && !isInputShutdown()) { |
|
|
|
waitForClose(); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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); |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { |
|
SSLLogger.finest( |
|
"discard plaintext while waiting for close", |
|
plainText); |
|
} |
|
} catch (Exception e) { |
|
handleException(e); |
|
} |
|
} |
|
} finally { |
|
appInput.readLock.unlock(); |
|
} |
|
} |
|
} |