Technical guide

Mobile certificate pinning rotation guide

Plan certificate pin rotation with overlapping keys, per-stack Android and iOS pin sets, stale-client tests and a recovery path that keeps TLS validation.

  • Identify the pin

    Record whether the app pins a leaf key or a certificate-authority key, and which network stacks enforce it. Rotation depends on both.

  • Overlap before switching

    Ship the replacement key as a backup pin before the server moves to it. Keep older installed clients in the decision.

  • Exercise recovery

    Rehearse failure and recovery without disabling certificate or hostname validation. A successful connection alone does not prove the intended trust policy.

Rotate a pinned certificate by shipping the replacement public key as a backup pin first, waiting until the installed base carries it, then switching the server and rehearsing recovery before the real change. Pin public keys rather than whole certificates, so a renewal that keeps the key needs no app release. Check every network stack in the app: Android’s Network Security Configuration and Apple’s NSPinnedDomains cover only the platform networking stacks. Android and iOS are configured and tested separately, and older installed versions, expiry and network failures belong in the test plan.

Identify the trust model

Start with the reason for pinning. Apple advises pinning only when the risk justifies it; pinning adds a restriction to normal TLS trust and never relaxes it. Record which service needs the extra restriction and who owns its certificate lifecycle.

Inventory each hostname, network stack and app release still in use. Both platforms pin hashes of a certificate’s public key (its SubjectPublicKeyInfo), so a new certificate on the same key is a non-event, while a new key is a change to plan.

RecordWhy it matters
Pin type and chain positionA leaf key and a CA key define different accepted identities
Hostnames and subdomain scopeThe policy must match the connections the app actually makes
Current and backup keysThe replacement must be accepted before it serves traffic
Network stacks per platformEach stack enforces only the pins it reads
Installed app versionsA new app configuration does not update existing installations
Server and client ownersBoth sides must agree the switch and recovery conditions

Keep the backup private key in the organisation’s key-management process. A public-key hash is configuration data; it is never a reason to give the private key to app developers. Use non-production certificates and authorised endpoints for the rehearsal.

Find every network stack

Platform pin configuration only protects the code that reads it.

  • Android. Network Security Configuration is enforced by the platform’s default trust manager. HttpsURLConnection follows it, and so do libraries that keep the default trust manager. OkHttp’s CertificatePinner is a separate pin set you maintain in code. Flutter’s dart:io client, native code using its own TLS library and any custom TrustManager need their own pins.
  • iOS. App Transport Security, including NSPinnedDomains, applies to URLSession and the APIs built on it. Apple states that ATS does not apply to lower-level networking interfaces such as the Network framework or CFNetwork. Flutter’s dart:io client and native TLS libraries also sit outside it.

For each stack, record where its pins live and how a new key reaches it. A rotation that updates the XML or Info.plist but forgets an in-code pin set breaks that path on switch day.

Otukan pins public keys, supports backup keys and an expiry policy, and declares a recovery path with the first pinned release. It confirms that pinning covers the networking path used by Flutter and React Native apps on each platform. For product scope, see mobile app certificate pinning.

Prepare, distribute and switch

Configure Android

  1. Locate the Network Security Configuration XML and the android:networkSecurityConfig attribute in the manifest. Confirm the code path under test uses it.
  2. Review the relevant domain-config and its pin-set. Android accepts a chain only when it contains at least one pinned key, in addition to normal certificate and hostname checks.
  3. List the current and backup key hashes as pin digest="SHA-256" entries. Google recommends always including a backup key.
  4. Inspect any expiration attribute on the pin-set. After that date Android stops enforcing the pins; expiry trades protection for availability and is not a backup key.
  5. Test the release configuration, not a debug build. debug-overrides can add trust anchors in debuggable builds only, so a debug connection proves nothing about the shipped policy.
  6. Update every in-code pin set, such as CertificatePinner, in the same release.

