Skip to content

fix(auth): use an unencrypted key for mTLS certificate rotation - #18709

Open
Thanatos9404 wants to merge 1 commit into
googleapis:mainfrom
Thanatos9404:fix-auth-cert-callback-unencrypted-key
Open

Thanatos9404 wants to merge 1 commit into
googleapis:mainfrom
Thanatos9404:fix-auth-cert-callback-unencrypted-key

Conversation

@Thanatos9404

Copy link
Copy Markdown

call_client_cert_callback() in google/auth/transport/_mtls_helper.py called get_client_ssl_credentials(generate_encrypted_key=True) and then dropped the passphrase. With SecureConnect (context_aware_metadata.json) that adds --with_passphrase to the cert provider command, so the key it returns is encrypted.

Its only caller is check_parameters_for_unauthorized_response(), which the 401 certificate rotation in AuthorizedSession.request() and AuthorizedHttp.urlopen() uses. Both pass the key to configure_mtls_channel() without a passphrase, so load_cert_chain() gets an encrypted key with password=None.

This passes generate_encrypted_key=False, as get_client_cert_and_key() already does for the initial mTLS setup, and updates test_call_client_cert_callback to expect it. A new test drives the SecureConnect path through a fake cert provider that returns an encrypted key only for --with_passphrase, and checks that the returned key loads without a passphrase. Workload certificates from certificate_config.json are returned before the flag is read, so they are not affected, and the async rotation path does not use this function.

The change does not touch requests.py or urllib3.py, so it is independent of #18381. The rotation paths added there also go through check_parameters_for_unauthorized_response() and get the unencrypted key too.

Testing (Python 3.13 on Windows, Python 3.12 on Linux):

  • The updated test_call_client_cert_callback and the new test_call_client_cert_callback_secure_connect_unencrypted_key fail against _mtls_helper.py from main (the first with Expected: get_client_ssl_credentials(generate_encrypted_key=False), Actual: ...(generate_encrypted_key=True); the second gets the encrypted key back) and pass with the change.

  • pytest tests/transport/test__mtls_helper.py tests/transport/test_requests.py tests/transport/test_urllib3.py tests/transport/test_mtls.py: 371 passed.

  • pytest tests tests_async on Linux: 2263 passed, 7 skipped.

  • ruff check --select I and ruff format --check (ruff 0.14.14 with the flags from the lint session) and flake8 on the two changed files: no findings.

  • On Linux, passing an encrypted key with no passphrase to _MutualTlsAdapter with no terminal attached printed Enter PEM pass phrase: and raised MutualTLSChannelError (caused by OSError(22, 'Invalid argument')); with a terminal it waits for input. The same key unencrypted loads fine.

  • Make sure to open an issue as a bug/issue before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea

  • Ensure the tests and linter pass

  • Code coverage does not decrease (if any source code was changed)

  • Appropriate docs were updated (if necessary)

Fixes #18467 🦕.

call_client_cert_callback() asked get_client_ssl_credentials() for an
encrypted key (generate_encrypted_key=True) and then dropped the
passphrase. With SecureConnect this adds --with_passphrase to the cert
provider command, so the returned key is encrypted.

The 401 certificate rotation path in AuthorizedSession.request() and
AuthorizedHttp.urlopen() passes that key to configure_mtls_channel()
without a passphrase, so load_cert_chain() receives an encrypted key
with password=None. OpenSSL then prompts for a pass phrase on the
terminal, blocking if one is attached, or fails, which surfaces as
MutualTLSChannelError.

Request an unencrypted key instead, as get_client_cert_and_key()
already does for the initial mTLS setup.

Fixes googleapis#18467
@Thanatos9404
Thanatos9404 requested review from a team as code owners October 15, 2026 21:53

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request modifies call_client_cert_callback in _mtls_helper.py to request an unencrypted key by setting generate_encrypted_key=False when calling get_client_ssl_credentials, as callers load the key without a passphrase. It also updates existing tests and adds a new test test_call_client_cert_callback_secure_connect_unencrypted_key to verify this behavior. No review comments were provided, and I have no additional feedback.

@nbayati
nbayati self-requested a review October 15, 2026 22:16

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

auth: call_client_cert_callback() discards passphrase for encrypted keys

1 participant