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