# Mobile Signing

> iOS and Android code-signing identity management — Apple certificates, App IDs, provisioning profiles, APNs (.p8) and App Store Connect API keys, fastlane match; Android upload key vs app signing key, keystores, Play App Signing, and Play Console service accounts. Use when setting up or fixing code signing, when a certificate or provisioning profile expires, when a build fails to sign in CI, when rotating or resetting keys, when onboarding a machine or a new developer to signing, or when someone asks about keystores, .p8 keys, match, Play App Signing, "no signing certificate found", "provisioning profile doesn't include signing certificate", or "upload key vs app signing key".

- Skill: `kaakati/mobile-signing` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add kaakati/mobile-signing`
- Raw SKILL.md: https://api.skillmd.com/api/skills/kaakati/mobile-signing/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: Kaakati (https://skillmd.com/u/kaakati)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/kaakati/mobile-signing

---


# Mobile Signing — identities you cannot regenerate

Signing is the one part of mobile release engineering where a mistake is **permanent**. Most
build problems are annoying; losing an app signing key means **you can never update that app
again** — not "call support", not "restore from backup". Google's own words:

> *"If you lose access to your app signing key or your key is compromised, Google cannot retrieve
> the app signing key for you, and you will not be able to release new versions of your app to
> users as updates to the original app."* … *"You cannot regenerate a previously generated key."*

So this skill's rules are ordered by **blast radius**, not by workflow.

## The irreversibility table — read this before touching anything

| Identity | Lose it and… | Recoverable? |
|---|---|---|
| **Android app signing key**, *not* enrolled in Play App Signing | The app is dead. You ship a **new listing** and every user must reinstall manually. | ❌ **Never** |
| **Android app signing key**, enrolled in Play App Signing | Nothing — Google holds it. | ✅ n/a |
| **Android upload key** | Nothing lasting — request a reset in Play Console. | ✅ Reset |
| **Apple distribution certificate** | Revoke and reissue; rebuild profiles. Annoying, not fatal. | ✅ Reissue |
| **APNs auth key (.p8)** | Cannot re-download; generate a new one and update servers. | ⚠️ Replace |
| **App Store Connect API key (.p8)** | Cannot re-download; revoke and issue a new one. | ⚠️ Replace |

**The single highest-value action in this whole skill: enrol in Play App Signing.** It converts
the only unrecoverable failure in the table into a recoverable one. It is also **required** for
Android App Bundles, which are required for new apps — so most teams already have it and don't
know what it bought them.

## The rule that prevents the disaster

**You upload with the upload key. Google signs with the app signing key.** They are different
keys with different owners, and conflating them is why teams think a lost keystore is survivable
(it is, *only* if enrolled) or panic when it isn't (it isn't, if not).

- Never commit a keystore or a `.p8`/`.p12` to git — ever, even a private repo, even briefly.
  Git history is forever and a leaked signing key is a supply-chain compromise of every user's
  device.
- Never email or Slack them. Use the team's secret manager.
- **Back up the upload keystore and its passwords** somewhere the team — not one laptop — can
  reach. The developer who generated it will leave.
- `.p8` files **download exactly once**. Save them at creation or start over.

## Decision: how do we distribute signing identities?

| Team shape | Approach | Why |
|---|---|---|
| More than one developer, or any CI | **fastlane match** (Apple) | One encrypted git/S3 store; machines sync, nobody hand-manages Keychain |
| Solo, no CI | Xcode automatic signing | match's setup cost isn't repaid yet |
| Any Android | **Play App Signing + upload keystore in CI secrets** | The only posture where a lost key isn't fatal |
| Enterprise / strict | App Store Connect API key + short-lived CI creds | No human Apple ID in CI |

**Do not let CI use a person's Apple ID.** It breaks when they leave, when 2FA prompts, and it
grants CI everything that human can do. Use an **App Store Connect API key** scoped to the job.

## Expiry is a scheduled outage you can see coming

Certificates and profiles expire on a fixed date, and the failure always lands the morning of a
release. Put the dates in a calendar the team reads:

- Apple distribution certificate → **expires; rebuild/reissue before it does**
- Provisioning profiles → expire, and **a profile is invalidated when its certificate is
  revoked** (revoking a "broken" cert breaks every profile built on it — this is the classic
  self-inflicted outage)
- APNs `.p8` auth key → does **not** expire (its advantage over legacy `.p12` push certs)
- Play upload/app signing keys → set a long validity (25+ years) at creation; a keystore that
  expires mid-life is a self-inflicted version of the unrecoverable case

## Deep guides (read on demand, do not preload)

- Apple identities end-to-end — certificate types, App IDs & capabilities, provisioning profile
  anatomy, the `.p8` keys, fastlane match, and decoding the real meaning of "no signing
  certificate found" / "profile doesn't include signing certificate"
  → `references/ios-identities.md`
- Android keystores — generating an upload key, enrolling in Play App Signing, what Google holds
  vs what you hold, resetting a lost upload key, Play Console service accounts, and the
  `signingConfigs` that must never contain a literal password
  → `references/android-keystores.md`
- Holding these secrets in CI — base64 round-tripping, ephemeral keychains, what must never be
  echoed, and least-privilege API keys → `references/ci-signing-secrets.md`

Shipping the signed build to testers is a separate task — see the `mobile-beta-release` skill.

## Why this skill breaks the house rule on CI credentials

`std-infrastructure` states it plainly: *"Prefer no credential at all. CI authenticates by
**federation, not keys** — OIDC to AWS"*, and *"no long-lived AWS keys in CI."* Everything here
does the opposite: a `.p8`, a keystore, a service-account JSON, all long-lived, all sitting in CI
secrets.

**That is a real exception, not an oversight, and it is worth knowing which.** Apple's App Store
Connect API key *is* a `.p8` private key — there is no OIDC path to App Store Connect to prefer
instead. The rule cannot be satisfied, so the mitigation moves to what you *can* control: least
privilege on the key, a short rotation, never echoing it, an ephemeral keychain that dies with the
runner, and the knowledge that a leaked signing key is a supply-chain compromise of every user's
device rather than one cloud account.

Do not go looking for the OIDC option on the Apple side; it does not exist, and time spent hunting
for it is time not spent rotating the key you actually have. If a federated path exists for a given
provider, prefer it — but confirm it at that provider's docs rather than assuming the AWS answer
transfers.

Depth on holding these safely → `references/ci-signing-secrets.md`.

