diff options
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/transport-devices.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/transport-devices.js | 93 |
1 files changed, 66 insertions, 27 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/transport-devices.js b/packages/meshbay-hub/src/meshbay_hub/static/transport-devices.js index 4291cf8..1c6fc78 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/transport-devices.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/transport-devices.js @@ -94,7 +94,7 @@ extendTransport(class { * this node sees this account (and not at all in an open-join group). */ async joinGroup(userId, groupId, code) { - if (!this._sessionKeys || !this._sessionKeys.skEdB64 || !this._sessionKeys.skXB64) { + if (!this._identity) { throw new Error('Identity keys unavailable in this browser — sign in again'); } if (!this._nonceNode || !this.nodePk) { @@ -102,13 +102,13 @@ extendTransport(class { } const C = window.MeshBayCrypto; - const pkEdB64 = await _pkEdFromSk(this._sessionKeys.skEdB64); - const pkXB64 = await _pkFromSk(this._sessionKeys.skXB64); + const { pkEdB64, pkXB64 } = this._identity; const ts = Math.floor(Date.now() / 1000); - const transcript = C.joinTranscript( - this.nodePk, groupId || '', userId, pkEdB64, pkXB64, this._nonceNode, ts); - const sig = await window.MeshBayKeys.signBytes(this._sessionKeys.skEdB64, transcript); + const sig = await this._identity.signAs('join', { + nodePk: this.nodePk, groupId: groupId || '', userId, + nonceNode: C.b64encode(this._nonceNode), ts, + }); const resp = await this._sendAndWait({ type: 'join_request', @@ -133,9 +133,8 @@ extendTransport(class { // Unwrap with our own secret key — the node wrapped for the public key we // just proved we hold, so nobody else can open this. - const skXRaw = Uint8Array.from(atob(this._sessionKeys.skXB64), c => c.charCodeAt(0)); const myPkX = Uint8Array.from(atob(pkXB64), c => c.charCodeAt(0)); - const gekRaw = await C.unwrapGEK(resp, skXRaw, myPkX); + const gekRaw = await C.unwrapGEK(resp, (pk) => this._identity.shared(pk), myPkX); this._gekRaw = gekRaw; // What the node's roster says this identity is, which is not what the hub // says: `operator` here means this browser's key was paired with the node, @@ -154,15 +153,14 @@ extendTransport(class { * device cannot be handed a substituted key and sign for it by mistake. */ async requestDeviceAdd(userId) { - if (!this._sessionKeys || !this._sessionKeys.skEdB64) { + if (!this._identity) { throw new Error('Identity keys unavailable in this browser — sign in again'); } if (!this._nonceNode || !this.nodePk) { throw new Error('Handshake incomplete — reconnect and retry'); } const C = window.MeshBayCrypto; - const pkEdB64 = await _pkEdFromSk(this._sessionKeys.skEdB64); - const pkXB64 = await _pkFromSk(this._sessionKeys.skXB64); + const { pkEdB64, pkXB64 } = this._identity; // 40 bits from the platform CSPRNG, in the same alphabet as a pairing code // so it reads and types the same way. @@ -174,10 +172,10 @@ extendTransport(class { const codeHash = await C.deviceCodeHash( C.normalizeCode(code), pkEdB64, pkXB64); const ts = Math.floor(Date.now() / 1000); - const transcript = C.deviceRequestTranscript( - this.nodePk, userId, pkEdB64, pkXB64, codeHash, this._nonceNode, ts); - const sig = await window.MeshBayKeys.signBytes( - this._sessionKeys.skEdB64, transcript); + const sig = await this._identity.signAs('device_request', { + nodePk: this.nodePk, userId, codeHash, + nonceNode: C.b64encode(this._nonceNode), ts, + }); const resp = await this._sendAndWait({ type: 'device_add_request', v: '0.1', @@ -196,7 +194,7 @@ extendTransport(class { * nothing to sign and nothing for a person to misread. */ async approveDevice(userId, code) { - if (!this._sessionKeys || !this._sessionKeys.skEdB64) { + if (!this._identity) { throw new Error('Identity keys unavailable in this browser — sign in again'); } if (!this._nonceNode || !this.nodePk) { @@ -229,10 +227,10 @@ extendTransport(class { async _countersign(userId, codeHash, pkEdB64, pkXB64) { const C = window.MeshBayCrypto; const ts = Math.floor(Date.now() / 1000); - const transcript = C.deviceAddTranscript( - this.nodePk, userId, pkEdB64, pkXB64, this._nonceNode, ts); - const sig = await window.MeshBayKeys.signBytes( - this._sessionKeys.skEdB64, transcript); + const sig = await this._identity.signAs('device_add', { + nodePk: this.nodePk, userId, pkEd: pkEdB64, pkX: pkXB64, + nonceNode: C.b64encode(this._nonceNode), ts, + }); const resp = await this._sendAndWait({ type: 'device_add', v: '0.1', pk_ed25519: pkEdB64, pk_x25519: pkXB64, code_hash: codeHash, ts, sig, @@ -251,10 +249,10 @@ extendTransport(class { async revokeDevice(userId, pkEdB64, pkXB64) { const C = window.MeshBayCrypto; const ts = Math.floor(Date.now() / 1000); - const transcript = C.deviceAddTranscript( - this.nodePk, userId, pkEdB64, pkXB64, this._nonceNode, ts); - const sig = await window.MeshBayKeys.signBytes( - this._sessionKeys.skEdB64, transcript); + const sig = await this._identity.signAs('device_revoke', { + nodePk: this.nodePk, userId, pkEd: pkEdB64, + nonceNode: C.b64encode(this._nonceNode), ts, + }); const resp = await this._sendAndWait({ type: 'device_revoke', v: '0.1', pk_ed25519: pkEdB64, ts, sig, }); @@ -265,9 +263,11 @@ extendTransport(class { /** * Withdraw our key backup from this node. * - * The counterpart of storeKeypairBundle: turning the setting off has to remove - * what is already stored, not merely stop adding to it — otherwise the blob - * stays on every node the account has ever joined (C4). + * The counterpart of storeKeypairBundle: turning browser access off has to + * remove what is already stored, not merely stop adding to it — otherwise + * the blob stays on every node the account has ever joined (C4). Called by + * `settleNodeBundle` only, for an account whose identities the desktop + * application holds. */ async deleteKeypairBundle() { const msg = await this._sendAndWait({ @@ -277,6 +277,45 @@ extendTransport(class { return msg; } + /** + * Leave on this node what should be there, once the connection is made. + * + * In a browser: the bundle of an identity just created, as always — it is + * how the next browser becomes the same person here. In the desktop + * application, which keeps the identity itself: nothing when the account + * has no browser access (and whatever was left before is withdrawn), or the + * identity sealed under the current key when it has — sealed again whenever + * the key changed since, which is what carries a passphrase change to nodes + * that were offline when it happened. + */ + async settleNodeBundle() { + const id = this._identity; + if (!id) return; + if (!id.native) { + if (this._newNodeBundle) { + await this.storeKeypairBundle(this._newNodeBundle, this._newNodeBundleRecovery); + this._newNodeBundle = null; + this._newNodeBundleRecovery = null; + } + return; + } + const P = window.MeshBayPlatform.keys; + const uid = this._userId; + if (!await P.browserAccess(uid)) { + if (this._nodeHasBundle) { + await this.deleteKeypairBundle(); + this._nodeHasBundle = false; + } + return; + } + if (this._nodeHasBundle && id.sealedWith && id.sealedWith === await P.fingerprint(uid)) return; + const sealed = await P.sealBundle(uid, this.nodePk); + await this.storeKeypairBundle(sealed.bundle, null); + await P.markSealed(uid, this.nodePk, sealed.fingerprint); + id.sealedWith = sealed.fingerprint; + this._nodeHasBundle = true; + } + async storeKeypairBundle(bundleEnc, recoveryEnc) { const msg = await this._sendAndWait({ type: 'keypair_bundle_store', |