/* * Copyright (C) 2007 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.apache.harmony.xnet.provider.jsse; import dalvik.system.BlockGuard; import dalvik.system.CloseGuard; import java.io.FileDescriptor; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetAddress; import java.net.Socket; import java.net.SocketException; import java.security.PrivateKey; import java.security.SecureRandom; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Logger; import javax.net.ssl.HandshakeCompletedEvent; import javax.net.ssl.HandshakeCompletedListener; import javax.net.ssl.SSLException; import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLSession; import javax.net.ssl.X509TrustManager; import javax.security.auth.x500.X500Principal; import org.apache.harmony.security.provider.cert.X509CertImpl; /** * Implementation of the class OpenSSLSocketImpl based on OpenSSL. *
* This class only supports SSLv3 and TLSv1. This should be documented elsewhere * later, for example in the package.html or a separate reference document. *
* Extensions to SSLSocket include: *
IOException
if network fails
*/
@Override
public void startHandshake() throws IOException {
startHandshake(true);
}
/**
* Checks whether the socket is closed, and throws an exception.
*
* @throws SocketException
* if the socket is closed.
*/
private void checkOpen() throws SocketException {
if (isClosed()) {
throw new SocketException("Socket is closed");
}
}
/**
* Perform the handshake
* @param full If true, disable handshake cutthrough for a fully synchronous handshake
*/
public synchronized void startHandshake(boolean full) throws IOException {
synchronized (handshakeLock) {
checkOpen();
if (!handshakeStarted) {
handshakeStarted = true;
} else {
return;
}
}
// note that this modifies the global seed, not something specific to the connection
final int seedLengthInBytes = NativeCrypto.RAND_SEED_LENGTH_IN_BYTES;
final SecureRandom secureRandom = sslParameters.getSecureRandomMember();
if (secureRandom == null) {
NativeCrypto.RAND_load_file("/dev/urandom", seedLengthInBytes);
} else {
NativeCrypto.RAND_seed(secureRandom.generateSeed(seedLengthInBytes));
}
final boolean client = sslParameters.getUseClientMode();
final int sslCtxNativePointer = (client) ?
sslParameters.getClientSessionContext().sslCtxNativePointer :
sslParameters.getServerSessionContext().sslCtxNativePointer;
this.sslNativePointer = 0;
boolean exception = true;
try {
sslNativePointer = NativeCrypto.SSL_new(sslCtxNativePointer);
guard.open("close");
// setup server certificates and private keys.
// clients will receive a call back to request certificates.
if (!client) {
SetIOException
if an I/O error occurs when creating
* the input stream, the socket is closed, the socket is not
* connected, or the socket input has been shutdown.
*/
@Override
public InputStream getInputStream() throws IOException {
checkOpen();
synchronized (this) {
if (is == null) {
is = new SSLInputStream();
}
return is;
}
}
/**
* Returns an output stream for this SSL socket using native calls to the
* OpenSSL library.
*
* @return an output stream for writing bytes to this socket.
* @throws IOException
if an I/O error occurs when creating
* the output stream, or no connection to the socket exists.
*/
@Override
public OutputStream getOutputStream() throws IOException {
checkOpen();
synchronized (this) {
if (os == null) {
os = new SSLOutputStream();
}
return os;
}
}
/**
* This method is not supported for this SSLSocket implementation
* because reading from an SSLSocket may involve writing to the
* network.
*/
@Override
public void shutdownInput() throws IOException {
throw new UnsupportedOperationException();
}
/**
* This method is not supported for this SSLSocket implementation
* because writing to an SSLSocket may involve reading from the
* network.
*/
@Override
public void shutdownOutput() throws IOException {
throw new UnsupportedOperationException();
}
/**
* This inner class provides input data stream functionality
* for the OpenSSL native implementation. It is used to
* read data received via SSL protocol.
*/
private class SSLInputStream extends InputStream {
SSLInputStream() throws IOException {
/**
/* Note: When startHandshake() throws an exception, no
* SSLInputStream object will be created.
*/
OpenSSLSocketImpl.this.startHandshake(false);
}
/**
* Reads one byte. If there is no data in the underlying buffer,
* this operation can block until the data will be
* available.
* @return read value.
* @throws IOException
*/
@Override
public int read() throws IOException {
BlockGuard.getThreadPolicy().onNetwork();
synchronized (readLock) {
checkOpen();
return NativeCrypto.SSL_read_byte(sslNativePointer, socket.getFileDescriptor$(),
OpenSSLSocketImpl.this, getSoTimeout());
}
}
/**
* Method acts as described in spec for superclass.
* @see java.io.InputStream#read(byte[],int,int)
*/
@Override
public int read(byte[] buf, int offset, int byteCount) throws IOException {
BlockGuard.getThreadPolicy().onNetwork();
synchronized (readLock) {
checkOpen();
Arrays.checkOffsetAndCount(buf.length, offset, byteCount);
if (byteCount == 0) {
return 0;
}
return NativeCrypto.SSL_read(sslNativePointer, socket.getFileDescriptor$(),
OpenSSLSocketImpl.this, buf, offset, byteCount, getSoTimeout());
}
}
}
/**
* This inner class provides output data stream functionality
* for the OpenSSL native implementation. It is used to
* write data according to the encryption parameters given in SSL context.
*/
private class SSLOutputStream extends OutputStream {
SSLOutputStream() throws IOException {
/**
/* Note: When startHandshake() throws an exception, no
* SSLOutputStream object will be created.
*/
OpenSSLSocketImpl.this.startHandshake(false);
}
/**
* Method acts as described in spec for superclass.
* @see java.io.OutputStream#write(int)
*/
@Override
public void write(int b) throws IOException {
BlockGuard.getThreadPolicy().onNetwork();
synchronized (writeLock) {
checkOpen();
NativeCrypto.SSL_write_byte(sslNativePointer, socket.getFileDescriptor$(),
OpenSSLSocketImpl.this, b);
}
}
/**
* Method acts as described in spec for superclass.
* @see java.io.OutputStream#write(byte[],int,int)
*/
@Override
public void write(byte[] buf, int offset, int byteCount) throws IOException {
BlockGuard.getThreadPolicy().onNetwork();
synchronized (writeLock) {
checkOpen();
Arrays.checkOffsetAndCount(buf.length, offset, byteCount);
if (byteCount == 0) {
return;
}
NativeCrypto.SSL_write(sslNativePointer, socket.getFileDescriptor$(),
OpenSSLSocketImpl.this, buf, offset, byteCount);
}
}
}
/**
* The SSL session used by this connection is returned. The SSL session
* determines which cipher suite should be used by all connections within
* that session and which identities have the session's client and server.
* This method starts the SSL handshake.
* @return the SSLSession.
* @throws IOException
if the handshake fails
*/
@Override
public SSLSession getSession() {
if (sslSession == null) {
try {
startHandshake(true);
} catch (IOException e) {
// return an invalid session with
// invalid cipher suite of "SSL_NULL_WITH_NULL_NULL"
return SSLSessionImpl.NULL_SESSION;
}
}
return sslSession;
}
/**
* Registers a listener to be notified that a SSL handshake
* was successfully completed on this connection.
* @throws IllegalArgumentException
if listener is null.
*/
@Override
public void addHandshakeCompletedListener(
HandshakeCompletedListener listener) {
if (listener == null) {
throw new IllegalArgumentException("Provided listener is null");
}
if (listeners == null) {
listeners = new ArrayList();
}
listeners.add(listener);
}
/**
* The method removes a registered listener.
* @throws IllegalArgumentException if listener is null or not registered
*/
@Override
public void removeHandshakeCompletedListener(
HandshakeCompletedListener listener) {
if (listener == null) {
throw new IllegalArgumentException("Provided listener is null");
}
if (listeners == null) {
throw new IllegalArgumentException(
"Provided listener is not registered");
}
if (!listeners.remove(listener)) {
throw new IllegalArgumentException(
"Provided listener is not registered");
}
}
/**
* Returns true if new SSL sessions may be established by this socket.
*
* @return true if the session may be created; false if a session already
* exists and must be resumed.
*/
@Override
public boolean getEnableSessionCreation() {
return sslParameters.getEnableSessionCreation();
}
/**
* Set a flag for the socket to inhibit or to allow the creation of a new
* SSL sessions. If the flag is set to false, and there are no actual
* sessions to resume, then there will be no successful handshaking.
*
* @param flag true if session may be created; false
* if a session already exists and must be resumed.
*/
@Override
public void setEnableSessionCreation(boolean flag) {
sslParameters.setEnableSessionCreation(flag);
}
/**
* The names of the cipher suites which could be used by the SSL connection
* are returned.
* @return an array of cipher suite names
*/
@Override
public String[] getSupportedCipherSuites() {
return NativeCrypto.getSupportedCipherSuites();
}
/**
* The names of the cipher suites that are in use in the actual the SSL
* connection are returned.
*
* @return an array of cipher suite names
*/
@Override
public String[] getEnabledCipherSuites() {
return enabledCipherSuites.clone();
}
/**
* This method enables the cipher suites listed by
* getSupportedCipherSuites().
*
* @param suites names of all the cipher suites to
* put on use
* @throws IllegalArgumentException when one or more of the
* ciphers in array suites are not supported, or when the array
* is null.
*/
@Override
public void setEnabledCipherSuites(String[] suites) {
enabledCipherSuites = NativeCrypto.checkEnabledCipherSuites(suites);
}
/**
* The names of the protocols' versions that may be used on this SSL
* connection.
* @return an array of protocols names
*/
@Override
public String[] getSupportedProtocols() {
return NativeCrypto.getSupportedProtocols();
}
/**
* The names of the protocols' versions that are in use on this SSL
* connection.
*
* @return an array of protocols names
*/
@Override
public String[] getEnabledProtocols() {
return enabledProtocols.clone();
}
/**
* This method enables the protocols' versions listed by
* getSupportedProtocols().
*
* @param protocols The names of all the protocols to allow
*
* @throws IllegalArgumentException when one or more of the names in the
* array are not supported, or when the array is null.
*/
@Override
public void setEnabledProtocols(String[] protocols) {
enabledProtocols = NativeCrypto.checkEnabledProtocols(protocols);
}
/**
* The names of the compression methods that may be used on this SSL
* connection.
* @return an array of compression methods
*/
public String[] getSupportedCompressionMethods() {
return NativeCrypto.getSupportedCompressionMethods();
}
/**
* The names of the compression methods versions that are in use
* on this SSL connection.
*
* @return an array of compression methods
*/
public String[] getEnabledCompressionMethods() {
return enabledCompressionMethods.clone();
}
/**
* This method enables the compression method listed by
* getSupportedCompressionMethods().
*
* @param methods The names of all the compression methods to allow
*
* @throws IllegalArgumentException when one or more of the names in the
* array are not supported, or when the array is null.
*/
public void setEnabledCompressionMethods (String[] methods) {
enabledCompressionMethods = NativeCrypto.checkEnabledCompressionMethods(methods);
}
/**
* This method enables session ticket support.
*
* @param useSessionTickets True to enable session tickets
*/
public void setUseSessionTickets(boolean useSessionTickets) {
this.useSessionTickets = useSessionTickets;
}
/**
* This method gives true back if the SSL socket is set to client mode.
*
* @return true if the socket should do the handshaking as client.
*/
public boolean getUseSessionTickets() {
return useSessionTickets;
}
/**
* This method enables Server Name Indication
*
* @param hostname the desired SNI hostname, or null to disable
*/
public void setHostname(String hostname) {
this.hostname = hostname;
}
/**
* This method returns the current SNI hostname
*
* @return a host name if SNI is enabled, or null otherwise
*/
public String getHostname() {
return hostname;
}
/**
* This method gives true back if the SSL socket is set to client mode.
*
* @return true if the socket should do the handshaking as client.
*/
public boolean getUseClientMode() {
return sslParameters.getUseClientMode();
}
/**
* This method set the actual SSL socket to client mode.
*
* @param mode true if the socket starts in client
* mode
* @throws IllegalArgumentException if mode changes during
* handshake.
*/
@Override
public void setUseClientMode(boolean mode) {
if (handshakeStarted) {
throw new IllegalArgumentException(
"Could not change the mode after the initial handshake has begun.");
}
sslParameters.setUseClientMode(mode);
}
/**
* Returns true if the SSL socket requests client's authentication. Relevant
* only for server sockets!
*
* @return true if client authentication is desired, false if not.
*/
@Override
public boolean getWantClientAuth() {
return sslParameters.getWantClientAuth();
}
/**
* Returns true if the SSL socket needs client's authentication. Relevant
* only for server sockets!
*
* @return true if client authentication is desired, false if not.
*/
@Override
public boolean getNeedClientAuth() {
return sslParameters.getNeedClientAuth();
}
/**
* Sets the SSL socket to use client's authentication. Relevant only for
* server sockets!
*
* @param need true if client authentication is
* desired, false if not.
*/
@Override
public void setNeedClientAuth(boolean need) {
sslParameters.setNeedClientAuth(need);
}
/**
* Sets the SSL socket to use client's authentication. Relevant only for
* server sockets! Notice that in contrast to setNeedClientAuth(..) this
* method will continue the negotiation if the client decide not to send
* authentication credentials.
*
* @param want true if client authentication is
* desired, false if not.
*/
@Override
public void setWantClientAuth(boolean want) {
sslParameters.setWantClientAuth(want);
}
/**
* This method is not supported for SSLSocket implementation.
*/
@Override
public void sendUrgentData(int data) throws IOException {
throw new SocketException(
"Method sendUrgentData() is not supported.");
}
/**
* This method is not supported for SSLSocket implementation.
*/
@Override
public void setOOBInline(boolean on) throws SocketException {
throw new SocketException(
"Methods sendUrgentData, setOOBInline are not supported.");
}
/**
* Set the read timeout on this socket. The SO_TIMEOUT option, is specified
* in milliseconds. The read operation will block indefinitely for a zero
* value.
*
* @param timeout the read timeout value
* @throws SocketException if an error occurs setting the option
*/
@Override
public void setSoTimeout(int timeoutMilliseconds) throws SocketException {
super.setSoTimeout(timeoutMilliseconds);
this.timeoutMilliseconds = timeoutMilliseconds;
}
@Override
public int getSoTimeout() throws SocketException {
return timeoutMilliseconds;
}
/**
* Set the handshake timeout on this socket. This timeout is specified in
* milliseconds and will be used only during the handshake process.
*
* @param timeout the handshake timeout value
*/
public void setHandshakeTimeout(int timeoutMilliseconds) throws SocketException {
this.handshakeTimeoutMilliseconds = timeoutMilliseconds;
}
/**
* Closes the SSL socket. Once closed, a socket is not available for further
* use anymore under any circumstance. A new socket must be created.
*
* @throws IOException
if an I/O error happens during the
* socket's closure.
*/
@Override
public void close() throws IOException {
// TODO: Close SSL sockets using a background thread so they close
// gracefully.
synchronized (handshakeLock) {
if (!handshakeStarted) {
// prevent further attemps to start handshake
handshakeStarted = true;
synchronized (this) {
free();
if (socket != this) {
if (autoClose && !socket.isClosed()) socket.close();
} else {
if (!super.isClosed()) super.close();
}
}
return;
}
}
NativeCrypto.SSL_interrupt(sslNativePointer);
synchronized (this) {
synchronized (writeLock) {
synchronized (readLock) {
// Shut down the SSL connection, per se.
try {
if (handshakeStarted) {
BlockGuard.getThreadPolicy().onNetwork();
NativeCrypto.SSL_shutdown(sslNativePointer, socket.getFileDescriptor$(),
this);
}
} catch (IOException ignored) {
/*
* Note that although close() can throw
* IOException, the RI does not throw if there
* is problem sending a "close notify" which
* can happen if the underlying socket is closed.
*/
}
/*
* Even if the above call failed, it is still safe to free
* the native structs, and we need to do so lest we leak
* memory.
*/
free();
if (socket != this) {
if (autoClose && !socket.isClosed())
socket.close();
} else {
if (!super.isClosed())
super.close();
}
}
}
}
}
private void free() {
if (sslNativePointer == 0) {
return;
}
NativeCrypto.SSL_free(sslNativePointer);
sslNativePointer = 0;
guard.close();
}
@Override protected void finalize() throws Throwable {
try {
/*
* Just worry about our own state. Notably we do not try and
* close anything. The SocketImpl, either our own
* PlainSocketImpl, or the Socket we are wrapping, will do
* that. This might mean we do not properly SSL_shutdown, but
* if you want to do that, properly close the socket yourself.
*
* The reason why we don't try to SSL_shutdown, is that there
* can be a race between finalizers where the PlainSocketImpl
* finalizer runs first and closes the socket. However, in the
* meanwhile, the underlying file descriptor could be reused
* for another purpose. If we call SSL_shutdown, the
* underlying socket BIOs still have the old file descriptor
* and will write the close notify to some unsuspecting
* reader.
*/
if (guard != null) {
guard.warnIfOpen();
}
free();
} finally {
super.finalize();
}
}
}