TLS 1.3 & PKI — handshake, trust, pinning

networking · memo

In one line: TLS gives confidentiality (AEAD), integrity and server authentication: an ephemeral ECDHE exchange makes the keys (forward secrecy), and a certificate chain ending in a root the device already trusts proves the key belongs to the hostname. TLS 1.3 (RFC 8446) does it in 1 RTT; resumption can send data in 0 RTT — replayable.

Download PDF Print view LaTeX source

TLS 1.3 & PKI — handshake, trust, pinning — figure 1

How it works

  • Key exchange: each side sends an ephemeral ECDHE public key; the shared secret feeds HKDF → handshake, then traffic keys. The certificate key only signs (CertificateVerify) — it never encrypts a secret.
  • Forward secrecy: ephemeral keys are discarded, so a server key stolen later cannot decrypt recorded sessions. 1.2 static-RSA key transport had no FS.
  • 1.3 suites name only AEAD + hash: TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256. Group (x25519, P-256) and signature (ECDSA, RSA-PSS, Ed25519) are negotiated separately; a 1.2 suite bundled all four (ECDHE-RSA-AES128-GCM-SHA256).
  • Wrong key_share guess → HelloRetryRequest → 2 RTT. Resumption: ticket → PSK; PSK + fresh ECDHE keeps FS, 0-RTT data does not.

TLS 1.2 (RFC 5246) vs 1.3

1.21.3
handshake2 RTT (Hello → KeyExchange + Finished)1 RTT; 0-RTT on resumption
key exchangeRSA transport or (EC)DHE(EC)DHE only: FS mandatory
certificatesent in cleartextencrypted
removed—RSA kx, CBC, RC4, SHA-1, compression, renegotiation

Validation — what SecTrust checks

  1. Path leaf → intermediates → a root in the trust store; signature at every link; intermediates CA:TRUE.
  2. Validity: notBefore ≤ now ≤ notAfter — the device clock.
  3. Hostname vs SAN (iOS 13+: CN ignored; EKU serverAuth, RSA ≥ 2048, SHA-2).
  4. Revocation: CRL (list) · OCSP (live query, leaks who you visit) · stapling (server attaches a signed OCSP answer). Clients soft-fail: blocked responder = pass. Let’s Encrypt ended OCSP in 2025 (CRLs).
  5. CT (RFC 6962): public append-only logs; Apple requires SCTs. Then your policy: ATS, pins — after default validation, never instead.

Example — mTLS: answer the client-certificate challenge

func urlSession(_ s: URLSession, didReceive c: URLAuthenticationChallenge)
    async -> (URLSession.AuthChallengeDisposition, URLCredential?) {
  switch c.protectionSpace.authenticationMethod {
  case NSURLAuthenticationMethodClientCertificate:
    let id = keychainIdentity()  // SecIdentity = cert + private key
    return (.useCredential, URLCredential(identity: id,
            certificates: nil, persistence: .forSession))
  default:                       // server trust: keep default checks
    return (.performDefaultHandling, nil)
  }
}

Pinning — what to pin

PinSurvivesRisk
leaf certnothing: every renewalbricks old binaries every 90 days
leaf SPKIrenewal with same keykey compromise → backup pin
interm. SPKIany leaf re-key by that CACA rotates its intermediate
root SPKIalmost everythingweak: any leaf of that CA passes

Always ≥ 2 pins: current + an offline backup key. No code: NSPinnedDomains → NSPinnedLeafIdentities / NSPinnedCAIdentities (SPKI-SHA256-BASE64). Have a kill path (pins in remote config) or accept a forced update.

mTLS and ATS

mTLS: server sends CertificateRequest; client answers Certificate + CertificateVerify — proves possession of a key, no bearer secret on the wire. Cost: issuing + rotating a per-device cert (SecPKCS12Import of a .p12). ATS: HTTPS, TLS ≥ 1.2, FS suites, valid chain; narrow NSExceptionDomains; NSAllowsArbitraryLoads needs a review reason.

Failures you actually meet

missing intermediatebrowser OK, others fail: serve the full chain
expired root / cross-signDST Root CA X3 (30 Sep 2021) broke old devices
device clock wrong-1201 bad date · -1204 not yet valid
name not in SAN-1202 server certificate untrusted
private CA / self-signed-1203 unknown root: trust the root, not the leaf
http:// under ATS-1022 (ATS requires a secure connection)
proxy / captive portalMITM certificate: pinning fails, as designed

Interview traps

  • “Disable validation for testing” ships a MITM: encryption to the attacker. Use a test CA.
  • 0-RTT is not free speed: early data is replayable — idempotent GETs only.
  • A DV cert proves domain control at issuance, not honesty.
  • SNI leaks the hostname in cleartext; ECH closes it.
  • SecKeyCopyExternalRepresentation gives the raw key, not SPKI — prepend the ASN.1 header before hashing or the pin never matches.

Remember

“Ephemeral keys encrypt, certificates sign, the chain proves the name.”

Likely questions

  1. Why 1 RTT? — key_share guessed in ClientHello.
  2. Forward secrecy? — ephemeral ECDHE; old traffic stays safe.
  3. Safari OK, app fails? — server omits the intermediate.
  4. Pin what? — SPKI (leaf or CA) + offline backup pin.