/* * 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.conscrypt; 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.InvalidKeyException; 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 javax.net.ssl.HandshakeCompletedEvent; import javax.net.ssl.HandshakeCompletedListener; import javax.net.ssl.SSLException; import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLProtocolException; import javax.net.ssl.SSLSession; import javax.net.ssl.X509TrustManager; import javax.security.auth.x500.X500Principal; import static libcore.io.OsConstants.*; import libcore.io.ErrnoException; import libcore.io.Libcore; import libcore.io.Streams; import libcore.io.StructTimeval; /** * Implementation of the class OpenSSLSocketImpl based on OpenSSL. *
* Extensions to SSLSocket include: *
IOException
*/
@Override
public int read() throws IOException {
return Streams.readSingleByte(this);
}
/**
* 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();
}
/**
* Method acts as described in spec for superclass.
* @see java.io.OutputStream#write(int)
*/
@Override
public void write(int oneByte) throws IOException {
Streams.writeSingleByte(this, oneByte);
}
/**
* 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, writeTimeoutMilliseconds);
}
}
}
@Override public SSLSession getSession() {
if (sslSession == null) {
try {
startHandshake();
} catch (IOException e) {
// return an invalid session with
// invalid cipher suite of "SSL_NULL_WITH_NULL_NULL"
return SSLSessionImpl.NULL_SESSION;
}
}
return sslSession;
}
@Override public void addHandshakeCompletedListener(
HandshakeCompletedListener listener) {
if (listener == null) {
throw new IllegalArgumentException("Provided listener is null");
}
if (listeners == null) {
listeners = new ArrayListThis method needs to be invoked before the handshake starts. * * @throws IllegalStateException if this is a client socket or if the handshake has already * started. */ public void setChannelIdEnabled(boolean enabled) { if (getUseClientMode()) { throw new IllegalStateException("Client mode"); } if (handshakeStarted) { throw new IllegalStateException( "Could not enable/disable Channel ID after the initial handshake has" + " begun."); } this.channelIdEnabled = 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. */ public byte[] getChannelId() throws SSLException { if (getUseClientMode()) { throw new IllegalStateException("Client mode"); } if (!handshakeCompleted) { throw new IllegalStateException( "Channel ID is only available after handshake completes"); } return NativeCrypto.SSL_get_tls_channel_id(sslNativePointer); } /** * 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. */ public void setChannelIdPrivateKey(PrivateKey privateKey) { if (!getUseClientMode()) { throw new IllegalStateException("Server mode"); } if (handshakeStarted) { throw new IllegalStateException( "Could not change Channel ID private key after the initial handshake has" + " begun."); } if (privateKey == null) { this.channelIdEnabled = false; this.channelIdPrivateKey = null; } else { this.channelIdEnabled = true; try { this.channelIdPrivateKey = OpenSSLKey.fromPrivateKey(privateKey); } catch (InvalidKeyException e) { // Will have error in startHandshake } } } @Override public boolean getUseClientMode() { return sslParameters.getUseClientMode(); } @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); } @Override public boolean getWantClientAuth() { return sslParameters.getWantClientAuth(); } @Override public boolean getNeedClientAuth() { return sslParameters.getNeedClientAuth(); } @Override public void setNeedClientAuth(boolean need) { sslParameters.setNeedClientAuth(need); } @Override public void setWantClientAuth(boolean want) { sslParameters.setWantClientAuth(want); } @Override public void sendUrgentData(int data) throws IOException { throw new SocketException("Method sendUrgentData() is not supported."); } @Override public void setOOBInline(boolean on) throws SocketException { throw new SocketException("Methods sendUrgentData, setOOBInline are not supported."); } @Override public void setSoTimeout(int readTimeoutMilliseconds) throws SocketException { super.setSoTimeout(readTimeoutMilliseconds); this.readTimeoutMilliseconds = readTimeoutMilliseconds; } @Override public int getSoTimeout() throws SocketException { return readTimeoutMilliseconds; } /** * Note write timeouts are not part of the javax.net.ssl.SSLSocket API */ public void setSoWriteTimeout(int writeTimeoutMilliseconds) throws SocketException { this.writeTimeoutMilliseconds = writeTimeoutMilliseconds; StructTimeval tv = StructTimeval.fromMillis(writeTimeoutMilliseconds); try { Libcore.os.setsockoptTimeval(getFileDescriptor$(), SOL_SOCKET, SO_SNDTIMEO, tv); } catch (ErrnoException errnoException) { throw errnoException.rethrowAsSocketException(); } } /** * Note write timeouts are not part of the javax.net.ssl.SSLSocket API */ public int getSoWriteTimeout() throws SocketException { return writeTimeoutMilliseconds; } /** * Set the handshake timeout on this socket. This timeout is specified in * milliseconds and will be used only during the handshake process. */ public void setHandshakeTimeout(int handshakeTimeoutMilliseconds) throws SocketException { this.handshakeTimeoutMilliseconds = handshakeTimeoutMilliseconds; } @Override public void close() throws IOException { // TODO: Close SSL sockets using a background thread so they close gracefully. synchronized (handshakeLock) { if (!handshakeStarted) { // prevent further attempts to start handshake handshakeStarted = true; synchronized (this) { free(); if (socket != this) { if (autoClose && !socket.isClosed()) socket.close(); } else { if (!super.isClosed()) super.close(); } } return; } } synchronized (this) { // Interrupt any outstanding reads or writes before taking the writeLock and readLock NativeCrypto.SSL_interrupt(sslNativePointer); 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. */ } finally { /* * 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(); } } @Override public FileDescriptor getFileDescriptor$() { if (socket == this) { return super.getFileDescriptor$(); } else { return socket.getFileDescriptor$(); } } /** * Returns the protocol agreed upon by client and server, or null if no * protocol was agreed upon. */ public byte[] getNpnSelectedProtocol() { return NativeCrypto.SSL_get_npn_negotiated_protocol(sslNativePointer); } /** * Returns the protocol agreed upon by client and server, or {@code null} if * no protocol was agreed upon. */ public byte[] getAlpnSelectedProtocol() { return NativeCrypto.SSL_get0_alpn_selected(sslNativePointer); } /** * Sets the list of protocols this peer is interested in. If null no * protocols will be used. * * @param npnProtocols a non-empty array of protocol names. From * SSL_select_next_proto, "vector of 8-bit, length prefixed byte * strings. The length byte itself is not included in the length. A byte * string of length 0 is invalid. No byte string may be truncated.". */ public void setNpnProtocols(byte[] npnProtocols) { if (npnProtocols != null && npnProtocols.length == 0) { throw new IllegalArgumentException("npnProtocols.length == 0"); } this.npnProtocols = npnProtocols; } /** * Sets the list of protocols this peer is interested in. If the list is * {@code null}, no protocols will be used. * * @param alpnProtocols a non-empty array of protocol names. From * SSL_select_next_proto, "vector of 8-bit, length prefixed byte * strings. The length byte itself is not included in the length. * A byte string of length 0 is invalid. No byte string may be * truncated.". */ public void setAlpnProtocols(byte[] alpnProtocols) { if (alpnProtocols != null && alpnProtocols.length == 0) { throw new IllegalArgumentException("alpnProtocols.length == 0"); } this.alpnProtocols = alpnProtocols; } }