-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Add support for Conscrypt with Netty #3401
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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,16 +31,24 @@ | |
| 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. | ||
| */ | ||
| @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)", | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we expect the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Spoke offline. The main reason I didn't do that is because then the error is reported in two places: the log message and the exception. Right now the log messages are just informational and it's only after you get the exception that you can look at them and think "these might be useful." |
||
| 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); | ||
| } | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be wrapped in a function to provide an easier to understand stack trace if this ends up throwing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could, although I find it more confusing to have static methods that are called before static initialization completes. It's necessary and helpful at times, but when unnecessary I try to avoid it as I find it to be bug-prone.