diff options
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.js | 61 |
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; |