From a44046c081f232166a4d98c275ccd66f90ad09b4 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Fri, 25 Aug 2017 13:01:57 -0700 Subject: [PATCH 1/2] Move TlsTest from interop-testing to netty --- netty/build.gradle | 7 ++- .../src/test/java/io/grpc/netty}/TlsTest.java | 49 ++++++++++--------- 2 files changed, 32 insertions(+), 24 deletions(-) rename {interop-testing/src/test/java/io/grpc/testing/integration => netty/src/test/java/io/grpc/netty}/TlsTest.java (88%) diff --git a/netty/build.gradle b/netty/build.gradle index 9dbeed893b4..dcbffdadc76 100644 --- a/netty/build.gradle +++ b/netty/build.gradle @@ -6,7 +6,8 @@ dependencies { // Tests depend on base class defined by core module. testCompile project(':grpc-core').sourceSets.test.output, - project(':grpc-testing') + project(':grpc-testing'), + project(':grpc-testing-proto') testRuntime libraries.netty_tcnative signature "org.codehaus.mojo.signature:java17:1.0@signature" } @@ -29,6 +30,10 @@ project.sourceSets { } } +test { + // Allow testing Jetty ALPN in TlsTest + jvmArgs "-javaagent:" + configurations.alpnagent.asPath +} jmh { // Workaround diff --git a/interop-testing/src/test/java/io/grpc/testing/integration/TlsTest.java b/netty/src/test/java/io/grpc/netty/TlsTest.java similarity index 88% rename from interop-testing/src/test/java/io/grpc/testing/integration/TlsTest.java rename to netty/src/test/java/io/grpc/netty/TlsTest.java index fa0abbe0f99..3ee2ba5c19b 100644 --- a/interop-testing/src/test/java/io/grpc/testing/integration/TlsTest.java +++ b/netty/src/test/java/io/grpc/netty/TlsTest.java @@ -14,24 +14,23 @@ * limitations under the License. */ -package io.grpc.testing.integration; +package io.grpc.netty; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import com.google.common.base.Throwables; import com.google.common.util.concurrent.MoreExecutors; -import com.google.protobuf.EmptyProtos.Empty; import io.grpc.ManagedChannel; import io.grpc.Server; import io.grpc.ServerBuilder; import io.grpc.Status; import io.grpc.StatusRuntimeException; import io.grpc.internal.testing.TestUtils; -import io.grpc.netty.GrpcSslContexts; -import io.grpc.netty.NegotiationType; -import io.grpc.netty.NettyChannelBuilder; -import io.grpc.netty.NettyServerBuilder; +import io.grpc.stub.StreamObserver; +import io.grpc.testing.protobuf.SimpleRequest; +import io.grpc.testing.protobuf.SimpleResponse; +import io.grpc.testing.protobuf.SimpleServiceGrpc; import io.netty.handler.ssl.ClientAuth; import io.netty.handler.ssl.OpenSsl; import io.netty.handler.ssl.SslContext; @@ -57,7 +56,7 @@ /** - * Integration tests for GRPC's TLS support. + * Integration tests for Netty's TLS support. */ @RunWith(Parameterized.class) public class TlsTest { @@ -124,7 +123,7 @@ public void basicClientServerIntegrationTest() throws Exception { TestUtils.loadX509Cert("ca.pem") }; server = serverBuilder(0, serverCertFile, serverPrivateKeyFile, serverTrustedCaCerts) - .addService(new TestServiceImpl(executor)) + .addService(new SimpleServiceImpl()) .build() .start(); @@ -138,12 +137,11 @@ public void basicClientServerIntegrationTest() throws Exception { .keyManager(clientCertChainFile, clientPrivateKeyFile) .trustManager(clientTrustedCaCerts) .build()); - TestServiceGrpc.TestServiceBlockingStub client = TestServiceGrpc.newBlockingStub(channel); + SimpleServiceGrpc.SimpleServiceBlockingStub client = SimpleServiceGrpc.newBlockingStub(channel); // Send an actual request, via the full GRPC & network stack, and check that a proper // response comes back. - Empty request = Empty.getDefaultInstance(); - client.emptyCall(request); + client.unaryRpc(SimpleRequest.getDefaultInstance()); } @@ -160,7 +158,7 @@ public void serverRejectsUntrustedClientCert() throws Exception { TestUtils.loadX509Cert("ca.pem") }; server = serverBuilder(0, serverCertFile, serverPrivateKeyFile, serverTrustedCaCerts) - .addService(new TestServiceImpl(executor)) + .addService(new SimpleServiceImpl()) .build() .start(); @@ -176,12 +174,11 @@ public void serverRejectsUntrustedClientCert() throws Exception { .keyManager(clientCertChainFile, clientPrivateKeyFile) .trustManager(clientTrustedCaCerts) .build()); - TestServiceGrpc.TestServiceBlockingStub client = TestServiceGrpc.newBlockingStub(channel); + SimpleServiceGrpc.SimpleServiceBlockingStub client = SimpleServiceGrpc.newBlockingStub(channel); // Check that the TLS handshake fails. - Empty request = Empty.getDefaultInstance(); try { - client.emptyCall(request); + client.unaryRpc(SimpleRequest.getDefaultInstance()); fail("TLS handshake should have failed, but didn't; received RPC response"); } catch (StatusRuntimeException e) { // GRPC reports this situation by throwing a StatusRuntimeException that wraps either a @@ -207,7 +204,7 @@ public void noClientAuthFailure() throws Exception { TestUtils.loadX509Cert("ca.pem") }; server = serverBuilder(0, serverCertFile, serverPrivateKeyFile, serverTrustedCaCerts) - .addService(new TestServiceImpl(executor)) + .addService(new SimpleServiceImpl()) .build() .start(); @@ -218,12 +215,11 @@ public void noClientAuthFailure() throws Exception { channel = clientChannel(server.getPort(), clientContextBuilder .trustManager(clientTrustedCaCerts) .build()); - TestServiceGrpc.TestServiceBlockingStub client = TestServiceGrpc.newBlockingStub(channel); + SimpleServiceGrpc.SimpleServiceBlockingStub client = SimpleServiceGrpc.newBlockingStub(channel); // Check that the TLS handshake fails. - Empty request = Empty.getDefaultInstance(); try { - client.emptyCall(request); + client.unaryRpc(SimpleRequest.getDefaultInstance()); fail("TLS handshake should have failed, but didn't; received RPC response"); } catch (StatusRuntimeException e) { // GRPC reports this situation by throwing a StatusRuntimeException that wraps either a @@ -249,7 +245,7 @@ public void clientRejectsUntrustedServerCert() throws Exception { TestUtils.loadX509Cert("ca.pem") }; server = serverBuilder(0, serverCertFile, serverPrivateKeyFile, serverTrustedCaCerts) - .addService(new TestServiceImpl(executor)) + .addService(new SimpleServiceImpl()) .build() .start(); @@ -263,12 +259,11 @@ public void clientRejectsUntrustedServerCert() throws Exception { .keyManager(clientCertChainFile, clientPrivateKeyFile) .trustManager(clientTrustedCaCerts) .build()); - TestServiceGrpc.TestServiceBlockingStub client = TestServiceGrpc.newBlockingStub(channel); + SimpleServiceGrpc.SimpleServiceBlockingStub client = SimpleServiceGrpc.newBlockingStub(channel); // Check that the TLS handshake fails. - Empty request = Empty.getDefaultInstance(); try { - client.emptyCall(request); + client.unaryRpc(SimpleRequest.getDefaultInstance()); fail("TLS handshake should have failed, but didn't; received RPC response"); } catch (StatusRuntimeException e) { // GRPC reports this situation by throwing a StatusRuntimeException that wraps either a @@ -302,4 +297,12 @@ private static ManagedChannel clientChannel(int port, SslContext sslContext) thr .sslContext(sslContext) .build(); } + + private static class SimpleServiceImpl extends SimpleServiceGrpc.SimpleServiceImplBase { + @Override + public void unaryRpc(SimpleRequest req, StreamObserver respOb) { + respOb.onNext(SimpleResponse.getDefaultInstance()); + respOb.onCompleted(); + } + } } From f4f6c318e71025614df455f9a575847f76767c9a Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Thu, 15 Feb 2018 17:14:10 -0800 Subject: [PATCH 2/2] netty: Add support for Conscrypt --- build.gradle | 2 + .../java/io/grpc/internal/MoreThrowables.java | 2 +- interop-testing/build.gradle | 4 +- .../integration/TestServiceClient.java | 4 +- .../integration/TestServiceServer.java | 2 + .../io/grpc/testing/integration/Util.java | 41 ----- .../testing/integration/Http2NettyTest.java | 3 - .../testing/integration/Http2OkHttpTest.java | 2 +- netty/build.gradle | 3 +- .../java/io/grpc/netty/GrpcSslContexts.java | 155 ++++++++++++++---- .../src/test/java/io/grpc/netty/TlsTest.java | 64 ++++++-- .../io/grpc/internal/testing/TestUtils.java | 40 +++++ 12 files changed, 225 insertions(+), 97 deletions(-) diff --git a/build.gradle b/build.gradle index 22cefcf1e2b..08728492e07 100644 --- a/build.gradle +++ b/build.gradle @@ -216,6 +216,8 @@ subprojects { 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 3e30c97222a..e310bf8bbd8 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 0fb71a1c7ec..3de5573919f 100644 --- a/interop-testing/build.gradle +++ b/interop-testing/build.gradle @@ -27,10 +27,10 @@ dependencies { 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 2e4eee73f2f..bdf7eb1366e 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 @@ public class TestServiceClient { * 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 bc683837b24..d34b7dbeaed 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 35cd948e79b..dcd4c703c5e 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 @@ public static void assertEquals(List expected, } } } - - 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 ad5b286d599..da1e6471ea6 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 @@ protected AbstractServerImplBuilder getServerBuilder() { .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 @@ protected ManagedChannel createChannel() { .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 ffa5c1883be..2f6f6e80cc7 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 class Http2OkHttpTest extends AbstractInteropTest { 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 dcbffdadc76..cc10105fc90 100644 --- a/netty/build.gradle +++ b/netty/build.gradle @@ -8,7 +8,8 @@ dependencies { 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 4c67ac4eb83..15dfb63b963 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 @@ private GrpcSslContexts() {} 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 @@ public static SslContextBuilder configure(SslContextBuilder builder) { @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); + } } /** - * Returns OpenSSL if available, otherwise returns the JDK 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. */ - private static SslProvider defaultSslProvider() { - return OpenSsl.isAvailable() ? SslProvider.OPENSSL : SslProvider.JDK; + @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); } /** - * Attempts to select the best {@link ApplicationProtocolConfig} for the given - * {@link SslProvider}. + * Returns OpenSSL if available, otherwise returns the JDK provider. */ - private static ApplicationProtocolConfig selectApplicationProtocolConfig(SslProvider provider) { - switch (provider) { - case JDK: { - if (JettyTlsUtil.isJettyAlpnConfigured()) { - return ALPN; - } - if (JettyTlsUtil.isJettyNpnConfigured()) { - return NPN; - } - if (JettyTlsUtil.isJava9AlpnAvailable()) { - return ALPN; + private static SslProvider defaultSslProvider() { + 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"); + } + + 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; } - // 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 3ee2ba5c19b..c99d7f1ce1b 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 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 @@ private ServerBuilder serverBuilder(int port, File serverCertChainFile, 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 8ca934ec37b..59fe4172c51 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 @@ public static X509Certificate loadX509Cert(String fileName) } } + 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. */