Skip to content

Commit fd647d8

Browse files
committed
test(pqc): simplify BigQuery PQC verification sample to use default client options and programmatic properties
TAG=agy CONV=385b9ab5-874c-4c9a-b331-66dab51fef61
1 parent 164044a commit fd647d8

2 files changed

Lines changed: 91 additions & 71 deletions

File tree

pqc-verification/README.md

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -66,35 +66,38 @@ If successful, you will see `BUILD SUCCESS` and both `testGrpcPqc` and `testHttp
6666

6767
---
6868

69-
## 4. Standalone BigQuery PQC Tracing Sample
69+
## 4. Standalone BigQuery PQC Verification Sample
7070

71-
The class `BqPqcTest` runs a live connection to Google Cloud BigQuery, intercepts TLS sockets, and traces the negotiated curve/groups to verify `X25519MLKEM768` is used.
71+
The class `BqPqcTest` runs a live connection to Google Cloud BigQuery using the default client settings. Since the PQC changes are enabled by default in the underlying HTTP client transport, this sample does not require any custom PQC configurations.
7272

73-
### Run with Maven
74-
To execute the BigQuery trace sample:
73+
### Run the Sample
74+
You can run the sample directly from your IDE, or via Maven. The project ID is resolved automatically via Application Default Credentials (ADC), and TLS/SSL handshake tracing is configured programmatically:
7575

7676
```shell
7777
cd pqc-verification
7878

