Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
64be800
chore: [wip] PQC POC 2
diegomarquezp May 14, 2026
7c915c7
chore: update CI workflow to use googleapis/google-http-java-client b…
diegomarquezp May 14, 2026
f0478ae
chore: fix CI workflow setup order and step numbers
diegomarquezp May 14, 2026
408496f
chore: address Gemini review comments for PQC POC
diegomarquezp May 19, 2026
ae47748
Merge branch 'main' into chore/pqc-poc-2
diegomarquezp May 19, 2026
a61bd9d
test(bigquery): include bigquery for testing
diegomarquezp May 20, 2026
6b8816b
Merge remote-tracking branch 'origin/main' into chore/pqc-poc-2
diegomarquezp May 20, 2026
5be6b97
Merge branch 'chore/pqc-poc-2' of https://github.com/googleapis/googl…
diegomarquezp May 20, 2026
4d9e72c
refactor: zero-config programmatic PQC auto-upgrades using http-clien…
diegomarquezp May 20, 2026
6bbb7fc
test: Resolve Java 17 SSLParameters namedGroups compatibility with Bo…
diegomarquezp May 20, 2026
a472a87
fix(gax-grpc): use out of the box behavior for PQC
diegomarquezp May 21, 2026
2d59672
chore: revert changes in instantiating channel providers
diegomarquezp May 21, 2026
498bbb2
test: use vanilla client approach
diegomarquezp May 22, 2026
a50066c
test: use vanilla clients
diegomarquezp May 22, 2026
30acb1a
test: fix tests
diegomarquezp May 22, 2026
0225fac
test: partial implemnetation of programatically enabled PQC in local …
diegomarquezp May 23, 2026
5bff2c4
test: simplify tests
diegomarquezp May 23, 2026
f0e9e46
build: move test yaml to relevant folder
diegomarquezp May 23, 2026
35606e2
chore: simplify poms
diegomarquezp May 23, 2026
bf1afbe
fix: restore unwanted changes
diegomarquezp May 23, 2026
d536c3d
chore: format
diegomarquezp May 23, 2026
c38b4c9
Revert "build: move test yaml to relevant folder"
diegomarquezp May 23, 2026
99cf802
build: update pqc-tests.yml
diegomarquezp May 23, 2026
e290225
ci: explicitly install sdk-platform-java and snapshot bigquery/transl…
diegomarquezp May 23, 2026
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
65 changes: 65 additions & 0 deletions .github/workflows/pqc-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: PQC Connectivity Integration Tests

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
pqc-tests:
runs-on: ubuntu-latest

steps:
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
cache: 'maven'

# 1. Checkout sibling HTTP Client repository (MUST point to your modified fork/branch containing PQC JJSSE fixes)
- name: Checkout google-http-java-client
uses: actions/checkout@v4
with:
repository: <your-github-username>/google-http-java-client # UPDATE with your fork
ref: <your-pqc-branch> # UPDATE with your branch containing PQC JJSSE fixes
path: google-http-java-client

# 2. Build and install modified google-http-client SNAPSHOT locally
- name: Build and Install google-http-java-client
run: |
cd google-http-java-client
mvn clean install -DskipTests=true -Dcheckstyle.skip -Dclirr.skip -Denforcer.skip -Dfmt.skip

# 3. Checkout this monorepo
- name: Checkout google-cloud-java-pqc
uses: actions/checkout@v4
with:
path: google-cloud-java-pqc

# 4. Build the entire monorepo core components required by the tests
- name: Build and Install Core Dependency Reactor
run: |
cd google-cloud-java-pqc
mvn clean install -pl sdk-platform-java/pqc-test/pqc-test-snapshot,sdk-platform-java/pqc-test/pqc-test-release -am -T 1.5C -Dcheckstyle.skip -Dclirr.skip -Denforcer.skip -Dfmt.skip -DskipTests=true

# 5. Run Snapshot PQC Tests (EXPECT PASS)
- name: Run Snapshot PQC Connectivity Tests (Expect PASS)
run: |
cd google-cloud-java-pqc/sdk-platform-java/pqc-test/pqc-test-snapshot
mvn install -Dcheckstyle.skip -Dclirr.skip -Denforcer.skip -Dfmt.skip -Dtest=RunPqcTest

