org.conscrypt.AbstractConscryptSocket Maven / Gradle / Ivy
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.conscrypt;
import static org.conscrypt.Preconditions.checkArgument;
import static org.conscrypt.Preconditions.checkNotNull;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InputStream;
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.channels.SocketChannel;
import java.security.PrivateKey;
import java.util.ArrayList;
import java.util.List;
import javax.net.ssl.HandshakeCompletedEvent;
import javax.net.ssl.HandshakeCompletedListener;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
/**
* Abstract base class for all Conscrypt {@link SSLSocket} classes.
*/
abstract class AbstractConscryptSocket extends SSLSocket {
final Socket socket;
private final boolean autoClose;
/**
* The peer's DNS hostname if it was supplied during creation. Note that
* this may be a raw IP address, so it should be checked before use with
* extensions that don't use it like Server Name Indication (SNI).
*/
private String peerHostname;
/**
* The peer's port if it was supplied during creation. Should only be set if
* {@link #peerHostname} is also set.
*/
private final int peerPort;
private final PeerInfoProvider peerInfoProvider = new PeerInfoProvider() {
@Override
String getHostname() {
return AbstractConscryptSocket.this.getHostname();
}
@Override
String getHostnameOrIP() {
return AbstractConscryptSocket.this.getHostnameOrIP();
}
@Override
int getPort() {
return AbstractConscryptSocket.this.getPort();
}
};
private final List listeners =
new ArrayList(2);
/**
* Local cache of timeout to avoid getsockopt on every read and
* write for non-wrapped sockets. Note that this is not used when delegating
* to another socket.
*/
private int readTimeoutMilliseconds;
AbstractConscryptSocket() throws IOException {
this.socket = this;
this.peerHostname = null;
this.peerPort = -1;
this.autoClose = false;
}
AbstractConscryptSocket(String hostname, int port) throws IOException {
super(hostname, port);
this.socket = this;
this.peerHostname = hostname;
this.peerPort = port;
this.autoClose = false;
}
AbstractConscryptSocket(InetAddress address, int port) throws IOException {
super(address, port);
this.socket = this;
this.peerHostname = null;
this.peerPort = -1;
this.autoClose = false;
}
AbstractConscryptSocket(String hostname, int port, InetAddress clientAddress, int clientPort)
throws IOException {
super(hostname, port, clientAddress, clientPort);
this.socket = this;
this.peerHostname = hostname;
this.peerPort = port;
this.autoClose = false;
}
AbstractConscryptSocket(InetAddress address, int port, InetAddress clientAddress,
int clientPort) throws IOException {
super(address, port, clientAddress, clientPort);
this.socket = this;
this.peerHostname = null;
this.peerPort = -1;
this.autoClose = false;
}
AbstractConscryptSocket(Socket socket, String hostname, int port, boolean autoClose)
throws IOException {
this.socket = checkNotNull(socket, "socket");
this.peerHostname = hostname;
this.peerPort = port;
this.autoClose = autoClose;
}
@Override
public final void connect(SocketAddress endpoint) throws IOException {
connect(endpoint, 0);
}
/**
* Try to extract the peer's hostname if it's available from the endpoint address.
*/
@Override
public final void connect(SocketAddress endpoint, int timeout) throws IOException {
if (peerHostname == null && endpoint instanceof InetSocketAddress) {
peerHostname =
Platform.getHostStringFromInetSocketAddress((InetSocketAddress) endpoint);
}
if (isDelegating()) {
socket.connect(endpoint, timeout);
} else {
super.connect(endpoint, timeout);
}
}
@Override
public void bind(SocketAddress bindpoint) throws IOException {
if (isDelegating()) {
socket.bind(bindpoint);
} else {
super.bind(bindpoint);
}
}
@Override
@SuppressWarnings("UnsynchronizedOverridesSynchronized")
public void close() throws IOException {
if (isDelegating()) {
if (autoClose && !socket.isClosed()) {
socket.close();
}
} else {
if (!super.isClosed()) {
super.close();
}
}
}
@Override
public InetAddress getInetAddress() {
if (isDelegating()) {
return socket.getInetAddress();
}
return super.getInetAddress();
}
@Override
public InetAddress getLocalAddress() {
if (isDelegating()) {
return socket.getLocalAddress();
}
return super.getLocalAddress();
}
@Override
public int getLocalPort() {
if (isDelegating()) {
return socket.getLocalPort();
}
return super.getLocalPort();
}
@Override
public SocketAddress getRemoteSocketAddress() {
if (isDelegating()) {
return socket.getRemoteSocketAddress();
}
return super.getRemoteSocketAddress();
}
@Override
public SocketAddress getLocalSocketAddress() {
if (isDelegating()) {
return socket.getLocalSocketAddress();
}
return super.getLocalSocketAddress();
}
@Override
public final int getPort() {
if (isDelegating()) {
return socket.getPort();
}
if (peerPort != -1) {
// Return the port that has been explicitly set in the constructor.
return peerPort;
}
return super.getPort();
}
@Override
public void addHandshakeCompletedListener(HandshakeCompletedListener listener) {
checkArgument(listener != null, "Provided listener is null");
listeners.add(listener);
}
@Override
public void removeHandshakeCompletedListener(HandshakeCompletedListener listener) {
checkArgument(listener != null, "Provided listener is null");
if (!listeners.remove(listener)) {
throw new IllegalArgumentException("Provided listener is not registered");
}
}
/* @Override */
public FileDescriptor getFileDescriptor$() {
if (isDelegating()) {
return Platform.getFileDescriptor(socket);
}
return Platform.getFileDescriptorFromSSLSocket(this);
}
@Override
@SuppressWarnings("UnsynchronizedOverridesSynchronized")
public final void setSoTimeout(int readTimeoutMilliseconds) throws SocketException {
if (isDelegating()) {
socket.setSoTimeout(readTimeoutMilliseconds);
} else {
super.setSoTimeout(readTimeoutMilliseconds);
this.readTimeoutMilliseconds = readTimeoutMilliseconds;
}
}
@Override
@SuppressWarnings("UnsynchronizedOverridesSynchronized")
public final int getSoTimeout() throws SocketException {
if (isDelegating()) {
return socket.getSoTimeout();
}
return readTimeoutMilliseconds;
}
@Override
public final void sendUrgentData(int data) throws IOException {
throw new SocketException("Method sendUrgentData() is not supported.");
}
@Override
public final void setOOBInline(boolean on) throws SocketException {
throw new SocketException("Method setOOBInline() is not supported.");
}
@Override
public boolean getOOBInline() throws SocketException {
return false;
}
@Override
public SocketChannel getChannel() {
// TODO(nmittler): Support channels?
return null;
}
@Override
public InputStream getInputStream() throws IOException {
if (isDelegating()) {
return socket.getInputStream();
}
return super.getInputStream();
}
@Override
public OutputStream getOutputStream() throws IOException {
if (isDelegating()) {
return socket.getOutputStream();
}
return super.getOutputStream();
}
@Override
public void setTcpNoDelay(boolean on) throws SocketException {
if (isDelegating()) {
socket.setTcpNoDelay(on);
} else {
super.setTcpNoDelay(on);
}
}
@Override
public boolean getTcpNoDelay() throws SocketException {
if (isDelegating()) {
return socket.getTcpNoDelay();
}
return super.getTcpNoDelay();
}
@Override
public void setSoLinger(boolean on, int linger) throws SocketException {
if (isDelegating()) {
socket.setSoLinger(on, linger);
} else {
super.setSoLinger(on, linger);
}
}
@Override
public int getSoLinger() throws SocketException {
if (isDelegating()) {
return socket.getSoLinger();
}
return super.getSoLinger();
}
@Override
@SuppressWarnings("UnsynchronizedOverridesSynchronized")
public void setSendBufferSize(int size) throws SocketException {
if (isDelegating()) {
socket.setSendBufferSize(size);
} else {
super.setSendBufferSize(size);
}
}
@Override
@SuppressWarnings("UnsynchronizedOverridesSynchronized")
public int getSendBufferSize() throws SocketException {
if (isDelegating()) {
return socket.getSendBufferSize();
}
return super.getSendBufferSize();
}
@Override
@SuppressWarnings("UnsynchronizedOverridesSynchronized")
public void setReceiveBufferSize(int size) throws SocketException {
if (isDelegating()) {
socket.setReceiveBufferSize(size);
} else {
super.setReceiveBufferSize(size);
}
}
@Override
@SuppressWarnings("UnsynchronizedOverridesSynchronized")
public int getReceiveBufferSize() throws SocketException {
if (isDelegating()) {
return socket.getReceiveBufferSize();
}
return super.getReceiveBufferSize();
}
@Override
public void setKeepAlive(boolean on) throws SocketException {
if (isDelegating()) {
socket.setKeepAlive(on);
} else {
super.setKeepAlive(on);
}
}
@Override
public boolean getKeepAlive() throws SocketException {
if (isDelegating()) {
return socket.getKeepAlive();
}
return super.getKeepAlive();
}
@Override
public void setTrafficClass(int tc) throws SocketException {
if (isDelegating()) {
socket.setTrafficClass(tc);
} else {
super.setTrafficClass(tc);
}
}
@Override
public int getTrafficClass() throws SocketException {
if (isDelegating()) {
return socket.getTrafficClass();
}
return super.getTrafficClass();
}
@Override
public void setReuseAddress(boolean on) throws SocketException {
if (isDelegating()) {
socket.setReuseAddress(on);
} else {
super.setReuseAddress(on);
}
}
@Override
public boolean getReuseAddress() throws SocketException {
if (isDelegating()) {
return socket.getReuseAddress();
}
return super.getReuseAddress();
}
@Override
public void shutdownInput() throws IOException {
if (isDelegating()) {
socket.shutdownInput();
} else {
super.shutdownInput();
}
}
@Override
public void shutdownOutput() throws IOException {
if (isDelegating()) {
socket.shutdownOutput();
} else {
super.shutdownOutput();
}
}
@Override
public boolean isConnected() {
if (isDelegating()) {
return socket.isConnected();
}
return super.isConnected();
}
@Override
public boolean isBound() {
if (isDelegating()) {
return socket.isBound();
}
return super.isBound();
}
@Override
public boolean isClosed() {
if (isDelegating()) {
return socket.isClosed();
}
return super.isClosed();
}
@Override
public boolean isInputShutdown() {
if (isDelegating()) {
return socket.isInputShutdown();
}
return super.isInputShutdown();
}
@Override
public boolean isOutputShutdown() {
if (isDelegating()) {
return socket.isOutputShutdown();
}
return super.isOutputShutdown();
}
@Override
public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) {
if (isDelegating()) {
socket.setPerformancePreferences(connectionTime, latency, bandwidth);
} else {
super.setPerformancePreferences(connectionTime, latency, bandwidth);
}
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder("SSL socket over ");
if (isDelegating()) {
builder.append(socket.toString());
} else {
builder.append(super.toString());
}
return builder.toString();
}
/**
* Returns the hostname that was supplied during socket creation. No DNS resolution is
* attempted before returning the hostname.
*/
String getHostname() {
return peerHostname;
}
/**
* This method enables Server Name Indication
*
* @param hostname the desired SNI hostname, or null to disable
*/
void setHostname(String hostname) {
peerHostname = hostname;
}
/**
* For the purposes of an SSLSession, we want a way to represent the supplied hostname
* or the IP address in a textual representation. We do not want to perform reverse DNS
* lookups on this address.
*/
String getHostnameOrIP() {
if (peerHostname != null) {
return peerHostname;
}
InetAddress peerAddress = getInetAddress();
if (peerAddress != null) {
return Platform.getOriginalHostNameFromInetAddress(peerAddress);
}
return null;
}
/**
* Note write timeouts are not part of the javax.net.ssl.SSLSocket API
*/
void setSoWriteTimeout(int writeTimeoutMilliseconds) throws SocketException {
throw new SocketException("Method setSoWriteTimeout() is not supported.");
}
/**
* Note write timeouts are not part of the javax.net.ssl.SSLSocket API
*/
int getSoWriteTimeout() throws SocketException {
return 0;
}
/**
* Set the handshake timeout on this socket. This timeout is specified in
* milliseconds and will be used only during the handshake process.
*/
void setHandshakeTimeout(int handshakeTimeoutMilliseconds) throws SocketException {
throw new SocketException("Method setHandshakeTimeout() is not supported.");
}
final void checkOpen() throws SocketException {
if (isClosed()) {
throw new SocketException("Socket is closed");
}
}
final PeerInfoProvider peerInfoProvider() {
return peerInfoProvider;
}
/**
* Called by {@link #notifyHandshakeCompletedListeners()} to get the currently active session.
* Unlike {@link #getSession()}, this method must not block.
*/
abstract SSLSession getActiveSession();
abstract void setApplicationProtocolSelector(ApplicationProtocolSelectorAdapter selector);
final void notifyHandshakeCompletedListeners() {
if (listeners != null && !listeners.isEmpty()) {
// notify the listeners
HandshakeCompletedEvent event = new HandshakeCompletedEvent(this, getActiveSession());
for (HandshakeCompletedListener listener : listeners) {
try {
listener.handshakeCompleted(event);
} catch (RuntimeException e) {
// The RI runs the handlers in a separate thread,
// which we do not. But we try to preserve their
// behavior of logging a problem and not killing
// the handshaking thread just because a listener
// has a problem.
Thread thread = Thread.currentThread();
thread.getUncaughtExceptionHandler().uncaughtException(thread, e);
}
}
}
}
private boolean isDelegating() {
// Checking for null to handle the case of calling virtual methods in the super class
// constructor.
return socket != null && socket != this;
}
/* @Override */
@SuppressWarnings("MissingOverride") // For compilation with Java 6.
public abstract SSLSession getHandshakeSession();
/**
* This method enables session ticket support.
*
* @param useSessionTickets True to enable session tickets
*/
abstract void setUseSessionTickets(boolean useSessionTickets);
/**
* Enables/disables TLS Channel ID for this server socket.
*
* This method needs to be invoked before the handshake starts.
*
* @throws IllegalStateException if this is a client socket or if the handshake has already
* started.
*/
abstract void setChannelIdEnabled(boolean enabled);
/**
* Gets the TLS Channel ID for this server socket. Channel ID is only available once the
* handshake completes.
*
* @return channel ID or {@code null} if not available.
*
* @throws IllegalStateException if this is a client socket or if the handshake has not yet
* completed.
* @throws SSLException if channel ID is available but could not be obtained.
*/
abstract byte[] getChannelId() throws SSLException;
/**
* Sets the {@link PrivateKey} to be used for TLS Channel ID by this client socket.
*
*
This method needs to be invoked before the handshake starts.
*
* @param privateKey private key (enables TLS Channel ID) or {@code null} for no key (disables
* TLS Channel ID). The private key must be an Elliptic Curve (EC) key based on the NIST
* P-256 curve (aka SECG secp256r1 or ANSI X9.62 prime256v1).
*
* @throws IllegalStateException if this is a server socket or if the handshake has already
* started.
*/
abstract void setChannelIdPrivateKey(PrivateKey privateKey);
/**
* Returns null always for backward compatibility.
* @deprecated NPN is not supported
*/
@Deprecated
byte[] getNpnSelectedProtocol() {
return null;
}
/**
* This method does nothing and is kept for backward compatibility.
* @deprecated NPN is not supported
*/
@Deprecated
void setNpnProtocols(byte[] npnProtocols) {}
/**
* Returns the protocol agreed upon by client and server, or {@code null} if
* no protocol was agreed upon.
*
* @deprecated use {@link #getApplicationProtocol()} instead.
*/
@Deprecated
abstract byte[] getAlpnSelectedProtocol();
/**
* Sets the list of ALPN protocols. This method internally converts the protocols to their
* wire-format form.
*
* @param alpnProtocols the list of ALPN protocols
* @deprecated use {@link #setApplicationProtocols(String[])} instead.
*/
@Deprecated
abstract void setAlpnProtocols(String[] alpnProtocols);
/**
* Alternate version of {@link #setAlpnProtocols(String[])} that directly sets the list of
* ALPN in the wire-format form used by BoringSSL (length-prefixed 8-bit strings).
* Requires that all strings be encoded with US-ASCII.
*
* @param alpnProtocols the encoded form of the ALPN protocol list
* @deprecated Use {@link #setApplicationProtocols(String[])} instead.
*/
@Deprecated
abstract void setAlpnProtocols(byte[] alpnProtocols);
/**
* Sets the list of ALPN protocols.
*
* @param protocols the list of ALPN protocols
*/
@SuppressWarnings("MissingOverride") // For compiling pre Java 9.
abstract void setApplicationProtocols(String[] protocols);
/**
* Returns the list of supported ALPN protocols.
*/
@SuppressWarnings("MissingOverride") // For compiling pre Java 9.
abstract String[] getApplicationProtocols();
@SuppressWarnings("MissingOverride") // For compiling pre Java 9.
public abstract String getApplicationProtocol();
@SuppressWarnings("MissingOverride") // For compiling pre Java 9.
public abstract String getHandshakeApplicationProtocol();
/**
* Sets an application-provided ALPN protocol selector. If provided, this will override
* the list of protocols set by {@link #setApplicationProtocols(String[])}.
*/
abstract void setApplicationProtocolSelector(ApplicationProtocolSelector selector);
/**
* Returns the tls-unique channel binding value for this connection, per RFC 5929. This
* will return {@code null} if there is no such value available, such as if the handshake
* has not yet completed or this connection is closed.
*/
abstract byte[] getTlsUnique();
/**
* Exports a value derived from the TLS master secret as described in RFC 5705.
*
* @param label the label to use in calculating the exported value. This must be
* an ASCII-only string.
* @param context the application-specific context value to use in calculating the
* exported value. This may be {@code null} to use no application context, which is
* treated differently than an empty byte array.
* @param length the number of bytes of keying material to return.
* @return a value of the specified length, or {@code null} if the handshake has not yet
* completed or the connection has been closed.
* @throws SSLException if the value could not be exported.
*/
abstract byte[] exportKeyingMaterial(String label, byte[] context, int length)
throws SSLException;
}