Repository navigation
fix(windows): read the Netskope chain from the certificate store and keep the intermediate - #117
Merged
aberoham merged 3 commits intoOct 26, 2026
Conversation
…keep the intermediate The hardened Netskope client leaves only nscacert.pem.enc on disk, so fumitm_windows.py told the user to export the root by hand. A hand-exported root alone then failed the Google Cloud SDK installer with: CA cert does not include key usage extension The Netskope root carries no Key Usage extension, and Python 3.13+ turns on VERIFY_X509_STRICT by default, which rejects any chain that reaches it. With the tenant intermediate (which has a Key Usage extension) in the bundle, VERIFY_X509_PARTIAL_CHAIN stops chain building there and the root is never examined. A root-only bundle reproduces the failure on 3.13 and 3.14 and passes on 3.12; root plus intermediate passes on all three. - Fall back to Cert:\LocalMachine\Root (CN certadmin) and Cert:\LocalMachine\CA (CN ca.<tenant>.goskope.com) when the PEM files are absent or encrypted, mirroring the macOS keychain fallback, and always add the intermediate from the store when a source yields the root alone. - Replace the install-mode fingerprint check, which unlinked an open temporary file (refused on Windows) and so appended another copy of the root on every run, with pure-Python body matching. Append only the blocks a bundle lacks. - Never seed a tool bundle from a file that holds only the proxy certificate, and rebuild our own bundle from the Windows store when it does; warn instead of silently creating an empty bundle when the system export returns nothing.
Address the Codex and agy reviews of this pull request. - Assemble every rebuilt bundle in a temporary file and move it into place with os.replace. Write nothing when the Windows root export is empty, and set environment variables only after a write succeeds. - Top up our own bundle in place when it lacks a provider certificate, such as a newly added intermediate, instead of rebuilding it from the store and risking its system roots. - Treat an unreadable bundle, or a certificate file with no certificate, as unknown rather than "provider-only" or "already present". - Parse only complete BEGIN/END pairs, and append each missing certificate once even when the source repeats it. - Query the certificate store with exact common names, only currently valid certificates, and errors that stop the script. Return every intermediate issued by the store's root, and always ask the store rather than inferring the intermediate from a certificate count. - Export system roots from both the LocalMachine and CurrentUser Root stores. - Install each certificate of the chain separately into the Windows stores and the Java keystore (alias, alias-2), since X509Certificate2 and keytool read only the first certificate in a file. - Report provider-only bundles in status for Python, npm, gcloud and Git, and replace them during --fix. NODE_EXTRA_CA_CERTS is exempt because it adds to Node's built-in roots. - Detect the stAgentSvc.exe service and encrypted .enc certificate files in the Netskope connection check, and remove the status temporary file even when a check fails. - Run the Windows tests on macOS and Linux with a winreg stub, move the __main__ block to the end, and add tests for each case above.
Address the agy follow-up review. - check_java_status requires an alias for each certificate of the chain (alias, alias-2), as setup_java_cert imports them. - setup_git_cert no longer creates the directory of a missing http.sslCAInfo file before the system export is known to be non-empty, and falls back to the managed bundle when the build fails. - Remove a duplicated docstring line and show the per-certificate file in the Java dry-run message.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A colleague on Windows with the hardened Netskope client (only
nscacert.pem.encon disk) was told byfumitm_windows.pyto export the root by hand. After doing so and running--fix, the Google Cloud SDK installer failed with:Three things were wrong, confirmed on the machine:
ca.<tenant>.goskope.com) has one. Python 3.13+ enablesVERIFY_X509_STRICTby default, so any chain that reaches the root is rejected. With the intermediate in the bundle,VERIFY_X509_PARTIAL_CHAINstops chain building there and the root is never examined. Reproduced againstdl.google.com: a root-only bundle fails on 3.13 and 3.14 and passes on 3.12; root plus intermediate passes on all three.NamedTemporaryFilewhile it was still open, which Windows refuses, so every--fixappended another copy of the root. The user'spython\ca-bundle.pemheld three identical roots.REQUESTS_CA_BUNDLEfile holding only the root, so nothing outside Netskope's interception was trusted either.Fix
Cert:\LocalMachine\Root(CNcertadmin) andCert:\LocalMachine\CA(CNca.<tenant>.goskope.com) when the PEM files are absent or encrypted, mirroring the macOS keychain fallback, and always add the intermediate from the store when a source yields the root alone. The exact PowerShell was verified on the affected machine through its own Python.fumitm.pyalready does) and append only the blocks a bundle lacks.Tests
13 new cases in
test_suite/test_fumitm_windows.pycovering store export parsing, the encrypted-file fallback, root-only sources gaining the intermediate, every-block existence checks, idempotent appends, and provider-only bundle repair. All 25 cases in the file pass (run on macOS withwinregstubbed; CI runs them onwindows-latest).ruffand the code-quality suite pass.Not in this PR
setup_gcloud_certon Windows assumes gcloud reads the Windows certificate store. It does not; it falls back toREQUESTS_CA_BUNDLEwhencore/custom_ca_certs_fileis unset, which is why the Python bundle fix is enough here. That assumption deserves its own change.