• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2015 The gRPC Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package io.grpc.netty;
18 
19 import static com.google.common.base.Preconditions.checkArgument;
20 
21 import com.google.errorprone.annotations.CanIgnoreReturnValue;
22 import io.grpc.ExperimentalApi;
23 import io.grpc.internal.MoreThrowables;
24 import io.netty.handler.codec.http2.Http2SecurityUtil;
25 import io.netty.handler.ssl.ApplicationProtocolConfig;
26 import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol;
27 import io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior;
28 import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior;
29 import io.netty.handler.ssl.OpenSsl;
30 import io.netty.handler.ssl.SslContextBuilder;
31 import io.netty.handler.ssl.SslProvider;
32 import io.netty.handler.ssl.SupportedCipherSuiteFilter;
33 import java.io.File;
34 import java.io.InputStream;
35 import java.lang.reflect.InvocationTargetException;
36 import java.lang.reflect.Method;
37 import java.security.Provider;
38 import java.security.Security;
39 import java.util.Arrays;
40 import java.util.Collections;
41 import java.util.List;
42 import java.util.logging.Level;
43 import java.util.logging.Logger;
44 
45 /**
46  * Utility for configuring SslContext for gRPC.
47  */
48 @SuppressWarnings("deprecation")
49 @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1784")
50 public class GrpcSslContexts {
51   private static final Logger logger = Logger.getLogger(GrpcSslContexts.class.getName());
52 
GrpcSslContexts()53   private GrpcSslContexts() {}
54 
55   /*
56    * The experimental "grpc-exp" string identifies gRPC (and by implication
57    * HTTP/2) when used over TLS. This indicates to the server that the client
58    * will only send gRPC traffic on the h2 connection and is negotiated in
59    * preference to h2 when the client and server support it, but is not
60    * standardized. Support for this may be removed at any time.
61    */
62   private static final String GRPC_EXP_VERSION = "grpc-exp";
63 
64   // The "h2" string identifies HTTP/2 when used over TLS
65   private static final String HTTP2_VERSION = "h2";
66 
67   /*
68    * List of ALPN/NPN protocols in order of preference. GRPC_EXP_VERSION
69    * requires that HTTP2_VERSION be present and that GRPC_EXP_VERSION should be
70    * preferenced over HTTP2_VERSION.
71    */
72   static final List<String> NEXT_PROTOCOL_VERSIONS =
73       Collections.unmodifiableList(Arrays.asList(GRPC_EXP_VERSION, HTTP2_VERSION));
74 
75   /*
76    * These configs use ACCEPT due to limited support in OpenSSL.  Actual protocol enforcement is
77    * done in ProtocolNegotiators.
78    */
79   private static final ApplicationProtocolConfig ALPN = new ApplicationProtocolConfig(
80       Protocol.ALPN,
81       SelectorFailureBehavior.NO_ADVERTISE,
82       SelectedListenerFailureBehavior.ACCEPT,
83       NEXT_PROTOCOL_VERSIONS);
84 
85   private static final ApplicationProtocolConfig NPN = new ApplicationProtocolConfig(
86       Protocol.NPN,
87       SelectorFailureBehavior.NO_ADVERTISE,
88       SelectedListenerFailureBehavior.ACCEPT,
89       NEXT_PROTOCOL_VERSIONS);
90 
91   private static final ApplicationProtocolConfig NPN_AND_ALPN = new ApplicationProtocolConfig(
92       Protocol.NPN_AND_ALPN,
93       SelectorFailureBehavior.NO_ADVERTISE,
94       SelectedListenerFailureBehavior.ACCEPT,
95       NEXT_PROTOCOL_VERSIONS);
96 
97   private static final String SUN_PROVIDER_NAME = "SunJSSE";
98   private static final Method IS_CONSCRYPT_PROVIDER;
99 
100   static {
101     Method method = null;
102     try {
103       Class<?> conscryptClass = Class.forName("org.conscrypt.Conscrypt");
104       method = conscryptClass.getMethod("isConscrypt", Provider.class);
105     } catch (ClassNotFoundException ex) {
106       logger.log(Level.FINE, "Conscrypt class not found. Not using Conscrypt", ex);
107     } catch (NoSuchMethodException ex) {
108       throw new AssertionError(ex);
109     }
110     IS_CONSCRYPT_PROVIDER = method;
111   }
112 
113   /**
114    * Creates a SslContextBuilder with ciphers and APN appropriate for gRPC.
115    *
116    * @see SslContextBuilder#forClient()
117    * @see #configure(SslContextBuilder)
118    */
forClient()119   public static SslContextBuilder forClient() {
120     return configure(SslContextBuilder.forClient());
121   }
122 
123   /**
124    * Creates a SslContextBuilder with ciphers and APN appropriate for gRPC.
125    *
126    * @see SslContextBuilder#forServer(File, File)
127    * @see #configure(SslContextBuilder)
128    */
forServer(File keyCertChainFile, File keyFile)129   public static SslContextBuilder forServer(File keyCertChainFile, File keyFile) {
130     return configure(SslContextBuilder.forServer(keyCertChainFile, keyFile));
131   }
132 
133   /**
134    * Creates a SslContextBuilder with ciphers and APN appropriate for gRPC.
135    *
136    * @see SslContextBuilder#forServer(File, File, String)
137    * @see #configure(SslContextBuilder)
138    */
forServer( File keyCertChainFile, File keyFile, String keyPassword)139   public static SslContextBuilder forServer(
140       File keyCertChainFile, File keyFile, String keyPassword) {
141     return configure(SslContextBuilder.forServer(keyCertChainFile, keyFile, keyPassword));
142   }
143 
144   /**
145    * Creates a SslContextBuilder with ciphers and APN appropriate for gRPC.
146    *
147    * @see SslContextBuilder#forServer(InputStream, InputStream)
148    * @see #configure(SslContextBuilder)
149    */
forServer(InputStream keyCertChain, InputStream key)150   public static SslContextBuilder forServer(InputStream keyCertChain, InputStream key) {
151     return configure(SslContextBuilder.forServer(keyCertChain, key));
152   }
153 
154   /**
155    * Creates a SslContextBuilder with ciphers and APN appropriate for gRPC.
156    *
157    * @see SslContextBuilder#forServer(InputStream, InputStream, String)
158    * @see #configure(SslContextBuilder)
159    */
forServer( InputStream keyCertChain, InputStream key, String keyPassword)160   public static SslContextBuilder forServer(
161       InputStream keyCertChain, InputStream key, String keyPassword) {
162     return configure(SslContextBuilder.forServer(keyCertChain, key, keyPassword));
163   }
164 
165   /**
166    * Set ciphers and APN appropriate for gRPC. Precisely what is set is permitted to change, so if
167    * an application requires particular settings it should override the options set here.
168    */
169   @CanIgnoreReturnValue
configure(SslContextBuilder builder)170   public static SslContextBuilder configure(SslContextBuilder builder) {
171     return configure(builder, defaultSslProvider());
172   }
173 
174   /**
175    * Set ciphers and APN appropriate for gRPC. Precisely what is set is permitted to change, so if
176    * an application requires particular settings it should override the options set here.
177    */
178   @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1784")
179   @CanIgnoreReturnValue
configure(SslContextBuilder builder, SslProvider provider)180   public static SslContextBuilder configure(SslContextBuilder builder, SslProvider provider) {
181     switch (provider) {
182       case JDK:
183       {
184         Provider jdkProvider = findJdkProvider();
185         if (jdkProvider == null) {
186           throw new IllegalArgumentException(
187               "Could not find Jetty NPN/ALPN or Conscrypt as installed JDK providers");
188         }
189         return configure(builder, jdkProvider);
190       }
191       case OPENSSL:
192       {
193         ApplicationProtocolConfig apc;
194         if (OpenSsl.isAlpnSupported()) {
195           apc = NPN_AND_ALPN;
196         } else {
197           apc = NPN;
198         }
199         return builder
200             .sslProvider(SslProvider.OPENSSL)
201             .ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE)
202             .applicationProtocolConfig(apc);
203       }
204       default:
205         throw new IllegalArgumentException("Unsupported provider: " + provider);
206     }
207   }
208 
209   /**
210    * Set ciphers and APN appropriate for gRPC. Precisely what is set is permitted to change, so if
211    * an application requires particular settings it should override the options set here.
212    */
213   @CanIgnoreReturnValue
configure(SslContextBuilder builder, Provider jdkProvider)214   public static SslContextBuilder configure(SslContextBuilder builder, Provider jdkProvider) {
215     ApplicationProtocolConfig apc;
216     if (SUN_PROVIDER_NAME.equals(jdkProvider.getName())) {
217       // Jetty ALPN/NPN only supports one of NPN or ALPN
218       if (JettyTlsUtil.isJettyAlpnConfigured()) {
219         apc = ALPN;
220       } else if (JettyTlsUtil.isJettyNpnConfigured()) {
221         apc = NPN;
222       } else if (JettyTlsUtil.isJava9AlpnAvailable()) {
223         apc = ALPN;
224       } else {
225         throw new IllegalArgumentException(
226             SUN_PROVIDER_NAME + " selected, but Jetty NPN/ALPN unavailable");
227       }
228     } else if (isConscrypt(jdkProvider)) {
229       apc = ALPN;
230     } else {
231       throw new IllegalArgumentException("Unknown provider; can't configure: " + jdkProvider);
232     }
233     return builder
234         .sslProvider(SslProvider.JDK)
235         .ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE)
236         .applicationProtocolConfig(apc)
237         .sslContextProvider(jdkProvider);
238   }
239 
240   /**
241    * Returns OpenSSL if available, otherwise returns the JDK provider.
242    */
defaultSslProvider()243   private static SslProvider defaultSslProvider() {
244     if (OpenSsl.isAvailable()) {
245       logger.log(Level.FINE, "Selecting OPENSSL");
246       return SslProvider.OPENSSL;
247     }
248     Provider provider = findJdkProvider();
249     if (provider != null) {
250       logger.log(Level.FINE, "Selecting JDK with provider {0}", provider);
251       return SslProvider.JDK;
252     }
253     logger.log(Level.INFO, "netty-tcnative unavailable (this may be normal)",
254         OpenSsl.unavailabilityCause());
255     logger.log(Level.INFO, "Conscrypt not found (this may be normal)");
256     logger.log(Level.INFO, "Jetty ALPN unavailable (this may be normal)",
257         JettyTlsUtil.getJettyAlpnUnavailabilityCause());
258     throw new IllegalStateException(
259         "Could not find TLS ALPN provider; "
260         + "no working netty-tcnative, Conscrypt, or Jetty NPN/ALPN available");
261   }
262 
findJdkProvider()263   private static Provider findJdkProvider() {
264     for (Provider provider : Security.getProviders("SSLContext.TLS")) {
265       if (SUN_PROVIDER_NAME.equals(provider.getName())) {
266         if (JettyTlsUtil.isJettyAlpnConfigured()
267             || JettyTlsUtil.isJettyNpnConfigured()
268             || JettyTlsUtil.isJava9AlpnAvailable()) {
269           return provider;
270         }
271       } else if (isConscrypt(provider)) {
272         return provider;
273       }
274     }
275     return null;
276   }
277 
isConscrypt(Provider provider)278   private static boolean isConscrypt(Provider provider) {
279     if (IS_CONSCRYPT_PROVIDER == null) {
280       return false;
281     }
282     try {
283       return (Boolean) IS_CONSCRYPT_PROVIDER.invoke(null, provider);
284     } catch (IllegalAccessException ex) {
285       throw new AssertionError(ex);
286     } catch (InvocationTargetException ex) {
287       if (ex.getCause() != null) {
288         MoreThrowables.throwIfUnchecked(ex.getCause());
289         // If checked, just wrap up everything.
290       }
291       throw new AssertionError(ex);
292     }
293   }
294 
295   @SuppressWarnings("deprecation")
ensureAlpnAndH2Enabled( io.netty.handler.ssl.ApplicationProtocolNegotiator alpnNegotiator)296   static void ensureAlpnAndH2Enabled(
297       io.netty.handler.ssl.ApplicationProtocolNegotiator alpnNegotiator) {
298     checkArgument(alpnNegotiator != null, "ALPN must be configured");
299     checkArgument(alpnNegotiator.protocols() != null && !alpnNegotiator.protocols().isEmpty(),
300         "ALPN must be enabled and list HTTP/2 as a supported protocol.");
301     checkArgument(
302         alpnNegotiator.protocols().contains(HTTP2_VERSION),
303         "This ALPN config does not support HTTP/2. Expected %s, but got %s'.",
304         HTTP2_VERSION,
305         alpnNegotiator.protocols());
306   }
307 }
308