Skip to content

fix(ci): migrate Android signing to base64 secrets, pin cert SHA-256, fail closed - #212

Open
TimeToBuildBob wants to merge 2 commits into
ActivityWatch:masterfrom
TimeToBuildBob:fix/signing-hardening-208
Open

fix(ci): migrate Android signing to base64 secrets, pin cert SHA-256, fail closed#212
TimeToBuildBob wants to merge 2 commits into
ActivityWatch:masterfrom
TimeToBuildBob:fix/signing-hardening-208

Conversation

@TimeToBuildBob

Copy link
Copy Markdown
Contributor

Aligns with gptme/gptme Android release signing (tauri.yml release-android). Closes #208.

What changed

Keystore delivery: age → base64 secret

  • Before: android.jks.age (age-encrypted keystore) committed in-repo; decrypted in CI using KEY_ANDROID_JKS as the age identity key via adnsio/setup-age-action
  • After: KEY_ANDROID_JKS_B64 secret (base64-encoded keystore); decoded in CI with base64 --decode

Fastlane API credentials: same migration

  • Before: fastlane/api-...json.age committed in-repo; decrypted via KEY_FASTLANE_API
  • After: KEY_FASTLANE_API_B64 secret; decoded with base64 --decode

Fail closed on release tags

New Require signing configuration on release tags step exits with a descriptive error if any of KEY_ANDROID_JKS_B64, KEY_ANDROID_JKS_STOREPASS, KEY_ANDROID_JKS_KEYPASS, or ANDROID_CERT_SHA256 is missing — instead of the current silent fallback to an unsigned artifact.

Signer certificate SHA-256 pinning

New Verify APK signer certificate step in build-apk (and sign_apk.sh) checks the APK's actual signer cert against ANDROID_CERT_SHA256 (repo variable). Mismatch = hard failure. Skipped if the variable is not set.

adnsio/setup-age-action dropped

Removed from both build.yml (build-apk + release-fastlane) and release.yml. Not needed once both secrets are in base64.

Age-encrypted files removed

android.jks.age and fastlane/api-...json.age deleted from the repo (superseded by the new secrets). The keystore history in old commits is safe — the age file is still encrypted there.

Operator steps before next release tag

  1. Base64-encode the keystore:

    base64 < android.jks
    

    Add as KEY_ANDROID_JKS_B64 secret → Settings → Secrets → Actions

  2. Base64-encode the Fastlane API JSON:

    base64 < fastlane/api-8546008605074111507-287154-450dc77b365f.json
    

    Add as KEY_FASTLANE_API_B64 secret

  3. Get signer cert SHA-256 (from any existing signed APK):

    apksigner verify --print-certs dist/aw-android.apk | grep 'SHA-256'
    

    Add as ANDROID_CERT_SHA256 repo variable → Settings → Variables → Actions

  4. Keep existing passwords unchanged: KEY_ANDROID_JKS_STOREPASS and KEY_ANDROID_JKS_KEYPASS remain as-is.

  5. Remove old secrets: KEY_ANDROID_JKS and KEY_FASTLANE_API can be deleted after the new ones are in place.

Reference

  • gptme/gptme .github/workflows/tauri.yml release-android job
  • gptme/gptme docs/contributing.rst "Android release signing"

…osed

Aligns with gptme/gptme Android release signing (tauri.yml release-android).
Closes ActivityWatch#208.

Changes:
- build.yml build-apk: drop adnsio/setup-age-action; decode keystore from
  KEY_ANDROID_JKS_B64 (base64) instead of age-decrypting android.jks.age;
  add fail-closed check on release tags (requires KEY_ANDROID_JKS_B64,
  KEY_ANDROID_JKS_STOREPASS, KEY_ANDROID_JKS_KEYPASS, ANDROID_CERT_SHA256);
  verify APK signer cert SHA-256 after each build when ANDROID_CERT_SHA256
  repo variable is set.
