Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions api/src/main/java/io/grpc/ForwardingChannelBuilder2.java
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,14 @@ public T useTransportSecurity() {
return thisT();
}

@Override
public T preferJdkSslWithSecurityProvider(java.security.Provider provider) {
delegate().preferJdkSslWithSecurityProvider(provider);
return thisT();
}



@Deprecated
@Override
public T nameResolverFactory(NameResolver.Factory resolverFactory) {
Expand Down
15 changes: 15 additions & 0 deletions api/src/main/java/io/grpc/ManagedChannelBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,21 @@ public T useTransportSecurity() {
throw new UnsupportedOperationException();
}

/**
* Sets a custom {@link java.security.Provider} to use for secure connections,
* preferring the JDK's built-in SSL/TLS implementation with that provider.
*
* @param provider the Provider to use, or {@code null} to use defaults.
* @throws UnsupportedOperationException if the transport implementation does not support
* configuring SSL provider preferences or custom JDK {@code Provider}.
* @since 1.69.0
*/
public T preferJdkSslWithSecurityProvider(java.security.Provider provider) {
throw new UnsupportedOperationException();
}



/**
* Provides a custom {@link NameResolver.Factory} for the channel. If this method is not called,
* the builder will try the providers registered in the default {@link NameResolverRegistry} for
Expand Down
18 changes: 18 additions & 0 deletions netty/src/main/java/io/grpc/netty/GrpcSslContexts.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior;
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior;
import io.netty.handler.ssl.OpenSsl;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslProvider;
import io.netty.handler.ssl.SupportedCipherSuiteFilter;
Expand Down Expand Up @@ -223,6 +224,23 @@ public static SslContextBuilder configure(SslContextBuilder builder, Provider jd
.sslContextProvider(jdkProvider);
}

/**
* Returns a Netty {@link SslContext} that wraps the given JDK {@link javax.net.ssl.SSLContext} and is
* configured with ciphers and ALPN appropriate for gRPC.
*/
static SslContext configure(javax.net.ssl.SSLContext sslContext) {
return new io.netty.handler.ssl.JdkSslContext(
sslContext,
true, /* isClient */
Http2SecurityUtil.CIPHERS,
SupportedCipherSuiteFilter.INSTANCE,
ALPN,
io.netty.handler.ssl.ClientAuth.NONE,
null, /* protocols */
false /* startTls */
);
}

/**
* Returns OpenSSL if available, otherwise returns the JDK provider.
*/
Expand Down
19 changes: 19 additions & 0 deletions netty/src/main/java/io/grpc/netty/NettyChannelBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
import io.netty.channel.ReflectiveChannelFactory;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Collection;
Expand Down Expand Up @@ -394,6 +395,24 @@ public NettyChannelBuilder sslContext(SslContext sslContext) {
return this;
}

@CanIgnoreReturnValue
@Override
public NettyChannelBuilder preferJdkSslWithSecurityProvider(java.security.Provider provider) {
checkState(!freezeProtocolNegotiatorFactory,
"Cannot change security when using ChannelCredentials");
if (provider == null) {
return this;
}
try {
SslContext nettySslContext = GrpcSslContexts.configure(SslContextBuilder.forClient(), provider).build();
return sslContext(nettySslContext);
} catch (SSLException e) {
throw new RuntimeException("Failed to configure Netty SslContext with provider", e);
}
}



/**
* Sets the initial flow control window in bytes. Setting initial flow control window enables auto
* flow control tuning using bandwidth-delay product algorithm. To disable auto flow control
Expand Down
41 changes: 40 additions & 1 deletion netty/src/test/java/io/grpc/netty/NettyChannelBuilderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ public void failInvalidAuthority() {
@Test
public void sslContextCanBeNull() {
NettyChannelBuilder builder = new NettyChannelBuilder(getTestSocketAddress());
builder.sslContext(null);
builder.sslContext(noSslContext);
}

@Test
Expand Down Expand Up @@ -327,4 +327,43 @@ public void transportFactorySupportsNettyChannelCreds() {
NettyChannelCredentials.create(new PlaintextProtocolNegotiatorClientFactory()));
assertThat(result).isNotNull();
}

@Test
public void preferJdkSslWithSecurityProvider_configuresJdkSslContext() throws Exception {
NettyChannelBuilder builder = NettyChannelBuilder.forTarget("fakeTarget");
java.security.Provider provider = javax.net.ssl.SSLContext.getDefault().getProvider();
builder.preferJdkSslWithSecurityProvider(provider);

java.lang.reflect.Field factoryField = NettyChannelBuilder.class.getDeclaredField("protocolNegotiatorFactory");
factoryField.setAccessible(true);
Object factory = factoryField.get(builder);

java.lang.reflect.Field sslContextField = factory.getClass().getDeclaredField("sslContext");
sslContextField.setAccessible(true);
SslContext nettySslContext = (SslContext) sslContextField.get(factory);

assertThat(nettySslContext).isInstanceOf(io.netty.handler.ssl.JdkSslContext.class);
}

@Test
public void preferJdkSslWithSecurityProvider_null_isNoop() throws Exception {
NettyChannelBuilder builder = NettyChannelBuilder.forTarget("fakeTarget");
SslContext customContext = mock(SslContext.class);
org.mockito.Mockito.when(customContext.isClient()).thenReturn(true);
io.netty.handler.ssl.ApplicationProtocolNegotiator apn = mock(io.netty.handler.ssl.ApplicationProtocolNegotiator.class);
org.mockito.Mockito.when(apn.protocols()).thenReturn(java.util.Arrays.asList("h2"));
org.mockito.Mockito.when(customContext.applicationProtocolNegotiator()).thenReturn(apn);
builder.sslContext(customContext);

builder.preferJdkSslWithSecurityProvider(null);

java.lang.reflect.Field factoryField = NettyChannelBuilder.class.getDeclaredField("protocolNegotiatorFactory");
factoryField.setAccessible(true);
Object factory = factoryField.get(builder);

java.lang.reflect.Field sslContextField = factory.getClass().getDeclaredField("sslContext");
sslContextField.setAccessible(true);
assertThat(sslContextField.get(factory)).isSameInstanceAs(customContext);
}
Comment on lines +348 to +367

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Update the test to assert that passing null to preferJdkSslProvider resets the SSL context to defaults (i.e., null), rather than acting as a no-op.

Suggested change
@Test
public void preferJdkSslProviderJavax_null_isNoop() throws Exception {
NettyChannelBuilder builder = NettyChannelBuilder.forTarget("fakeTarget");
SslContext customContext = mock(SslContext.class);
org.mockito.Mockito.when(customContext.isClient()).thenReturn(true);
io.netty.handler.ssl.ApplicationProtocolNegotiator apn = mock(io.netty.handler.ssl.ApplicationProtocolNegotiator.class);
org.mockito.Mockito.when(apn.protocols()).thenReturn(java.util.Arrays.asList("h2"));
org.mockito.Mockito.when(customContext.applicationProtocolNegotiator()).thenReturn(apn);
builder.sslContext(customContext);
builder.preferJdkSslProvider(null);
java.lang.reflect.Field factoryField = NettyChannelBuilder.class.getDeclaredField("protocolNegotiatorFactory");
factoryField.setAccessible(true);
Object factory = factoryField.get(builder);
java.lang.reflect.Field sslContextField = factory.getClass().getDeclaredField("sslContext");
sslContextField.setAccessible(true);
assertThat(sslContextField.get(factory)).isSameInstanceAs(customContext);
}
@Test
public void preferJdkSslProviderJavax_null_resetsToDefault() throws Exception {
NettyChannelBuilder builder = NettyChannelBuilder.forTarget("fakeTarget");
SslContext customContext = mock(SslContext.class);
org.mockito.Mockito.when(customContext.isClient()).thenReturn(true);
io.netty.handler.ssl.ApplicationProtocolNegotiator apn = mock(io.netty.handler.ssl.ApplicationProtocolNegotiator.class);
org.mockito.Mockito.when(apn.protocols()).thenReturn(java.util.Arrays.asList("h2"));
org.mockito.Mockito.when(customContext.applicationProtocolNegotiator()).thenReturn(apn);
builder.sslContext(customContext);
builder.preferJdkSslProvider(null);
java.lang.reflect.Field factoryField = NettyChannelBuilder.class.getDeclaredField("protocolNegotiatorFactory");
factoryField.setAccessible(true);
Object factory = factoryField.get(builder);
java.lang.reflect.Field sslContextField = factory.getClass().getDeclaredField("sslContext");
sslContextField.setAccessible(true);
assertThat(sslContextField.get(factory)).isNull();
}

}

19 changes: 19 additions & 0 deletions okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,25 @@ public OkHttpChannelBuilder useTransportSecurity() {
return this;
}

@Override
public OkHttpChannelBuilder preferJdkSslWithSecurityProvider(java.security.Provider provider) {
Preconditions.checkState(!freezeSecurityConfiguration,
"Cannot change security when using ChannelCredentials");
if (provider == null) {
return this;
}
try {
javax.net.ssl.SSLContext sslContext = javax.net.ssl.SSLContext.getInstance("TLS", provider);
sslContext.init(null, null, null);
sslSocketFactory(sslContext.getSocketFactory());
return this;
} catch (java.security.GeneralSecurityException e) {
throw new RuntimeException("Failed to initialize SSLContext with provider", e);
}
}



/**
* Provides a custom scheduled executor service.
*
Expand Down
Loading