diff options
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/transport.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/transport.js | 48 |
1 files changed, 48 insertions, 0 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/transport.js b/packages/meshbay-hub/src/meshbay_hub/static/transport.js index 18faea1..50304f3 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/transport.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/transport.js @@ -232,6 +232,11 @@ class MeshBayTransport { || !await C.verifyNodeSignature(ack.node_pk, ack.sig, transcript)) { throw new Error('Node signature invalid — refusing connection'); } + // Trust On First Use (11.5.8). With C6 closed, a substituted node already + // fails the GEK proof — this covers the case where an attacker HAS the GEK + // (an ex-member, or a leaked key) and swaps the node underneath. + // Strict refusal: a warning users can click through is decorative. + _checkNodePin(nodeId, ack.node_pk); this.nodePk = ack.node_pk; return ack; @@ -692,5 +697,48 @@ function _extractDtlsFingerprint(sdp) { return bytes; } +// ── Node identity pinning (11.5.8) ─────────────────────────────────────────── + +const NODE_PIN_PREFIX = 'mb_nodepin_'; + +function _checkNodePin(nodeId, nodePk) { + if (!nodeId || !nodePk) return; + const key = NODE_PIN_PREFIX + nodeId; + + let pinned = null; + try { pinned = localStorage.getItem(key); } catch { return; } + + if (pinned === null) { + try { localStorage.setItem(key, nodePk); } catch {} + return; + } + if (pinned !== nodePk) { + throw new Error( + 'This node\'s identity key has changed. That is expected only if its ' + + 'operator reinstalled the node — otherwise someone may be impersonating ' + + 'it. Verify with the operator out of band, then clear the pin in ' + + 'Settings to accept the new key.'); + } +} + +/** Forget a pinned node identity — the deliberate escape hatch for a legitimate rotation. */ +function clearNodePin(nodeId) { + try { + if (nodeId) localStorage.removeItem(NODE_PIN_PREFIX + nodeId); + else { + for (const k of Object.keys(localStorage)) + if (k.startsWith(NODE_PIN_PREFIX)) localStorage.removeItem(k); + } + } catch {} +} + +function pinnedNodeCount() { + try { + return Object.keys(localStorage).filter(k => k.startsWith(NODE_PIN_PREFIX)).length; + } catch { return 0; } +} + // Export +MeshBayTransport.clearNodePin = clearNodePin; +MeshBayTransport.pinnedNodeCount = pinnedNodeCount; window.MeshBayTransport = MeshBayTransport; |