Skip to main content
RunBook Academy

Secrets, PKI & CertificatesXVIII · Incidents and RecoveryIncidentResponse

Private key compromise: TLS server keys

Advanced⏱ ~22 minopensslcurl

What you'll learn

  • Explain why renewing over the same public key leaves the attacker capability untouched
  • Prove that a deployed certificate carries a genuinely new key using public key digests
  • Assess honestly what revocation buys you against each class of client
  • Sequence the replacement so the service does not lose availability before it gains security

Prerequisites

Practice

Verified against OpenSSL 3.5.x teaching target; 3.0+ minimum · OpenSSH 10.x teaching target; 8.2+ minimum for certificate workflows · OpenBao 2.6.x · Smallstep step-ca 0.30.x · Certbot / Pebble Certbot current release; Pebble 2.10.x ACME test server · Kubernetes (cross-course target) 1.36.x · PostgreSQL 17.x · 2026-08-26

Not yet marked complete on this device.

A TLS server private key is compromised from the moment a copy of it leaves the host, not from the moment somebody uses it. What the holder of that key gains is the ability to complete a handshake as your service, to any client that can be steered onto their endpoint. The response has one goal: end that ability, quickly, without knocking your own service over on the way.

Renewal over the same key changes nothing

A certificate is a signed statement binding a name to a public key. When a certificate authority renews, it signs a fresh statement over the same SubjectPublicKeyInfo if that is what the request carries. The new certificate has new dates and a new serial, and it names exactly the same key.

That matters because of how the server proves it is entitled to the certificate. In TLS 1.3 the server does not decrypt anything with the certificate key; it signs a hash of the handshake transcript in the CertificateVerify message, and the client checks that signature against the public key in the certificate. Whoever holds the private key can produce that signature. Renewal over the same key hands the attacker a certificate with a later notAfter and tells them the incident is closed.

The break is made by generating a new key pair. Nothing else does it, and no amount of freshness on the certificate substitutes for it.

umask 077
KEYDIR=/etc/ssl/private
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 \
  -out "$KEYDIR/app-2026-08-26.key"
openssl req -new -key "$KEYDIR/app-2026-08-26.key" -sha256 \
  -subj "/CN=app.lab.example" \
  -addext "subjectAltName=DNS:app.lab.example" \
  -out /tmp/app-2026-08-26.csr

The umask 077 is not decoration. A replacement key created with the default mask and then copied by a deployment tool is the same incident again, one week later.

Proving the deployed certificate carries the new key

The check that a certificate and a key correspond is a digest comparison of the public key extracted from each. It is cheap, it is unambiguous, and it is the only verification that distinguishes a real key rotation from a renewal that quietly reused the old request.

$ openssl pkey -in app.key -pubout | openssl sha256
SHA2-256(stdin)= 75061de3387b8969c6c33ba0931ec0e541ad4c90a4ee2ec3e734e031af66874b
$ openssl x509 -in app.crt -noout -pubkey | openssl sha256
SHA2-256(stdin)= 75061de3387b8969c6c33ba0931ec0e541ad4c90a4ee2ec3e734e031af66874b

Matching digests prove the certificate on disk belongs to the key on disk. During a compromise response you need a second assertion on top of that: the digest must differ from the one recorded before the incident. Record the pre-incident digest as part of the evidence capture, because after the replacement it is the cleanest single line of proof that a new key pair reached production. Take the digest from the certificate the service is actually serving, not from the file you think it loaded.

What revocation actually buys you

Revoke, because it is correct and because some relying parties do check. Then plan as though nobody checks, because for the public web that is close to the truth.

Let’s Encrypt switched its OCSP responder off on 2025-08-06, having removed the OCSP URL from the Authority Information Access extension of its certificates on 2025-05-07, so an OCSP query about a certificate from that issuer has nowhere to go. Chrome does not perform online revocation checks by default and disabled revocation checking for extended validation certificates in 2022. Firefox from version 142 onwards runs with its CRLite mode set to 2 and performs no OCSP fetch for domain-validated certificates chaining to the Mozilla root store, and never for certificates with a lifetime under ten days. Must-staple is not the escape hatch either: Let’s Encrypt stopped issuing with it on 2025-05-07, the Baseline Requirements never mention it, and Firefox is the only mainstream client that enforces it.

