Add support for accessing tls-unique channel binding value. (#388)
diff --git a/common/src/jni/main/cpp/conscrypt/native_crypto.cc b/common/src/jni/main/cpp/conscrypt/native_crypto.cc
index f56762c..de92fa3 100644
--- a/common/src/jni/main/cpp/conscrypt/native_crypto.cc
+++ b/common/src/jni/main/cpp/conscrypt/native_crypto.cc
@@ -6751,6 +6751,41 @@
}
}
+// All verify_data values are currently 12 bytes long, but cipher suites are allowed
+// to customize the length of their verify_data (with a default of 12 bytes). We accept
+// up to 16 bytes so that we can check that the results are actually 12 bytes long in
+// tests and update this value if necessary.
+const size_t MAX_TLS_UNIQUE_LENGTH = 16;
+
+static jbyteArray NativeCrypto_SSL_get_tls_unique(JNIEnv* env, jclass, jlong ssl_address) {
+ SSL* ssl = to_SSL(env, ssl_address, true);
+ JNI_TRACE("ssl=%p NativeCrypto_SSL_get_tls_unique", ssl);
+ if (ssl == nullptr) {
+ return nullptr;
+ }
+
+ uint8_t data[MAX_TLS_UNIQUE_LENGTH];
+ size_t data_len;
+ int ret = SSL_get_tls_unique(ssl, data, &data_len, MAX_TLS_UNIQUE_LENGTH);
+
+ if (!ret || data_len == 0) {
+ JNI_TRACE("NativeCrypto_SSL_get_tls_unique(%p) => null", ssl);
+ return nullptr;
+ }
+
+ ScopedLocalRef<jbyteArray> byteArray(env, env->NewByteArray(static_cast<jsize>(data_len)));
+ if (byteArray.get() == nullptr) {
+ JNI_TRACE("NativeCrypto_SSL_get_tls_unique(%p) => creating byte array failed", ssl);
+ return nullptr;
+ }
+
+ env->SetByteArrayRegion(byteArray.get(), 0, static_cast<jsize>(data_len), (const jbyte*)data);
+ JNI_TRACE("NativeCrypto_SSL_get_tls_unique(%p) => %p [size=%zd]", ssl, byteArray.get(),
+ data_len);
+
+ return byteArray.release();
+}
+
static void NativeCrypto_SSL_use_psk_identity_hint(JNIEnv* env, jclass, jlong ssl_address,
jstring identityHintJava) {
SSL* ssl = to_SSL(env, ssl_address, true);
@@ -9584,6 +9619,7 @@
CONSCRYPT_NATIVE_METHOD(SSL_enable_ocsp_stapling, "(J)V"),
CONSCRYPT_NATIVE_METHOD(SSL_get_ocsp_response, "(J)[B"),
CONSCRYPT_NATIVE_METHOD(SSL_set_ocsp_response, "(J[B)V"),
+ CONSCRYPT_NATIVE_METHOD(SSL_get_tls_unique, "(J)[B"),
CONSCRYPT_NATIVE_METHOD(SSL_use_psk_identity_hint, "(JLjava/lang/String;)V"),
CONSCRYPT_NATIVE_METHOD(set_SSL_psk_client_callback_enabled, "(JZ)V"),
CONSCRYPT_NATIVE_METHOD(set_SSL_psk_server_callback_enabled, "(JZ)V"),
diff --git a/common/src/main/java/org/conscrypt/AbstractConscryptEngine.java b/common/src/main/java/org/conscrypt/AbstractConscryptEngine.java
index 96dc5b4..144fed4 100644
--- a/common/src/main/java/org/conscrypt/AbstractConscryptEngine.java
+++ b/common/src/main/java/org/conscrypt/AbstractConscryptEngine.java
@@ -157,4 +157,11 @@
* 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();
}
diff --git a/common/src/main/java/org/conscrypt/AbstractConscryptSocket.java b/common/src/main/java/org/conscrypt/AbstractConscryptSocket.java
index d6598d1..b2382f4 100644
--- a/common/src/main/java/org/conscrypt/AbstractConscryptSocket.java
+++ b/common/src/main/java/org/conscrypt/AbstractConscryptSocket.java
@@ -210,4 +210,11 @@
abstract void setApplicationProtocolSelector(ApplicationProtocolSelector selector);
abstract PeerInfoProvider peerInfoProvider();
+
+ /**
+ * 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();
}
diff --git a/common/src/main/java/org/conscrypt/Conscrypt.java b/common/src/main/java/org/conscrypt/Conscrypt.java
index 1ef35d7..8cadfd1 100644
--- a/common/src/main/java/org/conscrypt/Conscrypt.java
+++ b/common/src/main/java/org/conscrypt/Conscrypt.java
@@ -339,6 +339,15 @@
}
/**
+ * 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.
+ */
+ public static byte[] getTlsUnique(SSLSocket socket) {
+ return toConscrypt(socket).getTlsUnique();
+ }
+
+ /**
* Indicates whether the given {@link SSLEngine} was created by this distribution of Conscrypt.
*/
public static boolean isConscrypt(SSLEngine engine) {
@@ -529,4 +538,13 @@
public static String getApplicationProtocol(SSLEngine engine) {
return toConscrypt(engine).getApplicationProtocol();
}
+
+ /**
+ * 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.
+ */
+ public static byte[] getTlsUnique(SSLEngine engine) {
+ return toConscrypt(engine).getTlsUnique();
+ }
}
diff --git a/common/src/main/java/org/conscrypt/ConscryptEngine.java b/common/src/main/java/org/conscrypt/ConscryptEngine.java
index b38abba..6c96b47 100644
--- a/common/src/main/java/org/conscrypt/ConscryptEngine.java
+++ b/common/src/main/java/org/conscrypt/ConscryptEngine.java
@@ -1701,6 +1701,11 @@
selector == null ? null : new ApplicationProtocolSelectorAdapter(this, selector));
}
+ @Override
+ byte[] getTlsUnique() {
+ return ssl.getTlsUnique();
+ }
+
void setApplicationProtocolSelector(ApplicationProtocolSelectorAdapter adapter) {
sslParameters.setApplicationProtocolSelector(adapter);
}
diff --git a/common/src/main/java/org/conscrypt/ConscryptEngineSocket.java b/common/src/main/java/org/conscrypt/ConscryptEngineSocket.java
index 1426b69..c04957e 100644
--- a/common/src/main/java/org/conscrypt/ConscryptEngineSocket.java
+++ b/common/src/main/java/org/conscrypt/ConscryptEngineSocket.java
@@ -336,6 +336,11 @@
}
@Override
+ byte[] getTlsUnique() {
+ return engine.getTlsUnique();
+ }
+
+ @Override
public final boolean getUseClientMode() {
return engine.getUseClientMode();
}
diff --git a/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java b/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java
index a84964e..7864f4e 100644
--- a/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java
+++ b/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java
@@ -829,6 +829,11 @@
}
@Override
+ byte[] getTlsUnique() {
+ return ssl.getTlsUnique();
+ }
+
+ @Override
public final boolean getUseClientMode() {
return sslParameters.getUseClientMode();
}
diff --git a/common/src/main/java/org/conscrypt/Java8EngineWrapper.java b/common/src/main/java/org/conscrypt/Java8EngineWrapper.java
index 74999de..733a41d 100644
--- a/common/src/main/java/org/conscrypt/Java8EngineWrapper.java
+++ b/common/src/main/java/org/conscrypt/Java8EngineWrapper.java
@@ -296,6 +296,11 @@
}
@Override
+ byte[] getTlsUnique() {
+ return delegate.getTlsUnique();
+ }
+
+ @Override
public String getHandshakeApplicationProtocol() {
return delegate.getHandshakeApplicationProtocol();
}
diff --git a/common/src/main/java/org/conscrypt/NativeCrypto.java b/common/src/main/java/org/conscrypt/NativeCrypto.java
index e259907..c633ac9 100644
--- a/common/src/main/java/org/conscrypt/NativeCrypto.java
+++ b/common/src/main/java/org/conscrypt/NativeCrypto.java
@@ -895,6 +895,8 @@
static native void SSL_set_ocsp_response(long ssl, byte[] response);
+ static native byte[] SSL_get_tls_unique(long ssl);
+
static native void SSL_use_psk_identity_hint(long ssl, String identityHint) throws SSLException;
static native void set_SSL_psk_client_callback_enabled(long ssl, boolean enabled);
diff --git a/common/src/main/java/org/conscrypt/SslWrapper.java b/common/src/main/java/org/conscrypt/SslWrapper.java
index 6ddbb8b..2c5bca8 100644
--- a/common/src/main/java/org/conscrypt/SslWrapper.java
+++ b/common/src/main/java/org/conscrypt/SslWrapper.java
@@ -125,6 +125,10 @@
return NativeCrypto.SSL_get_ocsp_response(ssl);
}
+ byte[] getTlsUnique() {
+ return NativeCrypto.SSL_get_tls_unique(ssl);
+ }
+
byte[] getPeerTlsSctData() {
return NativeCrypto.SSL_get_signed_cert_timestamp_list(ssl);
}
diff --git a/openjdk-integ-tests/src/test/java/libcore/javax/net/ssl/SSLEngineTest.java b/openjdk-integ-tests/src/test/java/libcore/javax/net/ssl/SSLEngineTest.java
index e3b8ee6..838dd78 100644
--- a/openjdk-integ-tests/src/test/java/libcore/javax/net/ssl/SSLEngineTest.java
+++ b/openjdk-integ-tests/src/test/java/libcore/javax/net/ssl/SSLEngineTest.java
@@ -17,10 +17,12 @@
package libcore.javax.net.ssl;
import static org.conscrypt.TestUtils.UTF_8;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -47,6 +49,7 @@
import javax.net.ssl.X509ExtendedKeyManager;
import libcore.java.security.StandardNames;
import libcore.java.security.TestKeyStore;
+import org.conscrypt.Conscrypt;
import org.conscrypt.TestUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -931,6 +934,28 @@
}
}
+ @Test
+ public void test_SSLEngine_TlsUnique() throws Exception {
+ TestSSLEnginePair pair = TestSSLEnginePair.create(new TestSSLEnginePair.Hooks() {
+ @Override
+ void beforeBeginHandshake(SSLEngine client, SSLEngine server) {
+ assertNull(Conscrypt.getTlsUnique(client));
+ assertNull(Conscrypt.getTlsUnique(server));
+ }
+ });
+ try {
+ assertConnected(pair);
+
+ byte[] clientTlsUnique = Conscrypt.getTlsUnique(pair.client);
+ byte[] serverTlsUnique = Conscrypt.getTlsUnique(pair.server);
+ assertNotNull(clientTlsUnique);
+ assertNotNull(serverTlsUnique);
+ assertArrayEquals(clientTlsUnique, serverTlsUnique);
+ } finally {
+ pair.close();
+ }
+ }
+
private void assertConnected(TestSSLEnginePair e) {
assertConnected(e.client, e.server);
}
diff --git a/openjdk-integ-tests/src/test/java/libcore/javax/net/ssl/SSLSocketTest.java b/openjdk-integ-tests/src/test/java/libcore/javax/net/ssl/SSLSocketTest.java
index 3365c8c..ba99bbe 100644
--- a/openjdk-integ-tests/src/test/java/libcore/javax/net/ssl/SSLSocketTest.java
+++ b/openjdk-integ-tests/src/test/java/libcore/javax/net/ssl/SSLSocketTest.java
@@ -17,6 +17,7 @@
package libcore.javax.net.ssl;
import static org.conscrypt.TestUtils.UTF_8;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -122,6 +123,7 @@
import libcore.tlswire.record.TlsProtocols;
import libcore.tlswire.record.TlsRecord;
import libcore.tlswire.util.TlsProtocolVersion;
+import org.conscrypt.Conscrypt;
import org.conscrypt.TestUtils;
import org.junit.After;
import org.junit.Before;
@@ -2493,6 +2495,88 @@
}
}
+ @Test
+ public void test_SSLSocket_TlsUnique() throws Exception {
+ TestSSLSocketPair pair = TestSSLSocketPair.create();
+ try {
+ assertNull(Conscrypt.getTlsUnique(pair.client));
+ assertNull(Conscrypt.getTlsUnique(pair.server));
+
+ pair.connect();
+
+ byte[] clientTlsUnique = Conscrypt.getTlsUnique(pair.client);
+ byte[] serverTlsUnique = Conscrypt.getTlsUnique(pair.server);
+ assertNotNull(clientTlsUnique);
+ assertNotNull(serverTlsUnique);
+ assertArrayEquals(clientTlsUnique, serverTlsUnique);
+ } finally {
+ pair.close();
+ }
+ }
+
+ // Tests that all cipher suites have a 12-byte tls-unique channel binding value. If this
+ // test fails, that means some cipher suite has been added that uses a customized verify_data
+ // length and we need to update MAX_TLS_UNIQUE_LENGTH in native_crypto.cc to account for that.
+ @Test
+ public void test_SSLSocket_TlsUniqueLength() throws Exception {
+ // note the rare usage of non-RSA keys
+ TestKeyStore testKeyStore = new TestKeyStore.Builder()
+ .keyAlgorithms("RSA", "DSA", "EC", "EC_RSA")
+ .aliasPrefix("rsa-dsa-ec")
+ .ca(true)
+ .build();
+ KeyManager pskKeyManager =
+ PSKKeyManagerProxy.getConscryptPSKKeyManager(new PSKKeyManagerProxy() {
+ @Override
+ protected SecretKey getKey(
+ String identityHint, String identity, Socket socket) {
+ return newKey();
+ }
+
+ @Override
+ protected SecretKey getKey(
+ String identityHint, String identity, SSLEngine engine) {
+ return newKey();
+ }
+
+ private SecretKey newKey() {
+ return new SecretKeySpec("Just an arbitrary key".getBytes(UTF_8), "RAW");
+ }
+ });
+ TestSSLContext c = TestSSLContext.newBuilder()
+ .client(testKeyStore)
+ .server(testKeyStore)
+ .additionalClientKeyManagers(new KeyManager[] {pskKeyManager})
+ .additionalServerKeyManagers(new KeyManager[] {pskKeyManager})
+ .build();
+ for (String cipherSuite : c.clientContext.getSocketFactory().getSupportedCipherSuites()) {
+ if (cipherSuite.equals(StandardNames.CIPHER_SUITE_FALLBACK)
+ || cipherSuite.equals(StandardNames.CIPHER_SUITE_SECURE_RENEGOTIATION)) {
+ continue;
+ }
+ TestSSLSocketPair pair = TestSSLSocketPair.create(c);
+ try {
+ String[] cipherSuites =
+ new String[] {cipherSuite, StandardNames.CIPHER_SUITE_SECURE_RENEGOTIATION};
+ pair.connect(cipherSuites, cipherSuites);
+
+ assertEquals(cipherSuite, pair.client.getSession().getCipherSuite());
+
+ byte[] clientTlsUnique = Conscrypt.getTlsUnique(pair.client);
+ byte[] serverTlsUnique = Conscrypt.getTlsUnique(pair.server);
+ assertNotNull(clientTlsUnique);
+ assertNotNull(serverTlsUnique);
+ assertArrayEquals(clientTlsUnique, serverTlsUnique);
+ assertEquals(12, clientTlsUnique.length);
+ } catch (Exception e) {
+ throw new AssertionError("Cipher suite is " + cipherSuite, e);
+ } finally {
+ pair.client.close();
+ pair.server.close();
+ }
+ }
+ }
+
private static void setWriteTimeout(Object socket, int timeout) {
Exception ex = null;
try {