Skip to content

feat(api): (poc) add SslContext setting#12848

Draft
diegomarquezp wants to merge 5 commits into
grpc:masterfrom
diegomarquezp:chore/poc-prefer-jdk
Draft

feat(api): (poc) add SslContext setting#12848
diegomarquezp wants to merge 5 commits into
grpc:masterfrom
diegomarquezp:chore/poc-prefer-jdk

Conversation

@diegomarquezp
Copy link
Copy Markdown

This PR works as a POC for exposing a setting in ManagedChannelBuilder
that allows for preferring the JDK implementation in transports that
support it (e.g. netty), or throw an unsupported op exception for those
who don't.

This would allow gax-grpc to easily insert a custom ssl context that
contains the providers we need as follows:

      // Conscrypt can be an option too
      java.security.Provider bcProvider = new org.bouncycastle.jce.provider.BouncyCastleProvider();
      java.security.Provider bcJsseProvider =
          new org.bouncycastle.jsse.provider.BouncyCastleJsseProvider(bcProvider);

      javax.net.ssl.SSLContext sslContext =
          javax.net.ssl.SSLContext.getInstance("TLSv1.3", bcJsseProvider);
      sslContext.init(null, null, null);

      builder.preferJdkSslProvider(sslContext);

This was tested locally in googleapis/google-cloud-java#13388

@linux-foundation-easycla
Copy link
Copy Markdown

linux-foundation-easycla Bot commented Jun 8, 2026

CLA Signed
The committers listed above are authorized under a signed CLA.

@diegomarquezp
Copy link
Copy Markdown
Author

/gcbrun

@diegomarquezp
Copy link
Copy Markdown
Author

/gemini review

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces the preferJdkSslProvider(javax.net.ssl.SSLContext) method to ManagedChannelBuilder and implements it across ForwardingChannelBuilder2, NettyChannelBuilder, and OkHttpChannelBuilder to allow configuring a custom JDK SSLContext for secure connections. The review feedback correctly identifies that passing null to preferJdkSslProvider should reset the builder to its default SSL configuration (by delegating to sslContext(null) or sslSocketFactory(null)) rather than acting as a no-op, and suggests updating the corresponding unit tests to verify this reset behavior.

Comment on lines +403 to +405
if (sslContext == null) {
return this;
}
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

According to the Javadoc in ManagedChannelBuilder, passing null to preferJdkSslProvider should reset the builder to use defaults. Currently, passing null is a no-op, which prevents resetting the SSL context if a custom one was previously configured. We should delegate to sslContext(null) when sslContext is null.

Suggested change
if (sslContext == null) {
return this;
}
if (sslContext == null) {
return sslContext(null);
}

Comment on lines +494 to +496
if (sslContext != null) {
sslSocketFactory(sslContext.getSocketFactory());
}
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

According to the Javadoc in ManagedChannelBuilder, passing null to preferJdkSslProvider should reset the builder to use defaults. Currently, passing null is a no-op, which prevents resetting the SSL socket factory if a custom one was previously configured. We should delegate to sslSocketFactory(null) when sslContext is null.

    if (sslContext != null) {
      sslSocketFactory(sslContext.getSocketFactory());
    } else {
      sslSocketFactory(null);
    }

Comment on lines +348 to +367
@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);
}
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();
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant