-
Notifications
You must be signed in to change notification settings - Fork 696
test(gax): Add an integration test for testing post quantum encryption #8891
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f797f5a
754f7af
9413c6a
2a3c991
c050585
debed48
5f1a584
f458d3d
287333a
24d0969
96d231f
3d9146f
c7f0c97
17ec0ac
fe5df38
1a4ddb4
ff7c90c
ac2a911
9612e84
b1b54cc
d24604b
7bf096f
06adb75
1a6227a
305b50c
a2ccbd4
3320e5d
09b0c7e
fdd3586
bfc17ef
5b9e265
2c920c8
bcd6632
6a4aa1d
53131af
fc1fa41
35c1adb
e27adef
03cb81b
6e12527
0d13a4c
a7ee02f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,211 @@ | ||
| /** | ||
| * Copyright 2026 Google LLC | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| import * as fs from 'fs'; | ||
| import * as https from 'https'; | ||
| import * as path from 'path'; | ||
| import * as assert from 'assert'; | ||
| import {grpc, GoogleAuth, googleAuthLibrary} from 'google-gax'; | ||
| import {EchoClient} from 'showcase-echo-client'; | ||
| import {ShowcaseServer} from 'showcase-server'; | ||
|
|
||
| import * as os from 'os'; | ||
| import * as tls from 'tls'; | ||
|
|
||
| async function grpcPqcTest(pemBuffer: Buffer, port: number) { | ||
| let negotiatedGroupGrpc: string | undefined; | ||
| let clientSupportedGroupsGrpc: string | undefined; | ||
|
|
||
| const interceptor = (options: any, nextCall: any) => { | ||
| return new grpc.InterceptingCall(nextCall(options), { | ||
| start: (metadata: any, listener: any, next: any) => { | ||
| next(metadata, { | ||
| onReceiveMetadata: (receivedMetadata: any, nextListener: any) => { | ||
| const group = receivedMetadata.get('x-showcase-tls-group'); | ||
| if (group && group.length > 0) { | ||
| negotiatedGroupGrpc = group[0].toString(); | ||
| } | ||
| const supportedGroups = receivedMetadata.get( | ||
| 'x-showcase-tls-client-supported-groups' | ||
| ); | ||
| if (supportedGroups && supportedGroups.length > 0) { | ||
| clientSupportedGroupsGrpc = supportedGroups[0].toString(); | ||
| } | ||
| nextListener(receivedMetadata); | ||
| }, | ||
| }); | ||
| }, | ||
| }); | ||
| }; | ||
|
|
||
| const grpcClientOpts = { | ||
| grpc, | ||
| sslCreds: grpc.credentials.createSsl(pemBuffer), | ||
| servicePath: 'localhost', | ||
| port: port, | ||
| }; | ||
|
|
||
| const grpcClient = new EchoClient(grpcClientOpts); | ||
|
|
||
| const [responseGrpc] = await grpcClient.echo( | ||
| { content: 'grpc-pqc-test' }, | ||
| { | ||
| otherArgs: { | ||
| options: { | ||
| interceptors: [interceptor], | ||
| }, | ||
| }, | ||
| } | ||
| ); | ||
|
|
||
| assert.strictEqual(responseGrpc.content, 'grpc-pqc-test'); | ||
| assert.ok( | ||
| negotiatedGroupGrpc, | ||
| 'Expected negotiated TLS group in gRPC response metadata' | ||
| ); | ||
| assert.strictEqual(negotiatedGroupGrpc, 'X25519MLKEM768'); | ||
| assert.ok( | ||
| clientSupportedGroupsGrpc, | ||
| 'Expected client supported groups in gRPC response metadata' | ||
| ); | ||
| assert.ok( | ||
| clientSupportedGroupsGrpc.includes('X25519MLKEM768'), | ||
| 'Expected client to include X25519MLKEM768 in supported groups' | ||
| ); | ||
| } | ||
|
|
||
| async function httpRestFallbackPqcTest(pemBuffer: Buffer, port: number) { | ||
| let negotiatedGroupRest: string | undefined; | ||
| let clientSupportedGroupsRest: string | undefined; | ||
|
|
||
| const auth = new GoogleAuth({ | ||
| authClient: new googleAuthLibrary.PassThroughClient(), | ||
| }); | ||
|
|
||
| const originalFetch = auth.fetch.bind(auth); | ||
| (auth as any).fetch = async (url: string, opts: any) => { | ||
| if (url.startsWith('https:')) { | ||
| opts.agent = new https.Agent({ | ||
| ca: pemBuffer, | ||
| keepAlive: true, | ||
| }); | ||
| } | ||
| const res = await originalFetch(url, opts); | ||
| // res.headers is a Headers instance (with .get()) in standard Fetch API and a plain object in other HTTP clients. | ||
| const group = | ||
| typeof res.headers.get === 'function' | ||
| ? res.headers.get('x-showcase-tls-group') | ||
| : (res.headers as any)['x-showcase-tls-group']; | ||
| if (group) { | ||
| negotiatedGroupRest = group; | ||
| } | ||
| const supportedGroups = | ||
| typeof res.headers.get === 'function' | ||
| ? res.headers.get('x-showcase-tls-client-supported-groups') | ||
| : (res.headers as any)['x-showcase-tls-client-supported-groups']; | ||
| if (supportedGroups) { | ||
| clientSupportedGroupsRest = supportedGroups; | ||
| } | ||
| return res; | ||
| }; | ||
|
|
||
| const restClientOpts = { | ||
| fallback: true, | ||
| protocol: 'https', | ||
| servicePath: 'localhost', | ||
| port: port, | ||
| auth: auth, | ||
| }; | ||
|
|
||
| const restClient = new EchoClient(restClientOpts); | ||
| const [responseRest] = await restClient.echo({ content: 'rest-pqc-test' }); | ||
|
|
||
| assert.strictEqual(responseRest.content, 'rest-pqc-test'); | ||
| assert.ok( | ||
| negotiatedGroupRest, | ||
| 'Expected negotiated TLS group in REST response headers' | ||
| ); | ||
| assert.strictEqual(negotiatedGroupRest, 'X25519MLKEM768'); | ||
| assert.ok( | ||
| clientSupportedGroupsRest, | ||
| 'Expected client supported groups in REST response headers' | ||
| ); | ||
| assert.ok( | ||
| clientSupportedGroupsRest.includes('X25519MLKEM768'), | ||
| 'Expected client to include X25519MLKEM768 in supported groups' | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Tests Post Quantum Cryptography (PQC) using the specified CA cert and port. | ||
| * It verifies both gRPC and HTTP/REST clients by inspecting the negotiated TLS group. | ||
| * @param pemBuffer The CA certificate buffer. | ||
| * @param port The port the TLS showcase server is listening on. | ||
| */ | ||
| async function testPqc(pemBuffer: Buffer, port: number) { | ||
| await grpcPqcTest(pemBuffer, port); | ||
| await httpRestFallbackPqcTest(pemBuffer, port); | ||
| } | ||
|
|
||
| /** | ||
| * Spins up a TLS-enabled showcase server and runs the PQC compliance tests. | ||
| * Cleans up the generated certificate file after the tests complete. | ||
| */ | ||
| export async function runPqcComplianceTests() { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rather than a test that fails on specific Node versions, would it make sense to skip this test conditionally (i.e. on It could print a "skipping" message. This way it could be added to our CI more permanently.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. pushed a change for this |
||
| const [major, minor] = process.version.replace('v', '').split('.').map(Number); | ||
| if (major < 22 || (major === 22 && minor < 20)) { | ||
| console.log( | ||
| `skipping PQC compliance tests because node version is ${process.version}` | ||
| ); | ||
| return; | ||
| } | ||
|
|
||
| const originalShowcaseVersion = process.env['SHOWCASE_VERSION']; | ||
| process.env['SHOWCASE_VERSION'] = '0.41.1'; | ||
| const showcaseServerTls = new ShowcaseServer(); | ||
| const tlsPort = 7443; | ||
| const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pqc-test-')); | ||
| const caCertOutputFile = path.join(tempDir, 'showcase.pem'); | ||
| const pemPath = caCertOutputFile; | ||
|
|
||
| try { | ||
| if (fs.existsSync(pemPath)) { | ||
| fs.unlinkSync(pemPath); | ||
| } | ||
|
|
||
| const pemBuffer = await showcaseServerTls.start({ | ||
| tls: true, | ||
| port: `:${tlsPort}`, | ||
| caCertOutputFile: caCertOutputFile, | ||
| }); | ||
|
|
||
| if (!pemBuffer) { | ||
| throw new Error('Expected showcase server to return CA certificate buffer'); | ||
| } | ||
|
|
||
| await testPqc(pemBuffer, tlsPort); | ||
| } finally { | ||
| showcaseServerTls.stop(); | ||
| if (originalShowcaseVersion === undefined) { | ||
| delete process.env['SHOWCASE_VERSION']; | ||
| } else { | ||
| process.env['SHOWCASE_VERSION'] = originalShowcaseVersion; | ||
| } | ||
| if (fs.existsSync(tempDir)) { | ||
| fs.rmSync(tempDir, {recursive: true, force: true}); | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: if this triggers early (before showcase has written) it might read a partial file. Consider a few ms wait here with a comment to give it some time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done