aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/transport.js
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/transport.js')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/transport.js57
1 files changed, 46 insertions, 11 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/transport.js b/packages/meshbay-hub/src/meshbay_hub/static/transport.js
index ca9c60e..d636085 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/transport.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/transport.js
@@ -266,6 +266,39 @@ class MeshBayTransport {
return msg;
}
+ /**
+ * Authorize a privileged node operation with the user's Ed25519 identity key.
+ *
+ * The client rebuilds the signed transcript from the challenge fields and refuses
+ * to sign unless the operation and subject match what the user actually asked for.
+ * Previously the node sent 32 opaque random bytes and the client signed them
+ * blind, which let any peer obtain a signature over content of its choosing
+ * (finding H5).
+ */
+ async _authorizeAdminOp(challenge, expectedOp, expectedSubject, signFn) {
+ if (challenge.op !== expectedOp || challenge.subject !== expectedSubject) {
+ throw new Error(
+ `Refusing to sign: node asked to authorize "${challenge.op}" on ` +
+ `"${challenge.subject}", but the requested action was "${expectedOp}" ` +
+ `on "${expectedSubject}"`);
+ }
+ if (!signFn) throw new Error('Admin challenge received but no signing key available');
+
+ const transcript = window.MeshBayCrypto.adminTranscript(
+ challenge.op, challenge.node_pk, challenge.group_id,
+ challenge.subject, challenge.nonce, challenge.ts);
+
+ const signature = await signFn(transcript);
+ const ack = await this._sendAndWait({
+ type: 'admin_response',
+ v: '0.1',
+ op_id: challenge.op_id,
+ signature,
+ });
+ if (ack.type === 'error') throw new Error(ack.detail);
+ return ack;
+ }
+
async deleteFile(fileId, signFn) {
const msg = await this._sendAndWait({
type: 'file_delete',
@@ -274,16 +307,7 @@ class MeshBayTransport {
});
if (msg.type === 'error') throw new Error(msg.detail);
if (msg.type === 'admin_challenge') {
- if (!signFn) throw new Error('Admin challenge received but no signing key available');
- const signature = await signFn(msg.challenge);
- const ack = await this._sendAndWait({
- type: 'admin_response',
- v: '0.1',
- file_id: fileId,
- signature,
- });
- if (ack.type === 'error') throw new Error(ack.detail);
- return ack;
+ return this._authorizeAdminOp(msg, 'file_delete', fileId, signFn);
}
return msg;
}
@@ -304,7 +328,15 @@ class MeshBayTransport {
return msg;
}
- async storeGekBundle(userId, groupId, bundle) {
+ /**
+ * Store a wrapped GEK bundle on the node for a member.
+ *
+ * Node-operator operation: the node answers with an admin challenge and only the
+ * pinned operator key is accepted. Any member used to be able to write bundles —
+ * including one addressed to the operator, which the node then auto-adopted as the
+ * live group key (finding C5b).
+ */
+ async storeGekBundle(userId, groupId, bundle, signFn) {
const msg = await this._sendAndWait({
type: 'gek_bundle_store',
v: '0.1',
@@ -315,6 +347,9 @@ class MeshBayTransport {
wrapped_b64: bundle.wrapped_b64,
});
if (msg.type === 'error') throw new Error(msg.detail);
+ if (msg.type === 'admin_challenge') {
+ return this._authorizeAdminOp(msg, 'gek_bundle_store', userId, signFn);
+ }
return msg;
}