Configure iOS

  1. Inspect NSAppTransportSecurity → NSPinnedDomains in Info.plist for each relevant domain, including NSIncludesSubdomains where used.
  2. Identify whether the policy uses NSPinnedCAIdentities or NSPinnedLeafIdentities. Each entry is a dictionary holding an SPKI-SHA256-BASE64 value, and an array can hold several keys.
  3. Add the replacement key alongside the current one for the planned overlap. Review the choice of CA or leaf pinning with the service owner.
  4. Test the exact domains and subdomains the app uses. Do not transpose Android’s domain configuration into Apple’s structure.
  5. Update any pins enforced by code outside URLSession in the same release.

Run the operational change

Ship and test the overlapping client policy before touching the server. Set the release gate on evidence about installed versions, not on the assumption that everyone updates. Test the server’s replacement chain with the current release and with older retained builds.

Switch the test server, observe the agreed user journeys and then rehearse the recovery decision. Roll back to the previous server key only if it remains uncompromised and its certificates remain valid. A compromised key is never a recovery option merely because old clients recognise it.

Do not fetch a replacement pin policy over the same connection a pin failure blocks. Apple names an app update as the eventual recovery path and recommends designing the user experience for a failed connection. Treat any separate recovery channel as a security design with its own authentication and review.

Otukan works with your team to rehearse a key rotation before release.

Prove success and rejection

Record the app build, OS version, device, network stack, server chain, network conditions, expected result and reviewer for each run.

Test conditionExpected result
Valid current keySign-in and sensitive flows succeed
Valid replacement key in the backup setThe same flows succeed without bypassing trust checks
Otherwise valid but unpinned keyThe protected connection is rejected on every stack
Expired certificate or wrong hostnameOrdinary TLS validation still rejects the connection
Stale app without the replacement keyFailure and the documented recovery experience are exercised
Offline device or interrupted networkConnectivity loss is handled without labelling every outage an attack
Clean install and upgradeBoth paths keep the intended pin policy

Run the rejection cases on a legitimate supported device. Check what the user sees, whether sensitive operations stay restricted and whether diagnostics expose secrets. Compare the recovered state with the original trust policy, not just the return of network traffic.

  • Current and backup keys have been tested independently on each network stack.
  • Older installed releases have an explicit handling decision.
  • Sign-in, update and sensitive-flow regression tests are recorded.
  • Recovery keeps certificate and hostname validation in place.
  • The service owner and mobile reviewer approve the change record.

Broader threat context is covered on man-in-the-middle prevention. Other technical guides for Android and iOS teams are in the Otukan knowledge base.

Further reading

Common questions

  • Should I pin certificates or public keys?

    Public keys. Both platforms pin hashes of a certificate's SubjectPublicKeyInfo, so a renewal that keeps the same key needs no app release, while a new key is a change to plan. Otukan pins public keys, supports backup keys and an expiry policy, and declares a recovery path with the first pinned release.

  • Does NSPinnedDomains cover every network call in an iOS app?

    No. App Transport Security, including NSPinnedDomains, applies to URLSession and the APIs built on it. Apple states that ATS does not apply to lower-level interfaces such as the Network framework or CFNetwork, and Flutter's dart:io client and native TLS libraries also sit outside it, so they need their own pins.

  • Does Network Security Configuration cover OkHttp's CertificatePinner on Android?

    No. Network Security Configuration is enforced by the platform's default trust manager, which HttpsURLConnection and libraries keeping that trust manager follow. OkHttp's CertificatePinner is a separate pin set you maintain in code, and Flutter's dart:io client, native TLS code and any custom TrustManager need their own pins.

  • Is the Android pin-set expiration a backup mechanism?

    No. After the expiration date Android stops enforcing the pins, which trades protection for availability. A backup is a separate key: list the current and backup key hashes as pin digest="SHA-256" entries in the pin-set. Google recommends always including a backup key.

  • In what order should a pinned key rotation happen?

    Client first. Ship the replacement public key as a backup pin, wait until the installed base carries it, then switch the server and rehearse recovery before the real change. Set the release gate on evidence about installed versions, and never roll back to a compromised key merely because old clients recognise it.

Review your pin rotation plan

Tell us your platform, framework, network stack and the certificate change you need to test. We never ask for binaries, private keys, credentials or production data.

Or request a technical demo