Skip to content

Add missing Android signing cert to passkey origin allowlist and assetlinks.json#362

Merged
dahlia merged 3 commits into
hackers-pub:mainfrom
malkoG:fix/android-passkey-signing-certs
Jul 19, 2026
Merged

Add missing Android signing cert to passkey origin allowlist and assetlinks.json#362
dahlia merged 3 commits into
hackers-pub:mainfrom
malkoG:fix/android-passkey-signing-certs

Conversation

@malkoG

@malkoG malkoG commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Problem

Passkey registration/login fails on Android, depending on which build a user has installed. pub.hackers.android is distributed through two different channels with two different signing certificates:

  • Google Play Store — Google re-signs the app with its own Play App Signing key.
  • GitHub Releases / F-Droid (.github/workflows/fdroid-release.yml in hackers-pub/android) — signed directly with hackerspub-release.jks, the developer's own upload key.

Android's Credential Manager embeds the caller's cert hash into the WebAuthn origin as android:apk-key-hash:<base64url(SHA-256(signing cert))>, and the OS only returns this origin at all if the cert also appears in sha256_cert_fingerprints in /.well-known/assetlinks.json for the calling package. Both models/passkey.ts's PLATFORM_ORIGINS.android allowlist and assetlinks.json only listed one of the two certs (and not even the same one), so one distribution channel was always broken:

  • assetlinks.json listed only the Play Signing cert (AD:C6:2C:B4...).
  • models/passkey.ts listed only the upload-key cert (52:A0:14:21...UqAUIQLNMP2LKaPtgCsKvq...), from hackerspub-release.jks.

So Play Store installs failed WebAuthn origin verification server-side, and F-Droid/GitHub-Release installs failed the OS-level Digital Asset Links check before even reaching the server.

Fix

Add the missing fingerprint to both sides so each distribution channel's actual signing cert is present in both files:

  • models/passkey.ts: add android:apk-key-hash:rcYstHvAfSZI1cjlUYi_HoIztMxao3RJJdDEPDLXErs (Play Signing key) alongside the existing upload-key hash.
  • graphql/static/.well-known/assetlinks.json and web-next/public/.well-known/assetlinks.json: add 52:A0:14:21:...:64:D5:1A (upload key) alongside the existing Play Signing fingerprint.

Debug builds (pub.hackers.android.dev) were already correctly configured and are unchanged.

A companion PR keeps hackers-pub/android's docs/assetlinks.json reference copy in sync.

Verification

Fully reproducible, no external assumptions:

# 1. Play Store cert — from the actual distributed APK
apksigner verify --print-certs hackerspub-12.apk
# → SHA-256: adc62cb4...d712bb  (DN: CN=Android, O=Google Inc. — Play Signing key)

# 2. Upload/F-Droid cert — from the actual signing keystore
keytool -list -v -keystore hackerspub-release.jks -alias hackerspub
# → SHA-256: 52:A0:14:21:...:64:D5:1A

# 3. Confirm the origin-construction algorithm this app's Credential Manager
#    dependency actually uses (androidx.credentials:credentials:1.6.0, resolved
#    in this project's Gradle cache):
javap -p -c -classpath <extracted classes.jar> \
  'androidx.credentials.webauthn.WebAuthnUtilsApi28$Companion'
# → "android:apk-key-hash:" + Base64.encodeToString(SHA256(signingCert), 11)
#    (flag 11 = URL_SAFE | NO_PADDING | NO_WRAP)

# 4. Apply that transform to each cert from steps 1-2 and confirm it matches
#    what's now in models/passkey.ts / assetlinks.json:
python3 -c "
import base64
for hexstr in ['adc62cb47bc07d2648d5c8e55188bf1e8233b4cc5aa3744925d0c43c32d712bb',
               '52a0142102cd30fd8b29a3ed802b0abeafab372979398 41ab79e3905af64d51a'.replace(' ','')]:
    print(base64.urlsafe_b64encode(bytes.fromhex(hexstr)).decode().rstrip('='))"
# → rcYstHvAfSZI1cjlUYi_HoIztMxao3RJJdDEPDLXErs
# → UqAUIQLNMP2LKaPtgCsKvq-rNyl5OYQat545Ba9k1Ro

Also verified @simplewebauthn/server@13.1.1's verifyRegistrationResponse.js/verifyAuthenticationResponse.js (resolved in this repo's node_modules) does a plain expectedOrigin.includes(origin) string-equality check — no special parsing of android: origins — so both entries need to be byte-exact, which they now are.

Test plan

  • Install a Play-Store-signed build (or the downloaded hackerspub-12.apk), register a passkey, sign out, sign back in via passkey.
  • Install a build signed with hackerspub-release.jks (e.g. from a GitHub Release), repeat the same passkey register/login flow.
  • Confirm debug builds are unaffected.

AI Disclosure

