aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/tests')
-rw-r--r--packages/meshbay-hub/tests/harness/chat_send_probe.py5
-rw-r--r--packages/meshbay-hub/tests/test_identity_seam.py49
-rw-r--r--packages/meshbay-hub/tests/test_rewrap_fanout.py2
3 files changed, 53 insertions, 3 deletions
diff --git a/packages/meshbay-hub/tests/harness/chat_send_probe.py b/packages/meshbay-hub/tests/harness/chat_send_probe.py
index 7569628..e5cfc8b 100644
--- a/packages/meshbay-hub/tests/harness/chat_send_probe.py
+++ b/packages/meshbay-hub/tests/harness/chat_send_probe.py
@@ -171,7 +171,8 @@ function makeTransport(name, chatReply) {
tp._groupId = '__GROUP_ID__';
tp._gekRaw = hex('__GEK_HEX__');
tp.chatEpoch = 1;
- tp._sessionKeys = { skEdB64: SK_ED_B64 };
+ tp._identity = { pkEdB64: DEVICE_PK_B64,
+ sign: (bytes) => window.MeshBayKeys.signBytes(SK_ED_B64, bytes) };
tp.devicePk = DEVICE_PK_B64;
tp._send = (obj) => {
log.push('sent ' + obj.type);
@@ -314,7 +315,7 @@ async function runScenario(name, chatReply, duringSession) {
// connect() drops it before it touches the network. Nothing about this
// step is simulated, and the clear is not poked in by the test.
await tp.connect('node-1', 'token', tp._groupId, tp._gekRaw,
- tp._sessionKeys, null, 'me', 'user-me').then(
+ tp._identity, null, 'me', 'user-me').then(
() => log.push('reconnect: connect() unexpectedly succeeded'),
(e) => log.push('reconnect: connect() stopped at signaling, as expected: '
+ (e && e.message || e)));
diff --git a/packages/meshbay-hub/tests/test_identity_seam.py b/packages/meshbay-hub/tests/test_identity_seam.py
new file mode 100644
index 0000000..15db6d8
--- /dev/null
+++ b/packages/meshbay-hub/tests/test_identity_seam.py
@@ -0,0 +1,49 @@
+"""
+Nothing in the interface reads an identity private key except the identity.
+
+The transport sees an identity on a node as two public keys, a signature and an
+X25519 agreement (`transport.js`, `_identityFromKeys`). In a browser that object
+wraps keys held in the page; in the desktop application the keys stay in the
+main process and the same object asks it to sign. That only holds if no other
+code reaches past the object for a key — which is what every admin op, device
+link, chat message and app used to do, twenty times over, and is what this
+test refuses.
+"""
+
+import re
+
+from spa_source import STATIC
+
+# Where a private key may be named: minted and sealed (keyderive.js), wrapped
+# into an identity (transport.js), re-sealed during a passphrase change in a
+# browser (transport-rewrap.js).
+ALLOWED = {"keyderive.js", "transport.js", "transport-rewrap.js"}
+
+
+def test_only_the_identity_touches_a_private_key():
+ offenders = []
+ for path in STATIC.glob("*.js"):
+ if path.name in ALLOWED:
+ continue
+ text = path.read_text(encoding="utf-8")
+ if re.search(r"skEdB64|skXB64|sessionKeys|signBytes\(", text):
+ offenders.append(path.name)
+ assert not offenders, f"these read a private key directly: {offenders}"
+
+
+def test_in_the_transport_only_the_identity_factory_signs_with_a_raw_key():
+ text = (STATIC / "transport.js").read_text(encoding="utf-8")
+ factory = text[text.index("async function _identityFromKeys"):]
+ factory = factory[:factory.index("\n}\n")]
+ rest = text.replace(factory, "")
+ assert "signBytes(" in factory
+ assert "signBytes(" not in rest, "the transport signs with a raw key outside the identity"
+ assert "skXB64" not in rest.replace("id.skXB64", ""), \
+ "the transport reads the X25519 private key outside the identity"
+
+
+def test_a_group_key_is_unwrapped_through_the_identity():
+ crypto = (STATIC / "crypto.js").read_text(encoding="utf-8")
+ unwrap = crypto[crypto.index("async function unwrapGEK"):]
+ unwrap = unwrap[:unwrap.index("\n}\n")]
+ assert "shared(pkEphRaw)" in unwrap and "pkcs8" not in unwrap
diff --git a/packages/meshbay-hub/tests/test_rewrap_fanout.py b/packages/meshbay-hub/tests/test_rewrap_fanout.py
index 79a5bf5..9e93140 100644
--- a/packages/meshbay-hub/tests/test_rewrap_fanout.py
+++ b/packages/meshbay-hub/tests/test_rewrap_fanout.py
@@ -74,7 +74,7 @@ T.prototype.connect = async function (nodeId, _t, _g, _gek, _sk, bundleKey) {
rewrapOnlySeen.push(this._rewrapOnly === true);
const s = NODES[nodeId] || {};
if (s.throws) throw new Error(s.throws);
- this._sessionKeys = s.sessionKeys || null;
+ this._identity = s.sessionKeys ? { raw: s.sessionKeys } : null;
this._newNodeBundle = s.newNodeBundle || null;
return { ok: true };
};