aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/transport-rewrap.js
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/transport-rewrap.js')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/transport-rewrap.js61
1 files changed, 35 insertions, 26 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/transport-rewrap.js b/packages/meshbay-hub/src/meshbay_hub/static/transport-rewrap.js
index 6ae51d1..8bf884c 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/transport-rewrap.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/transport-rewrap.js
@@ -43,8 +43,11 @@ function _acWithTimeout(promise, ms, label) {
* @param {string} o.token a fresh access token
* @param {string} o.username
* @param {string} o.userId
- * @param {string} [o.oldPassphrase] omit in Flow B — connect falls back to the recovery copy
- * @param {string} o.newPassphrase
+ * @param {object} o.bundleKey the session key that opens the bundles as they are
+ * (keyderive.js `deriveBundleSessionKey`). In Flow B it
+ * opens nothing and connect falls back to the recovery copy.
+ * @param {object} [o.newBundleKey] the key to seal them under; defaults to `bundleKey`
+ * (the Profile backfill: same key, a recovery copy added)
* @param {string} [o.recoveryKey] the recovery mnemonic (Flow B,
* docs/MESHBAY_DESIGN.md §3.6).
* When given, the recovery-wrapped copy is read where the
@@ -54,28 +57,14 @@ function _acWithTimeout(promise, ms, label) {
*/
async function rewrapAllNodes(o) {
const K = window.MeshBayKeys;
- if (!K || !K.deriveEncryptionKey) {
+ if (!K || !K.encryptBundle) {
throw new Error('key module unavailable');
}
- let oldKey, newKey;
- if (o.bundleKey) {
- // "Keep the current passphrase key, just add / refresh the recovery copy"
- // — the Profile backfill (docs/MESHBAY_DESIGN.md §3.6). `o.bundleKey` is the
- // live {v2,v1} session key, so no passphrase is needed.
- oldKey = newKey = o.bundleKey;
- } else {
- // Flow B has no old passphrase; connect will fail the passphrase decrypt and
- // fall back to the recovery copy, so a placeholder key is fine for `oldKey`.
- const oldPass = o.oldPassphrase || o.newPassphrase;
- oldKey = {
- v2: await K.deriveEncryptionKey(oldPass, o.username),
- v1: await K.deriveEncryptionKeyV1(oldPass, o.username),
- };
- newKey = {
- v2: await K.deriveEncryptionKey(o.newPassphrase, o.username),
- v1: await K.deriveEncryptionKeyV1(o.newPassphrase, o.username),
- };
- }
+ if (!o.bundleKey) throw new Error('no bundle key in this session');
+ // Keys, never passphrases: each caller has derived them already, with the
+ // pepper only the hub holds (docs/MESHBAY_DESIGN.md §3.7).
+ const oldKey = o.bundleKey;
+ const newKey = o.newBundleKey || o.bundleKey;
const recoveryKey = o.recoveryKey
? await K.deriveRecoveryKey(o.recoveryKey, o.username)
: null;
@@ -110,8 +99,24 @@ async function rewrapAllNodes(o) {
try {
await _acWithTimeout(
tp.connect(n.node_id, o.token, g.id, null, null, oldKey,
- o.username, o.userId, null, recoveryKey),
+ o.username, o.userId, null, recoveryKey, undefined, n.pk_node),
30000, 'connect');
+ if (tp.identity && tp.identity.native) {
+ // The desktop application holds this identity: it seals, and only
+ // for an account with browser access — without it, nothing of the
+ // identity is on the node to re-seal.
+ const P = window.MeshBayPlatform.keys;
+ if (await P.browserAccess(o.userId)) {
+ const sealed = await P.sealBundle(o.userId, tp.nodePk,
+ { pending: Boolean(o.newBundleKey && o.newBundleKey.pending) });
+ const rec = o.recoveryKey
+ ? await P.sealRecovery(o.userId, tp.nodePk, o.recoveryKey, o.username) : null;
+ await tp.storeKeypairBundle(sealed.bundle, rec);
+ await P.markSealed(o.userId, tp.nodePk, sealed.fingerprint);
+ }
+ anyOk = true;
+ continue;
+ }
if (tp.newNodeBundle) {
// No identity existed on this node — connect just minted one under
// the old key. Don't persist it: the next time this group is opened
@@ -121,15 +126,19 @@ async function rewrapAllNodes(o) {
anyOk = true;
continue;
}
- const sk = tp.sessionKeys;
+ const sk = tp.identity && tp.identity.raw;
if (!sk) { lastErr = new Error('identity not recovered'); continue; }
const skEd = Uint8Array.from(atob(sk.skEdB64), c => c.charCodeAt(0));
const skX = Uint8Array.from(atob(sk.skXB64), c => c.charCodeAt(0));
- const reEnc = await K.encryptBundleWithKey(skEd, skX, newKey.v2);
+ // Sealed for this account on the node just connected to — the key
+ // that node proved during the handshake.
+ const sealedFor = { userId: o.userId, nodePk: tp.nodePk };
+ const reEnc = await K.encryptBundle(skEd, skX, await K.nodeBundleKey(newKey, tp.nodePk),
+ { ...sealedFor, pepperVersion: newKey.pepperVersion });
// In Flow B, refresh the recovery copy too (same R) so the node's
// passphrase copy and recovery copy stay in step.
const reRecovery = recoveryKey
- ? await K.encryptBundleWithKey(skEd, skX, recoveryKey)
+ ? await K.encryptBundle(skEd, skX, recoveryKey, { ...sealedFor, pepperVersion: 0 })
: null;
await tp.storeKeypairBundle(reEnc, reRecovery);
anyOk = true;