Investigated and drafted with Claude Code (claude-sonnet-5).

  • Root-cause investigation: cross-referencing hackerspub, hackerspub-android, hackerspub-ios, and hackerspub-android-prod; decompiling the resolved androidx.credentials:credentials:1.6.0 dependency; running apksigner/keytool against a real downloaded Play Store APK and the actual release keystore.
  • Code changes: the diffs in this PR.
  • Human-in-the-loop: all findings were challenged and independently re-verified at each step (including catching and correcting an initial fix that would have replaced rather than added a fingerprint, breaking the other distribution channel) before being applied.

malkoG added 2 commits July 19, 2026 15:37
Google re-signs Play Store-distributed APKs with its own Play App
Signing key, which differs from the upload keystore already listed
here. Passkey registration and login were failing for Play
Store-installed users because their Credential Manager origin
(android:apk-key-hash: derived from the Play Signing cert) never
matched any entry in this allowlist.

Assisted-by: Claude Code:claude-sonnet-5
pub.hackers.android is also distributed via GitHub Releases/F-Droid,
built directly from the upload keystore rather than re-signed by
Google Play. That certificate's fingerprint was missing from
assetlinks.json, so those installs failed Android's Digital Asset
Links check before a passkey request ever reached the server.

Assisted-by: Claude Code:claude-sonnet-5
@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@malkoG, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 42 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5992c876-12d4-4fc8-baac-e9cf4b4598ae

📥 Commits

Reviewing files that changed from the base of the PR and between 5110b35 and ba342a4.

📒 Files selected for processing (1)
  • models/passkey.test.ts
📝 Walkthrough

Walkthrough

Android Digital Asset Links now include an additional certificate fingerprint, and passkey configuration comments clarify Play App Signing versus upload/release keystores. The platform-specific passkey origin preference test was removed.

Changes

Android signing associations

Layer / File(s) Summary
Asset links and passkey signing updates
graphql/static/.well-known/assetlinks.json, web-next/public/.well-known/assetlinks.json, models/passkey.ts, models/passkey.test.ts
Both asset links files add a second Android certificate fingerprint. Passkey comments explain signing-key variants without changing origin hashes or logic, and the platform-specific origin preference test is removed.

Estimated code review effort: 1 (Trivial) | ~3 minutes

Possibly related PRs

Suggested reviewers: dahlia

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding the missing Android signing certificate to the passkey allowlist and assetlinks files.
Description check ✅ Passed The description is clearly about the same Android signing-certificate fix and matches the updated passkey and assetlinks changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds the Google Play App Signing key fingerprint and origin for the Android app to the asset links and passkey configurations across both the legacy and new stacks. The reviewer noted that the development package configuration is missing from the new stack's asset links (web-next/public/.well-known/assetlinks.json) and suggested adding it to match the legacy configuration and facilitate local debugging.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +11 to 15
"AD:C6:2C:B4:7B:C0:7D:26:48:D5:C8:E5:51:88:BF:1E:82:33:B4:CC:5A:A3:74:49:25:D0:C4:3C:32:D7:12:BB",
"52:A0:14:21:02:CD:30:FD:8B:29:A3:ED:80:2B:0A:BE:AF:AB:37:29:79:39:84:1A:B7:9E:39:05:AF:64:D5:1A"
]
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The pub.hackers.android.dev package is present in graphql/static/.well-known/assetlinks.json but is missing from web-next/public/.well-known/assetlinks.json. To ensure that developers can test and debug the Android application against the new stack (web-next/), we should include the dev package and its debug certificate fingerprint here as well.

        "AD:C6:2C:B4:7B:C0:7D:26:48:D5:C8:E5:51:88:BF:1E:82:33:B4:CC:5A:A3:74:49:25:D0:C4:3C:32:D7:12:BB",
        "52:A0:14:21:02:CD:30:FD:8B:29:A3:ED:80:2B:0A:BE:AF:AB:37:29:79:39:84:1A:B7:9E:39:05:AF:64:D5:1A"
      ]
    }
  },
  {
    "relation": [
      "delegate_permission/common.handle_all_urls",
      "delegate_permission/common.get_login_creds"
    ],
    "target": {
      "namespace": "android_app",
      "package_name": "pub.hackers.android.dev",
      "sha256_cert_fingerprints": [
        "CA:A4:96:E9:46:6C:68:29:7F:74:00:D6:33:45:F7:0B:F9:DD:81:B9:49:53:8B:94:32:B8:D0:60:B2:31:10:5B"
      ]
    }
  }

@malkoG
malkoG force-pushed the fix/android-passkey-signing-certs branch from 5110b35 to 00ae7c6 Compare July 19, 2026 06:52
@malkoG
malkoG force-pushed the fix/android-passkey-signing-certs branch from 00ae7c6 to ba342a4 Compare July 19, 2026 06:56
@dahlia
dahlia merged commit c29bd9e into hackers-pub:main Jul 19, 2026
6 checks passed
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.

2 participants