netty: Add support for Conscrypt
diff --git a/build.gradle b/build.gradle
index 22cefcf..0872849 100644
--- a/build.gradle
+++ b/build.gradle
@@ -216,6 +216,8 @@
netty_proxy_handler: "io.netty:netty-handler-proxy:${nettyVersion}",
netty_tcnative: 'io.netty:netty-tcnative-boringssl-static:2.0.7.Final',
+ conscrypt: 'org.conscrypt:conscrypt-openjdk-uber:1.0.1',
+
// Test dependencies.
junit: 'junit:junit:4.12',
mockito: 'org.mockito:mockito-core:1.9.5',
diff --git a/core/src/main/java/io/grpc/internal/MoreThrowables.java b/core/src/main/java/io/grpc/internal/MoreThrowables.java
index 3e30c97..e310bf8 100644
--- a/core/src/main/java/io/grpc/internal/MoreThrowables.java
+++ b/core/src/main/java/io/grpc/internal/MoreThrowables.java
@@ -20,7 +20,7 @@
/** Utility functions when interacting with {@link Throwable}s. */
// TODO(ejona): Delete this once we've upgraded to Guava 20 or later.
-final class MoreThrowables {
+public final class MoreThrowables {
/**
* Throws {code t} if it is an instance of {@link RuntimeException} or {@link Error}.
*
diff --git a/interop-testing/build.gradle b/interop-testing/build.gradle
index 0fb71a1..3de5573 100644
--- a/interop-testing/build.gradle
+++ b/interop-testing/build.gradle
@@ -27,10 +27,10 @@
project(':grpc-testing'),
libraries.junit,
libraries.mockito,
- libraries.netty_tcnative,
libraries.oauth_client,
libraries.truth
- runtime libraries.opencensus_impl
+ runtime libraries.opencensus_impl,
+ libraries.netty_tcnative
testCompile project(':grpc-context').sourceSets.test.output
}
diff --git a/interop-testing/src/main/java/io/grpc/testing/integration/TestServiceClient.java b/interop-testing/src/main/java/io/grpc/testing/integration/TestServiceClient.java
index 2e4eee7..bdf7eb1 100644
--- a/interop-testing/src/main/java/io/grpc/testing/integration/TestServiceClient.java
+++ b/interop-testing/src/main/java/io/grpc/testing/integration/TestServiceClient.java
@@ -46,8 +46,8 @@
* The main application allowing this client to be launched from the command line.
*/
public static void main(String[] args) throws Exception {
- // Let OkHttp use Conscrypt if it is available.
- Util.installConscryptIfAvailable();
+ // Let Netty or OkHttp use Conscrypt if it is available.
+ TestUtils.installConscryptIfAvailable();
final TestServiceClient client = new TestServiceClient();
client.parseArgs(args);
client.setUp();
diff --git a/interop-testing/src/main/java/io/grpc/testing/integration/TestServiceServer.java b/interop-testing/src/main/java/io/grpc/testing/integration/TestServiceServer.java
index bc68383..d34b7db 100644
--- a/interop-testing/src/main/java/io/grpc/testing/integration/TestServiceServer.java
+++ b/interop-testing/src/main/java/io/grpc/testing/integration/TestServiceServer.java
@@ -33,6 +33,8 @@
public class TestServiceServer {
/** The main application allowing this server to be launched from the command line. */
public static void main(String[] args) throws Exception {
+ // Let Netty use Conscrypt if it is available.
+ TestUtils.installConscryptIfAvailable();
final TestServiceServer server = new TestServiceServer();
server.parseArgs(args);
if (server.useTls) {
diff --git a/interop-testing/src/main/java/io/grpc/testing/integration/Util.java b/interop-testing/src/main/java/io/grpc/testing/integration/Util.java
index 35cd948..dcd4c70 100644
--- a/interop-testing/src/main/java/io/grpc/testing/integration/Util.java
+++ b/interop-testing/src/main/java/io/grpc/testing/integration/Util.java
@@ -19,10 +19,6 @@
import com.google.protobuf.MessageLite;
import io.grpc.Metadata;
import io.grpc.protobuf.ProtoUtils;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.security.Provider;
-import java.security.Security;
import java.util.List;
import org.junit.Assert;
@@ -66,41 +62,4 @@
}
}
}
-
- private static boolean conscryptInstallAttempted;
-
- /**
- * Add Conscrypt to the list of security providers, if it is available. If it appears to be
- * available but fails to load, this method will throw an exception. Since the list of security
- * providers is static, this method does nothing if the provider is not available or succeeded
- * previously.
- */
- public static void installConscryptIfAvailable() {
- if (conscryptInstallAttempted) {
- return;
- }
- Class<?> conscrypt;
- try {
- conscrypt = Class.forName("org.conscrypt.Conscrypt");
- } catch (ClassNotFoundException ex) {
- conscryptInstallAttempted = true;
- return;
- }
- Method newProvider;
- try {
- newProvider = conscrypt.getMethod("newProvider");
- } catch (NoSuchMethodException ex) {
- throw new RuntimeException("Could not find newProvider method on Conscrypt", ex);
- }
- Provider provider;
- try {
- provider = (Provider) newProvider.invoke(null);
- } catch (IllegalAccessException ex) {
- throw new RuntimeException("Could not invoke Conscrypt.newProvider", ex);
- } catch (InvocationTargetException ex) {
- throw new RuntimeException("Could not invoke Conscrypt.newProvider", ex);
- }
- Security.addProvider(provider);
- conscryptInstallAttempted = true;
- }
}
diff --git a/interop-testing/src/test/java/io/grpc/testing/integration/Http2NettyTest.java b/interop-testing/src/test/java/io/grpc/testing/integration/Http2NettyTest.java
index ad5b286..da1e647 100644
--- a/interop-testing/src/test/java/io/grpc/testing/integration/Http2NettyTest.java
+++ b/interop-testing/src/test/java/io/grpc/testing/integration/Http2NettyTest.java
@@ -26,7 +26,6 @@
import io.grpc.netty.NettyChannelBuilder;
import io.grpc.netty.NettyServerBuilder;
import io.netty.handler.ssl.ClientAuth;
-import io.netty.handler.ssl.SslProvider;
import io.netty.handler.ssl.SupportedCipherSuiteFilter;
import java.io.IOException;
import java.net.InetAddress;
@@ -53,7 +52,6 @@
.clientAuth(ClientAuth.REQUIRE)
.trustManager(TestUtils.loadCert("ca.pem"))
.ciphers(TestUtils.preferredTestCiphers(), SupportedCipherSuiteFilter.INSTANCE)
- .sslProvider(SslProvider.OPENSSL)
.build());
} catch (IOException ex) {
throw new RuntimeException(ex);
@@ -72,7 +70,6 @@
.keyManager(TestUtils.loadCert("client.pem"), TestUtils.loadCert("client.key"))
.trustManager(TestUtils.loadX509Cert("ca.pem"))
.ciphers(TestUtils.preferredTestCiphers(), SupportedCipherSuiteFilter.INSTANCE)
- .sslProvider(SslProvider.OPENSSL)
.build());
io.grpc.internal.TestingAccessor.setStatsImplementation(
builder, createClientCensusStatsModule());
diff --git a/interop-testing/src/test/java/io/grpc/testing/integration/Http2OkHttpTest.java b/interop-testing/src/test/java/io/grpc/testing/integration/Http2OkHttpTest.java
index ffa5c18..2f6f6e8 100644
--- a/interop-testing/src/test/java/io/grpc/testing/integration/Http2OkHttpTest.java
+++ b/interop-testing/src/test/java/io/grpc/testing/integration/Http2OkHttpTest.java
@@ -60,7 +60,7 @@
public static void loadConscrypt() throws Exception {
// Load conscrypt if it is available. Either Conscrypt or Jetty ALPN needs to be available for
// OkHttp to negotiate.
- Util.installConscryptIfAvailable();
+ TestUtils.installConscryptIfAvailable();
}
@Override
diff --git a/netty/build.gradle b/netty/build.gradle
index dcbffda..cc10105 100644
--- a/netty/build.gradle
+++ b/netty/build.gradle
@@ -8,7 +8,8 @@
testCompile project(':grpc-core').sourceSets.test.output,
project(':grpc-testing'),
project(':grpc-testing-proto')
- testRuntime libraries.netty_tcnative
+ testRuntime libraries.netty_tcnative,
+ libraries.conscrypt
signature "org.codehaus.mojo.signature:java17:1.0@signature"
}
diff --git a/netty/src/main/java/io/grpc/netty/GrpcSslContexts.java b/netty/src/main/java/io/grpc/netty/GrpcSslContexts.java
index 4c67ac4..15dfb63 100644
--- a/netty/src/main/java/io/grpc/netty/GrpcSslContexts.java
+++ b/netty/src/main/java/io/grpc/netty/GrpcSslContexts.java
@@ -20,6 +20,7 @@
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import io.grpc.ExperimentalApi;
+import io.grpc.internal.MoreThrowables;
import io.netty.handler.codec.http2.Http2SecurityUtil;
import io.netty.handler.ssl.ApplicationProtocolConfig;
import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol;
@@ -30,9 +31,15 @@
import io.netty.handler.ssl.SslProvider;
import io.netty.handler.ssl.SupportedCipherSuiteFilter;
import java.io.File;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.security.Provider;
+import java.security.Security;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
/**
* Utility for configuring SslContext for gRPC.
@@ -40,6 +47,8 @@
@SuppressWarnings("deprecation")
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1784")
public class GrpcSslContexts {
+ private static final Logger logger = Logger.getLogger(GrpcSslContexts.class.getName());
+
private GrpcSslContexts() {}
/*
@@ -84,6 +93,22 @@
SelectedListenerFailureBehavior.ACCEPT,
NEXT_PROTOCOL_VERSIONS);
+ private static final String SUN_PROVIDER_NAME = "SunJSSE";
+ private static final Method IS_CONSCRYPT_PROVIDER;
+
+ static {
+ Method method = null;
+ try {
+ Class<?> conscryptClass = Class.forName("org.conscrypt.Conscrypt");
+ method = conscryptClass.getMethod("isConscrypt", Provider.class);
+ } catch (ClassNotFoundException ex) {
+ logger.log(Level.FINE, "Conscrypt class not found. Not using Conscrypt", ex);
+ } catch (NoSuchMethodException ex) {
+ throw new AssertionError(ex);
+ }
+ IS_CONSCRYPT_PROVIDER = method;
+ }
+
/**
* Creates a SslContextBuilder with ciphers and APN appropriate for gRPC.
*
@@ -131,49 +156,115 @@
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1784")
@CanIgnoreReturnValue
public static SslContextBuilder configure(SslContextBuilder builder, SslProvider provider) {
- return builder.sslProvider(provider)
- .ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE)
- .applicationProtocolConfig(selectApplicationProtocolConfig(provider));
+ switch (provider) {
+ case JDK:
+ {
+ Provider jdkProvider = findJdkProvider();
+ if (jdkProvider == null) {
+ throw new IllegalArgumentException(
+ "Could not find Jetty NPN/ALPN or Conscrypt as installed JDK providers");
+ }
+ return configure(builder, jdkProvider);
+ }
+ case OPENSSL:
+ {
+ ApplicationProtocolConfig apc;
+ if (OpenSsl.isAlpnSupported()) {
+ apc = NPN_AND_ALPN;
+ } else {
+ apc = NPN;
+ }
+ return builder
+ .sslProvider(SslProvider.OPENSSL)
+ .ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE)
+ .applicationProtocolConfig(apc);
+ }
+ default:
+ throw new IllegalArgumentException("Unsupported provider: " + provider);
+ }
+ }
+
+ /**
+ * Set ciphers and APN appropriate for gRPC. Precisely what is set is permitted to change, so if
+ * an application requires particular settings it should override the options set here.
+ */
+ @CanIgnoreReturnValue
+ public static SslContextBuilder configure(SslContextBuilder builder, Provider jdkProvider) {
+ ApplicationProtocolConfig apc;
+ if (SUN_PROVIDER_NAME.equals(jdkProvider.getName())) {
+ // Jetty ALPN/NPN only supports one of NPN or ALPN
+ if (JettyTlsUtil.isJettyAlpnConfigured()) {
+ apc = ALPN;
+ } else if (JettyTlsUtil.isJettyNpnConfigured()) {
+ apc = NPN;
+ } else {
+ throw new IllegalArgumentException(
+ SUN_PROVIDER_NAME + " selected, but Jetty NPN/ALPN unavailable");
+ }
+ } else if (isConscrypt(jdkProvider)) {
+ apc = ALPN;
+ } else {
+ throw new IllegalArgumentException("Unknown provider; can't configure: " + jdkProvider);
+ }
+ return builder
+ .sslProvider(SslProvider.JDK)
+ .ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE)
+ .applicationProtocolConfig(apc)
+ .sslContextProvider(jdkProvider);
}
/**
* Returns OpenSSL if available, otherwise returns the JDK provider.
*/
private static SslProvider defaultSslProvider() {
- return OpenSsl.isAvailable() ? SslProvider.OPENSSL : SslProvider.JDK;
+ if (OpenSsl.isAvailable()) {
+ logger.log(Level.FINE, "Selecting OPENSSL");
+ return SslProvider.OPENSSL;
+ }
+ Provider provider = findJdkProvider();
+ if (provider != null) {
+ logger.log(Level.FINE, "Selecting JDK with provider {0}", provider);
+ return SslProvider.JDK;
+ }
+ logger.log(Level.INFO, "netty-tcnative unavailable (this may be normal)",
+ OpenSsl.unavailabilityCause());
+ logger.log(Level.INFO, "Conscrypt not found (this may be normal)");
+ logger.log(Level.INFO, "Jetty ALPN unavailable (this may be normal)",
+ JettyTlsUtil.getJettyAlpnUnavailabilityCause());
+ throw new IllegalStateException(
+ "Could not find TLS ALPN provider; "
+ + "no working netty-tcnative, Conscrypt, or Jetty NPN/ALPN available");
}
- /**
- * Attempts to select the best {@link ApplicationProtocolConfig} for the given
- * {@link SslProvider}.
- */
- private static ApplicationProtocolConfig selectApplicationProtocolConfig(SslProvider provider) {
- switch (provider) {
- case JDK: {
- if (JettyTlsUtil.isJettyAlpnConfigured()) {
- return ALPN;
+ private static Provider findJdkProvider() {
+ for (Provider provider : Security.getProviders("SSLContext.TLS")) {
+ if (SUN_PROVIDER_NAME.equals(provider.getName())) {
+ if (JettyTlsUtil.isJettyAlpnConfigured()
+ || JettyTlsUtil.isJettyNpnConfigured()
+ || JettyTlsUtil.isJava9AlpnAvailable()) {
+ return provider;
}
- if (JettyTlsUtil.isJettyNpnConfigured()) {
- return NPN;
- }
- if (JettyTlsUtil.isJava9AlpnAvailable()) {
- return ALPN;
- }
- // Use the ALPN cause since it is prefered.
- throw new IllegalArgumentException(
- "ALPN is not configured properly. See https://github.com/grpc/grpc-java/blob/master/SECURITY.md#troubleshooting"
- + " for more information.",
- JettyTlsUtil.getJettyAlpnUnavailabilityCause());
+ } else if (isConscrypt(provider)) {
+ return provider;
}
- case OPENSSL: {
- if (!OpenSsl.isAvailable()) {
- throw new IllegalArgumentException(
- "OpenSSL is not installed on the system.", OpenSsl.unavailabilityCause());
- }
- return OpenSsl.isAlpnSupported() ? NPN_AND_ALPN : NPN;
+ }
+ return null;
+ }
+
+ private static boolean isConscrypt(Provider provider) {
+ if (IS_CONSCRYPT_PROVIDER == null) {
+ return false;
+ }
+ try {
+ return (Boolean) IS_CONSCRYPT_PROVIDER.invoke(null, provider);
+ } catch (IllegalAccessException ex) {
+ throw new AssertionError(ex);
+ } catch (InvocationTargetException ex) {
+ if (ex.getCause() != null) {
+ MoreThrowables.throwIfUnchecked(ex.getCause());
+ // If checked, just wrap up everything.
}
- default:
- throw new IllegalArgumentException("Unsupported provider: " + provider);
+ throw new AssertionError(ex);
}
}
diff --git a/netty/src/test/java/io/grpc/netty/TlsTest.java b/netty/src/test/java/io/grpc/netty/TlsTest.java
index 3ee2ba5..c99d7f1 100644
--- a/netty/src/test/java/io/grpc/netty/TlsTest.java
+++ b/netty/src/test/java/io/grpc/netty/TlsTest.java
@@ -39,6 +39,8 @@
import java.io.File;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
+import java.security.Provider;
+import java.security.Security;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.concurrent.Executors;
@@ -48,6 +50,7 @@
import org.junit.After;
import org.junit.Assume;
import org.junit.Before;
+import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -61,41 +64,70 @@
@RunWith(Parameterized.class)
public class TlsTest {
+ public static enum TlsImpl {
+ TCNATIVE, JETTY, CONSCRYPT;
+ }
+
/**
* Iterable of various configurations to use for tests.
*/
@Parameters(name = "{0}")
public static Iterable<Object[]> data() {
return Arrays.asList(new Object[][] {
- {SslProvider.JDK}, {SslProvider.OPENSSL},
+ {TlsImpl.TCNATIVE}, {TlsImpl.JETTY}, {TlsImpl.CONSCRYPT},
});
}
@Parameter(value = 0)
- public SslProvider sslProvider;
+ public TlsImpl tlsImpl;
private ScheduledExecutorService executor;
private Server server;
private ManagedChannel channel;
+ private SslProvider sslProvider;
+ private Provider jdkProvider;
private SslContextBuilder clientContextBuilder;
+ @BeforeClass
+ public static void loadConscrypt() {
+ TestUtils.installConscryptIfAvailable();
+ }
+
@Before
public void setUp() throws NoSuchAlgorithmException {
executor = Executors.newSingleThreadScheduledExecutor();
- if (sslProvider == SslProvider.OPENSSL) {
- Assume.assumeTrue(OpenSsl.isAvailable());
+ switch (tlsImpl) {
+ case TCNATIVE:
+ Assume.assumeTrue(OpenSsl.isAvailable());
+ sslProvider = SslProvider.OPENSSL;
+ break;
+ case JETTY:
+ Assume.assumeTrue(Arrays.asList(
+ SSLContext.getDefault().getSupportedSSLParameters().getCipherSuites())
+ .contains("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"));
+ sslProvider = SslProvider.JDK;
+ jdkProvider = Security.getProvider("SunJSSE");
+ Assume.assumeNotNull(jdkProvider);
+ try {
+ GrpcSslContexts.configure(SslContextBuilder.forClient(), jdkProvider);
+ } catch (IllegalArgumentException ex) {
+ Assume.assumeNoException("Jetty ALPN does not seem available", ex);
+ }
+ break;
+ case CONSCRYPT:
+ sslProvider = SslProvider.JDK;
+ jdkProvider = Security.getProvider("Conscrypt");
+ Assume.assumeNotNull(jdkProvider);
+ break;
+ default:
+ throw new AssertionError();
}
+ clientContextBuilder = SslContextBuilder.forClient();
if (sslProvider == SslProvider.JDK) {
- Assume.assumeTrue(Arrays.asList(
- SSLContext.getDefault().getSupportedSSLParameters().getCipherSuites())
- .contains("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"));
- try {
- GrpcSslContexts.configure(SslContextBuilder.forClient(), SslProvider.JDK);
- } catch (IllegalArgumentException ex) {
- Assume.assumeNoException("Jetty ALPN does not seem available", ex);
- }
+ GrpcSslContexts.configure(clientContextBuilder, jdkProvider);
+ } else {
+ GrpcSslContexts.configure(clientContextBuilder, sslProvider);
}
- clientContextBuilder = GrpcSslContexts.configure(SslContextBuilder.forClient(), sslProvider);
}
@After
@@ -281,7 +313,11 @@
File serverPrivateKeyFile, X509Certificate[] serverTrustedCaCerts) throws IOException {
SslContextBuilder sslContextBuilder
= SslContextBuilder.forServer(serverCertChainFile, serverPrivateKeyFile);
- GrpcSslContexts.configure(sslContextBuilder, sslProvider);
+ if (sslProvider == SslProvider.JDK) {
+ GrpcSslContexts.configure(sslContextBuilder, jdkProvider);
+ } else {
+ GrpcSslContexts.configure(sslContextBuilder, sslProvider);
+ }
sslContextBuilder.trustManager(serverTrustedCaCerts)
.clientAuth(ClientAuth.REQUIRE);
diff --git a/testing/src/main/java/io/grpc/internal/testing/TestUtils.java b/testing/src/main/java/io/grpc/internal/testing/TestUtils.java
index 8ca934e..59fe417 100644
--- a/testing/src/main/java/io/grpc/internal/testing/TestUtils.java
+++ b/testing/src/main/java/io/grpc/internal/testing/TestUtils.java
@@ -24,12 +24,15 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
+import java.security.Security;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
@@ -142,6 +145,43 @@
}
}
+ private static boolean conscryptInstallAttempted;
+
+ /**
+ * Add Conscrypt to the list of security providers, if it is available. If it appears to be
+ * available but fails to load, this method will throw an exception. Since the list of security
+ * providers is static, this method does nothing if the provider is not available or succeeded
+ * previously.
+ */
+ public static void installConscryptIfAvailable() {
+ if (conscryptInstallAttempted) {
+ return;
+ }
+ Class<?> conscrypt;
+ try {
+ conscrypt = Class.forName("org.conscrypt.Conscrypt");
+ } catch (ClassNotFoundException ex) {
+ conscryptInstallAttempted = true;
+ return;
+ }
+ Method newProvider;
+ try {
+ newProvider = conscrypt.getMethod("newProvider");
+ } catch (NoSuchMethodException ex) {
+ throw new RuntimeException("Could not find newProvider method on Conscrypt", ex);
+ }
+ Provider provider;
+ try {
+ provider = (Provider) newProvider.invoke(null);
+ } catch (IllegalAccessException ex) {
+ throw new RuntimeException("Could not invoke Conscrypt.newProvider", ex);
+ } catch (InvocationTargetException ex) {
+ throw new RuntimeException("Could not invoke Conscrypt.newProvider", ex);
+ }
+ Security.addProvider(provider);
+ conscryptInstallAttempted = true;
+ }
+
/**
* Creates an SSLSocketFactory which contains {@code certChainFile} as its only root certificate.
*/