aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests/test_desktop_keyring.py
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/tests/test_desktop_keyring.py')
-rw-r--r--packages/meshbay-hub/tests/test_desktop_keyring.py106
1 files changed, 99 insertions, 7 deletions
diff --git a/packages/meshbay-hub/tests/test_desktop_keyring.py b/packages/meshbay-hub/tests/test_desktop_keyring.py
index 673a00e..963ee55 100644
--- a/packages/meshbay-hub/tests/test_desktop_keyring.py
+++ b/packages/meshbay-hub/tests/test_desktop_keyring.py
@@ -62,9 +62,39 @@ const v = JSON.parse(fs.readFileSync(input, 'utf8'));
out.sealed_here = ring.sealBundle(v.userId, v.node).bundle;
out.playlist_key = ring.playlistKey(v.userId);
- // 2. a signature and an X25519 agreement that the other side can check.
- const msg = Buffer.from('a transcript');
- out.sig = ring.sign(v.userId, v.node, msg.toString('base64'));
+ // 2. signatures by kind, built here from fields, for the reference to check;
+ // and an X25519 agreement that the other side can check.
+ const ts = Math.floor(Date.now() / 1000);
+ const nonceNode = Buffer.alloc(32, 7).toString('base64');
+ const other = require('crypto').generateKeyPairSync('ed25519').publicKey
+ .export({ format: 'der', type: 'spki' }).subarray(12).toString('base64');
+ const F = {
+ join: { nodePk: v.node, groupId: 'grp-1', userId: v.userId, nonceNode, ts },
+ device_hello: { nodePk: v.node, groupId: 'grp-1', userId: v.userId, nonceNode, ts },
+ device_request: { nodePk: v.node, userId: v.userId, codeHash: 'ab'.repeat(32), nonceNode, ts },
+ device_add: { nodePk: v.node, userId: v.userId, pkEd: other, pkX: other, nonceNode, ts },
+ device_revoke: { nodePk: v.node, userId: v.userId, pkEd: other, nonceNode, ts },
+ chat: { groupId: 'grp-1', epoch: 3, nonce: Buffer.alloc(12, 1).toString('base64'),
+ ct: Buffer.from('ciphertext').toString('base64') },
+ admin: { op: 'file_delete', nodePk: v.node, groupId: 'grp-1', subject: 'file-9',
+ nonce: Buffer.alloc(32, 2).toString('base64'), ts },
+ };
+ out.fields = F; out.signed = {};
+ for (const [kind, f] of Object.entries(F)) {
+ out.signed[kind] = ring.signAs(v.userId, v.node, kind, f);
+ }
+
+ const refusal = async (kind, f) => {
+ try { await ring.signAs(v.userId, v.node, kind, f); return null; }
+ catch (e) { return e.code || e.message; }
+ };
+ out.refused = {
+ bytes: await refusal('raw', { bytes: 'YQ==' }),
+ other_node: await refusal('join', { ...F.join, nodePk: 'T3RoZXJOb2Rl' }),
+ other_account: await refusal('join', { ...F.join, userId: 'someone-else' }),
+ stale: await refusal('join', { ...F.join, ts: ts - 3600 }),
+ };
+
const eph = require('crypto').generateKeyPairSync('x25519');
const ephPub = eph.publicKey.export({ format: 'der', type: 'spki' }).subarray(12);
out.shared_here = ring.shared(v.userId, v.node, ephPub.toString('base64'));
@@ -94,10 +124,16 @@ const v = JSON.parse(fs.readFileSync(input, 'utf8'));
out.retired = (() => { try { ring.openBundle(v.userId, 'NODE-R',
{ bundleEnc: Buffer.from('MBK2' + 'x'.repeat(40)).toString('base64') }); }
catch (e) { return e.code; } })();
+ out.access_default = ring.browserAccess('someone-else');
+ ring.setBrowserAccess(v.userId, false);
+ const refusedSeal = (fn) => { try { fn(); return null; } catch (e) { return e.message; } };
+ out.sealing_while_access_off = {
+ bundle: refusedSeal(() => ring.sealBundle(v.userId, v.node)),
+ recovery: refusedSeal(() => ring.sealRecovery(v.userId, v.node, 'A'.repeat(56), v.user)),
+ };
ring.forgetSession(v.userId);
out.after_sign_out = { session: ring.hasSession(v.userId),
identity_kept: !!ring.identity(v.userId, v.node) };
- out.access_default = ring.browserAccess('someone-else');
ring.setBrowserAccess(v.userId, false);
out.access_after_off = ring.browserAccess(v.userId);
out.stored_json = JSON.stringify(store);
@@ -155,10 +191,66 @@ def test_the_playlist_key_is_the_pages(out):
_reference_master(), "meshbay:playlists:v2")
-def test_signatures_and_agreements_check_out_with_the_public_keys(out):
+def _reference_transcript(kind, f, pub):
+ from meshbay_common.adminop import admin_transcript
+ from meshbay_common.chatbox import signing_transcript
+ from meshbay_common.device import (
+ device_add_transcript,
+ device_hello_transcript,
+ device_request_transcript,
+ device_revoke_transcript,
+ )
+ from meshbay_common.join import join_transcript
+ nonce_node = base64.b64decode(f.get("nonceNode", ""))
+ ed, x = pub["pkEdB64"], pub["pkXB64"]
+ return {
+ "join": lambda: join_transcript(f["nodePk"], f["groupId"], f["userId"], ed, x,
+ nonce_node, f["ts"]),
+ "device_hello": lambda: device_hello_transcript(f["nodePk"], f["groupId"],
+ f["userId"], ed, nonce_node, f["ts"]),
+ "device_request": lambda: device_request_transcript(
+ f["nodePk"], f["userId"], ed, x, f["codeHash"], nonce_node, f["ts"]),
+ "device_add": lambda: device_add_transcript(f["nodePk"], f["userId"], f["pkEd"],
+ f["pkX"], nonce_node, f["ts"]),
+ "device_revoke": lambda: device_revoke_transcript(f["nodePk"], f["userId"], f["pkEd"],
+ nonce_node, f["ts"]),
+ "chat": lambda: signing_transcript(f["groupId"], f["epoch"], base64.b64decode(ed),
+ base64.b64decode(f["nonce"]),
+ base64.b64decode(f["ct"])),
+ "admin": lambda: admin_transcript(f["op"], f["nodePk"], f["groupId"], f["subject"],
+ base64.b64decode(f["nonce"]), f["ts"]),
+ }[kind]()
+
+
+def test_every_kind_signs_the_specifications_bytes(out):
+ """
+ The keyring builds the transcript itself from fields; what it signs must be
+ exactly what meshbay_common builds, or the node refuses every join, chat
+ line and admin operation from the desktop application.
+ """
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
- Ed25519PublicKey.from_public_bytes(base64.b64decode(out["minted_pub"]["pkEdB64"])).verify(
- base64.b64decode(out["sig"]), b"a transcript")
+ pub = out["minted_pub"]
+ key = Ed25519PublicKey.from_public_bytes(base64.b64decode(pub["pkEdB64"]))
+ assert set(out["signed"]) == {"join", "device_hello", "device_request", "device_add",
+ "device_revoke", "chat", "admin"}
+ for kind, sig in out["signed"].items():
+ key.verify(base64.b64decode(sig), _reference_transcript(kind, out["fields"][kind], pub))
+
+
+def test_the_page_names_a_kind_and_never_the_bytes(out):
+ r = out["refused"]
+ assert "nothing is signed as" in r["bytes"]
+ assert "another node" in r["other_node"]
+ assert "another account" in r["other_account"]
+ assert "not now" in r["stale"]
+
+
+def test_nothing_is_sealed_for_a_browser_while_browser_access_is_off(out):
+ for what, err in out["sealing_while_access_off"].items():
+ assert err and "browser access is off" in err, what
+
+
+def test_agreements_check_out_with_the_public_keys(out):
assert out["shared_here"] == out["shared_there"]