QCecuring - Enterprise Security Solutions

What is Certificate Pinning

Mounith Reddy

Key Takeaways

  • Certificate pinning hardcodes which certificate (or public key) a client will accept — rejecting all others, even if CA-signed
  • Browsers deprecated HTTP Public Key Pinning (HPKP) because misconfiguration permanently locked users out of websites
  • Mobile apps still use pinning extensively to prevent traffic interception on compromised devices
  • Certificate Transparency (CT) has largely replaced pinning as the defense against rogue CA issuance

Certificate pinning is a security mechanism where a client is configured to accept only specific certificates (or public keys) for a given domain, rather than trusting any certificate signed by any CA in its trust store. Standard TLS trusts hundreds of root CAs — if any one of them is compromised or coerced, it can issue a valid certificate for any domain. Pinning narrows this trust to exactly the certificates you expect, rejecting everything else regardless of its CA signature.


Why it matters

  • Defense against CA compromise — the public CA ecosystem includes 100+ root CAs trusted by browsers. If any single CA is compromised (DigiNotar 2011, Symantec issues 2015-2017), it can issue fraudulent certificates that pass standard validation. Pinning rejects these because they don’t match the pinned value.
  • Prevents government interception — state actors have obtained fraudulent certificates from CAs under their jurisdiction to perform surveillance. Pinning prevents these certificates from being accepted by pinned clients.
  • Mobile app security — on rooted/jailbroken devices, attackers can install custom root CAs to intercept app traffic. Pinning ensures the app only trusts its expected server certificate, even if the device’s trust store is compromised.
  • API client hardening — service-to-service communication can pin the expected server certificate or CA, preventing MITM even if the network is compromised and a rogue CA certificate is injected.

How it works

  1. Determine what to pin — choose the certificate’s public key (SPKI hash), the intermediate CA’s public key, or the leaf certificate itself
  2. Embed pin in client — hardcode the pin in the application (mobile app binary, API client config, or HTTP header)
  3. Client connects — performs standard TLS handshake and receives the server’s certificate chain
  4. Pin validation — after standard chain validation passes, the client checks if any certificate in the chain matches a pinned value
  5. Match = proceed — if a pin matches, the connection continues normally
  6. No match = abort — if no pin matches, the connection is terminated even though the certificate is technically valid

What to pin (in order of preference):

  • Intermediate CA public key — survives leaf certificate renewal. Breaks only if you switch CAs entirely.
  • Root CA public key — most resilient to rotation, but trusts everything that CA issues.
  • Leaf certificate public key — most restrictive, but breaks on every certificate renewal unless you pin the next certificate in advance.

In real systems

Mobile app pinning (Android — Network Security Config):

<!-- res/xml/network_security_config.xml -->
<network-security-config>
    <domain-config>
        <domain includeSubdomains="true">api.example.com</domain>
        <pin-set expiration="2026-06-01">
            <!-- Pin the intermediate CA's SPKI hash -->
            <pin digest="SHA-256">YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg=</pin>
            <!-- Backup pin (different CA) for rotation -->
            <pin digest="SHA-256">sRHdihwgkaib1P1gxX8HFszlM+4hMvCalm+9a+bI2lg=</pin>
        </pin-set>
    </domain-config>
</network-security-config>

iOS App Transport Security (ATS):

// Pinning via URLSession delegate
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge,
                completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
    guard let serverTrust = challenge.protectionSpace.serverTrust,
          let certificate = SecTrustGetCertificateAtIndex(serverTrust, 0) else {
        completionHandler(.cancelAuthenticationChallenge, nil)
        return
    }
    let serverKey = SecCertificateCopyKey(certificate)
    // Compare hash of serverKey against pinned hash
}

OkHttp (Android/Java):

val client = OkHttpClient.Builder()
    .certificatePinner(CertificatePinner.Builder()
        .add("api.example.com", "sha256/YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg=")
        .add("api.example.com", "sha256/sRHdihwgkaib1P1gxX8HFszlM+4hMvCalm+9a+bI2lg=")
        .build())
    .build()

Generating pin hash:

# Extract SPKI hash from a certificate
openssl x509 -in cert.pem -pubkey -noout | \
  openssl pkey -pubin -outform DER | \
  openssl dgst -sha256 -binary | base64

Where it breaks

Certificate rotation without pin update — you pin the leaf certificate’s public key. The certificate is renewed (new key pair generated), and the pin no longer matches. Every client with the old pin hardcoded rejects the new certificate. For mobile apps, this means a broken app until users update — and you can’t force updates. Always pin the intermediate CA or include a backup pin for the next expected certificate.

HPKP self-denial-of-service — HTTP Public Key Pinning (now deprecated) let websites send pinning instructions via HTTP headers. If you set a pin, then lost the private key or switched CAs, browsers would refuse to connect for the entire max-age duration (up to months). There was no recovery mechanism. Several sites accidentally locked themselves out. This is why Chrome removed HPKP support in 2018.

Pinning blocks legitimate security response — your CA is compromised and you need to switch to a different CA immediately. But your mobile app pins the old CA’s public key. You can’t switch CAs without breaking all existing app installations. The security mechanism designed to protect against CA compromise now prevents you from responding to one. Always include a backup pin from a different CA.


Operational insight

Certificate pinning has largely been replaced by Certificate Transparency (CT) for web browsers. CT provides a public, auditable log of all issued certificates — if a rogue CA issues a certificate for your domain, it appears in CT logs and can be detected. This achieves the same goal (detecting unauthorized certificates) without the operational risk of pinning (locking users out). Pinning remains valuable in mobile apps and API clients where you control the client and can update pins, but for websites, CT + CAA DNS records provide equivalent protection without the self-DoS risk.


Ready to Secure Your Enterprise?

Experience how our cryptographic solutions simplify, centralize, and automate identity management for your entire organization.

Stay ahead on cryptography & PKI

Get monthly insights on certificate management, post-quantum readiness, and enterprise security. No spam.

We respect your privacy. Unsubscribe anytime.