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 {