Skip to content

Whatsapp - #4

Merged
ehsan6sha merged 5 commits into
mainfrom
whatsapp
Mar 13, 2026
Merged

Whatsapp#4
ehsan6sha merged 5 commits into
mainfrom
whatsapp

Conversation

@ehsan6sha

Copy link
Copy Markdown
Member

Added Apps with Whatsapp being first one that automatically backs up and restores applicaiton data

@ehsan6sha
ehsan6sha merged commit 2564058 into main Mar 13, 2026
@ehsan6sha
ehsan6sha deleted the whatsapp branch March 13, 2026 22:11
ehsan6sha added a commit that referenced this pull request May 19, 2026
Four client-side fixes for the seed-auth flow. Pairs with the
matching server-side fixes in
https://github.com/functionland/pinning-service (see those commit
messages for the full audit narrative).

## #1 — Registration replay (CRITICAL)

`signInModeB` and `signInModeC` previously generated their own
challenge via `Random.secure()` and signed it locally before
POSTing register-mode-{b,c}. The server accepted the client-supplied
challenge without tracking it as single-use, so a captured request
body could be replayed to mint fresh (perpetually-valid, DT-1) JWTs.

Fix: both methods now fetch a server-issued challenge first
(`IssuerClient.challenge(uid, purpose: 'register-mode-{b,c}')`) and
sign that. The server consumes the same nonce in its store; replay
returns HTTP 401 CHALLENGE_INVALID.

`IssuerClient.challenge` gained a `purpose` parameter; the previous
default behavior (`'sign-in'`) is preserved when the parameter is
omitted. The now-unused `_randomBytes` helper was removed.

## #2 — NFKC inconsistency between KEK and effective_user_id (CRITICAL)

`_canonicalKekInputModeB` / `_canonicalKekInputModeC` in
`auth_service.dart` passed `utf8.encode(seed)` raw to Argon2id. The
matching `fula.computeEffectiveUserIdModeB/c` and
`fula.deriveSigningSeed` Rust FFI calls NFC-normalize the seed
internally. Consequence: a Mode B user typing a non-ASCII password
in NFC on Device A vs NFD on Device B got the SAME `effective_user_id`
(same vault on the server) but DIFFERENT master KEKs → permanent
cross-device decryption failure.

Fix: extracted the canonical-input logic to a new helper
`lib/core/utils/canonical_kek_input.dart` that NFC-normalizes the
seed using the `unorm_dart` package (pure Dart, no native deps —
the upstream `unicode_normalization` package doesn't exist on pub.dev
under that name). 8 unit tests cover NFC=NFD equivalence,
separator-injection resistance, determinism, and distinct-input
distinctness.

Note: my commit messages on `7fa2f32` and the audit transcript use
"NFKC"; the Rust code calls `.nfc()` (canonical-composed NFC, not
the compatibility-decomposing NFKC). Both are fine; the code is
self-consistent; the Dart side now uses the same NFC.

## #3 — Mode B signing key not OAuth-bound (CRITICAL)

`fula.deriveSigningSeed(seed)` derives the 32-byte Ed25519 seed
from `BLAKE3_derive_key("fula:signing-key:v2", NFC(seed))` — the
password ONLY. Two Mode B users under different OAuth identities
but the same password derived IDENTICAL keypairs. Combined with the
public users-index CBOR (which exposes effective_user_ids), this
would let one of them sign in to the other's vault: read the target's
effective_user_id from the public CBOR, sign the sign-in transcript
with their own (identical) keypair, server verifies, mints JWT.

Fix: new helper `lib/core/utils/seed_signing_input.dart` constructs
a tagged-and-length-separated string for Mode B
(`'b\x00$provider\x00$oauthSub\x00$password'`) that's passed to
`deriveSigningSeed` instead of the raw password. The signing seed is
now bound to the full `(provider, oauth_sub, password)` tuple. Mode C
stays seed-only (no OAuth to bind to); the leading `'b\x00'` tag
guarantees Mode B inputs can't collide with Mode C inputs. 6 unit
tests cover the distinctness and collision-resistance properties.

## #4 — `has_mode_a` flag wiring + logic

The server's `has_mode_a` field on the register-mode-b response was
both miscomputed (counted seed_users rows for the same oauth_sub,
which detects "other seed vaults" not "Mode A account") AND ignored
by the client (`SeedAuthResult.hasModeA` was returned by `IssuerClient`
but `signInModeB`/`signInGoogleModeB`/`signInAppleModeB` dropped it
when returning `AuthUser?`).

Fix server-side (separate pinning-service commit): replaced the
helper with `checkModeAExistsForEmail` doing a direct PK lookup on
`webui_users` keyed by `SHA-256(lowercase(email))`. Fix client-side:
`signInModeB` / `signInGoogleModeB` / `signInAppleModeB` now return
`({AuthUser user, bool hasModeA})?`. The Mode B sign-in screen
checks `result.hasModeA` and shows an "Existing vault detected"
warning dialog ("Your existing files are NOT in this vault — to
access them, sign out and use Standard security") before navigating
home.

## Files

New:
- `lib/core/utils/canonical_kek_input.dart` + unit test (8 cases)
- `lib/core/utils/seed_signing_input.dart` + unit test (6 cases)

Modified:
- `lib/core/services/auth_service.dart` — challenge round-trip,
  canonical KEK helper usage, Mode B signing input, record-typed
  return values for the convenience wrappers.
- `lib/core/services/issuer_client.dart` — `purpose` param on
  `challenge()`.
- `lib/features/onboarding/screens/mode_b_signin_screen.dart` —
  Existing-vault warning dialog wired to `result.hasModeA`.
- `pubspec.yaml` — `unorm_dart: ^0.3.0` for Dart-side NFC.

`flutter analyze` clean on all touched files (2 pre-existing INFO
warnings on auth_service.dart, neither from this change).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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