7979
# Run using exec-maven-plugin
80-
mvn clean compile exec:java -Dproject.id="your-gcp-project-id"
80+
mvn clean compile exec:java
8181
```
8282

8383
### Expected Output
84-
If Conscrypt is configured correctly and your environment supports PQC, you will see output tracing the handshake:
84+
The program will automatically intercept the TLS handshake and assert on the negotiated curve. If successful, you will see a validation summary at the end of execution:
85+
8586
```
8687
[DEBUG] Java Version: 17.0.19
8788
[DEBUG] Java Runtime: 17.0.19+10
8889
[DEBUG] Java VM : OpenJDK 64-Bit Server VM (17.0.19+10)
89-
[DEBUG] Conscrypt Version: 2.6.0
90-
Registered Conscrypt provider at position 1.
91-
Initializing BigQuery client for project: your-gcp-project-id
92-
Listing datasets using BigQuery Client with TLS tracing...
93-
[TLS TRACE] Handshake Completed
94-
Protocol : TLSv1.3
95-
CipherSuite: TLS_AES_128_GCM_SHA256
96-
Curve Name : X25519MLKEM768 (via Conscrypt OpenSSLSocketImpl.getCurveNameForTesting)
97-
Is PQC? : YES (Hybrid Post-Quantum)
90+
Initializing default BigQuery client for project: your-gcp-project-id
91+
Listing datasets using default BigQuery Client...
9892
- my_dataset1
9993
- my_dataset2
94+
Success! BigQuery datasets retrieved successfully.
95+
96+
==================================================
97+
TLS Handshake Verification Results:
98+
Protocol : TLSv1.3
99+
Cipher Suite : TLS_AES_128_GCM_SHA256
100+
Negotiated KEX: X25519MLKEM768
101+
==================================================
102+
VERIFICATION SUCCESS: PQC Hybrid key exchange negotiated successfully!
100103
```

pqc-verification/src/main/java/com/google/cloud/pqc/BqPqcTest.java

Lines changed: 73 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,25 @@
2828
import java.net.Socket;
2929
import java.net.UnknownHostException;
3030
import java.security.Security;
31+
import java.util.concurrent.atomic.AtomicReference;
3132
import javax.net.ssl.SSLContext;
3233
import javax.net.ssl.SSLSocket;
3334
import javax.net.ssl.SSLSocketFactory;
3435
import org.conscrypt.Conscrypt;
3536
import org.conscrypt.OpenSSLSocketImpl;
3637

3738
/**
38-
* A reproduction sample to trace TLS handshake details (protocol, cipher suite, and negotiated
39-
* curve) for Google Cloud BigQuery client calls, verifying that PQC (X25519MLKEM768) is negotiated.
40-
*
41-
* <p>This code requires Conscrypt on the classpath to enable and detect PQC algorithms.
39+
* A verification sample to programmatically trace and assert TLS handshake details (protocol,
40+
* cipher suite, and negotiated curve) for Google Cloud BigQuery client calls, verifying that PQC
41+
* (X25519MLKEM768) is negotiated.
4242
*/
4343
public class BqPqcTest {
4444

45+
private static final String EXPECTED_PQC_CURVE = "X25519MLKEM768";
46+
private static final AtomicReference<String> negotiatedCurve = new AtomicReference<>();
47+
private static final AtomicReference<String> negotiatedProtocol = new AtomicReference<>();
48+
private static final AtomicReference<String> negotiatedCipherSuite = new AtomicReference<>();
49+
4550
public static void main(String[] args) throws Exception {
4651
System.out.println("[DEBUG] Java Version: " + System.getProperty("java.version"));
4752
System.out.println("[DEBUG] Java Runtime: " + System.getProperty("java.runtime.version"));
@@ -51,77 +56,101 @@ public static void main(String[] args) throws Exception {
5156
+ " ("
5257
+ System.getProperty("java.vm.version")
5358
+ ")");
54-
try {
55-
System.out.println("[DEBUG] Conscrypt Version: " + Conscrypt.version());
59+
60+
// Ensure Conscrypt is registered locally for our tracing context lookup
61+
if (Security.getProvider("Conscrypt") == null) {
5662
Security.addProvider(Conscrypt.newProvider());
57-
System.out.println("Registered Conscrypt provider.");
58-
} catch (Throwable t) {
59-
System.out.println("[DEBUG] Failed to register or get Conscrypt version: " + t.getMessage());
6063
}
6164

62-
// 1. Build custom HttpTransportFactory with Tracing SSLSocketFactory
63-
HttpTransportFactory transportFactory = new TracingHttpTransportFactory();
65+
String projectId = System.getProperty("project.id");
66+
if (projectId == null || projectId.isEmpty()) {
67+
projectId = System.getenv("GOOGLE_CLOUD_PROJECT");
68+
}
69+
if (projectId == null || projectId.isEmpty()) {
70+
try {
71+
projectId = BigQueryOptions.getDefaultInstance().getProjectId();
72+
} catch (Exception e) {
73+
// Ignore if defaults are not configured
74+
}
75+
}
76+
if (projectId == null || projectId.isEmpty()) {
77+
System.err.println("Error: Google Cloud Project ID could not be resolved automatically.");
78+
System.err.println(
79+
"Please set the GOOGLE_CLOUD_PROJECT environment variable, or configure Application"
80+
+ " Default Credentials.");
81+
System.exit(1);
82+
}
6483

84+
// 1. Build custom HttpTransportFactory with Tracing SSLSocketFactory to capture the handshake
85+
// curve
86+
HttpTransportFactory transportFactory = new TracingHttpTransportFactory();
6587
HttpTransportOptions transportOptions =
6688
HttpTransportOptions.newBuilder().setHttpTransportFactory(transportFactory).build();
6789

68-
// 3. Initialize BigQuery client
69-
String projectId = System.getProperty("project.id", "lawrence-test-project-2");
70-
System.out.println("Initializing BigQuery client for project: " + projectId);
71-
90+
System.out.println("Initializing default BigQuery client for project: " + projectId);
7291
BigQuery bigquery =
7392
BigQueryOptions.newBuilder()
7493
.setProjectId(projectId)
7594
.setTransportOptions(transportOptions)
7695
.build()
7796
.getService();
7897

79-
// 4. Trigger a call to list datasets
80-
System.out.println("Listing datasets using BigQuery Client with TLS tracing...");
98+
System.out.println("Listing datasets using default BigQuery Client...");
8199
try {
82100
for (Dataset dataset : bigquery.listDatasets().iterateAll()) {
83101
System.out.println("- " + dataset.getDatasetId().getDataset());
84102
}
103+
System.out.println("Success! BigQuery datasets retrieved successfully.");
85104
} catch (Exception e) {
86105
System.err.println("API call failed: " + e.getMessage());
87106
e.printStackTrace();
88107
}
89-
}
90108

91-
private static void logHandshakeDetails(
92-
String protocol,
93-
String cipherSuite,
94-
String curve,
95-
String methodUsed,
96-
String socketClassName) {
97-
System.out.println("[TLS TRACE] Handshake Completed");
98-
System.out.println(" Protocol : " + protocol);
99-
System.out.println(" CipherSuite: " + cipherSuite);
100-
if (curve != null) {
101-
System.out.println(" Curve Name : " + curve + " (via Conscrypt " + methodUsed + ")");
102-
boolean isPqc =
103-
curve.equalsIgnoreCase("X25519MLKEM768")
104-
|| curve.toLowerCase().contains("mlkem")
105-
|| curve.toLowerCase().contains("kyber");
106-
System.out.println(
107-
" Is PQC? : " + (isPqc ? "YES (Hybrid Post-Quantum)" : "NO (Classical)"));
109+
// 2. Perform Programmatic Assertion on the Negotiated Curve
110+
String curve = negotiatedCurve.get();
111+
String protocol = negotiatedProtocol.get();
112+
String cipherSuite = negotiatedCipherSuite.get();
113+
114+
System.out.println("\n==================================================");
115+
System.out.println("TLS Handshake Verification Results:");
116+
System.out.println(" Protocol : " + protocol);
117+
System.out.println(" Cipher Suite : " + cipherSuite);
118+
System.out.println(" Negotiated KEX: " + curve);
119+
System.out.println("==================================================");
120+
121+
if (curve == null) {
122+
System.err.println("ERROR: No TLS handshake was intercepted!");
123+
System.exit(1);
124+
}
125+
126+
if (EXPECTED_PQC_CURVE.equalsIgnoreCase(curve)) {
127+
System.out.println("VERIFICATION SUCCESS: PQC Hybrid key exchange negotiated successfully!");
108128
} else {
109-
System.out.println(" Curve Name : Unknown");
110-
System.out.println(" Socket Class: " + socketClassName);
111-
System.out.println(" Is PQC? : UNKNOWN (Use Conscrypt to detect)");
129+
System.err.println(
130+
"VERIFICATION FAILED: Expected PQC Key Exchange "
131+
+ EXPECTED_PQC_CURVE
132+
+ " but negotiated: "
133+
+ curve);
134+
System.exit(1);
112135
}
113136
}
114137

115138
private static class TracingHttpTransportFactory implements HttpTransportFactory {
116139
@Override
117140
public HttpTransport create() {
118141
try {
119-
// Build a standard Conscrypt-backed TLS context
142+
// Resolve Conscrypt SSLContext to delegate to.
143+
// We must initialize it with the Conscrypt TrustManagerFactory to prevent
144+
// "Unknown authType: GENERIC" exceptions under TLS 1.3 when validating server certificates.
120145
SSLContext sslContext = SSLContext.getInstance("TLS", "Conscrypt");
121-
sslContext.init(null, null, null);
146+
javax.net.ssl.TrustManagerFactory trustManagerFactory =
147+
javax.net.ssl.TrustManagerFactory.getInstance(
148+
javax.net.ssl.TrustManagerFactory.getDefaultAlgorithm(), "Conscrypt");
149+
trustManagerFactory.init((java.security.KeyStore) null);
150+
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
122151
SSLSocketFactory conscryptFactory = sslContext.getSocketFactory();
123152

124-
// Wrap it in the tracing factory so we can log handshake outcomes
153+
// Wrap the socket factory to intercept handshakes
125154
SSLSocketFactory tracingFactory = new TracingSSLSocketFactory(conscryptFactory);
126155

127156
// NetHttpTransport automatically wraps our tracing factory to enforce PQC named groups
@@ -132,11 +161,6 @@ public HttpTransport create() {
132161
}
133162
}
134163

135-
/**
136-
* A wrapper SSLSocketFactory that registers a handshake completion listener to intercept and
137-
* print TLS metadata (protocol, cipher suite, and negotiated group/curve) for developer
138-
* visibility and testing.
139-
*/
140164
private static class TracingSSLSocketFactory extends SSLSocketFactory {
141165
private final SSLSocketFactory delegate;
142166

@@ -150,20 +174,13 @@ private Socket wrap(Socket socket) {
150174
sslSocket.addHandshakeCompletedListener(
151175
event -> {
152176
try {
153-
String cipherSuite = event.getCipherSuite();
154-
String protocol = event.getSession().getProtocol();
177+
negotiatedCipherSuite.set(event.getCipherSuite());
178+
negotiatedProtocol.set(event.getSession().getProtocol());
155179
Socket rawSocket = event.getSocket();
156180

157-
String curve = null;
158-
String methodUsed = "";
159-
160181
if (rawSocket instanceof OpenSSLSocketImpl) {
161-
curve = ((OpenSSLSocketImpl) rawSocket).getCurveNameForTesting();
162-
methodUsed = "OpenSSLSocketImpl.getCurveNameForTesting";
182+
negotiatedCurve.set(((OpenSSLSocketImpl) rawSocket).getCurveNameForTesting());
163183
}
164-
165-
logHandshakeDetails(
166-
protocol, cipherSuite, curve, methodUsed, rawSocket.getClass().getName());
167184
} catch (Exception e) {
168185
System.err.println("Failed to log TLS handshake: " + e.getMessage());
169186
}

0 commit comments

Comments
 (0)