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