How-to guide · Trust a CA

How to work behind a TLS-inspecting corporate proxy, safely

In one sentence

You will have confirmed that your traffic is inspected, obtained your company's inspection root through a trusted channel, and made each tool trust it with verification still on.

Task
Trust a CA
Platforms
  • Windows
  • macOS
  • Linux
  • Java
  • Python
  • Node.js
Who it is for
Level
Practitioner · 4 steps
On this page
  1. Before you start
  2. Steps
  3. 1. Confirm that your traffic is inspected
  4. 2. Get the root through a trusted channel
  5. 3. Check the file
  6. 4. Make each tool trust it
  7. Check it worked
  8. If something goes wrong
  9. Why this works
  10. Go deeper

Before you start

  • The exact error from the tool that fails, for example curl, git, pip, npm, Java or Docker.
  • A way to reach your IT or security team, who own the inspection root.
  • Administrator rights only if you change a system-wide trust store.

Many companies inspect web traffic with TLS inspection. The proxy ends your TLS connection, checks the real server’s certificate itself, decrypts and inspects the traffic, then encrypts it again towards you with a certificate it creates on the spot for that site. Those certificates are signed by a company root that IT installs on managed laptops. Browsers use the roots IT adds to the operating system’s trust store, so they work. Tools with their own store do not know that root, so they fail, often with self-signed certificate in certificate chain.

Be clear about what this means. The proxy can read everything you send, including passwords and tokens; that is its purpose, and it is legitimate only when your organisation runs it under a published policy. Any root in your trust store can vouch for any website, so trust only the inspection root your organisation controls.

Steps

1. Confirm that your traffic is inspected

Ask who issued the certificate for a well-known public site:

openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -issuer

Without inspection, the issuer is a public CA. Behind an inspecting proxy, it names your company or its security product. Here is what that looks like, reproduced in our lab with a stand-in root:

subject=CN = example.com
issuer=C = BE, O = Example Corp, CN = Example Corp TLS Inspection Root

The full s_client output ends with Verify return code: 19 (self-signed certificate in certificate chain), because the proxy sends its root and OpenSSL does not trust it. A name alone proves nothing: confirm with IT that this is their proxy.

2. Get the root through a trusted channel

Best: from IT. Ask for the inspection root as a PEM file and for its SHA-256 fingerprint, published somewhere you trust, such as the intranet or a ticket.

If IT already installed it on your Windows or macOS laptop (your browser works), export it from the operating system’s store. On Windows, in PowerShell:

Get-ChildItem Cert:\LocalMachine\Root | Where-Object Subject -like '*Example Corp*' |
  Export-Certificate -FilePath .\corp-root.cer -Type CERT
certutil -encode corp-root.cer corp-root.pem

Export-Certificate -Type CERT writes DER, and certutil -encode turns it into PEM. On macOS, security find-certificate -c "Example Corp TLS Inspection Root" -p /Library/Keychains/System.keychain > corp-root.pem prints it as PEM. Browsers can also export it: open the certificate viewer for any site, select the top certificate of the chain, and export it.

Do not take the root from the inspected connection alone. A root you received over the very connection you are trying to verify proves nothing until its fingerprint matches what IT publishes.

3. Check the file

openssl x509 -in corp-root.pem -noout -subject -issuer -fingerprint -sha256 -ext basicConstraints
subject=C=BE, O=Example Corp, CN=Example Corp TLS Inspection Root
issuer=C=BE, O=Example Corp, CN=Example Corp TLS Inspection Root
sha256 Fingerprint=38:54:01:A3:09:92:DC:C8:5D:C5:F5:8D:26:C7:59:D9:85:0D:7A:33:B1:6E:5A:5C:7F:79:19:82:B1:EC:9F:38
X509v3 Basic Constraints: critical
    CA:TRUE

Subject equals issuer, CA:TRUE, and the fingerprint matches IT’s. The file is a certificate, not a secret; if it contains PRIVATE KEY, stop and tell IT.

4. Make each tool trust it

Some settings add a root to the tool’s defaults; others replace the whole list. A replacement must contain the public roots as well, or public sites will fail off the corporate network and on sites the proxy does not inspect. On Linux, after you add the root to the system store, /etc/ssl/certs/ca-certificates.crt (Debian and Ubuntu) is such a combined bundle.

Tool Safe setting Adds or replaces Details
Operating system (Windows, macOS, Linux, WSL, containers) The system trust store Adds Windows, macOS, Linux
curl System store on Linux; curl.exe on Windows uses the Windows store; or --cacert / CURL_CA_BUNDLE --cacert replaces curl docs
git System store on Linux; git config --global http.sslBackend schannel on Windows; or http.sslCAInfo http.sslCAInfo replaces git-config
Python Requests: REQUESTS_CA_BUNDLE; the ssl module: SSL_CERT_FILE; both pointing to a combined bundle. Recent pip versions also read the operating system’s store REQUESTS_CA_BUNDLE replaces; SSL_CERT_FILE replaces the default file Python and Node.js guide
Node.js, npm NODE_EXTRA_CA_CERTS=/path/corp-root.pem, or --use-system-ca in recent Node.js Adds Python and Node.js guide
Java Import into cacerts or the application’s trust store Adds Java guide

Build a combined bundle yourself when you need one:

cat /etc/ssl/certs/ca-certificates.crt corp-root.pem > ~/ca-bundle-plus-corp.pem
export CURL_CA_BUNDLE=~/ca-bundle-plus-corp.pem

WSL distributions and container images have their own Linux stores: the root IT installed in Windows is not in them. Add it there too, and bake it into images at build time.

Check it worked

Run the strict test against a public site with the root as the only trust anchor:

openssl s_client -connect example.com:443 -servername example.com -CAfile corp-root.pem -verify_return_error </dev/null 2>&1 | grep 'Verify return code'

Expect Verify return code: 0 (ok) on the corporate network. Then run the command that failed. In our lab, the same requests before and after:

# curl, before and after CURL_CA_BUNDLE pointed to a combined bundle
# (the second line is printed by -w '%{http_code} verify=%{ssl_verify_result}')
curl: (60) SSL certificate problem: self-signed certificate in certificate chain
200 verify=0

# Node.js, before and after NODE_EXTRA_CA_CERTS=corp-root.pem
SELF_SIGNED_CERT_IN_CHAIN: self-signed certificate in certificate chain
OK, authorized: true

# Python ssl, before and after loading corp-root.pem
[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate in certificate chain (_ssl.c:1000)
with corp root: TLSv1.3

The chain checker names known inspection products and shows where a chain ends:

Try it: Chain checkerOpen the full tool

Runs in your browser. Nothing you paste is sent anywhere.

Every certificate the server sends, leaf first. The output of openssl s_client -showcerts -connect example.com:443 works as it is: we ignore the text around the certificates.

If something goes wrong

Why this works

A client accepts a chain when it can link it to a root already in its own trust store. Behind the proxy, every chain ends at the company root, so adding exactly that root to the store each tool uses makes validation succeed while the client still checks every signature, date and host name. Who do we trust? explains trust stores and TLS inspection, Certificate chains explains path validation, and The TLS 1.3 handshake explains where the certificate check sits in a connection.

Go deeper

Early edition. Checked against the standards; an independent expert review is in progress. Found a mistake? Tell us.

Last reviewed Edition 0.9