- build.yml release-fastlane: drop adnsio/setup-age-action; decode Fastlane
  API JSON from KEY_FASTLANE_API_B64 (base64) instead of age-decryption.
- release.yml: same Fastlane API migration.
- scripts/sign_apk.sh: add cert SHA-256 verification when ANDROID_CERT_SHA256
  is set; use newest build-tools version (sort -V | tail -1).
- Remove android.jks.age and fastlane/...json.age (superseded by secrets).

Operator steps before next release tag:
1. base64-encode keystore: base64 < android.jks
   Add as KEY_ANDROID_JKS_B64 secret in repo Settings → Secrets → Actions.
2. base64-encode Fastlane API JSON:
   base64 < fastlane/api-8546008605074111507-287154-450dc77b365f.json
   Add as KEY_FASTLANE_API_B64 secret.
3. Get signer cert SHA-256:
   apksigner verify --print-certs dist/aw-android.apk | grep 'SHA-256'
   Add as ANDROID_CERT_SHA256 repo variable (Settings → Variables → Actions).
4. Keep KEY_ANDROID_JKS_STOREPASS and KEY_ANDROID_JKS_KEYPASS unchanged.
5. Remove old KEY_ANDROID_JKS and KEY_FASTLANE_API secrets.
@greptile-apps

greptile-apps Bot commented Aug 5, 2026

Copy link
Copy Markdown

Greptile Summary

The PR migrates Android signing and Fastlane credentials from repository-hosted age-encrypted files to base64-encoded GitHub secrets.

  • Requires complete Android signing configuration for release tags.
  • Decodes the keystore and Fastlane service-account credentials with restricted file permissions.
  • Pins and verifies the APK signer certificate SHA-256 digest.
  • Removes the obsolete age setup action and encrypted credential files.

Confidence Score: 5/5

The PR appears safe to merge, with release signing failures handled before artifacts are published.

The changed workflows preserve signing and Fastlane credential delivery while adding a release-tag configuration gate and signer-certificate verification; no actionable changed-code failure remains.

Important Files Changed

Filename Overview
.github/workflows/build.yml Migrates CI credentials to base64 secrets, enforces release-tag signing configuration, and verifies the APK signer certificate before publication.
.github/workflows/release.yml Replaces age-based Fastlane credential decryption with base64 secret decoding.
scripts/sign_apk.sh Selects the newest installed apksigner and optionally validates the signed APK certificate against a pinned SHA-256 digest.
android.jks.age Removes the superseded age-encrypted Android keystore.
fastlane/api-8546008605074111507-287154-450dc77b365f.json.age Removes the superseded age-encrypted Fastlane service-account credential.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    Tag[Release tag] --> Gate{Signing configuration complete?}
    Gate -->|No| Fail[Fail release]
    Gate -->|Yes| Decode[Decode base64 keystore]
    Decode --> Build[Build APK and AAB]
    Build --> Sign[Sign artifacts]
    Sign --> Verify[Verify APK signer SHA-256]
    Verify -->|Mismatch| Fail
    Verify -->|Match| Publish[Publish release artifacts]
    FastlaneSecret[Base64 Fastlane credential] --> Credential[Decode API JSON]
    Credential --> Publish
Loading

Reviews (1): Last reviewed commit: "fix(ci): migrate signing to base64 secre..." | Re-trigger Greptile

@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

CI-green and mergeable — waiting only on a maintainer click.

This PR is ready to merge, but the bot has pull-only access to this repo and can't self-merge — surfacing it here so it isn't lost. The monitoring loop will stop re-flagging it now that this note is posted.

@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

Found and fixed one acceptance-criteria gap before merge: AABs were signed but never verified. Commit d2ddd94 now runs jarsigner -verify -strict immediately after signing, so certificate/signature warnings fail the release before upload.

bash -n scripts/sign_apk.sh and git diff --check pass. The push restarted current-head CI/review; please wait for those checks before merging.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Align Android signing hardening with gptme: base64 keystore secret, cert pinning, fail-closed

1 participant