|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
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.misc.JavaNetInetAddressAccess; |
|
import jdk.internal.misc.SharedSecrets; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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 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, 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); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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, 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); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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 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 { |
|
|
|
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 |
|
|
|
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 (IOException ioe) { |
|
conContext.fatal(Alert.HANDSHAKE_FAILURE, |
|
"Couldn't kickstart handshaking", ioe); |
|
} catch (Exception oe) { |
|
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 |
|
|
|
@Override |
|
public void close() throws IOException { |
|
if (tlsIsClosed) { |
|
return; |
|
} |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { |
|
SSLLogger.fine("duplex close of SSLSocket"); |
|
} |
|
|
|
try { |
|
|
|
if (!isOutputShutdown()) { |
|
duplexCloseOutput(); |
|
} |
|
|
|
|
|
if (!isInputShutdown()) { |
|
duplexCloseInput(); |
|
} |
|
|
|
if (!isClosed()) { |
|
|
|
closeSocket(false); |
|
} |
|
} catch (IOException ioe) { |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { |
|
SSLLogger.warning("SSLSocket duplex close failed", 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; |
|
} |
|
} |
|
|
|
// Need a lock here so that the user_canceled alert and the |
|
|
|
try { |
|
synchronized (conContext.outputRecord) { |
|
|
|
if (useUserCanceled) { |
|
conContext.warning(Alert.USER_CANCELED); |
|
} |
|
|
|
|
|
conContext.warning(Alert.CLOSE_NOTIFY); |
|
} |
|
} finally { |
|
if (!conContext.isOutboundClosed()) { |
|
conContext.outputRecord.close(); |
|
} |
|
|
|
if ((autoClose || !isLayered()) && !super.isOutputShutdown()) { |
|
super.shutdownOutput(); |
|
} |
|
} |
|
|
|
if (!isInputShutdown()) { |
|
bruteForceCloseInput(hasCloseReceipt); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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()) { |
|
conContext.inputRecord.close(); |
|
} |
|
|
|
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? |
|
// |
|
|
|
if (checkCloseNotify && !conContext.isInputCloseNotified && |
|
(conContext.isNegotiated || conContext.handshakeContext != null)) { |
|
|
|
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 |
|
|
|
@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 |
|
|
|
if (conContext.isNegotiated || conContext.isBroken || |
|
conContext.isInboundClosed() || |
|
conContext.isOutboundClosed()) { |
|
return; |
|
} |
|
|
|
startHandshake(); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private class AppInputStream extends InputStream { |
|
|
|
private final byte[] oneByte = new byte[1]; |
|
|
|
|
|
private ByteBuffer buffer; |
|
|
|
|
|
private volatile boolean appDataIsAvailable; |
|
|
|
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(); |
|
} |
|
|
|
|
|
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 |
|
|
|
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) { |
|
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; |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public synchronized long skip(long n) throws IOException { |
|
|
|
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) { |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { |
|
SSLLogger.warning("input stream close failed", ioe); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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(); |
|
} |
|
|
|
|
|
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) { |
|
|
|
conContext.fatal(Alert.HANDSHAKE_FAILURE, she); |
|
} catch (IOException e) { |
|
conContext.fatal(Alert.UNEXPECTED_MESSAGE, e); |
|
} |
|
|
|
// Is the sequence number is nearly overflow, or has the key usage |
|
|
|
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) { |
|
|
|
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<SSLSocket, List<String>, String> selector) { |
|
conContext.sslConfig.socketAPSelector = selector; |
|
} |
|
|
|
@Override |
|
public synchronized BiFunction<SSLSocket, List<String>, String> |
|
getHandshakeApplicationProtocolSelector() { |
|
return conContext.sslConfig.socketAPSelector; |
|
} |
|
|
|
|
|
|
|
*/ |
|
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 (IOException ioe) { |
|
if (!(ioe instanceof SSLException)) { |
|
throw new SSLException("readHandshakeRecord", ioe); |
|
} else { |
|
throw 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; |
|
synchronized (this) { |
|
plainText = decode(buffer); |
|
} |
|
if (plainText.contentType == ContentType.APPLICATION_DATA.id && |
|
buffer.position() > 0) { |
|
return buffer; |
|
} |
|
} catch (SSLException ssle) { |
|
throw ssle; |
|
} 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) { |
|
|
|
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(); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
synchronized void doneConnect() throws IOException { |
|
// In server mode, it is not necessary to set host and serverNames. |
|
|
|
if ((peerHost == null) || (peerHost.length() == 0)) { |
|
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; |
|
} |
|
|
|
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.length() != 0)) { |
|
|
|
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 synchronized void setHost(String host) { |
|
this.peerHost = host; |
|
this.conContext.sslConfig.serverNames = |
|
Utilities.addToSNIServerNameList( |
|
conContext.sslConfig.serverNames, host); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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; |
|
} |
|
} |
|
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) { |
|
|
|
closeSocket(false); |
|
} else { |
|
|
|
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()) { |
|
|
|
waitForClose(); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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); |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { |
|
SSLLogger.finest( |
|
"discard plaintext while waiting for close", plainText); |
|
} |
|
} catch (Exception e) { |
|
handleException(e); |
|
} |
|
} |
|
} |
|
} |