Skip to content

encrypt passphrase full#711

Closed
aaspinwall wants to merge 1 commit into
mainfrom
feat/encrypt-passphrase
Closed

encrypt passphrase full#711
aaspinwall wants to merge 1 commit into
mainfrom
feat/encrypt-passphrase

Conversation

@aaspinwall

Copy link
Copy Markdown
Collaborator

No description provided.

@aaspinwall aaspinwall force-pushed the feat/encrypt-passphrase branch from b5fd981 to 0d1f6ef Compare April 16, 2026 20:37
aaspinwall added a commit that referenced this pull request Jul 8, 2026
The passphrase (which unlocks the user's keys) was persisted as plaintext in
localStorage['lb/passphrase'] — including the value shared into the add-on via
the token bridge. Encrypt it at rest with a non-extractable AES-GCM-256 key held
in IndexedDB (per-origin; shared across the add-on's moz-extension pages):

- passphraseEncryption.ts: getOrCreatePassphraseKey (single-flight, IndexedDB),
  encryptPassphrase/decryptPassphrase (fresh 12-byte IV per call, auth-tagged).
- Storage.storePassPhrase encrypts (plaintext fallback only when crypto/IndexedDB
  are unavailable, e.g. tests) and sets an in-memory plaintext cache
  synchronously; getPassPhrase reads that cache; initializePassphrase decrypts on
  load and migrates a legacy plaintext value in place; clear() drops the cache.
- keychain.load() and restoreKeysUsingLocalStorage() hydrate the cache, and the
  router guard hydrates once per navigation so fresh windows (e.g. the
  /passphrase print tab) and cold reloads read the passphrase correctly.
- makeBackup/restoreFromBackup now await storePassPhrase so the encrypted write
  is guaranteed to persist.

Tests cover encrypt/decrypt round-trip, IV uniqueness, tamper + wrong-key
rejection, no-plaintext-at-rest, legacy migration, and cache/clear behavior;
the at-rest path, IndexedDB key persistence, and non-extractable key were also
verified in a real browser.

Note: the token-bridge transit value (browser.storage.local) stays plaintext —
it is transient (consumed + removed) and the web/extension origins can't share
the IndexedDB key; out of scope here.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
aaspinwall added a commit that referenced this pull request Jul 9, 2026
The passphrase (which unlocks the user's keys) was persisted as plaintext in
localStorage['lb/passphrase'] — including the value shared into the add-on via
the token bridge. Encrypt it at rest with a non-extractable AES-GCM-256 key held
in IndexedDB (per-origin; shared across the add-on's moz-extension pages):

- passphraseEncryption.ts: getOrCreatePassphraseKey (single-flight, IndexedDB),
  encryptPassphrase/decryptPassphrase (fresh 12-byte IV per call, auth-tagged).
- Storage.storePassPhrase encrypts (plaintext fallback only when crypto/IndexedDB
  are unavailable, e.g. tests) and sets an in-memory plaintext cache
  synchronously; getPassPhrase reads that cache; initializePassphrase decrypts on
  load and migrates a legacy plaintext value in place; clear() drops the cache.
- keychain.load() and restoreKeysUsingLocalStorage() hydrate the cache, and the
  router guard hydrates once per navigation so fresh windows (e.g. the
  /passphrase print tab) and cold reloads read the passphrase correctly.
- makeBackup/restoreFromBackup now await storePassPhrase so the encrypted write
  is guaranteed to persist.

Tests cover encrypt/decrypt round-trip, IV uniqueness, tamper + wrong-key
rejection, no-plaintext-at-rest, legacy migration, and cache/clear behavior;
the at-rest path, IndexedDB key persistence, and non-extractable key were also
verified in a real browser.

Note: the token-bridge transit value (browser.storage.local) stays plaintext —
it is transient (consumed + removed) and the web/extension origins can't share
the IndexedDB key; out of scope here.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@aaspinwall

Copy link
Copy Markdown
Collaborator Author

Recreated it here #982

@aaspinwall aaspinwall closed this Jul 9, 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