Skip to content

Commit bdad053

Browse files
committed
feat: add post-quantum cryptography (PQC) integration tests and fixtures
1 parent 247e2ad commit bdad053

2 files changed

Lines changed: 89 additions & 7 deletions

File tree

packages/gapic-generator/tests/system/conftest.py

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import os
1919
import pytest
2020
import pytest_asyncio
21+
from requests.adapters import HTTPAdapter
22+
from urllib3.poolmanager import PoolManager
2123

2224
from typing import Sequence, Tuple
2325

@@ -328,7 +330,7 @@ def _read_response_metadata_stream(self):
328330
def intercept_unary_unary(self, continuation, client_call_details, request):
329331
self._add_request_metadata(client_call_details)
330332
response = continuation(client_call_details, request)
331-
metadata = [(k, str(v)) for k, v in response.trailing_metadata()]
333+
metadata = [(k, str(v)) for k, v in response.initial_metadata()] + [(k, str(v)) for k, v in response.trailing_metadata()]
332334
self.response_metadata = metadata
333335
return response
334336

@@ -453,36 +455,64 @@ async def intercepted_echo_grpc_async():
453455
return EchoAsyncClient(transport=transport), interceptor
454456

455457

458+
class HostNameIgnoringAdapter(HTTPAdapter):
459+
"""Custom HTTPAdapter that disables hostname verification for local self-signed certs."""
460+
def init_poolmanager(self, connections, maxsize, block=False, **pool_kwargs):
461+
self.poolmanager = PoolManager(
462+
num_pools=connections,
463+
maxsize=maxsize,
464+
block=block,
465+
assert_hostname=False,
466+
**pool_kwargs
467+
)
468+
469+
456470
@pytest.fixture
457-
def intercepted_echo_rest():
471+
def intercepted_echo_rest(use_mtls):
458472
transport_name = "rest"
459473
transport_cls = EchoClient.get_transport_class(transport_name)
460474
interceptor = EchoMetadataClientRestInterceptor()
461475

462-
# The custom host explicitly bypasses https.
476+
url_scheme = "https" if use_mtls else "http"
463477
transport = transport_cls(
464478
credentials=ga_credentials.AnonymousCredentials(),
465479
host="localhost:7469",
466-
url_scheme="http",
480+
url_scheme=url_scheme,
467481
interceptor=interceptor,
468482
)
483+
if use_mtls:
484+
dir = os.path.dirname(__file__)
485+
cert_path = os.path.join(dir, "../cert/mtls.crt")
486+
key_path = os.path.join(dir, "../cert/mtls.key")
487+
transport._session.verify = cert_path
488+
transport._session.cert = (cert_path, key_path)
489+
transport._session.mount("https://", HostNameIgnoringAdapter())
490+
469491
return EchoClient(transport=transport), interceptor
470492

471493

472494
@pytest.fixture
473-
def intercepted_echo_rest_async():
495+
def intercepted_echo_rest_async(use_mtls):
474496
if not HAS_ASYNC_REST_ECHO_TRANSPORT:
475497
pytest.skip("Skipping test with async rest.")
476498

477499
transport_name = "rest_asyncio"
478500
transport_cls = EchoAsyncClient.get_transport_class(transport_name)
479501
interceptor = EchoMetadataClientRestAsyncInterceptor()
480502

481-
# The custom host explicitly bypasses https.
503+
url_scheme = "https" if use_mtls else "http"
482504
transport = transport_cls(
483505
credentials=async_anonymous_credentials(),
484506
host="localhost:7469",
485-
url_scheme="http",
507+
url_scheme=url_scheme,
486508
interceptor=interceptor,
487509
)
510+
if use_mtls:
511+
dir = os.path.dirname(__file__)
512+
cert_path = os.path.join(dir, "../cert/mtls.crt")
513+
key_path = os.path.join(dir, "../cert/mtls.key")
514+
transport._session.verify = cert_path
515+
transport._session.cert = (cert_path, key_path)
516+
transport._session.mount("https://", HostNameIgnoringAdapter())
517+
488518
return EchoAsyncClient(transport=transport), interceptor
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import pytest
16+
from google import showcase
17+
18+
@pytest.fixture
19+
def run_pqc_test(use_mtls):
20+
if not use_mtls:
21+
pytest.skip("PQC integration test requires mTLS (--mtls flag) to be enabled.")
22+
23+
@pytest.mark.parametrize(
24+
"transport_fixture",
25+
["intercepted_echo_grpc", "intercepted_echo_rest"]
26+
)
27+
def test_pqc_negotiated_group(run_pqc_test, request, transport_fixture):
28+
"""Verifies that the generated client library negotiates PQC with the Showcase server."""
29+
client, interceptor = request.getfixturevalue(transport_fixture)
30+
31+
# Make secure call using the standard client library fixture
32+
response = client.echo(request=showcase.EchoRequest(content="Verify PQC connection."))
33+
assert response.content == "Verify PQC connection."
34+
35+
# Extract negotiated group and supported groups from response headers
36+
negotiated_group = None
37+
supported_groups = None
38+
for key, value in interceptor.response_metadata:
39+
if key.lower() == "x-showcase-tls-group":
40+
negotiated_group = value
41+
elif key.lower() == "x-showcase-tls-client-supported-groups":
42+
supported_groups = value
43+
44+
assert negotiated_group is not None, "Failed: Showcase server did not return negotiated TLS group header."
45+
assert supported_groups is not None, "Failed: Showcase server did not return client advertised supported groups."
46+
47+
print(f"\n[PQC Verification] ({transport_fixture}) Negotiated TLS Group: {negotiated_group}")
48+
print(f"[PQC Verification] ({transport_fixture}) Client Advertised Supported Groups: {supported_groups}")
49+
50+
# Enforce PQC compliance (this will fail if not using MLKEM/Kyber)
51+
assert "MLKEM" in negotiated_group or "Kyber" in negotiated_group, \
52+
f"Failed: {transport_fixture} Connection is NOT PQC-compliant! Negotiated: {negotiated_group}"

0 commit comments

Comments
 (0)