Skip to content

Commit afc05d1

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 afc05d1

2 files changed

Lines changed: 87 additions & 72 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: 69 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import com.google.api.client.http.HttpTransport;
2020
import com.google.api.client.http.javanet.NetHttpTransport;
21+
import com.google.api.client.util.SslUtils;
2122
import com.google.auth.http.HttpTransportFactory;
2223
import com.google.cloud.bigquery.BigQuery;
2324
import com.google.cloud.bigquery.BigQueryOptions;
@@ -28,20 +29,25 @@
2829
import java.net.Socket;
2930
import java.net.UnknownHostException;
3031
import java.security.Security;
32+
import java.util.concurrent.atomic.AtomicReference;
3133
import javax.net.ssl.SSLContext;
3234
import javax.net.ssl.SSLSocket;
3335
import javax.net.ssl.SSLSocketFactory;
3436
import org.conscrypt.Conscrypt;
3537
import org.conscrypt.OpenSSLSocketImpl;
3638

3739
/**
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.
40+
* A verification sample to programmatically trace and assert TLS handshake details (protocol,
41+
* cipher suite, and negotiated curve) for Google Cloud BigQuery client calls, verifying that PQC
42+
* (X25519MLKEM768) is negotiated.
4243
*/
4344
public class BqPqcTest {
4445

46+
private static final String EXPECTED_PQC_CURVE = "X25519MLKEM768";
47+
private static final AtomicReference<String> negotiatedCurve = new AtomicReference<>();
48+
private static final AtomicReference<String> negotiatedProtocol = new AtomicReference<>();
49+
private static final AtomicReference<String> negotiatedCipherSuite = new AtomicReference<>();
50+
4551
public static void main(String[] args) throws Exception {
4652
System.out.println("[DEBUG] Java Version: " + System.getProperty("java.version"));
4753
System.out.println("[DEBUG] Java Runtime: " + System.getProperty("java.runtime.version"));
@@ -51,77 +57,95 @@ public static void main(String[] args) throws Exception {
5157
+ " ("
5258
+ System.getProperty("java.vm.version")
5359
+ ")");
54-
try {
55-
System.out.println("[DEBUG] Conscrypt Version: " + Conscrypt.version());
60+
61+
// Ensure Conscrypt is registered locally for our tracing context lookup
62+
if (Security.getProvider("Conscrypt") == null) {
5663
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());
6064
}
6165

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

85+
// 1. Build custom HttpTransportFactory with Tracing SSLSocketFactory to capture the handshake
86+
// curve
87+
HttpTransportFactory transportFactory = new TracingHttpTransportFactory();
6588
HttpTransportOptions transportOptions =
6689
HttpTransportOptions.newBuilder().setHttpTransportFactory(transportFactory).build();
6790

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-
91+
System.out.println("Initializing default BigQuery client for project: " + projectId);
7292
BigQuery bigquery =
7393
BigQueryOptions.newBuilder()
7494
.setProjectId(projectId)
7595
.setTransportOptions(transportOptions)
7696
.build()
7797
.getService();
7898

79-
// 4. Trigger a call to list datasets
80-
System.out.println("Listing datasets using BigQuery Client with TLS tracing...");
99+
System.out.println("Listing datasets using default BigQuery Client...");
81100
try {
82101
for (Dataset dataset : bigquery.listDatasets().iterateAll()) {
83102
System.out.println("- " + dataset.getDatasetId().getDataset());
84103
}
104+
System.out.println("Success! BigQuery datasets retrieved successfully.");
85105
} catch (Exception e) {
86106
System.err.println("API call failed: " + e.getMessage());
87107
e.printStackTrace();
88108
}
89-
}
90109

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)"));
110+
// 2. Perform Programmatic Assertion on the Negotiated Curve
111+
String curve = negotiatedCurve.get();
112+
String protocol = negotiatedProtocol.get();
113+
String cipherSuite = negotiatedCipherSuite.get();
114+
115+
System.out.println("\n==================================================");
116+
System.out.println("TLS Handshake Verification Results:");
117+
System.out.println(" Protocol : " + protocol);
118+
System.out.println(" Cipher Suite : " + cipherSuite);
119+
System.out.println(" Negotiated KEX: " + curve);
120+
System.out.println("==================================================");
121+
122+
if (curve == null) {
123+
System.err.println("ERROR: No TLS handshake was intercepted!");
124+
System.exit(1);
125+
}
126+
127+
if (EXPECTED_PQC_CURVE.equalsIgnoreCase(curve)) {
128+
System.out.println("VERIFICATION SUCCESS: PQC Hybrid key exchange negotiated successfully!");
108129
} 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)");
130+
System.err.println(
131+
"VERIFICATION FAILED: Expected PQC Key Exchange "
132+
+ EXPECTED_PQC_CURVE
133+
+ " but negotiated: "
134+
+ curve);
135+
System.exit(1);
112136
}
113137
}
114138

115139
private static class TracingHttpTransportFactory implements HttpTransportFactory {
116140
@Override
117141
public HttpTransport create() {
118142
try {
119-
// Build a standard Conscrypt-backed TLS context
120-
SSLContext sslContext = SSLContext.getInstance("TLS", "Conscrypt");
121-
sslContext.init(null, null, null);
143+
// Obtain the default TLS SSLContext (which handles Conscrypt and TrustManager resolution by
144+
// default)
145+
SSLContext sslContext = SslUtils.getDefaultTlsSslContext();
122146
SSLSocketFactory conscryptFactory = sslContext.getSocketFactory();
123147

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

127151
// NetHttpTransport automatically wraps our tracing factory to enforce PQC named groups
@@ -132,11 +156,6 @@ public HttpTransport create() {
132156
}
133157
}
134158

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-
*/
140159
private static class TracingSSLSocketFactory extends SSLSocketFactory {
141160
private final SSLSocketFactory delegate;
142161

@@ -150,20 +169,13 @@ private Socket wrap(Socket socket) {
150169
sslSocket.addHandshakeCompletedListener(
151170
event -> {
152171
try {
153-
String cipherSuite = event.getCipherSuite();
154-
String protocol = event.getSession().getProtocol();
172+
negotiatedCipherSuite.set(event.getCipherSuite());
173+
negotiatedProtocol.set(event.getSession().getProtocol());
155174
Socket rawSocket = event.getSocket();
156175

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

0 commit comments

Comments
 (0)