3434import static org .junit .jupiter .api .Assertions .assertNotNull ;
3535import static org .junit .jupiter .api .Assertions .fail ;
3636
37- import com .google .api .gax .core .NoCredentialsProvider ;
3837import io .grpc .netty .shaded .io .grpc .netty .NettyChannelBuilder ;
3938import io .grpc .netty .shaded .io .netty .handler .ssl .ApplicationProtocolConfig ;
4039import io .grpc .netty .shaded .io .netty .handler .ssl .SslContext ;
4140import io .grpc .netty .shaded .io .netty .handler .ssl .SslContextBuilder ;
42- import io .grpc .netty .shaded .io .netty .handler .ssl .SslProvider ;
4341import io .grpc .netty .shaded .io .netty .handler .ssl .util .InsecureTrustManagerFactory ;
4442import java .io .InputStream ;
4543import java .security .KeyStore ;
46- import java .util .ArrayList ;
47- import java .util .List ;
4844import org .junit .jupiter .api .AfterAll ;
4945import org .junit .jupiter .api .BeforeAll ;
5046import org .junit .jupiter .api .Test ;
5147
52- /**
53- * PqcConnectivityTest serves as the base integration validation suite for confirming transparent,
54- * zero-config Post-Quantum Cryptography (PQC) auto-upgrades across all Google Cloud Java SDK
55- * transports.
56- *
57- * <h3>Design and Architectural Workflow</h3>
58- *
59- * <p>The validation framework operates via an end-to-end hermetic handshake architecture:
60- *
61- * <pre>
62- * +---------------------------------------+ +-----------------------------------------+
63- * | Vanilla App Client Code | | PqcTestServer (Enforces MLKEM768)|
64- * | (e.g. BigQueryOptions.getDefaultInst) | +-----------------------------------------+
65- * +---------------------------------------+ ^
66- * | |
67- * v |
68- * +---------------------------------------+ |
69- * | google-cloud-core-http | |
70- * | (DefaultHttpTransportFactory) | |
71- * +---------------------------------------+ |
72- * | |
73- * v |
74- * +---------------------------------------+ |
75- * | google-http-java-client | |
76- * | (SslUtils.getTlsSslContext() JJSSE) | |
77- * +---------------------------------------+ |
78- * | |
79- * v |
80- * +---------------------------------------+ |
81- * | PqcDelegatingSSLSocketFactory | |
82- * | (Wraps default BCSSLSocketFactory) | |
83- * +---------------------------------------+ |
84- * | |
85- * +-----------------[TLSv1.3 MLKEM768 Hybrid Handshake]
86- * </pre>
87- *
88- * <ul>
89- * <li><b>Auto-Upgrade Detection:</b> The test dynamically detects if the current classpath
90- * includes the snapshot version of <code>google-http-java-client</code> (which contains
91- * <code>PqcDelegatingSSLSocketFactory</code>).
92- * <li><b>Zero-Config Integration:</b> If supported, Bouncy Castle JSSE is promoted to the default
93- * security provider (position 1). The standard client generation libraries automatically wrap
94- * all outbound transport connections in post-quantum hybrid key exchanges (enforcing
95- * ML-KEM-768 and classical curves) without requiring manual transport option overrides.
96- * <li><b>Automatic Fallback:</b> In release test scopes (where older library builds lack PQC
97- * features), the test silently skips dynamic JCA promotion, validating that classical TLS 1.3
98- * paths remain fully robust and operational.
99- * </ul>
100- */
10148public abstract class PqcConnectivityTest {
10249
10350 private static Process serverProcess ;
104- protected static int httpPqcPort ;
105- protected static int httpClassicalPort ;
10651 protected static int grpcPqcPort ;
10752 protected static int grpcClassicalPort ;
108- private static boolean isPqcSupported ;
10953 private static KeyStore ks ;
11054
111- /**
112- * Configures the integration test harness environment before test cases are executed.
113- *
114- * <p><b>Harness Execution Flow:</b>
115- *
116- * <ol>
117- * <li>Extracts the secure PKCS12 validation certificate (<code>pqctest.p12</code>) from the
118- * classpath to a localized temp file to guarantee isolated execution.
119- * <li>Configures JVM standard truststore system properties (<code>javax.net.ssl.trustStore
120- * </code>) to point to the extracted certificate, enabling clean default SSLContext
121- * verification.
122- * <li>Inspects the runtime classpath to determine if PQC wrapper auto-upgrades are active.
123- * <li>If PQC is supported, registers <code>BouncyCastleJsseProvider</code> at position 1. This
124- * automatically causes all standard vanilla clients instantiating default <code>SSLContext
125- * </code> to negotiate PQC.
126- * <li>Spins up the hermetic <code>PqcTestServer</code> in a separate JVM process.
127- * </ol>
128- */
129- /**
130- * Configures the integration test harness environment before test cases are executed.
131- *
132- * <p><b>Detailed Security & Keystore Configuration Architecture:</b>
133- *
134- * <ul>
135- * <li><b>What is a Keystore (PKCS12):</b> A PKCS12 keystore (<code>pqctest.p12</code>) is a
136- * secure key database containing the server's private key and its self-signed public
137- * certificate. The server uses it during the TLS handshake to prove its identity and
138- * establish a secure channel.
139- * <li><b>How Encryption Works:</b> The certificate itself does not encrypt message data
140- * directly. Instead, during the TLS handshake, the client and server negotiate a symmetric
141- * session key using post-quantum cryptography (ML-KEM). This session key is then used to
142- * encrypt and decrypt all sent/received HTTP/gRPC data.
143- * <li><b>Why a Custom Temporary Truststore is Required:</b> Because the server uses a
144- * self-signed test certificate, it is not signed by any public Certificate Authority (CA)
145- * trusted by the standard JRE truststore (<code>cacerts</code>). Without registering a
146- * custom truststore containing this certificate, standard JRE TLS clients will reject the
147- * connection with an <code>SSLHandshakeException</code>. We extract the certificate to a
148- * temporary file and point <code>javax.net.ssl.trustStore</code> to it, thereby trusting it
149- * scope-specifically for this test run without polluting or mutating the user's system-wide
150- * JRE truststore.
151- * <li><b>JCA Provider Registration:</b> Registers <code>BouncyCastleJsseProvider</code> at
152- * provider position 1. This registers Bouncy Castle as the primary security provider,
153- * causing all standard default <code>SSLContext</code> and vanilla client factories to
154- * utilize Bouncy Castle JSSE and negotiate PQC automatically.
155- * </ul>
156- */
15755 protected boolean clientSupportsPqc () {
15856 return true ;
15957 }
@@ -163,16 +61,6 @@ protected boolean clientSupportsPqc() {
16361 @ BeforeAll
16462 public static void setup () throws Exception {
16563
166- // Dynamically detect if PQC auto-upgrade wrapping is supported by current
167- // classpath
168- // dependencies (Snapshot vs Release)
169- try {
170- Class .forName ("com.google.api.client.http.javanet.PqcPeerHostSSLSocketFactory" );
171- isPqcSupported = true ;
172- } catch (ClassNotFoundException e ) {
173- isPqcSupported = false ;
174- }
175-
17664 ks = KeyStore .getInstance ("PKCS12" );
17765 try (InputStream is = PqcConnectivityTest .class .getResourceAsStream ("/pqctest.p12" )) {
17866 if (is == null ) {
@@ -201,30 +89,22 @@ public static void setup() throws Exception {
20189 serverProcess .getInputStream (), java .nio .charset .StandardCharsets .UTF_8 ));
20290
20391 String line ;
204- boolean httpPqcFound = false ;
205- boolean httpClassicalFound = false ;
20692 boolean grpcPqcFound = false ;
20793 boolean grpcClassicalFound = false ;
20894
20995 // Wait for the server process to output its HTTP and gRPC ports
21096 long startTime = System .currentTimeMillis ();
21197 while ((line = reader .readLine ()) != null ) {
21298 System .out .println ("[SERVER-OUT] " + line );
213- if (line .startsWith ("HTTP_PQC_PORT: " )) {
214- httpPqcPort = Integer .parseInt (line .substring (15 ).trim ());
215- httpPqcFound = true ;
216- } else if (line .startsWith ("HTTP_CLASSICAL_PORT: " )) {
217- httpClassicalPort = Integer .parseInt (line .substring (21 ).trim ());
218- httpClassicalFound = true ;
219- } else if (line .startsWith ("GRPC_PQC_PORT: " )) {
99+ if (line .startsWith ("GRPC_PQC_PORT: " )) {
220100 grpcPqcPort = Integer .parseInt (line .substring (15 ).trim ());
221101 grpcPqcFound = true ;
222102 } else if (line .startsWith ("GRPC_CLASSICAL_PORT: " )) {
223103 grpcClassicalPort = Integer .parseInt (line .substring (21 ).trim ());
224104 grpcClassicalFound = true ;
225105 }
226106
227- if (httpPqcFound && httpClassicalFound && grpcPqcFound && grpcClassicalFound ) {
107+ if (grpcPqcFound && grpcClassicalFound ) {
228108 break ;
229109 }
230110
@@ -236,7 +116,7 @@ public static void setup() throws Exception {
236116 }
237117 }
238118
239- if (!httpPqcFound || ! httpClassicalFound || ! grpcPqcFound || !grpcClassicalFound ) {
119+ if (!grpcPqcFound || !grpcClassicalFound ) {
240120 throw new RuntimeException ("PqcTestServer failed to initialize ephemeral ports!" );
241121 }
242122
@@ -315,69 +195,6 @@ public void testGrpcClassicalServer() throws Exception {
315195 runGrpcTest (grpcClassicalPort , true );
316196 }
317197
318-
319- private static void configureGrpcChannelForPqc (io .grpc .ManagedChannelBuilder <?> builder ) {
320- if (!(builder instanceof NettyChannelBuilder )) {
321- throw new IllegalArgumentException (
322- "Unsupported channel builder type: " + builder .getClass ().getName ());
323- }
324- NettyChannelBuilder nettyBuilder = (NettyChannelBuilder ) builder ;
325-
326- // Ensures HTTP/2 is used as it's required by gRPC
327- ApplicationProtocolConfig apn =
328- new ApplicationProtocolConfig (
329- ApplicationProtocolConfig .Protocol .ALPN ,
330- ApplicationProtocolConfig .SelectorFailureBehavior .NO_ADVERTISE ,
331- ApplicationProtocolConfig .SelectedListenerFailureBehavior .ACCEPT ,
332- "h2" );
333-
334- try {
335- java .security .Provider bcProvider = new org .bouncycastle .jce .provider .BouncyCastleProvider ();
336- java .security .Provider bcJsseProvider =
337- new org .bouncycastle .jsse .provider .BouncyCastleJsseProvider (bcProvider );
338-
339- SslContext shadedSslContext =
340- SslContextBuilder .forClient ()
341- .sslProvider (SslProvider .JDK )
342- .sslContextProvider (bcJsseProvider )
343- .protocols ("TLSv1.3" )
344- .applicationProtocolConfig (apn )
345- .trustManager (InsecureTrustManagerFactory .INSTANCE )
346- .build ();
347-
348- nettyBuilder .sslContext (shadedSslContext );
349- } catch (Exception e ) {
350- throw new RuntimeException ("Failed to configure shaded gRPC Netty channel for PQC" , e );
351- }
352- }
353-
354- private static void configureGrpcChannelForClassical (io .grpc .ManagedChannelBuilder <?> builder ) {
355- if (!(builder instanceof NettyChannelBuilder )) {
356- throw new IllegalArgumentException (
357- "Unsupported channel builder type: " + builder .getClass ().getName ());
358- }
359- NettyChannelBuilder nettyBuilder = (NettyChannelBuilder ) builder ;
360-
361- ApplicationProtocolConfig apn =
362- new ApplicationProtocolConfig (
363- ApplicationProtocolConfig .Protocol .ALPN ,
364- ApplicationProtocolConfig .SelectorFailureBehavior .NO_ADVERTISE ,
365- ApplicationProtocolConfig .SelectedListenerFailureBehavior .ACCEPT ,
366- "h2" );
367-
368- try {
369- SslContext shadedSslContext =
370- SslContextBuilder .forClient ()
371- .applicationProtocolConfig (apn )
372- .trustManager (InsecureTrustManagerFactory .INSTANCE )
373- .build ();
374-
375- nettyBuilder .sslContext (shadedSslContext );
376- } catch (Exception e ) {
377- throw new RuntimeException ("Failed to configure shaded gRPC Netty channel for Classical" , e );
378- }
379- }
380-
381198 private static class ByteMarshaller implements io .grpc .MethodDescriptor .Marshaller <byte []> {
382199 @ Override
383200 public InputStream stream (byte [] value ) {
0 commit comments