Skip to content

Commit fe4099e

Browse files
committed
test: fix pqc server
1 parent e290225 commit fe4099e

6 files changed

Lines changed: 312 additions & 347 deletions

File tree

.github/workflows/pqc-tests.yml

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -40,34 +40,26 @@ jobs:
4040
cd google-http-java-client
4141
mvn clean install -DskipTests=true -Dcheckstyle.skip -Dclirr.skip -Denforcer.skip -Dfmt.skip
4242
43-
# 5. Build and Install sdk-platform-java core libraries first (establishes JCA dependencies in local Maven cache)
43+
# 5. Build and Install sdk-platform-java core libraries first (excluding circular pqc-test)
4444
- name: Build and Install sdk-platform-java Core
4545
run: |
4646
cd google-cloud-java-pqc/sdk-platform-java
47-
mvn clean install -T 1.5C -Dcheckstyle.skip -Dclirr.skip -Denforcer.skip -Dfmt.skip -DskipTests=true
47+
mvn clean install -pl '!pqc-test' -T 1.5C -Dcheckstyle.skip -Dclirr.skip -Denforcer.skip -Dfmt.skip -DskipTests=true
4848
49-
# 6. Build and Install snapshot bigquery, java-translate, and pqc-test targets
49+
# 6. Build and Install snapshot bigquery, java-translate, and pqc-test targets (specifying actual sub-modules)
5050
- name: Build and Install Client Snapshot Libraries and Test Modules
5151
run: |
5252
cd google-cloud-java-pqc
53-
mvn clean install -pl java-bigquery,java-translate,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
53+
mvn clean install -pl java-bigquery/google-cloud-bigquery,java-translate/google-cloud-translate,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
5454
5555
# 7. Run Snapshot PQC Tests (EXPECT PASS)
5656
- name: Run Snapshot PQC Connectivity Tests (Expect PASS)
5757
run: |
5858
cd google-cloud-java-pqc/sdk-platform-java/pqc-test/pqc-test-snapshot
5959
mvn install -Dcheckstyle.skip -Dclirr.skip -Denforcer.skip -Dfmt.skip -Dtest=RunPqcTest
6060
61-
# 8. Run Release PQC Tests (EXPECT FAIL)
62-
- name: Run Release PQC Connectivity Tests (Expect FAIL)
63-
# We expect this step to fail. If it passes, it means release libraries are negotiating PQC (which is incorrect).
64-
# Thus we run it and assert that the maven command fails (exit code != 0).
61+
# 8. Run Release PQC Tests (Expect PASS because tests assert negative behavior and pass)
62+
- name: Run Release PQC Connectivity Tests
6563
run: |
6664
cd google-cloud-java-pqc/sdk-platform-java/pqc-test/pqc-test-release
67-
if mvn install -Dcheckstyle.skip -Dclirr.skip -Denforcer.skip -Dfmt.skip -Dtest=RunPqcTest; then
68-
echo "Error: Release tests passed but they were expected to fail!"
69-
exit 1
70-
else
71-
echo "Success: Release tests failed-fast as expected."
72-
exit 0
73-
fi
65+
mvn install -Dcheckstyle.skip -Dclirr.skip -Denforcer.skip -Dfmt.skip -Dtest=RunPqcTest

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,35 @@
3939
<artifactId>bctls-jdk18on</artifactId>
4040
<version>1.84</version>
4141
</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>
4272
</dependencies>
4373
</project>

sdk-platform-java/pqc-test/pqc-test-common/src/main/java/com/google/api/gax/httpjson/PqcConnectivityTest.java

Lines changed: 160 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,25 @@
3131
package com.google.api.gax.httpjson;
3232

3333
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;
3436

