Skip to content

Keryx Android production signing

Approved: 26 August 2026

This document is the P3-01 signing packet for Keryx for SafeCall on Google Play. It records the Play App Signing model, upload-key custody, Gradle fail-closed wiring, AAB verification, recovery drill procedure, and evidence handoff.

It does not claim iOS distribution signing (#110), CI signing gates (#112), store record verification (#114), Play Console form paste (#125), or that FGS/FCM delivery architecture is implemented in the release binary (#96 policy only).

Purpose and non-goals

Purpose

  • Approve production Android signing identity for com.sourcectl.keryx.
  • Require release builds to use an upload keystore outside Git — never debug signing.
  • Document custodians, backup, rotation triggers, and recovery drill steps.
  • Provide verification commands and evidence archive requirements for #109 close.

Non-goals

  • Committing keystores, passwords, or Play service-account JSON.
  • Replacing SafeCall Nav (mobile/) signing — out of scope unless requested.
  • Automating CI secret injection (#112).
  • Store upload or Internal-track promotion without separate release authority.

Package identity

FieldValue
Application IDcom.sourcectl.keryx
Publishersourcectl (P0-01)
Store product nameKeryx for SafeCall
Default Play languageEnglish + Czech listing parity (P0-02)

Play App Signing model

Google Play App Signing holds the app signing key (Google-managed). Keryx operators hold the upload key used to sign AABs before upload.

KeyCustodyIn Git
App signing keyGoogle (Play App Signing)Never — public cert/fingerprint only
Upload keySecret store (secrets/ on NAS) + backup custodianNever — *.jks gitignored

Enrollment (Play Console, current Help as of August 2026)

Official pages: Create and set up your app, Use Play App Signing, Prepare and roll out a release, Countries/regions, Category. May 2026 Console change: App signing lives under Protected with Play (not “App integrity”).

  1. Create the app: Home → Create app. Name Keryx for SafeCall, default language English, App, Free. Accept Developer Program Policies, US export laws, and Play App Signing Terms of Service. Package com.sourcectl.keryx is set by the first AAB, not this form.
  2. Category: left menu Grow users → Store presence → Store settings → App category → Business. Do not paste listing copy.
  3. Countries: select the app → Test and release → ProductionCountries/regions tab → add Czech Republic only (remove others). Testing tracks sync with production unless you unsync. If Production has no countries yet, set the same on Test and release → Testing → Internal testingCountries/regions.
  4. Play App Signing: new apps are enrolled when you accept the ToS and upload the first AAB. To inspect keys: left menu Protected with Play, then Play Store distribution / Play Store protectionPlay app signing (Google renamed this in May 2026). Confirm upload-cert SHA-256 0D:AC:61:B1:5F:85:91:3F:2B:21:20:5E:46:14:81:90:20:13:85:8B:76:D2:00:91:36:FD:30:9D:18:D8:0D:AA.
  5. First AAB: Test and release → Testing → Internal testing → Create new release (or Create new release on that page). Follow the on-screen Play App Signing prompt. Upload keryx/build/app/outputs/bundle/release/app-release.aab. Save as draft is enough; do not start a production rollout. If Create new release is disabled, finish the Dashboard setup tasks first.

Archive a screenshot of the Play app signing page (no secrets) in ~/sourcectl/keryx-release-evidence/. Operator verified 30 August 2026: upload SHA-256 matches GLaDOS; Czech Republic only; internal-testing AAB saved as draft (OA-109-play-record, OA-109-play-signing).

Upload-key loss without Play reset is a launch blocker. Document reset procedure in controlled store records, not in this repository.

Upload keystore creation

Generate outside routine commits; store only in NAS secrets/ (secret-store.md). Do this on GLaDOS (primary). MiniVan-3 gets a copy of the same .jks for OA-109-recovery — do not generate a second keystore.

Operator steps (OA-109-keystore)

From the repo root. storeFile=../upload-keystore.jks is relative to keryx/android/app/ (Gradle), so the file must live at keryx/android/upload-keystore.jks.

  1. Confirm keytool exists (keytool -help). It ships with a JDK (Android Studio’s JBR is fine).
  2. Create the keystore (prompts for store password, then key password — use the same strong password for both unless you have a reason not to):
bash
cd keryx/android
keytool -genkey -v \
  -keystore upload-keystore.jks \
  -storetype JKS \
  -keyalg RSA \
  -keysize 2048 \
  -validity 10000 \
  -alias upload \
  -dname "CN=Keryx for SafeCall, OU=Keryx, O=SOURCE CONTROL SINGLE MEMBER P.C., C=GR"
cd ../..
  1. Create gitignored key.properties:
bash
cp keryx/android/key.properties.example keryx/android/key.properties

Edit keryx/android/key.properties so it looks like this (passwords filled, storeFile unchanged):

properties
storePassword=<the store password from keytool>
keyPassword=<the key password from keytool>
keyAlias=upload
storeFile=../upload-keystore.jks
  1. Confirm Git will not see them:
bash
git check-ignore -v keryx/android/key.properties keryx/android/upload-keystore.jks
git status --short keryx/android/

Both files must be ignored. Never git add them.

  1. Copy into NAS secrets/ (secret-store.md; separate files from checksums in evidence/):
    • file upload-keystore.jks
    • store password
    • key password
    • alias upload Do not put passwords in git, Slack, or ~/sourcectl/keryx-release-evidence/.
  2. Build and verify on GLaDOS:
bash
cd keryx && flutter build appbundle --release && cd ..
bun run verify:keryx-aab

Copy the printed AAB SHA-256 and upload-cert fingerprint into ~/sourcectl/keryx-release-evidence/ (checksums only).

Never commit upload-keystore.jks, passwords, or key.properties. If you lose the upload key without a Play reset, you cannot update the Play listing. Do not create a second keystore “just in case.”

Local and CI signing inputs

Copy the committed example:

bash
cp keryx/android/key.properties.example keryx/android/key.properties
# Edit passwords and storeFile path — file is gitignored

Example shape (keryx/android/key.properties.example):

properties
storePassword=
keyPassword=
keyAlias=upload
storeFile=../upload-keystore.jks

Gradle loads keryx/android/key.properties from the Android root project (keryx/android/). Release builds fail closed when the file or keystore is missing — see keryx/android/app/build.gradle.kts.

Optional CI overrides (future #112): mount secrets and set paths via env or copy key.properties into the build workspace from CI variables (KERYX_KEY_* naming reserved; not wired until #112).

Custody matrix

Personal contact values live in controlled store records (#114); this packet defines roles only.

RoleResponsibility
Primary custodianCreates upload keystore; maintains key.properties locally; runs first signed build and Play enrollment
Backup custodianHolds duplicate keystore export in NAS secrets/ (secret-store.md); performs recovery drill
Release authorityApproves rotation/revocation and Play upload-key reset
Audit logNAS evidence/ — build commit, fingerprint, checksum, operator, date

Rotation / revocation triggers

  • Suspected keystore or password compromise.
  • Custodian offboarding without backup transfer.
  • Play Console upload-key mismatch or failed verification.
  • Google Play upload-key reset request.

After rotation: new upload keystore → update Play upload certificate → archive new SHA-256 → recovery drill on backup path.

Build and verify release AAB

bash
cd keryx
flutter analyze && flutter test
flutter build appbundle --release
cd ..
bun run verify:keryx-aab

Optional fingerprint gate:

bash
bun run verify:keryx-aab -- --expect-fingerprint=<SHA-256>

Default AAB path: keryx/build/app/outputs/bundle/release/app-release.aab.

Verification checklist

Archive in controlled evidence (not git):

  • [ ] jarsigner -verify passes (verify:keryx-aab exit 0)
  • [ ] SHA-256 upload certificate fingerprint recorded
  • [ ] AAB SHA-256 checksum recorded
  • [ ] Source commit hash and build timestamp recorded
  • [ ] debuggable=false on release manifest (Flutter release default)
  • [ ] Package com.sourcectl.keryx, version code/name match intended release
  • [ ] Play Console upload cert matches local fingerprint
  • [ ] Play App Signing enrollment noted (screenshot or ticket reference)

Recovery drill procedure

Second authorized environment (different machine or restored secret mount):

  1. Obtain upload keystore + passwords from NAS secrets/ (secret-store.md) — not from git.
  2. Create keryx/android/key.properties from key.properties.example.
  3. Check out the same commit (or current release tag per versioning.md).
  4. Run flutter build appbundle --release.
  5. Run bun run verify:keryx-aab — fingerprint must match primary build.
  6. Record: date, operator, commit, fingerprint, checksum, environment ID in controlled audit log.

Failure to reproduce fingerprint blocks #109 close.

Evidence handoff

Remaining workOwner issue
Reproducible AAB/IPA pipeline command#111 — recorded 1 September 2026 (bun run release:keryx -- --contracts)
CI signing secrets + verify gate#112
Store records, roles, evidence archive#114
Play Console paste and submission#125
iOS distribution signing#110ios-signing.md (App ID com.sourcectl.keryxapp; GLaDOS + MiniVan-3 IPAs verified; .p12 on NAS)

Launch posture

Closing P3-01 / #109 means production upload signing is wired fail-closed, a verified signed release AAB exists, Play App Signing is enrolled, custody is documented, and recovery is proven on a second authorized environment.

It does not mean Internal/Production track promotion, CI automation, iOS signing, or that every store form is submitted.