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
12 changes: 12 additions & 0 deletions google-http-client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,18 @@
<groupId>io.opencensus</groupId>
<artifactId>opencensus-contrib-http-util</artifactId>
</dependency>
<!--
Conscrypt is marked as provided scope because google-http-java-client is a general-purpose
HTTP transport library. Downstream users who do not require PQC support or prefer other security
providers (such as standard JRE SunJSSE or Bouncy Castle) should not be forced to include Conscrypt
in their runtime classpath. For Google Cloud SDK clients, the Conscrypt dependency is provided
transitively via downstream BOMs or libraries (e.g. gax-httpjson).
-->
<dependency>
<groupId>org.conscrypt</groupId>
<artifactId>conscrypt-openjdk-uber</artifactId>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>com.google.guava</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,17 @@
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 java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocketFactory;
import org.conscrypt.Conscrypt;

/**
* Thread-safe HTTP low-level transport based on the {@code java.net} package.
Expand Down Expand Up @@ -81,6 +86,31 @@ private static Proxy defaultProxy() {

private static final String SHOULD_USE_PROXY_FLAG = "com.google.api.client.should_use_proxy";

private static final Logger logger = Logger.getLogger(NetHttpTransport.class.getName());

/**
* Post-Quantum Cryptography (PQC) hybrid key exchange groups ordered by preference.
*
* <p>"X25519MLKEM768" is the primary hybrid post-quantum key exchange algorithm combining X25519
* elliptic curve with ML-KEM-768. "X25519" is provided as a classical fallback group.
*/
private static final String[] PQC_GROUPS = new String[] {"X25519MLKEM768", "X25519"};

private static final String TLS_ALGORITHM = "TLS";

private static final String CONSCRYPT_PROVIDER = "Conscrypt";

private static final boolean CAN_USE_JDK_NAMED_GROUPS_API = checkJdkNamedGroupsApiAvailability();

private static boolean checkJdkNamedGroupsApiAvailability() {
try {
SSLParameters.class.getMethod("setNamedGroups", String[].class);
return true;
} catch (Throwable t) {
return false;
}
}

private final ConnectionFactory connectionFactory;

/** SSL socket factory or {@code null} for the default. */
Expand Down Expand Up @@ -208,6 +238,16 @@ 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;

/** Named groups to enforce on the SSL socket. Defaults to {@code PQC_GROUPS}. */
private String[] namedGroups = PQC_GROUPS.clone();

/**
* Sets the HTTP proxy or {@code null} to use the proxy settings from <a
* href="http://docs.oracle.com/javase/7/docs/api/java/net/doc-files/net-properties.html">system
Expand Down Expand Up @@ -362,14 +402,115 @@ public Builder setHostnameVerifier(HostnameVerifier hostnameVerifier) {
return this;
}

/**
* Sets the security provider to use for SSL context.
*
* <p>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;
}

/**
* Sets the custom TLS named groups (curves) to configure on SSL sockets created by this
* transport.
*
* <p>By default, this is initialized to include the Post-Quantum hybrid curve {@code
* "X25519MLKEM768"} and classical fallback curve {@code "X25519"}.
*
* <p>Pass a custom list to override these defaults. Pass {@code null} or an empty array to
* disable custom named groups configuration entirely (preventing socket wrapping and Conscrypt
* JCA provider instantiation).
*
* @param namedGroups TLS named groups to configure on sockets, or {@code null} to disable
* @return this builder
*/
public Builder setNamedGroups(String[] namedGroups) {
this.namedGroups = namedGroups == null ? null : namedGroups.clone();
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 factory = sslSocketFactory;
if (factory == null) {
factory = createDefaultSslSocketFactory();
}
if (namedGroups == null || namedGroups.length == 0) {
return factory;
}
return new PqcEnforcingSSLSocketFactory(factory, namedGroups);
}

/**
* Instantiates and initializes a default {@link SSLSocketFactory} utilizing the resolved
* security provider and default TrustManagers. Falls back to the system default socket factory
* if initialization fails.
*/
private SSLSocketFactory createDefaultSslSocketFactory() {
try {
SSLContext sslContext = createSslContext();
sslContext.init(null, null, null);
return sslContext.getSocketFactory();
} catch (Exception e) {
// If SSLContext initialization fails entirely, fall back to the JVM's default
// system-wide SSLSocketFactory.
return (SSLSocketFactory) SSLSocketFactory.getDefault();
}
}

/**
* Resolves and instantiates the {@link SSLContext} for "TLS", prioritizing the configured
* custom security provider, falling back to inserting and loading the Conscrypt provider, and
* defaulting to the JRE's standard SSLContext if Conscrypt is unavailable.
*/
private SSLContext createSslContext() throws GeneralSecurityException {
// 1. If a custom security provider is configured, use it
if (securityProvider != null) {
return SSLContext.getInstance(TLS_ALGORITHM, securityProvider);
}
// 2. Default: Try Conscrypt (assumed to be available as part of SDK)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Can we always pass the Conscrypt provider in? So this library may not have to be dependent on Conscrypt?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I think that is an option. Right now that's the intention of provided scope, library isn't dependent on Conscrypt and will only enable it by default if it's used via a library in the Java SDK

if (namedGroups != null && namedGroups.length > 0) {
try {
return SSLContext.getInstance(TLS_ALGORITHM, Conscrypt.newProvider());
} catch (Throwable e) {
if (CAN_USE_JDK_NAMED_GROUPS_API) {
logger.log(
Level.INFO,
"Conscrypt not available. Falling back to JDK default (will use native JRE named"
+ " groups).");
} else {
logger.log(
Level.WARNING,
"Conscrypt security provider not available. Falling back to JDK default.",
e);
}
}
}
// 3. Fallback to standard JDK
return SSLContext.getInstance(TLS_ALGORITHM);
}

/** 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);
}
}
}
Loading
Loading