Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright (c) 2026 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/

package com.google.api.client.http.javanet;

import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;

/**
* An {@link SSLSocketFactory} wrapper that intercepts all socket creation entrypoints (both default
* and bound socket creations) and applies the custom user-provided {@link SslSocketConfigurator}
* callback to the socket before returning it.
*
* <p>This factory delegates all standard socket actions to the underlying default or custom {@link
* SSLSocketFactory} instance. Sockets are intercepted and cast to {@link SSLSocket} for callback
* execution.
*
* @since 2.1.2
*/
final class ConfigurableSSLSocketFactory extends SSLSocketFactory {
private final SSLSocketFactory delegate;
private final SslSocketConfigurator configurator;

ConfigurableSSLSocketFactory(SSLSocketFactory delegate, SslSocketConfigurator configurator) {
this.delegate = delegate;
this.configurator = configurator;
}

private Socket configure(Socket socket) {
if (socket instanceof SSLSocket && configurator != null) {
configurator.configure((SSLSocket) socket);
}
return socket;
}

@Override
public String[] getDefaultCipherSuites() {
return delegate.getDefaultCipherSuites();
}

@Override
public String[] getSupportedCipherSuites() {
return delegate.getSupportedCipherSuites();
}

@Override
public Socket createSocket() throws IOException {
return configure(delegate.createSocket());
}

@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(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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,15 @@
import java.net.URL;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.Provider;
import java.security.cert.CertificateFactory;
import java.util.Arrays;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;

/**
* Thread-safe HTTP low-level transport based on the {@code java.net} package.
Expand Down Expand Up @@ -84,7 +87,7 @@ private static Proxy defaultProxy() {
private final ConnectionFactory connectionFactory;

/** SSL socket factory or {@code null} for the default. */
private final SSLSocketFactory sslSocketFactory;
final SSLSocketFactory sslSocketFactory;

/** Host name verifier or {@code null} for the default. */
private final HostnameVerifier hostnameVerifier;
Expand Down Expand Up @@ -189,6 +192,12 @@ public static final class Builder {
/** SSL socket factory or {@code null} for the default. */
private SSLSocketFactory sslSocketFactory;

/** Security provider to use or {@code null} for default. */
private Provider securityProvider;

/** Custom SSLSocket configurator or {@code null} to disable callback configuration. */
SslSocketConfigurator sslSocketConfigurator;

/** Host name verifier or {@code null} for the default. */
private HostnameVerifier hostnameVerifier;

Expand Down Expand Up @@ -289,8 +298,9 @@ public Builder trustCertificatesFromStream(InputStream certificateStream)
* @since 1.14
*/
public Builder trustCertificates(KeyStore trustStore) throws GeneralSecurityException {
SSLContext sslContext = SslUtils.getTlsSslContext();
SslUtils.initSslContext(sslContext, trustStore, SslUtils.getPkixTrustManagerFactory());
SSLContext sslContext = SslUtils.getTlsSslContext(securityProvider);
SslUtils.initSslContext(
sslContext, trustStore, SslUtils.getPkixTrustManagerFactory(securityProvider));
return setSslSocketFactory(sslContext.getSocketFactory());
}

Expand All @@ -313,14 +323,14 @@ public Builder trustCertificates(
if (mtlsKeyStore != null && mtlsKeyStore.size() > 0) {
this.isMtls = true;
}
SSLContext sslContext = SslUtils.getTlsSslContext();
SSLContext sslContext = SslUtils.getTlsSslContext(securityProvider);
SslUtils.initSslContext(
sslContext,
trustStore,
SslUtils.getPkixTrustManagerFactory(),
SslUtils.getPkixTrustManagerFactory(securityProvider),
mtlsKeyStore,
mtlsKeyStorePassword,
SslUtils.getDefaultKeyManagerFactory());
SslUtils.getDefaultKeyManagerFactory(securityProvider));
return setSslSocketFactory(sslContext.getSocketFactory());
}

Expand All @@ -345,12 +355,69 @@ public SSLSocketFactory getSslSocketFactory() {
return sslSocketFactory;
}

/** Sets the SSL socket factory or {@code null} for the default. */
/**
* Sets the SSL socket factory or {@code null} for the default.
*
* <p>Note: If a custom {@link SslSocketConfigurator} is also provided, it will wrap and apply
* its configuration callback to all sockets created by this factory.
*/
public Builder setSslSocketFactory(SSLSocketFactory sslSocketFactory) {
this.sslSocketFactory = sslSocketFactory;
return this;
}

/**
* Sets the custom security provider or {@code null} to use the default JRE provider.
*
* <p>When enabling Post-Quantum Cryptography (PQC) transport:
*
* <ul>
* <li>On JDK 8-19: A custom JCA provider (such as Conscrypt or BouncyCastle) must be
* configured via this method, in addition to configuring a custom {@link
* SslSocketConfigurator} callback to select the hybrid/PQC curves using provider-specific
* APIs.
* <li>On JDK 20-26: A custom provider (like Conscrypt) is recommended, but a custom {@link
* SslSocketConfigurator} invoking {@code SSLParameters.setNamedGroups(String[])} directly
* can be used natively without a custom JCA provider if standard JSSE supports the
* curves.
* <li>On JDK 27+: Neither a custom provider nor a configurator is required as PQC algorithms
* are negotiated natively by default.
* </ul>
*
* @param securityProvider provider to use
*/
public Builder setSecurityProvider(Provider securityProvider) {
Comment thread
lqiu96 marked this conversation as resolved.
this.securityProvider = securityProvider;
return this;
}

/**
* Sets the custom {@link SslSocketConfigurator} callback to configure active SSLSockets.
*
* <p>If both a custom {@link SSLSocketFactory} (via {@link
* #setSslSocketFactory(SSLSocketFactory)}) and a custom configurator are set, the configurator
* callback will be applied to all sockets created by the custom socket factory. If no custom
* factory is provided, the configurator will wrap and apply to sockets created by the default
* resolved socket factory.
*
* <p>When enabling Post-Quantum Cryptography (PQC) transport:
*
* <ul>
* <li>On JDK 20-26: Callers can configure a custom {@link SslSocketConfigurator} that sets
* the named groups (such as {@code X25519MLKEM768}) directly on {@code SSLParameters}.
* <li>On JDK 8-19: Callers must provide a custom {@link SslSocketConfigurator} implementation
* to inspect the socket types and invoke provider-specific API extensions (e.g. Conscrypt
* JNI interfaces) to configure the curves.
* </ul>
*
* @param configurator the callback configurator
* @since 2.1.2
*/
public Builder setSslSocketConfigurator(SslSocketConfigurator configurator) {
this.sslSocketConfigurator = configurator;
return this;
}

/** Returns the host name verifier or {@code null} for the default. */
public HostnameVerifier getHostnameVerifier() {
return hostnameVerifier;
Expand All @@ -362,14 +429,65 @@ public Builder setHostnameVerifier(HostnameVerifier hostnameVerifier) {
return this;
}

/**
* Resolves the {@link SSLSocketFactory} to be used by the transport.
*
* <p>If a custom factory has been set via {@link #setSslSocketFactory(SSLSocketFactory)}, it
* will be returned. Otherwise, a default SSL socket factory will be constructed via {@link
* #createDefaultSslSocketFactory()}.
*
* @return the resolved {@link SSLSocketFactory}
*/
SSLSocketFactory resolveSslSocketFactory() {
if (securityProvider == null && sslSocketConfigurator == null) {
return sslSocketFactory;
}
SSLSocketFactory factory =
sslSocketFactory != null ? sslSocketFactory : createDefaultSslSocketFactory();
if (sslSocketConfigurator != null) {
return new ConfigurableSSLSocketFactory(factory, sslSocketConfigurator);
}
return factory;
}

/**
* Constructs a default {@link SSLSocketFactory} configured with the specified {@link Provider}.
*
* <p>This method initializes an {@link SSLContext} and resolves its {@link TrustManagerFactory}
* using the same security provider (if provided), ensuring compatibility for TLS handshakes
* when using custom providers (such as Conscrypt).
*
* @return the initialized default {@link SSLSocketFactory}
*/
SSLSocketFactory createDefaultSslSocketFactory() {
try {
SSLContext sslContext = SslUtils.getTlsSslContext(securityProvider);
TrustManager[] trustManagers = null;
if (securityProvider != null) {
try {
TrustManagerFactory tmf = SslUtils.getDefaultTrustManagerFactory(securityProvider);
tmf.init((KeyStore) null);
trustManagers = tmf.getTrustManagers();
} catch (Exception e) {
// Ignore and fall back to default
}
}
sslContext.init(null, trustManagers, null);
return sslContext.getSocketFactory();
} catch (Exception e) {
return (SSLSocketFactory) SSLSocketFactory.getDefault();
}
}

/** 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 resolvedFactory = resolveSslSocketFactory();
Comment thread
lqiu96 marked this conversation as resolved.
return this.proxy == null
? new NetHttpTransport(connectionFactory, sslSocketFactory, hostnameVerifier, isMtls)
: new NetHttpTransport(this.proxy, sslSocketFactory, hostnameVerifier, isMtls);
? new NetHttpTransport(connectionFactory, resolvedFactory, hostnameVerifier, isMtls)
: new NetHttpTransport(this.proxy, resolvedFactory, hostnameVerifier, isMtls);
}
}
}
Loading
Loading