• 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.ConscryptLoader;
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.security.Provider;
36 import java.security.Security;
37 import java.util.Arrays;
38 import java.util.Collections;
39 import java.util.List;
40 import java.util.logging.Level;
41 import java.util.logging.Logger;
42 
43 /**
44  * Utility for configuring SslContext for gRPC.
45  */
46 @SuppressWarnings("deprecation")
47 @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1784")
48 public class GrpcSslContexts {
49   private static final Logger logger = Logger.getLogger(GrpcSslContexts.class.getName());
50 
GrpcSslContexts()51   private GrpcSslContexts() {}
52 
53   // The "h2" string identifies HTTP/2 when used over TLS
54   private static final String HTTP2_VERSION = "h2";
55 
56   /*
57    * List of ALPN/NPN protocols in order of preference.
58    */
59   private static final List<String> NEXT_PROTOCOL_VERSIONS =
60       Collections.unmodifiableList(Arrays.asList(HTTP2_VERSION));
61 
62   /*
63    * These configs use ACCEPT due to limited support in OpenSSL.  Actual protocol enforcement is
64    * done in ProtocolNegotiators.
65    */
66   private static final ApplicationProtocolConfig ALPN = new ApplicationProtocolConfig(
67       Protocol.ALPN,
68       SelectorFailureBehavior.NO_ADVERTISE,
69       SelectedListenerFailureBehavior.ACCEPT,
70       NEXT_PROTOCOL_VERSIONS);
71 
72   private static final ApplicationProtocolConfig NPN = new ApplicationProtocolConfig(
73       Protocol.NPN,
74       SelectorFailureBehavior.NO_ADVERTISE,
75       SelectedListenerFailureBehavior.ACCEPT,
76       NEXT_PROTOCOL_VERSIONS);
77 
78   private static final ApplicationProtocolConfig NPN_AND_ALPN = new ApplicationProtocolConfig(
79       Protocol.NPN_AND_ALPN,
80       SelectorFailureBehavior.NO_ADVERTISE,
81       SelectedListenerFailureBehavior.ACCEPT,
82       NEXT_PROTOCOL_VERSIONS);
83 
84   private static final String SUN_PROVIDER_NAME = "SunJSSE";
85   private static final String IBM_PROVIDER_NAME = "IBMJSSE2";
86   private static final String OPENJSSE_PROVIDER_NAME = "OpenJSSE";
87 
88   /**
89    * Creates an SslContextBuilder with ciphers and APN appropriate for gRPC.
90    *
91    * @see SslContextBuilder#forClient()
92    * @see #configure(SslContextBuilder)
93    */
forClient()94   public static SslContextBuilder forClient() {
95     return configure(SslContextBuilder.forClient());
96   }
97 
98   /**
99    * Creates an SslContextBuilder with ciphers and APN appropriate for gRPC.
100    *
101    * @see SslContextBuilder#forServer(File, File)
102    * @see #configure(SslContextBuilder)
103    */
forServer(File keyCertChainFile, File keyFile)104   public static SslContextBuilder forServer(File keyCertChainFile, File keyFile) {
105     return configure(SslContextBuilder.forServer(keyCertChainFile, keyFile));
106   }
107 
108   /**
109    * Creates an SslContextBuilder with ciphers and APN appropriate for gRPC.
110    *
111    * @see SslContextBuilder#forServer(File, File, String)
112    * @see #configure(SslContextBuilder)
113    */
forServer( File keyCertChainFile, File keyFile, String keyPassword)114   public static SslContextBuilder forServer(
115       File keyCertChainFile, File keyFile, String keyPassword) {
116     return configure(SslContextBuilder.forServer(keyCertChainFile, keyFile, keyPassword));
117   }
118 
119   /**
120    * Creates an SslContextBuilder with ciphers and APN appropriate for gRPC.
121    *
122    * @see SslContextBuilder#forServer(InputStream, InputStream)
123    * @see #configure(SslContextBuilder)
124    */
forServer(InputStream keyCertChain, InputStream key)125   public static SslContextBuilder forServer(InputStream keyCertChain, InputStream key) {
126     return configure(SslContextBuilder.forServer(keyCertChain, key));
127   }
128 
129   /**
130    * Creates an SslContextBuilder with ciphers and APN appropriate for gRPC.
131    *
132    * @see SslContextBuilder#forServer(InputStream, InputStream, String)
133    * @see #configure(SslContextBuilder)
134    */
forServer( InputStream keyCertChain, InputStream key, String keyPassword)135   public static SslContextBuilder forServer(
136       InputStream keyCertChain, InputStream key, String keyPassword) {
137     return configure(SslContextBuilder.forServer(keyCertChain, key, keyPassword));
138   }
139 
140   /**
141    * Set ciphers and APN appropriate for gRPC. Precisely what is set is permitted to change, so if
142    * an application requires particular settings it should override the options set here.
143    */
144   @CanIgnoreReturnValue
configure(SslContextBuilder builder)145   public static SslContextBuilder configure(SslContextBuilder builder) {
146     return configure(builder, defaultSslProvider());
147   }
148 
149   /**
150    * Set ciphers and APN appropriate for gRPC. Precisely what is set is permitted to change, so if
151    * an application requires particular settings it should override the options set here.
152    */
153   @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1784")
154   @CanIgnoreReturnValue
configure(SslContextBuilder builder, SslProvider provider)155   public static SslContextBuilder configure(SslContextBuilder builder, SslProvider provider) {
156     switch (provider) {
157       case JDK: {
158         Provider jdkProvider = findJdkProvider();
159         if (jdkProvider == null) {
160           throw new IllegalArgumentException(
161               "Could not find Jetty NPN/ALPN or Conscrypt as installed JDK providers");
162         }
163         return configure(builder, jdkProvider);
164       }
165       case OPENSSL: {
166         ApplicationProtocolConfig apc;
167         if (OpenSsl.isAlpnSupported()) {
168           apc = NPN_AND_ALPN;
169         } else {
170           apc = NPN;
171         }
172         return builder
173             .sslProvider(SslProvider.OPENSSL)
174             .ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE)
175             .applicationProtocolConfig(apc);
176       }
177       default:
178         throw new IllegalArgumentException("Unsupported provider: " + provider);
179     }
180   }
181 
182   /**
183    * Set ciphers and APN appropriate for gRPC. Precisely what is set is permitted to change, so if
184    * an application requires particular settings it should override the options set here.
185    */
186   @CanIgnoreReturnValue
configure(SslContextBuilder builder, Provider jdkProvider)187   public static SslContextBuilder configure(SslContextBuilder builder, Provider jdkProvider) {
188     ApplicationProtocolConfig apc;
189     if (SUN_PROVIDER_NAME.equals(jdkProvider.getName())) {
190       // Jetty ALPN/NPN only supports one of NPN or ALPN
191       if (JettyTlsUtil.isJettyAlpnConfigured()) {
192         apc = ALPN;
193       } else if (JettyTlsUtil.isJettyNpnConfigured()) {
194         apc = NPN;
195       } else if (JettyTlsUtil.isJava9AlpnAvailable()) {
196         apc = ALPN;
197       } else {
198         throw new IllegalArgumentException(
199             jdkProvider.getName() + " selected, but Java 9+ and Jetty NPN/ALPN unavailable");
200       }
201     } else if (IBM_PROVIDER_NAME.equals(jdkProvider.getName())
202         || OPENJSSE_PROVIDER_NAME.equals(jdkProvider.getName())) {
203       if (JettyTlsUtil.isJava9AlpnAvailable()) {
204         apc = ALPN;
205       } else {
206         throw new IllegalArgumentException(
207             jdkProvider.getName() + " selected, but Java 9+ ALPN unavailable");
208       }
209     } else if (ConscryptLoader.isConscrypt(jdkProvider)) {
210       apc = ALPN;
211       // TODO: Conscrypt triggers failures in the TrustManager.
212       // https://github.com/grpc/grpc-java/issues/7765
213       builder.protocols("TLSv1.2");
214     } else {
215       throw new IllegalArgumentException("Unknown provider; can't configure: " + jdkProvider);
216     }
217     return builder
218         .sslProvider(SslProvider.JDK)
219         .ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE)
220         .applicationProtocolConfig(apc)
221         .sslContextProvider(jdkProvider);
222   }
223 
224   /**
225    * Returns OpenSSL if available, otherwise returns the JDK provider.
226    */
defaultSslProvider()227   private static SslProvider defaultSslProvider() {
228     if (OpenSsl.isAvailable()) {
229       logger.log(Level.FINE, "Selecting OPENSSL");
230       return SslProvider.OPENSSL;
231     }
232     Provider provider = findJdkProvider();
233     if (provider != null) {
234       logger.log(Level.FINE, "Selecting JDK with provider {0}", provider);
235       return SslProvider.JDK;
236     }
237     logger.log(Level.INFO, "Java 9 ALPN API unavailable (this may be normal)");
238     logger.log(Level.INFO, "netty-tcnative unavailable (this may be normal)",
239         OpenSsl.unavailabilityCause());
240     logger.log(Level.INFO, "Conscrypt not found (this may be normal)",
241         ConscryptHolder.UNAVAILABILITY_CAUSE);
242     logger.log(Level.INFO, "Jetty ALPN unavailable (this may be normal)",
243         JettyTlsUtil.getJettyAlpnUnavailabilityCause());
244     throw new IllegalStateException(
245         "Could not find TLS ALPN provider; "
246         + "no working netty-tcnative, Conscrypt, or Jetty NPN/ALPN available");
247   }
248 
findJdkProvider()249   private static Provider findJdkProvider() {
250     for (Provider provider : Security.getProviders("SSLContext.TLS")) {
251       if (SUN_PROVIDER_NAME.equals(provider.getName())) {
252         if (JettyTlsUtil.isJettyAlpnConfigured()
253             || JettyTlsUtil.isJettyNpnConfigured()
254             || JettyTlsUtil.isJava9AlpnAvailable()) {
255           return provider;
256         }
257       } else if (IBM_PROVIDER_NAME.equals(provider.getName())
258           || OPENJSSE_PROVIDER_NAME.equals(provider.getName())) {
259         if (JettyTlsUtil.isJava9AlpnAvailable()) {
260           return provider;
261         }
262       } else if (ConscryptLoader.isConscrypt(provider)) {
263         return provider;
264       }
265     }
266     if (ConscryptHolder.PROVIDER != null) {
267       return ConscryptHolder.PROVIDER;
268     }
269     return null;
270   }
271 
272   @SuppressWarnings("deprecation")
ensureAlpnAndH2Enabled( io.netty.handler.ssl.ApplicationProtocolNegotiator alpnNegotiator)273   static void ensureAlpnAndH2Enabled(
274       io.netty.handler.ssl.ApplicationProtocolNegotiator alpnNegotiator) {
275     checkArgument(alpnNegotiator != null, "ALPN must be configured");
276     checkArgument(alpnNegotiator.protocols() != null && !alpnNegotiator.protocols().isEmpty(),
277         "ALPN must be enabled and list HTTP/2 as a supported protocol.");
278     checkArgument(
279         alpnNegotiator.protocols().contains(HTTP2_VERSION),
280         "This ALPN config does not support HTTP/2. Expected %s, but got %s'.",
281         HTTP2_VERSION,
282         alpnNegotiator.protocols());
283   }
284 
285   private static class ConscryptHolder {
286     static final Provider PROVIDER;
287     static final Throwable UNAVAILABILITY_CAUSE;
288 
289     static {
290       Provider provider;
291       Throwable cause;
292       try {
293         provider = ConscryptLoader.newProvider();
294         cause = null;
295       } catch (Throwable t) {
296         provider = null;
297         cause = t;
298       }
299       PROVIDER = provider;
300       UNAVAILABILITY_CAUSE = cause;
301     }
302   }
303 }
304