Skip to content

feat(auth): require recovery PIN and strip identifying metadata from Drive backups#538

Merged
barrydeen merged 1 commit into
mainfrom
feat/google-backup-hardening
May 16, 2026
Merged

feat(auth): require recovery PIN and strip identifying metadata from Drive backups#538
barrydeen merged 1 commit into
mainfrom
feat/google-backup-hardening

Conversation

@barrydeen
Copy link
Copy Markdown
Owner

Summary

External review of the new "Continue with Google" / Drive backup flow surfaced several issues. This PR addresses the security and privacy ones; the unreleased feature lets us land them without a migration path.

What changed

  • Recovery PIN required. Backups are now encrypted with a key derived from PBKDF2-HMAC-SHA256 (600k iterations) over a 4–8 digit numeric PIN that the user sets at first sign-in, salted by HMAC(sub). Compromising the Google account alone no longer decrypts the nsec.
  • Use the signed sub claim, not parsed.id. GoogleIdTokenCredential.id is the user's email and can change (Workspace renames). The JWT sub is the stable Google account ID.
  • Opaque Drive filenames. Files are now wisp_bk_<uuid>.bin. The npub is no longer leaked to Drive and is recovered by decrypting. The delete-then-upload race in the old code is gone — each new account creates a fresh file.
  • Decoy pubkeys in the profile fetch. The chooser still enriches accounts with name + avatar, but the REQ now mixes the real backup pubkeys with 10 random kind-0 pubkeys pulled from a popular relay first, so the relay can't see which pubkeys are actually on this device.

UX changes

  • New SetupPin flow (enter → confirm → mismatch retry) on first sign-in, with a "if you forget this PIN your Nostr key is gone forever" warning.
  • New EnterPinForRestore flow when backups are found; wrong PIN surfaces inline and lets the user retry.

Notes

  • Unreleased feature, so the old wisp_nsec_<npub>.bin filename / email-based key derivation is dropped entirely with no migration path. Any orphaned files in testers' appDataFolder will not be listed and can be cleaned up manually.
  • 600k PBKDF2 iterations costs ~200–400ms on a phone; the derivation runs at most twice per sign-in.

Test plan

  • Fresh sign-in with no existing backups → SetupPin (Enter → Confirm) → account created and backup uploaded
  • Confirm-step mismatch → returns to Enter with mismatch error
  • Re-sign-in with one backup → EnterPinForRestore → correct PIN → chooser shows the account with name/avatar
  • Wrong PIN → "Incorrect PIN. Try again." with retry
  • From chooser, "Create another account" → uploads a second backup under the same PIN
  • Re-sign-in with two backups → correct PIN → both accounts appear in chooser
  • Verify Drive appDataFolder contains only wisp_bk_<uuid>.bin files (filenames don't contain npub)

…Drive backups

Google-account-only custody meant anyone with the Google login could
decrypt the nsec; the filename leaked the npub to Drive; the chooser's
profile prefetch told relays which npubs were on this device.

- Derive the backup key from PBKDF2-HMAC-SHA256(PIN, salt=HMAC(sub))
  with 600k iterations. PIN is a 4–8 digit numeric set during sign-in
  with a confirm step; mismatch and wrong-PIN paths surface inline.
- Pull `sub` from the signed ID token's JWT instead of
  GoogleIdTokenCredential.id (which is the email, not stable across
  Workspace renames).
- Use opaque `wisp_bk_<uuid>.bin` filenames and recover the npub by
  decrypting. Drop the delete-then-upload race since there's no longer
  a replace path.
- Seed the chooser's profile REQ with 10 decoy pubkeys pulled from a
  popular relay so observers can't pick the real backups out of the
  query.
@barrydeen barrydeen merged commit 1b4e250 into main May 16, 2026
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.

1 participant