From 77daf42e37d6d83a281ea0668752bbccf9cb5271 Mon Sep 17 00:00:00 2001 From: ohmayr Date: Wed, 8 Jul 2026 01:09:32 +0000 Subject: [PATCH 1/3] fix(auth): expand ECP custom TLS signer signature buffer for PQC --- .../auth/transport/_custom_tls_signer.py | 7 +++--- .../transport/test__custom_tls_signer.py | 23 +++++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/google/auth/transport/_custom_tls_signer.py b/packages/google-auth/google/auth/transport/_custom_tls_signer.py index 90143101ab07..16ae839f4ec1 100644 --- a/packages/google-auth/google/auth/transport/_custom_tls_signer.py +++ b/packages/google-auth/google/auth/transport/_custom_tls_signer.py @@ -156,9 +156,10 @@ def sign_callback(sig, sig_len, tbs, tbs_len): digest = _compute_sha256_digest(tbs, tbs_len) digestArray = ctypes.c_char * len(digest) - # reserve 2000 bytes for the signature, shoud be more then enough. - # RSA signature is 256 bytes, EC signature is 70~72. - sig_holder_len = 2000 + # Reserve 16384 bytes for the signature to accommodate Post-Quantum Cryptography (PQC) + # signatures. RSA signature is 256 bytes, EC signature is ~72 bytes, ML-DSA-65 is 3309 bytes, + # ML-DSA-87 is 4627 bytes, and SLH-DSA is up to 7856 bytes. + sig_holder_len = 16384 sig_holder = ctypes.create_string_buffer(sig_holder_len) signature_len = signer_lib.SignForPython( diff --git a/packages/google-auth/tests/transport/test__custom_tls_signer.py b/packages/google-auth/tests/transport/test__custom_tls_signer.py index fa210ee0b8d7..d94056a36820 100644 --- a/packages/google-auth/tests/transport/test__custom_tls_signer.py +++ b/packages/google-auth/tests/transport/test__custom_tls_signer.py @@ -110,6 +110,29 @@ def test_get_sign_callback(): assert returned_sign_len.value == mock_sig_len +def test_get_sign_callback_pqc_signature_size(): + # ML-DSA-65 (FIPS 204) signature is 3,309 bytes; ML-DSA-87 is 4,627 bytes. + mock_sig_len = 3309 + mock_signer_lib = mock.MagicMock() + mock_signer_lib.SignForPython.return_value = mock_sig_len + + sign_callback = _custom_tls_signer.get_sign_callback( + mock_signer_lib, FAKE_ENTERPRISE_CERT_FILE_PATH + ) + + to_be_signed = ctypes.POINTER(ctypes.c_ubyte)() + to_be_signed_len = 32 + returned_sig_array = (ctypes.c_ubyte * 4627)() + mock_sig_array = ctypes.cast(returned_sig_array, ctypes.POINTER(ctypes.c_ubyte)) + returned_sign_len = ctypes.c_ulong() + mock_sig_len_array = ctypes.byref(returned_sign_len) + + assert sign_callback( + mock_sig_array, mock_sig_len_array, to_be_signed, to_be_signed_len + ) + assert returned_sign_len.value == mock_sig_len + + def test_get_sign_callback_failed_to_sign(): # mock signer lib's SignForPython function. Set the sig len to be 0 to # indicate the signing failed. From d24930112a7f4c020c1fc64808e942125ff6fec1 Mon Sep 17 00:00:00 2001 From: ohmayr Date: Wed, 8 Jul 2026 08:34:54 +0000 Subject: [PATCH 2/3] fix(auth): expand ECP custom TLS signer signature buffer to 64KB for PQC and add bounds check --- .../auth/transport/_custom_tls_signer.py | 16 +++++--- .../transport/test__custom_tls_signer.py | 38 ++++++++++++++----- 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/packages/google-auth/google/auth/transport/_custom_tls_signer.py b/packages/google-auth/google/auth/transport/_custom_tls_signer.py index 16ae839f4ec1..4676d85484bc 100644 --- a/packages/google-auth/google/auth/transport/_custom_tls_signer.py +++ b/packages/google-auth/google/auth/transport/_custom_tls_signer.py @@ -156,10 +156,10 @@ def sign_callback(sig, sig_len, tbs, tbs_len): digest = _compute_sha256_digest(tbs, tbs_len) digestArray = ctypes.c_char * len(digest) - # Reserve 16384 bytes for the signature to accommodate Post-Quantum Cryptography (PQC) - # signatures. RSA signature is 256 bytes, EC signature is ~72 bytes, ML-DSA-65 is 3309 bytes, - # ML-DSA-87 is 4627 bytes, and SLH-DSA is up to 7856 bytes. - sig_holder_len = 16384 + # Reserve 65536 bytes (64 KiB) for the signature, which is the maximum signature size + # permitted by the TLS 1.3 protocol (RFC 8446). This covers all Post-Quantum Cryptography (PQC) + # algorithms including ML-DSA-87 (4627 B), SLH-DSA-128f (17088 B), and SLH-DSA-256f (49856 B). + sig_holder_len = 65536 sig_holder = ctypes.create_string_buffer(sig_holder_len) signature_len = signer_lib.SignForPython( @@ -170,8 +170,12 @@ def sign_callback(sig, sig_len, tbs, tbs_len): sig_holder_len, # sigHolderLen ) - if signature_len == 0: - # signing failed, return 0 + if signature_len <= 0 or signature_len > sig_holder_len: + _LOGGER.error( + "Invalid signature length %d returned from signer library (max %d)", + signature_len, + sig_holder_len, + ) return 0 sig_len[0] = signature_len diff --git a/packages/google-auth/tests/transport/test__custom_tls_signer.py b/packages/google-auth/tests/transport/test__custom_tls_signer.py index d94056a36820..053e1d5bc859 100644 --- a/packages/google-auth/tests/transport/test__custom_tls_signer.py +++ b/packages/google-auth/tests/transport/test__custom_tls_signer.py @@ -111,26 +111,44 @@ def test_get_sign_callback(): def test_get_sign_callback_pqc_signature_size(): - # ML-DSA-65 (FIPS 204) signature is 3,309 bytes; ML-DSA-87 is 4,627 bytes. - mock_sig_len = 3309 + # ML-DSA-65 is 3,309 B; SLH-DSA-128f is 17,088 B; SLH-DSA-256f is 49,856 B. + for mock_sig_len in (3309, 17088, 49856): + mock_signer_lib = mock.MagicMock() + mock_signer_lib.SignForPython.return_value = mock_sig_len + + sign_callback = _custom_tls_signer.get_sign_callback( + mock_signer_lib, FAKE_ENTERPRISE_CERT_FILE_PATH + ) + + to_be_signed = ctypes.POINTER(ctypes.c_ubyte)() + to_be_signed_len = 32 + returned_sig_array = (ctypes.c_ubyte * mock_sig_len)() + mock_sig_array = ctypes.cast(returned_sig_array, ctypes.POINTER(ctypes.c_ubyte)) + returned_sign_len = ctypes.c_ulong() + mock_sig_len_array = ctypes.byref(returned_sign_len) + + assert sign_callback( + mock_sig_array, mock_sig_len_array, to_be_signed, to_be_signed_len + ) + assert returned_sign_len.value == mock_sig_len + + +def test_get_sign_callback_oversized_signature(): + # Verify that signature lengths exceeding 65536 bytes fail gracefully (return 0). mock_signer_lib = mock.MagicMock() - mock_signer_lib.SignForPython.return_value = mock_sig_len + mock_signer_lib.SignForPython.return_value = 70000 sign_callback = _custom_tls_signer.get_sign_callback( mock_signer_lib, FAKE_ENTERPRISE_CERT_FILE_PATH ) - to_be_signed = ctypes.POINTER(ctypes.c_ubyte)() - to_be_signed_len = 32 - returned_sig_array = (ctypes.c_ubyte * 4627)() + returned_sig_array = (ctypes.c_ubyte * 100)() mock_sig_array = ctypes.cast(returned_sig_array, ctypes.POINTER(ctypes.c_ubyte)) returned_sign_len = ctypes.c_ulong() - mock_sig_len_array = ctypes.byref(returned_sign_len) - assert sign_callback( - mock_sig_array, mock_sig_len_array, to_be_signed, to_be_signed_len + assert not sign_callback( + mock_sig_array, ctypes.byref(returned_sign_len), None, 0 ) - assert returned_sign_len.value == mock_sig_len def test_get_sign_callback_failed_to_sign(): From 19c9d860c0b5222b83d92dc99dd43e6970c053d9 Mon Sep 17 00:00:00 2001 From: ohmayr Date: Wed, 8 Jul 2026 08:46:20 +0000 Subject: [PATCH 3/3] style(auth): reformat test__custom_tls_signer.py with black --- .../google-auth/tests/transport/test__custom_tls_signer.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/google-auth/tests/transport/test__custom_tls_signer.py b/packages/google-auth/tests/transport/test__custom_tls_signer.py index 053e1d5bc859..f2c2d3452a3e 100644 --- a/packages/google-auth/tests/transport/test__custom_tls_signer.py +++ b/packages/google-auth/tests/transport/test__custom_tls_signer.py @@ -146,9 +146,7 @@ def test_get_sign_callback_oversized_signature(): mock_sig_array = ctypes.cast(returned_sig_array, ctypes.POINTER(ctypes.c_ubyte)) returned_sign_len = ctypes.c_ulong() - assert not sign_callback( - mock_sig_array, ctypes.byref(returned_sign_len), None, 0 - ) + assert not sign_callback(mock_sig_array, ctypes.byref(returned_sign_len), None, 0) def test_get_sign_callback_failed_to_sign():