Skip to content

Commit a59aab0

Browse files
committed
chore: poc for gax-grpc using grpc's preferJdkSslProvider()
1 parent 16cff56 commit a59aab0

13 files changed

Lines changed: 1020 additions & 1 deletion

File tree

sdk-platform-java/gapic-generator-java-pom-parent/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
<!-- External dependencies, especially gRPC and Protobuf version, should be
2727
consistent across modules in this repository -->
2828
<javax.annotation-api.version>1.3.2</javax.annotation-api.version>
29-
<grpc.version>1.81.0</grpc.version>
29+
<grpc.version>1.82.0-SNAPSHOT</grpc.version>
3030
<google.http-client.version>2.1.0</google.http-client.version>
3131
<gson.version>2.13.2</gson.version>
3232
<guava.version>33.5.0-jre</guava.version>

sdk-platform-java/gax-java/gax-grpc/pom.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,16 @@
131131
<groupId>com.google.protobuf</groupId>
132132
<artifactId>protobuf-java</artifactId>
133133
</dependency>
134+
<dependency>
135+
<groupId>org.bouncycastle</groupId>
136+
<artifactId>bcprov-jdk18on</artifactId>
137+
<version>1.84</version>
138+
</dependency>
139+
<dependency>
140+
<groupId>org.bouncycastle</groupId>
141+
<artifactId>bctls-jdk18on</artifactId>
142+
<version>1.84</version>
143+
</dependency>
134144
</dependencies>
135145

136146
<build>

sdk-platform-java/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -812,13 +812,30 @@ public ManagedChannelBuilder<?> createDecoratedChannelBuilder() throws IOExcepti
812812
if (interceptorProvider != null) {
813813
builder.intercept(interceptorProvider.getInterceptors());
814814
}
815+
configurePqc(builder);
815816
if (channelConfigurator != null) {
816817
builder = channelConfigurator.apply(builder);
817818
}
818819

819820
return builder;
820821
}
821822

