Add public API for SSLEngines and SSLSockets
Bug: 110403421
Test: cts -m CtsLibcoreTestCases -t android.net.ssl
Change-Id: If7982975ed125d0e4a1ab6e79afbe0835b8807c4
diff --git a/Android.bp b/Android.bp
index 552a686..94050cc 100644
--- a/Android.bp
+++ b/Android.bp
@@ -191,6 +191,11 @@
],
}
+filegroup {
+ name: "conscrypt_public_api_files",
+ srcs: ["publicapi/src/main/java/**/*.java"]
+}
+
// Create the conscrypt library from the source produced by the srcgen/generate_android_src.sh
// script.
java_library {
@@ -198,7 +203,10 @@
installable: true,
hostdex: true,
- srcs: [":conscrypt_java_files"],
+ srcs: [
+ ":conscrypt_java_files",
+ ":conscrypt_public_api_files",
+ ],
// Conscrypt can be updated independently from the other core libraries so it must only depend
// on public SDK and intra-core APIs.
@@ -322,6 +330,7 @@
"repackaged/common/src/test/java/**/*.java",
"repackaged/openjdk-integ-tests/src/test/java/**/*.java",
"repackaged/testing/src/main/java/**/*.java",
+ "publicapi/src/test/java/**/*.java",
],
java_resource_dirs: [
// Resource directories do not need repackaging.
diff --git a/README.android b/README.android
new file mode 100644
index 0000000..f20bc2a
--- /dev/null
+++ b/README.android
@@ -0,0 +1,11 @@
+Conscrypt in Android is made up of the contents of the Conscrypt
+GitHub repo (https://github.com/google/conscrypt) plus some
+Android-specific additions. Specifically, the following are
+Android-only:
+
+Android.bp
+OWNERS
+README.android
+publicapi/...
+repackaged/...
+srcgen/...
diff --git a/publicapi/src/main/java/android/net/ssl/SSLEngines.java b/publicapi/src/main/java/android/net/ssl/SSLEngines.java
new file mode 100644
index 0000000..bcdcc1a
--- /dev/null
+++ b/publicapi/src/main/java/android/net/ssl/SSLEngines.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2018 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 android.net.ssl;
+
+import com.android.org.conscrypt.Conscrypt;
+import javax.net.ssl.SSLEngine;
+
+/**
+ * Static utility methods for accessing additional functionality of supported instances of
+ * {@link SSLEngine}. Engines from the platform TLS provider will be compatible with all
+ * methods in this class.
+ */
+public class SSLEngines {
+ private SSLEngines() {}
+
+ /**
+ * Returns whether the given engine can be used with the methods in this class. In general,
+ * only engines from the platform TLS provider are supported.
+ */
+ public static boolean isSupportedEngine(SSLEngine engine) {
+ return Conscrypt.isConscrypt(engine);
+ }
+
+ private static void checkSupported(SSLEngine e) {
+ if (!isSupportedEngine(e)) {
+ throw new IllegalArgumentException("Engine is not a supported engine.");
+ }
+ }
+
+ /**
+ * Enables or disables the use of session tickets.
+ *
+ * <p>This function must be called before the handshake is started or it will have no effect.
+ *
+ * @param engine the engine
+ * @param useSessionTickets whether to enable or disable the use of session tickets
+ * @throws IllegalArgumentException if the given engine is not a platform engine
+ */
+ public static void setUseSessionTickets(SSLEngine engine, boolean useSessionTickets) {
+ checkSupported(engine);
+ Conscrypt.setUseSessionTickets(engine, useSessionTickets);
+ }
+}
diff --git a/publicapi/src/main/java/android/net/ssl/SSLSockets.java b/publicapi/src/main/java/android/net/ssl/SSLSockets.java
new file mode 100644
index 0000000..948f61e
--- /dev/null
+++ b/publicapi/src/main/java/android/net/ssl/SSLSockets.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2018 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 android.net.ssl;
+
+import com.android.org.conscrypt.Conscrypt;
+import javax.net.ssl.SSLSocket;
+
+/**
+ * Static utility methods for accessing additional functionality of supported instances of
+ * {@link SSLSocket}. Sockets from the platform TLS provider will be compatible with all
+ * methods in this class.
+ */
+public class SSLSockets {
+ private SSLSockets() {}
+
+ /**
+ * Returns whether the given socket can be used with the methods in this class. In general,
+ * only sockets from the platform TLS provider are supported.
+ */
+ public static boolean isSupportedSocket(SSLSocket socket) {
+ return Conscrypt.isConscrypt(socket);
+ }
+
+ private static void checkSupported(SSLSocket s) {
+ if (!isSupportedSocket(s)) {
+ throw new IllegalArgumentException("Socket is not a supported socket.");
+ }
+ }
+
+ /**
+ * Enables or disables the use of session tickets.
+ *
+ * <p>This function must be called before the handshake is started or it will have no effect.
+ *
+ * @param socket the socket
+ * @param useSessionTickets whether to enable or disable the use of session tickets
+ * @throws IllegalArgumentException if the given socket is not a platform socket
+ */
+ public static void setUseSessionTickets(SSLSocket socket, boolean useSessionTickets) {
+ checkSupported(socket);
+ Conscrypt.setUseSessionTickets(socket, useSessionTickets);
+ }
+}
diff --git a/publicapi/src/test/java/android/net/ssl/SSLEnginesTest.java b/publicapi/src/test/java/android/net/ssl/SSLEnginesTest.java
new file mode 100644
index 0000000..66abec6
--- /dev/null
+++ b/publicapi/src/test/java/android/net/ssl/SSLEnginesTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2018 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 android.net.ssl;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.org.conscrypt.tlswire.TlsTester;
+import com.android.org.conscrypt.tlswire.handshake.ClientHello;
+import com.android.org.conscrypt.tlswire.handshake.HelloExtension;
+import java.nio.ByteBuffer;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLSession;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class SSLEnginesTest {
+
+ private static class BrokenSSLEngine extends SSLEngine {
+ @Override public SSLEngineResult wrap(ByteBuffer[] byteBuffers, int i, int i1,
+ ByteBuffer byteBuffer) throws SSLException { throw new AssertionError(); }
+ @Override public SSLEngineResult unwrap(ByteBuffer byteBuffer, ByteBuffer[] byteBuffers,
+ int i, int i1) throws SSLException { throw new AssertionError(); }
+ @Override public Runnable getDelegatedTask() { throw new AssertionError(); }
+ @Override public void closeInbound() throws SSLException { throw new AssertionError(); }
+ @Override public boolean isInboundDone() { throw new AssertionError(); }
+ @Override public void closeOutbound() { throw new AssertionError(); }
+ @Override public boolean isOutboundDone() { throw new AssertionError(); }
+ @Override public String[] getSupportedCipherSuites() { throw new AssertionError(); }
+ @Override public String[] getEnabledCipherSuites() { throw new AssertionError(); }
+ @Override public void setEnabledCipherSuites(String[] strings) { throw new AssertionError(); }
+ @Override public String[] getSupportedProtocols() { throw new AssertionError(); }
+ @Override public String[] getEnabledProtocols() { throw new AssertionError(); }
+ @Override public void setEnabledProtocols(String[] strings) { throw new AssertionError(); }
+ @Override public SSLSession getSession() { throw new AssertionError(); }
+ @Override public void beginHandshake() throws SSLException { throw new AssertionError(); }
+ @Override public SSLEngineResult.HandshakeStatus getHandshakeStatus() { throw new AssertionError(); }
+ @Override public void setUseClientMode(boolean b) { throw new AssertionError(); }
+ @Override public boolean getUseClientMode() { throw new AssertionError(); }
+ @Override public void setNeedClientAuth(boolean b) { throw new AssertionError(); }
+ @Override public boolean getNeedClientAuth() { throw new AssertionError(); }
+ @Override public void setWantClientAuth(boolean b) { throw new AssertionError(); }
+ @Override public boolean getWantClientAuth() { throw new AssertionError(); }
+ @Override public void setEnableSessionCreation(boolean b) { throw new AssertionError(); }
+ @Override public boolean getEnableSessionCreation() { throw new AssertionError(); }
+ }
+
+ private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0);
+
+ @Test
+ public void testIsSupported() throws Exception {
+ SSLEngine e = SSLContext.getDefault().createSSLEngine();
+ assertTrue(SSLEngines.isSupportedEngine(e));
+
+ e = new BrokenSSLEngine();
+ assertFalse(SSLEngines.isSupportedEngine(e));
+ }
+
+ @Test
+ public void testUseSessionTickets() throws Exception {
+ try {
+ SSLEngines.setUseSessionTickets(new BrokenSSLEngine(), true);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+
+ SSLEngine e = SSLContext.getDefault().createSSLEngine();
+ e.setUseClientMode(true);
+ SSLEngines.setUseSessionTickets(e, true);
+
+ ClientHello hello = getClientHello(e);
+ assertNotNull(hello.findExtensionByType(HelloExtension.TYPE_SESSION_TICKET));
+
+ e = SSLContext.getDefault().createSSLEngine();
+ e.setUseClientMode(true);
+ SSLEngines.setUseSessionTickets(e, false);
+
+ hello = getClientHello(e);
+ assertNull(hello.findExtensionByType(HelloExtension.TYPE_SESSION_TICKET));
+ }
+
+ private static ClientHello getClientHello(SSLEngine e) throws Exception {
+ ByteBuffer out = ByteBuffer.allocate(64 * 1024);
+
+ e.wrap(EMPTY_BUFFER, out);
+ out.flip();
+ byte[] data = new byte[out.limit()];
+ out.get(data);
+
+ return TlsTester.parseClientHello(data);
+ }
+}
diff --git a/publicapi/src/test/java/android/net/ssl/SSLSocketsTest.java b/publicapi/src/test/java/android/net/ssl/SSLSocketsTest.java
new file mode 100644
index 0000000..02df047
--- /dev/null
+++ b/publicapi/src/test/java/android/net/ssl/SSLSocketsTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2018 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 android.net.ssl;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.org.conscrypt.tlswire.TlsTester;
+import com.android.org.conscrypt.tlswire.handshake.ClientHello;
+import com.android.org.conscrypt.tlswire.handshake.HelloExtension;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import javax.net.ssl.HandshakeCompletedListener;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import tests.net.DelegatingSSLSocketFactory;
+
+@RunWith(JUnit4.class)
+public class SSLSocketsTest {
+
+ private static class BrokenSSLSocket extends SSLSocket {
+ @Override public String[] getSupportedCipherSuites() { throw new AssertionError(); }
+ @Override public String[] getEnabledCipherSuites() { throw new AssertionError(); }
+ @Override public void setEnabledCipherSuites(String[] strings) { throw new AssertionError(); }
+ @Override public String[] getSupportedProtocols() { throw new AssertionError(); }
+ @Override public String[] getEnabledProtocols() { throw new AssertionError(); }
+ @Override public void setEnabledProtocols(String[] strings) { throw new AssertionError(); }
+ @Override public SSLSession getSession() { throw new AssertionError(); }
+ @Override public void addHandshakeCompletedListener(
+ HandshakeCompletedListener handshakeCompletedListener) { throw new AssertionError(); }
+ @Override public void removeHandshakeCompletedListener(
+ HandshakeCompletedListener handshakeCompletedListener) { throw new AssertionError(); }
+ @Override public void startHandshake() { throw new AssertionError(); }
+ @Override public void setUseClientMode(boolean b) { throw new AssertionError(); }
+ @Override public boolean getUseClientMode() { throw new AssertionError(); }
+ @Override public void setNeedClientAuth(boolean b) { throw new AssertionError(); }
+ @Override public boolean getNeedClientAuth() { throw new AssertionError(); }
+ @Override public void setWantClientAuth(boolean b) { throw new AssertionError(); }
+ @Override public boolean getWantClientAuth() { throw new AssertionError(); }
+ @Override public void setEnableSessionCreation(boolean b) { throw new AssertionError(); }
+ @Override public boolean getEnableSessionCreation() { throw new AssertionError(); }
+ }
+
+ private ExecutorService executor;
+
+ @Before
+ public void setUp() {
+ executor = Executors.newCachedThreadPool();
+ }
+
+ @After
+ public void tearDown() throws InterruptedException {
+ executor.shutdown();
+ executor.awaitTermination(1, TimeUnit.SECONDS);
+ }
+
+ @Test
+ public void testIsSupported() throws Exception {
+ SSLSocket s = (SSLSocket) SSLSocketFactory.getDefault().createSocket();
+ assertTrue(SSLSockets.isSupportedSocket(s));
+
+ s = new BrokenSSLSocket();
+ assertFalse(SSLSockets.isSupportedSocket(s));
+ }
+
+ @Test
+ public void testUseSessionTickets() throws Exception {
+ try {
+ SSLSockets.setUseSessionTickets(new BrokenSSLSocket(), true);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+
+ SSLSocket s = (SSLSocket) SSLSocketFactory.getDefault().createSocket();
+ SSLSockets.setUseSessionTickets(s, true);
+
+ ClientHello hello = TlsTester.captureTlsHandshakeClientHello(executor,
+ new DelegatingSSLSocketFactory((SSLSocketFactory) SSLSocketFactory.getDefault()) {
+ @Override public SSLSocket configureSocket(SSLSocket socket) {
+ SSLSockets.setUseSessionTickets(socket, true);
+ return socket;
+ }
+ });
+ assertNotNull(hello.findExtensionByType(HelloExtension.TYPE_SESSION_TICKET));
+
+ hello = TlsTester.captureTlsHandshakeClientHello(executor,
+ new DelegatingSSLSocketFactory((SSLSocketFactory) SSLSocketFactory.getDefault()) {
+ @Override public SSLSocket configureSocket(SSLSocket socket) {
+ SSLSockets.setUseSessionTickets(socket, false);
+ return socket;
+ }
+ });
+ assertNull(hello.findExtensionByType(HelloExtension.TYPE_SESSION_TICKET));
+ }
+}