Skip to content

Encrypted shared secrets store

Status: Runnable from source for startup smoke; Deferred/incomplete integration for sharing and revocation.

examples/password-manager is a Vite, React, and Yjs reference application. Its typechecked production build and strict Chromium startup smoke test pass. It has no acceptance test proving two-user sharing, invitation delivery, restart recovery, or revocation. A vault lasts only for the current browser session; reopening one after a reload is tracked in issue #534.

Packages are unpublished. From a repository checkout prepared with the quick start, run:

Build and smoke-test the application:

Terminal window
yarn build
yarn workspace @peerborne/password-manager build
yarn test:e2e:password-manager

To run the Vite development server separately:

Terminal window
yarn workspace @peerborne/password-manager start

The smoke test proves startup only, not operational sharing.

A deployable application must generate, authenticate, persist, restore, and back up separate keys:

  • a stable ECDSA P-384 key pair for identity signing and ACL membership;
  • a stable ECDH P-256 KEM key pair whose private key permits deriveBits, for encrypted BeeKEM Welcomes;
  • document epoch keys and membership state needed for recovery.

The application, not Peerborne, owns identity binding and recovery. Exchange the recipient’s signing public key and raw 65-byte SEC1-uncompressed KEM public key over an authenticated out-of-band channel. A pasted, unauthenticated key is vulnerable to substitution.

Status: Illustrative core sequence; not an end-to-end application recipe. The direct document API has the necessary shape:

const kemKeyPair = await crypto.subtle.generateKey(
{ name: 'ECDH', namedCurve: 'P-256' },
true,
['deriveBits'],
);
await recipientDocumentRef.setKemKeyPair(kemKeyPair);
const recipientKemRaw = recipientDocumentRef.getKemPublicKeyRaw();
await ownerDocumentRef.addReader(recipientIdentityPublicKey, recipientKemRaw);
await ownerDocumentRef.addWriter(recipientIdentityPublicKey);

Each participant must call setKemKeyPair on the relevant document ref before receiving Welcomes or administering BeeKEM membership. Add the recipient as a reader with addReader(identity, kemRaw) before granting writer status; addReader requires the KEM bytes.

The React wrapper’s controls forward addReader(user, kemRaw) and setKemKeyPair(keyPair). The password-manager example generates a KEM key pair at login, installs it on each secret’s document ref, and shows its public half on the Settings page. Its permissions table requires a new member’s signing public key and raw KEM public key, both base64, before any membership change, so the owner sends a recipient-sealed Welcome. Its Chromium test promotes a reader added with a generated KEM key to editor in one browser. The example has no recipient flow that opens the shared secret afterwards, and its KEM key pair does not survive a reload, so no two-browser test proves that a second identity receives the Welcome and decrypts the document.

Status: Deferred/incomplete integration. Prefer completing and testing these items over copying a runnable vault snippet:

  • Persist and restore signing keys and P-256 KEM keys without logging or exposing private material.
  • Bind both public keys to the intended human through an authenticated channel.
  • Install KEM state on every opened document before invitation handling.
  • Add a reader with signing identity plus raw KEM key; only then add writer rights if needed.
  • Confirm the recipient decrypts and edits in a two-browser, two-identity test.
  • Test dropped, replayed, delayed, and out-of-order Welcome and PathUpdate messages.
  • Test restart before and after invitation, removal, and key rotation.
  • Provide explicit backup, export, recovery, and destructive-loss UX.
  • Reconcile the per-user index and secret document after partial dual-write failure.

BeeKEM reader mappings, tree state, and cached Welcomes have important memory-only paths. PathUpdate delivery is best effort, and restart, missed-update, multiwriter, and complete revocation behavior remain unresolved. Removing a member cannot erase plaintext or old keys they already copied. See Security and Limitations.