823+
private void configurePqc(ManagedChannelBuilder<?> builder) {
824+
try {
825+
java.security.Provider bcProvider = new org.bouncycastle.jce.provider.BouncyCastleProvider();
826+
java.security.Provider bcJsseProvider =
827+
new org.bouncycastle.jsse.provider.BouncyCastleJsseProvider(bcProvider);
828+
829+
javax.net.ssl.SSLContext sslContext =
830+
javax.net.ssl.SSLContext.getInstance("TLSv1.3", bcJsseProvider);
831+
sslContext.init(null, null, null);
832+
833+
builder.preferJdkSslProvider(sslContext);
834+
} catch (Exception e) {
835+
LOG.log(Level.WARNING, "Failed to configure gRPC channel for PQC", e);
836+
}
837+
}
838+
822839
private ManagedChannel createSingleChannel() throws IOException {
823840
ManagedChannelBuilder<?> builder = createDecoratedChannelBuilder();
824841

sdk-platform-java/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
<module>gapic-generator-java-bom</module>
2424
<module>java-shared-dependencies</module>
2525
<module>sdk-platform-java-config</module>
26+
<module>pqc-test</module>
2627
</modules>
2728
<!-- Do not deploy the aggregator POM -->
2829
<build>

sdk-platform-java/pqc-test/pom.xml

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<parent>
8+
<groupId>com.google.cloud</groupId>
9+
<artifactId>google-cloud-shared-config</artifactId>
10+
<version>1.17.0</version>
11+
</parent>
12+
13+
<groupId>com.google.api</groupId>
14+
<artifactId>pqc-test-parent</artifactId>
15+
<packaging>pom</packaging>
16+
<version>2.81.0-SNAPSHOT</version>
17+
18+
<modules>
19+
<module>pqc-test-common</module>
20+
<module>pqc-test-snapshot</module>
21+
<module>pqc-test-release</module>
22+
</modules>
23+
24+
<dependencyManagement>
25+
<dependencies>
26+
<dependency>
27+
<groupId>org.bouncycastle</groupId>
28+
<artifactId>bcprov-jdk18on</artifactId>
29+
<version>1.84</version>
30+
</dependency>
31+
<dependency>
32+
<groupId>org.bouncycastle</groupId>
33+
<artifactId>bctls-jdk18on</artifactId>
34+
<version>1.84</version>
35+
</dependency>
36+
<dependency>
37+
<groupId>org.bouncycastle</groupId>
38+
<artifactId>bcutil-jdk18on</artifactId>
39+
<version>1.84</version>
40+
</dependency>
41+
</dependencies>
42+
</dependencyManagement>
43+
44+
<build>
45+
<plugins>
46+
<plugin>
47+
<groupId>org.apache.maven.plugins</groupId>
48+
<artifactId>maven-surefire-plugin</artifactId>
49+
<configuration>
50+
<argLine>
51+
-Djavax.net.ssl.trustStore=${project.basedir}/../pqc-test-common/target/classes/pqctest.p12
52+
-Djavax.net.ssl.trustStorePassword=password
53+
-Djavax.net.ssl.trustStoreType=PKCS12
54+
</argLine>
55+
</configuration>
56+
</plugin>
57+
</plugins>
58+
</build>
59+
</project>
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<parent>
8+
<groupId>com.google.api</groupId>
9+
<artifactId>pqc-test-parent</artifactId>
10+
<version>2.81.0-SNAPSHOT</version>
11+
<relativePath>../pom.xml</relativePath>
12+
</parent>
13+
14+
<artifactId>pqc-test-common</artifactId>
15+
16+
<dependencies>
17+
<dependency>
18+
<groupId>org.junit.jupiter</groupId>
19+
<artifactId>junit-jupiter-api</artifactId>
20+
<version>5.10.2</version>
21+
</dependency>
22+
<dependency>
23+
<groupId>io.grpc</groupId>
24+
<artifactId>grpc-netty</artifactId>
25+
<version>1.81.0</version>
26+
</dependency>
27+
<dependency>
28+
<groupId>io.grpc</groupId>
29+
<artifactId>grpc-stub</artifactId>
30+
<version>1.81.0</version>
31+
</dependency>
32+
<dependency>
33+
<groupId>org.bouncycastle</groupId>
34+
<artifactId>bcprov-jdk18on</artifactId>
35+
<version>1.84</version>
36+
</dependency>
37+
<dependency>
38+
<groupId>org.bouncycastle</groupId>
39+
<artifactId>bctls-jdk18on</artifactId>
40+
<version>1.84</version>
41+
</dependency>
42+
<dependency>
43+
<groupId>com.google.api</groupId>
44+
<artifactId>gax</artifactId>
45+
<version>2.81.0-SNAPSHOT</version>
46+
</dependency>
47+
<dependency>
48+
<groupId>com.google.api</groupId>
49+
<artifactId>gax-httpjson</artifactId>
50+
<version>2.81.0-SNAPSHOT</version>
51+
</dependency>
52+
<dependency>
53+
<groupId>com.google.api</groupId>
54+
<artifactId>gax-grpc</artifactId>
55+
<version>2.81.0-SNAPSHOT</version>
56+
</dependency>
57+
<dependency>
58+
<groupId>com.google.http-client</groupId>
59+
<artifactId>google-http-client</artifactId>
60+
<version>2.1.1-SNAPSHOT</version>
61+
</dependency>
62+
<dependency>
63+
<groupId>com.google.cloud</groupId>
64+
<artifactId>google-cloud-bigquery</artifactId>
65+
<version>2.67.0-SNAPSHOT</version>
66+
</dependency>
67+
<dependency>
68+
<groupId>com.google.cloud</groupId>
69+
<artifactId>google-cloud-translate</artifactId>
70+
<version>2.93.0-SNAPSHOT</version>
71+
</dependency>
72+
</dependencies>
73+
</project>
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
/*
2+
* Copyright 2026 Google LLC
3+
*
4+
* Redistribution and use in source and binary forms, with or without
5+
* modification, are permitted provided that the following conditions are
6+
* met:
7+
*
8+
* * Redistributions of source code must retain the above copyright
9+
* notice, this list of conditions and the following disclaimer.
10+
* * Redistributions in binary form must reproduce the above
11+
* copyright notice, this list of conditions and the following disclaimer
12+
* in the documentation and/or other materials provided with the
13+
* distribution.
14+
* * Neither the name of Google LLC nor the names of its
15+
* contributors may be used to endorse or promote products derived from
16+
* this software without specific prior written permission.
17+
*
18+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21+
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22+
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23+
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24+
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25+
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26+
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29+
*/
30+
31+
package com.google.api.gax.httpjson;
32+
33+
import static org.junit.jupiter.api.Assertions.assertEquals;
34+
import static org.junit.jupiter.api.Assertions.assertNotNull;
35+
import static org.junit.jupiter.api.Assertions.fail;
36+
37+
import com.google.api.gax.core.NoCredentialsProvider;
38+
import com.google.cloud.translate.v3.LocationName;
39+
import com.google.cloud.translate.v3.TranslationServiceClient;
40+
import com.google.cloud.translate.v3.TranslationServiceSettings;
41+
import java.io.InputStream;
42+
import java.security.Provider;
43+
import java.security.Security;
44+
import java.util.Collections;
45+
import java.util.TreeSet;
46+
import org.junit.jupiter.api.AfterAll;
47+
import org.junit.jupiter.api.BeforeAll;
48+
import org.junit.jupiter.api.Test;
49+
50+
public abstract class PqcConnectivityTest {
51+
52+
private static Process serverProcess;
53+
protected static int grpcPqcPort;
54+
protected static int grpcClassicalPort;
55+
56+
protected boolean clientSupportsPqc() {
57+
return true;
58+
}
59+
60+
protected abstract boolean grpcTestShouldSucceed();
61+
62+
@BeforeAll
63+
public static void setup() throws Exception {
64+
65+
// 6. Spawn PqcTestServer in a separate background process to ensure physical
66+
// JVM runtime isolation!
67+
ProcessBuilder pb =
68+
new ProcessBuilder(
69+
"java",
70+
"-cp",
71+
System.getProperty("java.class.path"),
72+
"com.google.api.gax.pqc.PqcTestServer");
73+
74+
// Force merging of error stream to ease debugging in test output
75+
pb.redirectErrorStream(true);
76+
serverProcess = pb.start();
77+
78+
// Read server's stdout to dynamically capture the allocated ephemeral ports
79+
java.io.BufferedReader reader =
80+
new java.io.BufferedReader(
81+
new java.io.InputStreamReader(
82+
serverProcess.getInputStream(), java.nio.charset.StandardCharsets.UTF_8));
83+
84+
String line;
85+
boolean grpcPqcFound = false;
86+
boolean grpcClassicalFound = false;
87+
88+
// Wait for the server process to output its HTTP and gRPC ports
89+
long startTime = System.currentTimeMillis();
90+
while ((line = reader.readLine()) != null) {
91+
System.out.println("[SERVER-OUT] " + line);
92+
if (line.startsWith("GRPC_PQC_PORT: ")) {
93+
grpcPqcPort = Integer.parseInt(line.substring(15).trim());
94+
grpcPqcFound = true;
95+
} else if (line.startsWith("GRPC_CLASSICAL_PORT: ")) {
96+
grpcClassicalPort = Integer.parseInt(line.substring(21).trim());
97+
grpcClassicalFound = true;
98+
}
99+
100+
if (grpcPqcFound && grpcClassicalFound) {
101+
break;
102+
}
103+
104+
// Ephemeral port detection timeout (10 seconds) to fail-fast on server startup
105+
// errors
106+
if (System.currentTimeMillis() - startTime > 10000) {
107+
throw new RuntimeException(
108+
"Timeout waiting for PqcTestServer ephemeral ports to be printed!");
109+
}
110+
}
111+
112+
if (!grpcPqcFound || !grpcClassicalFound) {
113+
throw new RuntimeException("PqcTestServer failed to initialize ephemeral ports!");
114+
}
115+
116+
// Start a background thread to continuously drain the server's stdout
117+
Thread drainThread =
118+
new Thread(
119+
() -> {
120+
try {
121+
String l;
122+
while ((l = reader.readLine()) != null) {
123+
System.out.println("[SERVER-OUT] " + l);
124+
}
125+
} catch (java.io.IOException e) {
126+
// Ignore stream closed
127+
}
128+
});
129+
drainThread.setDaemon(true);
130+
drainThread.start();
131+
}
132+
133+
@AfterAll
134+
public static void teardown() {
135+
if (serverProcess != null) {
136+
// Forcibly destroy the background process and close standard streams to allow
137+
// clean exit
138+
serverProcess.destroyForcibly();
139+
}
140+
}
141+
142+
private void runGrpcTest(int port, boolean shouldSucceed) throws Exception {
143+
TranslationServiceSettings settings =
144+
TranslationServiceSettings.newBuilder()
145+
.setEndpoint("localhost:" + port)
146+
.setCredentialsProvider(NoCredentialsProvider.create())
147+
.build();
148+
149+
try (TranslationServiceClient client = TranslationServiceClient.create(settings)) {
150+
LocationName parent = LocationName.of("test-project", "global");
151+
client.translateText(parent, "es", Collections.singletonList("hello"));
152+
if (!shouldSucceed) {
153+
fail("Expected gRPC call to fail!");
154+
}
155+
} catch (Exception e) {
156+
if (shouldSucceed) {
157+
fail("Expected gRPC call to succeed, but failed: " + e.getMessage(), e);
158+
}
159+
}
160+
}
161+
162+
163+
164+
@Test
165+
public void testGrpcPqcServerEnforced() throws Exception {
166+
runGrpcTest(grpcPqcPort, grpcTestShouldSucceed());
167+
}
168+
169+
@Test
170+
public void testGrpcClassicalServer() throws Exception {
171+
runGrpcTest(grpcClassicalPort, true);
172+
}
173+
}

0 commit comments

Comments
 (0)