flowchart LR
    K["Key copied off host"] --> A["Attacker can complete\nCertificateVerify"]
    A --> N["Needs a network position\nto be reached"]
    A --> P["Cannot decrypt recorded\npast TLS 1.3 sessions"]
    A --> S["Stops only when no valid\ncertificate names that key"]

The diagram is the honest threat model. Possession of the key gives an attacker the ability to authenticate as your name, which they can only use if they can also get a client to talk to them, through hijacked DNS, a compromised network path or a misdirected client configuration. It does not give them your past traffic, because TLS 1.3 derives session keys from an ephemeral key exchange and the certificate key only signs. And their capability ends when no currently valid certificate names that public key, which is why replacement speed and short certificate lifetimes are the real controls rather than revocation.

That is also why the certificate lifetime ceiling matters operationally rather than bureaucratically. The Baseline Requirements cap public TLS certificate validity at 200 days as of 2026-03-15, dropping to 100 days from 2027-03-15 and 47 days from 2029-03-15, and define a short-lived certificate as one valid for no more than seven days. Every step down that ladder shortens the window in which a stolen key remains usable, without any client having to check anything.

Inside a private PKI the calculus changes, but only if you do the work. Revocation there is enforced by something concrete: a CRL that your servers and clients are configured to fetch and check, for example through the OpenSSL verification path with CRL checking enabled, or a reverse proxy configured with a CRL file. If nothing in your estate is configured to fetch and honour that CRL, publishing one is documentation, not enforcement.

Sequencing the replacement

The order is replace, then revoke, and it is deliberate. If you revoke first in an environment where revocation genuinely is enforced, you take your own service down while the attacker carries on unaffected. If you revoke first where it is not enforced, you gain nothing and still have the old key deployed.

  1. Generate the new key pair on the host that will serve it, and issue over it.
  2. Deploy and reload, then confirm from an independent client that the served certificate carries the new public key digest.
  3. Revoke the old certificate at the issuing CA.
  4. Destroy every copy of the old key: hosts, configuration management, images, backups within their retention policy, and the support bundle somebody attached to a ticket.
  5. Look for evidence of impersonation while the key was live, using certificate transparency for a public name and your own request logs and client error reports for a private one.
  6. Shorten the lifetime and automate renewal, so the next incident of this class has a smaller window by default.

Production discipline

  1. Record the public key digest of every production certificate. Without a pre-incident value there is no clean way to prove the replacement was a real rotation rather than a renewal.
  2. Never let a renewal path accept an old CSR by default. A stored CSR is a stored public key, and an automation that reuses it turns key rotation into a manual exception.
  3. Verify from outside the host. Read the certificate the listener is actually serving, because a reload that silently failed leaves the old key in memory and the new file on disk.
  4. Treat lifetime reduction as part of the fix. The incident review should end with a shorter certificate, not only with a replaced one.

Cross-course references

  • Linux for Production Sysadmins - Part LXXI (TLS and PKI) covers the file-level handling of private keys, ownership and mode, which is where most copies of a key are created without anyone deciding to create them.
  • Kubernetes for Production Sysadmins - Part LXXVI (Cluster Certificates) covers a control plane whose components hold keys in several places at once, so a single replacement there is a multi-file operation.
  • Observability for Production Sysadmins - Part XI (Blackbox Monitoring) covers the synthetic probe that reads the served certificate from outside, which is the independent channel this lesson insists on for verification.

Quiz

Knowledge check · 4 questions

  1. Q1. A TLS server private key for a public site is found in a world-readable backup archive. Which response ends the attacker capability?

  2. Q2. Renewing a compromised certificate with the same private key restores the security of the service.

  3. Q3. State the single check that proves a deployed certificate carries a genuinely new key, and name what you must have captured beforehand for it to mean anything.

  4. Q4. Decide the order of operations and justify why revocation is not first.

    An internal service at api.internal.example.com is fronted by nginx and uses a certificate from the organisation private CA with 90 days of validity. At 11:20 UTC a departing engineer confirms they copied /etc/ssl/private to a personal laptop three weeks ago. Internal clients are configured with the private root and, unlike the public web, several of them are configured to fetch and check the CA CRL.

Passing score: 75%. Answers are checked in this browser.