37+
import com.google.api.gax.core.NoCredentialsProvider;
38+
import com.google.api.gax.rpc.ApiException;
39+
import com.google.api.gax.rpc.StatusCode;
40+
import com.google.cloud.NoCredentials;
41+
import com.google.cloud.bigquery.*;
42+
import com.google.cloud.translate.v3.*;
43+
import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder;
44+
import io.grpc.netty.shaded.io.netty.handler.ssl.ApplicationProtocolConfig;
45+
import io.grpc.netty.shaded.io.netty.handler.ssl.SslContext;
46+
import io.grpc.netty.shaded.io.netty.handler.ssl.SslContextBuilder;
47+
import io.grpc.netty.shaded.io.netty.handler.ssl.SslProvider;
48+
import io.grpc.netty.shaded.io.netty.handler.ssl.util.InsecureTrustManagerFactory;
3549
import java.io.InputStream;
36-
import java.security.Security;
50+
import java.security.KeyStore;
51+
import java.util.ArrayList;
52+
import java.util.List;
3753
import org.junit.jupiter.api.AfterAll;
3854
import org.junit.jupiter.api.BeforeAll;
3955
import org.junit.jupiter.api.Test;
@@ -87,12 +103,15 @@
87103
* paths remain fully robust and operational.
88104
* </ul>
89105
*/
90-
public class PqcConnectivityTest {
106+
public abstract class PqcConnectivityTest {
91107

92108
private static Process serverProcess;
93-
protected static int httpPort;
94-
protected static int grpcPort;
109+
protected static int httpPqcPort;
110+
protected static int httpClassicalPort;
111+
protected static int grpcPqcPort;
112+
protected static int grpcClassicalPort;
95113
private static boolean isPqcSupported;
114+
private static KeyStore ks;
96115

97116
/**
98117
* Configures the integration test harness environment before test cases are executed.
@@ -144,42 +163,29 @@ protected boolean clientSupportsPqc() {
144163
return true;
145164
}
146165

166+
protected abstract boolean grpcTestShouldSucceed();
167+
147168
@BeforeAll
148169
public static void setup() throws Exception {
149170

150-
// Dynamically detect if PQC auto-upgrade wrapping is supported by current classpath
171+
// Dynamically detect if PQC auto-upgrade wrapping is supported by current
172+
// classpath
151173
// dependencies (Snapshot vs Release)
152174
try {
153-
Class.forName("com.google.api.client.http.javanet.PqcDelegatingSSLSocketFactory");
175+
Class.forName("com.google.api.client.http.javanet.PqcPeerHostSSLSocketFactory");
154176
isPqcSupported = true;
155177
} catch (ClassNotFoundException e) {
156178
isPqcSupported = false;
157179
}
158180

159-
// 1. Load the self-signed server validation certificate/keystore from test resources.
160-
java.security.KeyStore ks = java.security.KeyStore.getInstance("PKCS12");
181+
ks = KeyStore.getInstance("PKCS12");
161182
try (InputStream is = PqcConnectivityTest.class.getResourceAsStream("/pqctest.p12")) {
162183
if (is == null) {
163184
throw new RuntimeException("pqctest.p12 not found in classpath");
164185
}
165186
ks.load(is, "password".toCharArray());
166187
}
167188

168-
// 2. Save the keystore to a temporary file so the JRE's JSSE property system can access its
169-
// absolute path.
170-
java.io.File tempFile = java.io.File.createTempFile("pqctest", ".p12");
171-
tempFile.deleteOnExit();
172-
try (java.io.FileOutputStream fos = new java.io.FileOutputStream(tempFile)) {
173-
ks.store(fos, "password".toCharArray());
174-
}
175-
176-
// 3. Configure JVM default JSSE trust store system properties to trust the self-signed
177-
// validation certificate.
178-
// This allows the client to trust the certificate issued by our local server
179-
System.setProperty("javax.net.ssl.trustStore", tempFile.getAbsolutePath());
180-
System.setProperty("javax.net.ssl.trustStorePassword", "password");
181-
System.setProperty("javax.net.ssl.trustStoreType", "PKCS12");
182-
183189
// 6. Spawn PqcTestServer in a separate background process to ensure physical
184190
// JVM runtime isolation!
185191
ProcessBuilder pb =
@@ -200,22 +206,30 @@ public static void setup() throws Exception {
200206
serverProcess.getInputStream(), java.nio.charset.StandardCharsets.UTF_8));
201207

202208
String line;
203-
boolean httpPortFound = false;
204-
boolean grpcPortFound = false;
209+
boolean httpPqcFound = false;
210+
boolean httpClassicalFound = false;
211+
boolean grpcPqcFound = false;
212+
boolean grpcClassicalFound = false;
205213

206214
// Wait for the server process to output its HTTP and gRPC ports
207215
long startTime = System.currentTimeMillis();
208216
while ((line = reader.readLine()) != null) {
209217
System.out.println("[SERVER-OUT] " + line);
210-
if (line.startsWith("HTTP_PORT: ")) {
211-
httpPort = Integer.parseInt(line.substring(11).trim());
212-
httpPortFound = true;
213-
} else if (line.startsWith("GRPC_PORT: ")) {
214-
grpcPort = Integer.parseInt(line.substring(11).trim());
215-
grpcPortFound = true;
218+
if (line.startsWith("HTTP_PQC_PORT: ")) {
219+
httpPqcPort = Integer.parseInt(line.substring(15).trim());
220+
httpPqcFound = true;
221+
} else if (line.startsWith("HTTP_CLASSICAL_PORT: ")) {
222+
httpClassicalPort = Integer.parseInt(line.substring(21).trim());
223+
httpClassicalFound = true;
224+
} else if (line.startsWith("GRPC_PQC_PORT: ")) {
225+
grpcPqcPort = Integer.parseInt(line.substring(15).trim());
226+
grpcPqcFound = true;
227+
} else if (line.startsWith("GRPC_CLASSICAL_PORT: ")) {
228+
grpcClassicalPort = Integer.parseInt(line.substring(21).trim());
229+
grpcClassicalFound = true;
216230
}
217231

218-
if (httpPortFound && grpcPortFound) {
232+
if (httpPqcFound && httpClassicalFound && grpcPqcFound && grpcClassicalFound) {
219233
break;
220234
}
221235

@@ -227,7 +241,7 @@ public static void setup() throws Exception {
227241
}
228242
}
229243

230-
if (!httpPortFound || !grpcPortFound) {
244+
if (!httpPqcFound || !httpClassicalFound || !grpcPqcFound || !grpcClassicalFound) {
231245
throw new RuntimeException("PqcTestServer failed to initialize ephemeral ports!");
232246
}
233247

@@ -255,41 +269,130 @@ public static void teardown() {
255269
// clean exit
256270
serverProcess.destroyForcibly();
257271
}
258-
if (isPqcSupported) {
259-
Security.removeProvider("BCJSSE");
260-
Security.removeProvider("BC");
261-
}
262272
}
263273

264-
public void runTests() throws Exception {
265-
assertEquals(isPqcSupported, clientSupportsPqc());
266-
testHttpPqc();
267-
testGrpcPqc();
268-
testBigQueryPqc();
274+
private void runGrpcTest(int port, boolean shouldSucceed) throws Exception {
275+
TranslationServiceSettings.Builder settingsBuilder =
276+
TranslationServiceSettings.newBuilder()
277+
.setEndpoint("localhost:" + port)
278+
.setCredentialsProvider(NoCredentialsProvider.create());
279+
280+
com.google.api.gax.grpc.InstantiatingGrpcChannelProvider.Builder channelProviderBuilder =
281+
((com.google.api.gax.grpc.InstantiatingGrpcChannelProvider)
282+
settingsBuilder.getTransportChannelProvider())
283+
.toBuilder();
284+
channelProviderBuilder.setChannelConfigurator(
285+
new com.google.api.core.ApiFunction<
286+
io.grpc.ManagedChannelBuilder, io.grpc.ManagedChannelBuilder>() {
287+
@Override
288+
public io.grpc.ManagedChannelBuilder apply(io.grpc.ManagedChannelBuilder builder) {
289+
if (clientSupportsPqc()) {
290+
configureGrpcChannelForPqc(builder);
291+
} else {
292+
configureGrpcChannelForClassical(builder);
293+
}
294+
return builder;
295+
}
296+
});
297+
settingsBuilder.setTransportChannelProvider(channelProviderBuilder.build());
298+
299+
TranslationServiceSettings settings = settingsBuilder.build();
300+
301+
try (TranslationServiceClient client = TranslationServiceClient.create(settings)) {
302+
List<String> contents = new ArrayList<>();
303+
contents.add("house");
304+
TranslateTextRequest request =
305+
TranslateTextRequest.newBuilder()
306+
.setParent("projects/test-project")
307+
.addAllContents(contents)
308+
.build();
309+
310+
try {
311+
TranslateTextResponse response = client.translateText(request);
312+
if (!shouldSucceed) {
313+
fail("Expected gRPC call to fail!");
314+
}
315+
assertNotNull(response);
316+
} catch (ApiException e) {
317+
if (shouldSucceed) {
318+
fail("Expected gRPC call to succeed, but failed: " + e.getMessage(), e);
319+
}
320+
}
321+
}
269322
}
270323

271-
@Test
272-
public void testHttpPqc() throws Exception {}
324+
273325

274326
@Test
275-
public void testGrpcPqc() throws Exception {}
327+
public void testGrpcPqcServerEnforced() throws Exception {
328+
runGrpcTest(grpcPqcPort, grpcTestShouldSucceed());
329+
}
276330

277331
@Test
278-
public void testBigQueryPqc() throws Exception {}
332+
public void testGrpcClassicalServer() throws Exception {
333+
runGrpcTest(grpcClassicalPort, true);
334+
}
279335

280-
private static class ByteMarshaller implements io.grpc.MethodDescriptor.Marshaller<byte[]> {
281-
@Override
282-
public InputStream stream(byte[] value) {
283-
return new java.io.ByteArrayInputStream(value);
336+
337+
private static void configureGrpcChannelForPqc(io.grpc.ManagedChannelBuilder<?> builder) {
338+
if (!(builder instanceof NettyChannelBuilder)) {
339+
throw new IllegalArgumentException(
340+
"Unsupported channel builder type: " + builder.getClass().getName());
284341
}
342+
NettyChannelBuilder nettyBuilder = (NettyChannelBuilder) builder;
285343

286-
@Override
287-
public byte[] parse(InputStream stream) {
288-
try {
289-
return com.google.common.io.ByteStreams.toByteArray(stream);
290-
} catch (java.io.IOException e) {
291-
throw new RuntimeException(e);
292-
}
344+
// Ensures HTTP/2 is used as it's required by gRPC
345+
ApplicationProtocolConfig apn =
346+
new ApplicationProtocolConfig(
347+
ApplicationProtocolConfig.Protocol.ALPN,
348+
ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE,
349+
ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT,
350+
"h2");
351+
352+
try {
353+
java.security.Provider bcProvider = new org.bouncycastle.jce.provider.BouncyCastleProvider();
354+
java.security.Provider bcJsseProvider =
355+
new org.bouncycastle.jsse.provider.BouncyCastleJsseProvider(bcProvider);
356+
357+
SslContext shadedSslContext =
358+
SslContextBuilder.forClient()
359+
.sslProvider(SslProvider.JDK)
360+
.sslContextProvider(bcJsseProvider)
361+
.protocols("TLSv1.3")
362+
.applicationProtocolConfig(apn)
363+
.trustManager(InsecureTrustManagerFactory.INSTANCE)
364+
.build();
365+
366+
nettyBuilder.sslContext(shadedSslContext);
367+
} catch (Exception e) {
368+
throw new RuntimeException("Failed to configure shaded gRPC Netty channel for PQC", e);
369+
}
370+
}
371+
372+
private static void configureGrpcChannelForClassical(io.grpc.ManagedChannelBuilder<?> builder) {
373+
if (!(builder instanceof NettyChannelBuilder)) {
374+
throw new IllegalArgumentException(
375+
"Unsupported channel builder type: " + builder.getClass().getName());
376+
}
377+
NettyChannelBuilder nettyBuilder = (NettyChannelBuilder) builder;
378+
379+
ApplicationProtocolConfig apn =
380+
new ApplicationProtocolConfig(
381+
ApplicationProtocolConfig.Protocol.ALPN,
382+
ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE,
383+
ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT,
384+
"h2");
385+
386+
try {
387+
SslContext shadedSslContext =
388+
SslContextBuilder.forClient()
389+
.applicationProtocolConfig(apn)
390+
.trustManager(InsecureTrustManagerFactory.INSTANCE)
391+
.build();
392+
393+
nettyBuilder.sslContext(shadedSslContext);
394+
} catch (Exception e) {
395+
throw new RuntimeException("Failed to configure shaded gRPC Netty channel for Classical", e);
293396
}
294397
}
295398
}

0 commit comments

Comments
 (0)