aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/transport-devices.js
diff options
context:
space:
mode:
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.js93
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',