# 6. Run Release PQC Tests (EXPECT FAIL)
- name: Run Release PQC Connectivity Tests (Expect FAIL)
# We expect this step to fail. If it passes, it means release libraries are negotiating PQC (which is incorrect).
# Thus we run it and assert that the maven command fails (exit code != 0).
run: |
cd google-cloud-java-pqc/sdk-platform-java/pqc-test/pqc-test-release
if mvn install -Dcheckstyle.skip -Dclirr.skip -Denforcer.skip -Dfmt.skip -Dtest=RunPqcTest; then
echo "Error: Release tests passed but they were expected to fail!"
exit 1
else
echo "Success: Release tests failed-fast as expected."
exit 0
fi
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@

package com.google.auth.oauth2;

import com.google.api.client.util.SslUtils;
import java.security.GeneralSecurityException;
import com.google.api.client.http.HttpHeaders;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
Expand Down Expand Up @@ -104,7 +106,16 @@ enum Pkcs8Algorithm {
public static final String CLOUD_PLATFORM_SCOPE =
"https://www.googleapis.com/auth/cloud-platform";

static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
static final HttpTransport HTTP_TRANSPORT;
static {
try {
HTTP_TRANSPORT = new NetHttpTransport.Builder()
.setSslSocketFactory(SslUtils.getTlsSslContext().getSocketFactory())
.build();
} catch (GeneralSecurityException e) {
throw new RuntimeException("Failed to initialize PQC-hardened HTTP transport", e);
Comment thread
diegomarquezp marked this conversation as resolved.
Outdated
}
}

public static final HttpTransportFactory HTTP_TRANSPORT_FACTORY =
new DefaultHttpTransportFactory();
Expand Down
3 changes: 2 additions & 1 deletion sdk-platform-java/gapic-generator-java-pom-parent/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
</parent>

<properties>
<bouncycastle.version>1.80</bouncycastle.version>
<skipUnitTests>false</skipUnitTests>
<checkstyle.header.file>java.header</checkstyle.header.file>
<maven.compiler.release>8</maven.compiler.release>
Expand All @@ -27,7 +28,7 @@
consistent across modules in this repository -->
<javax.annotation-api.version>1.3.2</javax.annotation-api.version>
<grpc.version>1.81.0</grpc.version>
<google.http-client.version>2.1.0</google.http-client.version>
<google.http-client.version>2.1.1-SNAPSHOT</google.http-client.version>
Comment thread
diegomarquezp marked this conversation as resolved.
Outdated
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

Updating google.http-client.version to a -SNAPSHOT version in a parent POM is generally discouraged for release-track projects. This can lead to unstable builds and dependency resolution issues in environments without access to the specific snapshot repository. If this is necessary for the POC, please ensure it is reverted or replaced with a stable version before merging.

<gson.version>2.13.2</gson.version>
<guava.version>33.5.0-jre</guava.version>
<protobuf.version>4.33.2</protobuf.version>
Expand Down
11 changes: 11 additions & 0 deletions sdk-platform-java/gax-java/gax-grpc/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,17 @@
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>${bouncycastle.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bctls-jdk18on</artifactId>
<version>${bouncycastle.version}</version>
</dependency>

<!-- test dependencies -->
<dependency>
<groupId>io.grpc</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -812,13 +812,90 @@ public ManagedChannelBuilder<?> createDecoratedChannelBuilder() throws IOExcepti
if (interceptorProvider != null) {
builder.intercept(interceptorProvider.getInterceptors());
}
// Apply PQC configuration by default as a standard feature of GAX.
builder = applyPqcConfiguration(builder);

if (channelConfigurator != null) {
builder = channelConfigurator.apply(builder);
}

return builder;
}

private ManagedChannelBuilder<?> applyPqcConfiguration(ManagedChannelBuilder<?> builder) {
// Configure the PQ and classical hybrid named groups:
// 1. X25519MLKEM768 (codepoint 4588): Hybrid classical (X25519) + post-quantum (ML-KEM-768) key exchange.
// Provides defense-in-depth: if ML-KEM is compromised, security reverts to classical strength of X25519.
// 2. MLKEM768 (codepoint 1896): Pure post-quantum key exchange using ML-KEM-768.
// 3. X25519 (codepoint 29): Classical elliptic curve Diffie-Hellman key exchange, used as a fallback.
String[] hybridGroups = new String[] {"X25519MLKEM768", "MLKEM768", "X25519"};
String builderClassName = builder.getClass().getName();
boolean isShaded = "io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder".equals(builderClassName);
boolean isUnshaded = "io.grpc.netty.NettyChannelBuilder".equals(builderClassName);

if (isShaded || isUnshaded) {
try {
Object sslContext = buildOpenSslContext(isShaded, hybridGroups);
if (sslContext != null) {
setSslContextOnBuilder(builder, sslContext, isShaded);
return builder;
}
} catch (Exception e) {
Comment thread
diegomarquezp marked this conversation as resolved.
Outdated
// Graceful degradation: do not modify any global JVM property
}
}
return builder;
}

/**
* Dynamically configures and builds an OpenSsl SslContext targeting post-quantum groups.
*
* <p><b>Rationale for Reflection:</b>
* In the gax-grpc module, we maintain dual compatibility with both shaded Netty
* (io.grpc.netty.shaded) and unshaded Netty (io.grpc.netty) channel builders. Shaded Netty is
* a runtime dependency of gax-grpc rather than a compile-time dependency to prevent class
* path pollution.
*
* <p>By utilizing reflection here, we can check the runtime class type of the channel builder
* and dynamically resolve and configure the corresponding shaded or unshaded SslContextBuilder
* and OpenSslContextOption classes without requiring compile-time dependencies on shaded Netty.
*
* @param isShaded True if using shaded Netty, false if unshaded.
* @param groups Preference list of TLS named groups.
* @return Configured SslContext object.
*/
@SuppressWarnings("unchecked")
private Object buildOpenSslContext(boolean isShaded, String[] groups) throws Exception {
String prefix = isShaded ? "io.grpc.netty.shaded." : "";
Class<?> grpcSslContextsClass = Class.forName(prefix + "io.grpc.netty.GrpcSslContexts");
Class<?> sslContextBuilderClass = Class.forName(prefix + "io.netty.handler.ssl.SslContextBuilder");
Class<?> openSslContextOptionClass = Class.forName(prefix + "io.netty.handler.ssl.OpenSslContextOption");
Class<?> sslContextOptionClass = Class.forName(prefix + "io.netty.handler.ssl.SslContextOption");

// GrpcSslContexts.forClient() -> returns SslContextBuilder
java.lang.reflect.Method forClientMethod = grpcSslContextsClass.getMethod("forClient");
Object sslContextBuilder = forClientMethod.invoke(null);

// OpenSslContextOption.GROUPS
java.lang.reflect.Field groupsField = openSslContextOptionClass.getDeclaredField("GROUPS");
Object groupsOption = groupsField.get(null);

// SslContextBuilder.option(SslContextOption, Object)
java.lang.reflect.Method optionMethod = sslContextBuilderClass.getMethod("option", sslContextOptionClass, Object.class);
optionMethod.invoke(sslContextBuilder, groupsOption, groups);

// SslContextBuilder.build() -> returns SslContext
java.lang.reflect.Method buildMethod = sslContextBuilderClass.getMethod("build");
return buildMethod.invoke(sslContextBuilder);
}
Comment thread
diegomarquezp marked this conversation as resolved.
Outdated

private void setSslContextOnBuilder(Object builder, Object sslContext, boolean isShaded) throws Exception {
String prefix = isShaded ? "io.grpc.netty.shaded." : "";
Class<?> sslContextClass = Class.forName(prefix + "io.netty.handler.ssl.SslContext");
java.lang.reflect.Method sslContextMethod = builder.getClass().getMethod("sslContext", sslContextClass);
sslContextMethod.invoke(builder, sslContext);
}

private ManagedChannel createSingleChannel() throws IOException {
ManagedChannelBuilder<?> builder = createDecoratedChannelBuilder();

Expand Down
11 changes: 11 additions & 0 deletions sdk-platform-java/gax-java/gax-httpjson/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,17 @@
</properties>

<dependencies>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>${bouncycastle.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bctls-jdk18on</artifactId>
<version>${bouncycastle.version}</version>
</dependency>

<dependency>
<groupId>com.google.api</groupId>
<artifactId>gax</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
import com.google.auth.mtls.DefaultMtlsProviderFactory;
import com.google.auth.mtls.MtlsProvider;
import com.google.common.annotations.VisibleForTesting;
import javax.net.ssl.SSLContext;
import java.security.NoSuchAlgorithmException;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The imports for javax.net.ssl.SSLContext and java.security.NoSuchAlgorithmException are unused in this file and should be removed.

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
Expand Down Expand Up @@ -185,16 +187,26 @@ public TransportChannelProvider withCredentials(Credentials credentials) {
}

HttpTransport createHttpTransport() throws IOException, GeneralSecurityException {
if (mtlsProvider == null) {
return null;
}
if (certificateBasedAccess.useMtlsClientCertificate()) {
// 1. Get the scope-specific PQC-hardened SSLContext utilizing Bouncy Castle.
SSLContext sslContext = com.google.api.client.util.SslUtils.getTlsSslContext();

// 2. Initialize the NetHttpTransport builder pre-configured with our PQC SSL context.
NetHttpTransport.Builder builder = new NetHttpTransport.Builder()
.setSslSocketFactory(sslContext.getSocketFactory());

// 3. Verify if mTLS is supported and explicitly requested in the current client session.
if (mtlsProvider != null && certificateBasedAccess.useMtlsClientCertificate()) {
// 4. Retrieve the mutual TLS client key store from the session-specific mtlsProvider.
KeyStore mtlsKeyStore = mtlsProvider.getKeyStore();
// 5. Ensure key store is valid before configuring mutual TLS client certificates.
if (mtlsKeyStore != null) {
return new NetHttpTransport.Builder().trustCertificates(null, mtlsKeyStore, "").build();
// 6. Configure the mutual TLS certificates while preserving the PQC SSL context.
builder.trustCertificates(null, mtlsKeyStore, "");
}
}
return null;

// 7. Return the compiled and PQC-hardened NetHttpTransport instance.
return builder.build();
Comment thread
diegomarquezp marked this conversation as resolved.
Outdated
}

private HttpJsonTransportChannel createChannel() throws IOException, GeneralSecurityException {
Expand Down
1 change: 1 addition & 0 deletions sdk-platform-java/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<module>gapic-generator-java-bom</module>
<module>java-shared-dependencies</module>
<module>sdk-platform-java-config</module>
<module>pqc-test</module>
</modules>
<!-- Do not deploy the aggregator POM -->
<build>
Expand Down
24 changes: 24 additions & 0 deletions sdk-platform-java/pqc-test/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>com.google.api</groupId>
<artifactId>gapic-generator-java-pom-parent</artifactId>
<version>2.73.0-SNAPSHOT</version>
<relativePath>../gapic-generator-java-pom-parent</relativePath>
</parent>

<groupId>com.google.api</groupId>
<artifactId>pqc-test-parent</artifactId>
<packaging>pom</packaging>
<version>2.81.0-SNAPSHOT</version>

<modules>
<module>pqc-test-common</module>
<module>pqc-test-snapshot</module>
<module>pqc-test-release</module>
</modules>
</project>
53 changes: 53 additions & 0 deletions sdk-platform-java/pqc-test/pqc-test-common/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>com.google.api</groupId>
<artifactId>pqc-test-parent</artifactId>
<version>2.81.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

<artifactId>pqc-test-common</artifactId>

<dependencies>
<dependency>
<groupId>com.google.api</groupId>
<artifactId>gax-httpjson</artifactId>
<version>2.81.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.google.api</groupId>
<artifactId>gax-grpc</artifactId>
<version>2.81.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>${bouncycastle.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bctls-jdk18on</artifactId>
<version>${bouncycastle.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.10.2</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>${grpc.version}</version>
</dependency>
</dependencies>
</project>
Loading
Loading