Skip to content

Commit 2f97a2b

Browse files
committed
test(showcase): Add gRPC PQC Showcase integration tests
- Add testGrpcPqc_withTls Showcase TLS integration test in ITPostQuantumCryptography.java using built-in default gRPC transport, configured to trust the Showcase TLS server certificate directly. - Include concise Javadoc documenting gRPC built-in PQC behavior and explaining the purpose of GrpcTlsCapturingClientInterceptor and HTTP/2 metadata helper methods. - Declare capturedMetadata volatile and assign a merged copy in onHeaders for thread-safe metadata publication. - Assert negotiated group is equal to EXPECTED_PQC_GROUP for gRPC without asserting a supported groups list. - Leave all HTTP/JSON tests and setUp() 100% untouched. - Exclude all mTLS test resources, keys, certs, and mTLS logic.
1 parent 385e1f2 commit 2f97a2b

1 file changed

Lines changed: 113 additions & 0 deletions

File tree

java-showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITPostQuantumCryptography.java

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import com.google.api.client.http.javanet.NetHttpTransport;
2323
import com.google.api.gax.core.NoCredentialsProvider;
24+
import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider;
2425
import com.google.api.gax.httpjson.HttpJsonConscryptUtils;
2526
import com.google.api.gax.httpjson.HttpJsonMetadata;
2627
import com.google.api.gax.httpjson.InstantiatingHttpJsonChannelProvider;
@@ -29,6 +30,16 @@
2930
import com.google.showcase.v1beta1.EchoResponse;
3031
import com.google.showcase.v1beta1.EchoSettings;
3132
import com.google.showcase.v1beta1.it.util.HttpJsonCapturingClientInterceptor;
33+
import io.grpc.CallOptions;
34+
import io.grpc.Channel;
35+
import io.grpc.ClientCall;
36+
import io.grpc.ClientInterceptor;
37+
import io.grpc.ForwardingClientCall;
38+
import io.grpc.ForwardingClientCallListener;
39+
import io.grpc.Metadata;
40+
import io.grpc.MethodDescriptor;
41+
import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts;
42+
import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder;
3243
import java.io.File;
3344
import java.io.InputStream;
3445
import java.nio.file.Files;
@@ -266,4 +277,106 @@ private static KeyStore loadCaCert(String certPath) throws Exception {
266277
}
267278
return trustStore;
268279
}
280+
281+
/**
282+
* Integration test to verify Post-Quantum Cryptography (PQC) TLS negotiation for gRPC clients.
283+
*
284+
* <p>In gRPC-Java 1.83.0+, the default Netty transport (`grpc-netty-shaded`) bundles BoringSSL
285+
* (`netty-tcnative-boringssl-static`) with built-in PQC hybrid key exchange support (e.g.,
286+
* X25519MLKEM768). No custom socket configurator or security provider swapping is needed.
287+
*
288+
* <p>Because the local Showcase test server uses a self-signed CA certificate (written to {@link
289+
* #DEFAULT_CA_CERT_PATH}), we configure the gRPC transport channel builder directly to trust this
290+
* certificate via {@link GrpcSslContexts#forClient()}. This avoids mutating global JVM system
291+
* properties in {@code setUp()} and ensures HTTP/JSON tests remain completely isolated.
292+
*/
293+
@Test
294+
void testGrpcPqc_withTls() throws Exception {
295+
GrpcTlsCapturingClientInterceptor interceptor = new GrpcTlsCapturingClientInterceptor();
296+
297+
InstantiatingGrpcChannelProvider transportChannelProvider =
298+
EchoSettings.defaultGrpcTransportProviderBuilder()
299+
.setEndpoint(SECURE_ENDPOINT)
300+
.setInterceptorProvider(() -> Collections.singletonList(interceptor))
301+
.setChannelConfigurator(
302+
managedChannelBuilder -> {
303+
if (managedChannelBuilder instanceof NettyChannelBuilder) {
304+
try {
305+
// Explicitly trust the self-signed CA certificate created by the local
306+
// Showcase TLS connection without altering JVM-wide SSL trust stores.
307+
((NettyChannelBuilder) managedChannelBuilder)
308+
.sslContext(
309+
GrpcSslContexts.forClient()
310+
.trustManager(new File(DEFAULT_CA_CERT_PATH))
311+
.build());
312+
} catch (Exception e) {
313+
throw new RuntimeException("Failed to configure gRPC SSL context", e);
314+
}
315+
}
316+
return managedChannelBuilder;
317+
})
318+
.build();
319+
320+
EchoSettings settings =
321+
EchoSettings.newBuilder()
322+
.setCredentialsProvider(NoCredentialsProvider.create())
323+
.setTransportChannelProvider(transportChannelProvider)
324+
.build();
325+
326+
try (EchoClient client = EchoClient.create(settings)) {
327+
EchoResponse response =
328+
client.echo(EchoRequest.newBuilder().setContent("pqc-grpc-tls-test").build());
329+
assertThat(response.getContent()).isEqualTo("pqc-grpc-tls-test");
330+
331+
Metadata capturedHeaders = interceptor.capturedMetadata;
332+
assertThat(capturedHeaders).isNotNull();
333+
334+
// Verify that TLS 1.3 key exchange negotiated the expected PQC hybrid group (X25519MLKEM768).
335+
String negotiatedGroup = getGrpcSingleHeaderString(capturedHeaders, TLS_GROUP_HEADER);
336+
assertThat(negotiatedGroup).isEqualTo(EXPECTED_PQC_GROUP);
337+
}
338+
}
339+
340+
/**
341+
* Private gRPC client interceptor to capture the response headers from the Showcase server to
342+
* verify the PQC algorithm.
343+
*/
344+
private static class GrpcTlsCapturingClientInterceptor implements ClientInterceptor {
345+
volatile Metadata capturedMetadata;
346+
347+
@Override
348+
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
349+
MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
350+
return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(
351+
next.newCall(method, callOptions)) {
352+
@Override
353+
public void start(Listener<RespT> responseListener, Metadata headers) {
354+
super.start(
355+
new ForwardingClientCallListener.SimpleForwardingClientCallListener<RespT>(
356+
responseListener) {
357+
@Override
358+
public void onHeaders(Metadata headers) {
359+
Metadata copy = new Metadata();
360+
copy.merge(headers);
361+
capturedMetadata = copy;
362+
super.onHeaders(headers);
363+
}
364+
},
365+
headers);
366+
}
367+
};
368+
}
369+
}
370+
371+
/**
372+
* Private helper method required to extract a single string header from gRPC {@link Metadata}.
373+
*
374+
* @param metadata the captured gRPC response metadata
375+
* @param name the case-insensitive HTTP/2 header name
376+
* @return the string header value, or {@code null} if not present
377+
*/
378+
private static String getGrpcSingleHeaderString(Metadata metadata, String name) {
379+
Metadata.Key<String> key = Metadata.Key.of(name, Metadata.ASCII_STRING_MARSHALLER);
380+
return metadata.get(key);
381+
}
269382
}

0 commit comments

Comments
 (0)