Instead use {@link Builder} to modify behavior.
- */
public NetHttpTransport() {
- this((ConnectionFactory) null, null, null, false);
+ this(new Builder());
+ }
+
+ private NetHttpTransport(Builder builder) {
+ this(
+ builder.connectionFactory,
+ builder.resolveSslSocketFactory(),
+ builder.hostnameVerifier,
+ builder.isMtls);
}
/**
@@ -186,6 +208,16 @@ protected NetHttpRequest buildRequest(String method, String url) throws IOExcept
*/
public static final class Builder {
+ static {
+ try {
+ if (Security.getProvider("Conscrypt") == null) {
+ Security.addProvider(org.conscrypt.Conscrypt.newProvider());
+ }
+ } catch (NoClassDefFoundError | Exception ignored) {
+ // Conscrypt not available on classpath, fall back silently
+ }
+ }
+
/** SSL socket factory or {@code null} for the default. */
private SSLSocketFactory sslSocketFactory;
@@ -208,6 +240,13 @@ public static final class Builder {
/** Whether the transport is mTLS. Default value is {@code false}. */
private boolean isMtls;
+ /**
+ * Security provider to use for SSL context, or {@code null} for the default fallback. If not
+ * set, {@link NetHttpTransport} defaults to using Conscrypt (if available) and falls back to
+ * the default JDK provider.
+ */
+ private Provider securityProvider;
+
/**
* Sets the HTTP proxy or {@code null} to use the proxy settings from system
@@ -362,14 +401,162 @@ public Builder setHostnameVerifier(HostnameVerifier hostnameVerifier) {
return this;
}
+ /**
+ * Sets the security provider to use for SSL context.
+ *
+ * By default, {@link NetHttpTransport} will attempt to use Conscrypt as the security
+ * provider. If Conscrypt is not available on the system, it will fall back to the default JDK
+ * provider. Configuring a custom security provider here will override this default behavior and
+ * take precedence.
+ *
+ * @param securityProvider security provider to use
+ * @since 1.39
+ */
+ public Builder setSecurityProvider(Provider securityProvider) {
+ this.securityProvider = securityProvider;
+ return this;
+ }
+
+ /**
+ * Resolves the {@link SSLSocketFactory} to use, prioritizing user-configured factory, then
+ * custom security provider, defaulting to Conscrypt, and falling back to JDK.
+ */
+ private SSLSocketFactory resolveSslSocketFactory() {
+ SSLSocketFactory resolvedFactory = sslSocketFactory;
+ if (resolvedFactory == null) {
+ try {
+ SSLContext sslContext = null;
+ // 1. If a custom security provider is configured, use it
+ if (securityProvider != null) {
+ sslContext = SSLContext.getInstance("TLS", securityProvider);
+ } else {
+ // 2. Default: Try Conscrypt (assumed to be available as part of SDK)
+ try {
+ if (Security.getProvider("Conscrypt") == null) {
+ Security.addProvider(Conscrypt.newProvider());
+ }
+ sslContext = SSLContext.getInstance("TLS", "Conscrypt");
+ } catch (NoClassDefFoundError | Exception e) {
+ logger.log(
+ Level.WARNING,
+ "Conscrypt security provider not available. Falling back to JDK default.",
+ e);
+ }
+ }
+
+ if (sslContext == null) {
+ // 3. Fallback to standard JDK
+ sslContext = SSLContext.getInstance("TLS");
+ }
+
+ // Initialize SSLContext:
+ // - First param (KeyManager[]): null (use default JDK key managers)
+ // - Second param (TrustManager[]): null (use default JDK trust managers)
+ // - Third param (SecureRandom): null (use default JDK secure random)
+ sslContext.init(null, null, null);
+ resolvedFactory = sslContext.getSocketFactory();
+ } catch (Exception e) {
+ // If SSLContext initialization fails entirely, fall back to the JVM's default
+ // system-wide SSLSocketFactory.
+ resolvedFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
+ }
+ }
+
+ // Wrap factory to enforce PQC hybrid groups
+ return new PqcEnforcingSSLSocketFactory(resolvedFactory, PQC_GROUPS);
+ }
+
/** Returns a new instance of {@link NetHttpTransport} based on the options. */
public NetHttpTransport build() {
if (System.getProperty(SHOULD_USE_PROXY_FLAG) != null) {
setProxy(defaultProxy());
}
+ SSLSocketFactory resolvedSslSocketFactory = resolveSslSocketFactory();
return this.proxy == null
- ? new NetHttpTransport(connectionFactory, sslSocketFactory, hostnameVerifier, isMtls)
- : new NetHttpTransport(this.proxy, sslSocketFactory, hostnameVerifier, isMtls);
+ ? new NetHttpTransport(
+ connectionFactory, resolvedSslSocketFactory, hostnameVerifier, isMtls)
+ : new NetHttpTransport(this.proxy, resolvedSslSocketFactory, hostnameVerifier, isMtls);
+ }
+ }
+
+ /**
+ * An {@link SSLSocketFactory} wrapper that enforces Post-Quantum Cryptography (PQC) hybrid named
+ * groups (such as X25519MLKEM768) on compatible sockets.
+ *
+ * This wrapper is applied to the final resolved {@link SSLSocketFactory} before the transport
+ * is built. This ensures that even if the factory was configured and initialized via custom trust
+ * stores (e.g. {@link Builder#trustCertificates} which internally invokes {@code
+ * SslUtils.initSslContext}), the resulting sockets are still intercepted and configured to
+ * request PQC hybrid groups.
+ *
+ * If the socket is detected as a Conscrypt socket, it configures the requested named groups
+ * using Conscrypt's direct APIs. If the socket or provider does not support these groups, it
+ * falls back to the default TLS negotiation of the delegate factory.
+ */
+ private static class PqcEnforcingSSLSocketFactory extends SSLSocketFactory {
+ private final SSLSocketFactory delegate;
+ private final String[] groups;
+
+ PqcEnforcingSSLSocketFactory(SSLSocketFactory delegate, String[] groups) {
+ this.delegate = delegate;
+ this.groups = groups;
+ }
+
+ private Socket configure(Socket socket) {
+ if (socket instanceof SSLSocket) {
+ SSLSocket sslSocket = (SSLSocket) socket;
+ try {
+ if (Conscrypt.isConscrypt(sslSocket)) {
+ Conscrypt.setNamedGroups(sslSocket, groups);
+ }
+ } catch (NoClassDefFoundError | Exception ignored) {
+ // Fall back silently if Conscrypt classes are not loaded or the socket is not Conscrypt
+ }
+ }
+ return socket;
+ }
+
+ @Override
+ public String[] getDefaultCipherSuites() {
+ return delegate.getDefaultCipherSuites();
+ }
+
+ @Override
+ public String[] getSupportedCipherSuites() {
+ return delegate.getSupportedCipherSuites();
+ }
+
+ @Override
+ public Socket createSocket(Socket s, String host, int port, boolean autoClose)
+ throws IOException {
+ return configure(delegate.createSocket(s, host, port, autoClose));
+ }
+
+ @Override
+ public Socket createSocket() throws IOException {
+ return configure(delegate.createSocket());
+ }
+
+ @Override
+ public Socket createSocket(String host, int port) throws IOException {
+ return configure(delegate.createSocket(host, port));
+ }
+
+ @Override
+ public Socket createSocket(String host, int port, InetAddress localHost, int localPort)
+ throws IOException {
+ return configure(delegate.createSocket(host, port, localHost, localPort));
+ }
+
+ @Override
+ public Socket createSocket(InetAddress host, int port) throws IOException {
+ return configure(delegate.createSocket(host, port));
+ }
+
+ @Override
+ public Socket createSocket(
+ InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
+ return configure(delegate.createSocket(address, port, localAddress, localPort));
}
}
}
diff --git a/google-http-client/src/main/java/com/google/api/client/util/SslUtils.java b/google-http-client/src/main/java/com/google/api/client/util/SslUtils.java
index a578c7383..21c4094a6 100644
--- a/google-http-client/src/main/java/com/google/api/client/util/SslUtils.java
+++ b/google-http-client/src/main/java/com/google/api/client/util/SslUtils.java
@@ -51,17 +51,20 @@ public static SSLContext getSslContext() throws NoSuchAlgorithmException {
* @since 1.14
*/
public static SSLContext getTlsSslContext() throws NoSuchAlgorithmException {
- return SSLContext.getInstance("TLS");
+ try {
+ return SSLContext.getInstance("TLS", "Conscrypt");
+ } catch (Exception e) {
+ return SSLContext.getInstance("TLS");
+ }
}
- /**
- * Returns the default trust manager factory.
- *
- * @since 1.14
- */
public static TrustManagerFactory getDefaultTrustManagerFactory()
throws NoSuchAlgorithmException {
- return TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ try {
+ return TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm(), "Conscrypt");
+ } catch (Exception e) {
+ return TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ }
}
/**
@@ -70,7 +73,11 @@ public static TrustManagerFactory getDefaultTrustManagerFactory()
* @since 1.14
*/
public static TrustManagerFactory getPkixTrustManagerFactory() throws NoSuchAlgorithmException {
- return TrustManagerFactory.getInstance("PKIX");
+ try {
+ return TrustManagerFactory.getInstance("PKIX", "Conscrypt");
+ } catch (Exception e) {
+ return TrustManagerFactory.getInstance("PKIX");
+ }
}
/**
diff --git a/google-http-client/src/test/java/com/google/api/client/http/javanet/NetHttpTransportTest.java b/google-http-client/src/test/java/com/google/api/client/http/javanet/NetHttpTransportTest.java
index 87c5337c6..c04365b91 100644
--- a/google-http-client/src/test/java/com/google/api/client/http/javanet/NetHttpTransportTest.java
+++ b/google-http-client/src/test/java/com/google/api/client/http/javanet/NetHttpTransportTest.java
@@ -36,6 +36,7 @@
import java.net.InetSocketAddress;
import java.net.URL;
import java.security.KeyStore;
+import java.security.Provider;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.junit.Test;
@@ -241,4 +242,13 @@ public void handle(HttpExchange httpExchange) throws IOException {
response.disconnect();
}
}
+
+ @Test
+ public void testCustomSecurityProvider() throws Exception {
+ Provider customProvider = new Provider("TestProvider", 1.0, "Test Provider") {};
+ NetHttpTransport transport =
+ new NetHttpTransport.Builder().setSecurityProvider(customProvider).build();
+ // Verify it compiles and builds successfully with a custom provider
+ assertTrue(transport != null);
+ }
}