diff --git a/compiler/rt/libcore/crypto/src/main/java/org/conscrypt/NativeCrypto.java b/compiler/rt/libcore/crypto/src/main/java/org/conscrypt/NativeCrypto.java index 8b93a63cb..290107bd8 100755 --- a/compiler/rt/libcore/crypto/src/main/java/org/conscrypt/NativeCrypto.java +++ b/compiler/rt/libcore/crypto/src/main/java/org/conscrypt/NativeCrypto.java @@ -1015,9 +1015,9 @@ public static native void SSL_set_tlsext_host_name(long sslNativePointer, String /** * For clients, sets the list of supported ALPN protocols in wire-format - * (length-prefixed 8-bit strings) on an SSL context. + * (length-prefixed 8-bit strings). */ - public static native int SSL_CTX_set_alpn_protos(long sslCtxPointer, byte[] protos); + public static native int SSL_set_alpn_protos(long sslPointer, byte[] protos); /** * Returns the selected ALPN protocol. If the server did not select a diff --git a/compiler/rt/libcore/crypto/src/main/java/org/conscrypt/OpenSSLSocketImpl.java b/compiler/rt/libcore/crypto/src/main/java/org/conscrypt/OpenSSLSocketImpl.java index ece7893bf..23f7120d7 100755 --- a/compiler/rt/libcore/crypto/src/main/java/org/conscrypt/OpenSSLSocketImpl.java +++ b/compiler/rt/libcore/crypto/src/main/java/org/conscrypt/OpenSSLSocketImpl.java @@ -39,17 +39,18 @@ 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; +import static libcore.io.OsConstants.SOL_SOCKET; +import static libcore.io.OsConstants.SO_SNDTIMEO; + /** * Implementation of the class OpenSSLSocketImpl based on OpenSSL. *
@@ -64,36 +65,100 @@ public class OpenSSLSocketImpl
extends javax.net.ssl.SSLSocket
implements NativeCrypto.SSLHandshakeCallbacks {
+ private static final boolean DBG_STATE = false;
+
+ /**
+ * Protects handshakeStarted and handshakeCompleted.
+ */
+ private final Object stateLock = new Object();
+
+ /**
+ * The {@link OpenSSLSocketImpl} object is constructed, but {@link #startHandshake()}
+ * has not yet been called.
+ */
+ private static final int STATE_NEW = 0;
+
+ /**
+ * {@link #startHandshake()} has been called at least once.
+ */
+ private static final int STATE_HANDSHAKE_STARTED = 1;
+
+ /**
+ * {@link #handshakeCompleted()} has been called, but {@link #startHandshake()} hasn't
+ * returned yet.
+ */
+ private static final int STATE_HANDSHAKE_COMPLETED = 2;
+
+ /**
+ * {@link #startHandshake()} has completed but {@link #handshakeCompleted()} hasn't
+ * been called. This is expected behaviour in cut-through mode, where SSL_do_handshake
+ * returns before the handshake is complete. We can now start writing data to the socket.
+ */
+ private static final int STATE_READY_HANDSHAKE_CUT_THROUGH = 3;
+
+ /**
+ * {@link #startHandshake()} has completed and {@link #handshakeCompleted()} has been
+ * called.
+ */
+ private static final int STATE_READY = 4;
+
+ /**
+ * {@link #close()} has been called at least once.
+ */
+ private static final int STATE_CLOSED = 5;
+
+ // @GuardedBy("stateLock");
+ private int state = STATE_NEW;
+
+ /**
+ * Protected by synchronizing on stateLock. Starts as 0, set by
+ * startHandshake, reset to 0 on close.
+ */
+ // @GuardedBy("stateLock");
private long sslNativePointer;
- private InputStream is;
- private OutputStream os;
- private final Object handshakeLock = new Object();
- private final Object readLock = new Object();
- private final Object writeLock = new Object();
- private SSLParametersImpl sslParameters;
- private byte[] npnProtocols;
- private byte[] alpnProtocols;
+
+ /**
+ * Protected by synchronizing on stateLock. Starts as null, set by
+ * getInputStream.
+ */
+ // @GuardedBy("stateLock");
+ private SSLInputStream is;
+
+ /**
+ * Protected by synchronizing on stateLock. Starts as null, set by
+ * getInputStream.
+ */
+ // @GuardedBy("stateLock");
+ private SSLOutputStream os;
+
+ private final Socket socket;
+ private final boolean autoClose;
+ private final String wrappedHost;
+ private final int wrappedPort;
+ private final SSLParametersImpl sslParameters;
+ private final CloseGuard guard = CloseGuard.get();
+
private String[] enabledProtocols;
private String[] enabledCipherSuites;
+ private byte[] npnProtocols;
+ private byte[] alpnProtocols;
private boolean useSessionTickets;
private String hostname;
- /** Whether the TLS Channel ID extension is enabled. This field is server-side only. */
+
+ /**
+ * Whether the TLS Channel ID extension is enabled. This field is
+ * server-side only.
+ */
private boolean channelIdEnabled;
- /** Private key for the TLS Channel ID extension. This field is client-side only. */
- private OpenSSLKey channelIdPrivateKey;
- private OpenSSLSessionImpl sslSession;
- private final Socket socket;
- private boolean autoClose;
- private boolean handshakeStarted = false;
- private final CloseGuard guard = CloseGuard.get();
/**
- * Not set to true until the update from native that tells us the
- * full handshake is complete, since SSL_do_handshake can return
- * before the handshake is completely done due to
- * handshake_cutthrough support.
+ * Private key for the TLS Channel ID extension. This field is
+ * client-side only. Set during startHandshake.
*/
- private boolean handshakeCompleted = false;
+ private OpenSSLKey channelIdPrivateKey;
+
+ /** Set during startHandshake. */
+ private OpenSSLSessionImpl sslSession;
private ArrayListIOException
+ * @throws IOException
*/
@Override
public int read() throws IOException {
@@ -680,16 +889,36 @@ public int read() throws IOException {
@Override
public int read(byte[] buf, int offset, int byteCount) throws IOException {
BlockGuard.getThreadPolicy().onNetwork();
+
+ checkOpen();
+ Arrays.checkOffsetAndCount(buf.length, offset, byteCount);
+ if (byteCount == 0) {
+ return 0;
+ }
+
synchronized (readLock) {
- checkOpen();
- Arrays.checkOffsetAndCount(buf.length, offset, byteCount);
- if (byteCount == 0) {
- return 0;
+ synchronized (stateLock) {
+ if (state == STATE_CLOSED) {
+ throw new SocketException("socket is closed");
+ }
+
+ if (DBG_STATE) assertReadableOrWriteableState();
}
+
return NativeCrypto.SSL_read(sslNativePointer, socket.getFileDescriptor$(),
OpenSSLSocketImpl.this, buf, offset, byteCount, getSoTimeout());
}
}
+
+ public void awaitPendingOps() {
+ if (DBG_STATE) {
+ synchronized (stateLock) {
+ if (state != STATE_CLOSED) throw new AssertionError("State is: " + state);
+ }
+ }
+
+ synchronized (readLock) { }
+ }
}
/**
@@ -698,12 +927,15 @@ public int read(byte[] buf, int offset, int byteCount) throws IOException {
* 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();
+
+ /**
+ * OpenSSL only lets one thread write at a time, so this is used
+ * to make sure we serialize callers of SSL_write. Thread is
+ * already expected to have completed handshaking.
+ */
+ private final Object writeLock = new Object();
+
+ SSLOutputStream() {
}
/**
@@ -722,23 +954,43 @@ public void write(int oneByte) throws IOException {
@Override
public void write(byte[] buf, int offset, int byteCount) throws IOException {
BlockGuard.getThreadPolicy().onNetwork();
+ checkOpen();
+ Arrays.checkOffsetAndCount(buf.length, offset, byteCount);
+ if (byteCount == 0) {
+ return;
+ }
+
synchronized (writeLock) {
- checkOpen();
- Arrays.checkOffsetAndCount(buf.length, offset, byteCount);
- if (byteCount == 0) {
- return;
+ synchronized (stateLock) {
+ if (state == STATE_CLOSED) {
+ throw new SocketException("socket is closed");
+ }
+
+ if (DBG_STATE) assertReadableOrWriteableState();
}
+
NativeCrypto.SSL_write(sslNativePointer, socket.getFileDescriptor$(),
OpenSSLSocketImpl.this, buf, offset, byteCount, writeTimeoutMilliseconds);
}
}
+
+
+ public void awaitPendingOps() {
+ if (DBG_STATE) {
+ synchronized (stateLock) {
+ if (state != STATE_CLOSED) throw new AssertionError("State is: " + state);
+ }
+ }
+
+ synchronized (writeLock) { }
+ }
}
@Override public SSLSession getSession() {
if (sslSession == null) {
try {
- startHandshake();
+ waitForHandshake();
} catch (IOException e) {
// return an invalid session with
// invalid cipher suite of "SSL_NULL_WITH_NULL_NULL"
@@ -837,10 +1089,13 @@ 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.");
+
+ synchronized (stateLock) {
+ if (state != STATE_NEW) {
+ throw new IllegalStateException(
+ "Could not enable/disable Channel ID after the initial handshake has"
+ + " begun.");
+ }
}
this.channelIdEnabled = enabled;
}
@@ -859,9 +1114,12 @@ 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");
+
+ synchronized (stateLock) {
+ if (state != STATE_READY) {
+ throw new IllegalStateException(
+ "Channel ID is only available after handshake completes");
+ }
}
return NativeCrypto.SSL_get_tls_channel_id(sslNativePointer);
}
@@ -882,11 +1140,15 @@ 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.");
+
+ synchronized (stateLock) {
+ if (state != STATE_NEW) {
+ 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;
@@ -905,9 +1167,11 @@ public void setChannelIdPrivateKey(PrivateKey privateKey) {
}
@Override public void setUseClientMode(boolean mode) {
- if (handshakeStarted) {
- throw new IllegalArgumentException(
- "Could not change the mode after the initial handshake has begun.");
+ synchronized (stateLock) {
+ if (state != STATE_NEW) {
+ throw new IllegalArgumentException(
+ "Could not change the mode after the initial handshake has begun.");
+ }
}
sslParameters.setUseClientMode(mode);
}
@@ -977,65 +1241,90 @@ public void setHandshakeTimeout(int handshakeTimeoutMilliseconds) throws SocketE
@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;
+ SSLInputStream sslInputStream = null;
+ SSLOutputStream sslOutputStream = null;
- synchronized (this) {
- free();
+ synchronized (stateLock) {
+ if (state == STATE_CLOSED) {
+ // close() has already been called, so do nothing and return.
+ return;
+ }
- if (socket != this) {
- if (autoClose && !socket.isClosed()) socket.close();
- } else {
- if (!super.isClosed()) super.close();
- }
- }
+ int oldState = state;
+ state = STATE_CLOSED;
+
+ if (oldState == STATE_NEW) {
+ // The handshake hasn't been started yet, so there's no OpenSSL related
+ // state to clean up. We still need to close the underlying socket if
+ // we're wrapping it and were asked to autoClose.
+ closeUnderlyingSocket();
+ stateLock.notifyAll();
return;
}
- }
- synchronized (this) {
+ if (oldState != STATE_READY && oldState != STATE_READY_HANDSHAKE_CUT_THROUGH) {
+ // If we're in these states, we still haven't returned from startHandshake.
+ // We call SSL_interrupt so that we can interrupt SSL_do_handshake and then
+ // set the state to STATE_CLOSED. startHandshake will handle all cleanup
+ // after SSL_do_handshake returns, so we don't have anything to do here.
+ NativeCrypto.SSL_interrupt(sslNativePointer);
+
+ stateLock.notifyAll();
+ return;
+ }
- // Interrupt any outstanding reads or writes before taking the writeLock and readLock
+ stateLock.notifyAll();
+ // We've already returned from startHandshake, so we potentially have
+ // input and output streams to clean up.
+ sslInputStream = is;
+ sslOutputStream = os;
+ }
+
+ // Don't bother interrupting unless we have something to interrupt.
+ if (sslInputStream != null || sslOutputStream != null) {
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();
- }
- }
- }
- }
+ // Wait for the input and output streams to finish any reads they have in
+ // progress. If there are no reads in progress at this point, future reads will
+ // throw because state == STATE_CLOSED
+ if (sslInputStream != null) {
+ sslInputStream.awaitPendingOps();
+ }
+ if (sslOutputStream != null) {
+ sslOutputStream.awaitPendingOps();
+ }
+
+ shutdownAndFreeSslNative();
+ }
+
+ private void shutdownAndFreeSslNative() throws IOException {
+ try {
+ 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 {
+ free();
+ closeUnderlyingSocket();
+ }
+ }
+
+ private void closeUnderlyingSocket() throws IOException {
+ if (socket != this) {
+ if (autoClose && !socket.isClosed()) {
+ socket.close();
+ }
+ } else {
+ if (!super.isClosed()) {
+ super.close();
}
}
}
diff --git a/compiler/vm/rt/android/external/openssl/ssl/t1_lib.c b/compiler/vm/rt/android/external/openssl/ssl/t1_lib.c
index f1700561d..c9bbaf265 100755
--- a/compiler/vm/rt/android/external/openssl/ssl/t1_lib.c
+++ b/compiler/vm/rt/android/external/openssl/ssl/t1_lib.c
@@ -1592,15 +1592,22 @@ int ssl_parse_serverhello_tlsext(SSL *s, unsigned char **p, unsigned char *d, in
*al = TLS1_AD_DECODE_ERROR;
return 0;
}
- s->session->tlsext_ecpointformatlist_length = 0;
- if (s->session->tlsext_ecpointformatlist != NULL) OPENSSL_free(s->session->tlsext_ecpointformatlist);
- if ((s->session->tlsext_ecpointformatlist = OPENSSL_malloc(ecpointformatlist_length)) == NULL)
- {
- *al = TLS1_AD_INTERNAL_ERROR;
- return 0;
- }
- s->session->tlsext_ecpointformatlist_length = ecpointformatlist_length;
- memcpy(s->session->tlsext_ecpointformatlist, sdata, ecpointformatlist_length);
+ if (!s->hit)
+ {
+ if(s->session->tlsext_ecpointformatlist)
+ {
+ OPENSSL_free(s->session->tlsext_ecpointformatlist);
+ s->session->tlsext_ecpointformatlist = NULL;
+ }
+ s->session->tlsext_ecpointformatlist_length = 0;
+ if ((s->session->tlsext_ecpointformatlist = OPENSSL_malloc(ecpointformatlist_length)) == NULL)
+ {
+ *al = TLS1_AD_INTERNAL_ERROR;
+ return 0;
+ }
+ s->session->tlsext_ecpointformatlist_length = ecpointformatlist_length;
+ memcpy(s->session->tlsext_ecpointformatlist, sdata, ecpointformatlist_length);
+ }
#if 0
fprintf(stderr,"ssl_parse_serverhello_tlsext s->session->tlsext_ecpointformatlist ");
sdata = s->session->tlsext_ecpointformatlist;
diff --git a/compiler/vm/rt/android/libcore/crypto/src/main/native/org_conscrypt_NativeCrypto.cpp b/compiler/vm/rt/android/libcore/crypto/src/main/native/org_conscrypt_NativeCrypto.cpp
index 0daf2d9ac..fee8d3b18 100755
--- a/compiler/vm/rt/android/libcore/crypto/src/main/native/org_conscrypt_NativeCrypto.cpp
+++ b/compiler/vm/rt/android/libcore/crypto/src/main/native/org_conscrypt_NativeCrypto.cpp
@@ -5596,6 +5596,10 @@ class AppData {
public:
static AppData* create() {
UniquePtr