Python Certificate Pinning — Core Concepts

Why Standard TLS Isn’t Always Enough

When Python connects to an HTTPS server, it verifies the server’s certificate against a bundle of trusted Certificate Authorities (CAs). This bundle — typically Mozilla’s CA list via the certifi package — contains about 130 root CAs. Each of those can issue certificates for any domain.

The problem: if any of those 130+ CAs is compromised, coerced, or negligent, an attacker can obtain a valid certificate for your target domain. This has happened: DigiNotar (2011), Symantec (2017), and multiple incidents involving government-operated CAs.

Certificate pinning adds a second layer: your application knows exactly which certificate or public key the server should present, and rejects everything else — even certificates signed by trusted CAs.

Three Pinning Strategies

1. Leaf Certificate Pinning

Pin the exact certificate the server uses. Maximum security, but breaks whenever the server rotates its certificate (typically every 90 days with Let’s Encrypt, annually with commercial CAs).

2. Public Key Pinning

Pin the server’s public key. Since organizations often reuse the same key pair across certificate renewals, this survives routine rotation. It breaks only when the server generates a new key pair.

3. CA/Intermediate Pinning

Pin the intermediate or root CA that issues the server’s certificate. Less strict, but survives certificate and key rotation as long as the organization uses the same CA. Useful for pinning internal PKI.

Implementation with Python Requests

The requests library doesn’t have built-in pin verification, but you can extract and verify the certificate fingerprint using a custom transport adapter or by combining requests with lower-level ssl checks.

A more practical approach: restrict the CA bundle to only the certificates needed for your specific service, rather than trusting the full CA bundle.

For internal services where you control the PKI, point verify to your organization’s CA certificate file instead of the system bundle. This achieves CA-level pinning without custom code.

Implementation with ssl Module

Python’s ssl module offers fine-grained control. You can create an SSLContext with a restricted set of trusted certificates, disable loading system defaults, and enforce specific protocol versions and cipher suites.

The key steps: create a context, load only your pinned CA certificate, set check_hostname=True and verify_mode=ssl.CERT_REQUIRED. Any connection to the server that presents a certificate not chained to your pinned CA will fail with a verification error.

Extracting a Pin

To pin a server’s certificate, you first need to capture it:

Use OpenSSL’s s_client to connect and retrieve the certificate chain, then compute the SHA-256 fingerprint of the leaf certificate or its public key. Store this fingerprint in your application’s configuration.

For public key pinning, extract the Subject Public Key Info (SPKI) and hash it. This is the same format Chrome and Firefox use for HTTP Public Key Pinning (HPKP, now deprecated in browsers but still useful in applications).

Handling Certificate Rotation

The biggest operational challenge with pinning is certificate renewal. Strategies to avoid outages:

Backup pins — Pin both the current certificate/key and the next one. When rotation happens, the backup becomes primary and you add a new backup.

Pin the intermediate CA — Less strict but more resilient. Certificates from the same CA chain validate correctly without code changes.

Monitoring — Check pinned certificates’ expiration dates and alert weeks before they expire. Automate pin updates as part of your deployment pipeline.

Graceful degradation — In non-critical applications, log a warning on pin mismatch instead of hard-failing, giving operators time to update pins before connections break.

Common Misconception

“Pinning means I don’t need to validate the certificate chain.” Pinning is additional verification, not a replacement. You should still validate the full certificate chain, check revocation status, and verify the hostname. Pinning adds assurance that the specific certificate is the one you expect — it doesn’t replace the checks that standard TLS already performs.

When Not to Pin

Pinning makes sense for apps that communicate with a small number of known servers (your own API, a payment processor, a specific SaaS provider). It does not make sense for general-purpose HTTP clients, web browsers, or crawlers that connect to thousands of different servers — the operational burden of maintaining pins becomes unmanageable.

The one thing to remember: certificate pinning narrows your trust from “any of 130+ CAs” to “this specific identity” — a powerful defense against sophisticated impersonation attacks, with a real maintenance cost you must plan for.

pythonsecuritynetworking

See Also