aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests/test_bundle_kdf_parity.py
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-10-14 22:20:15 +0200
committerChristophe Besson <cbesson@gmail.com>2026-10-14 22:20:15 +0200
commit422647e7898b8df67cd11f8fb060aec23dec84aa (patch)
tree7ecca0e8df1bc8a7d26cf4b471bd44906dd9cfd1 /packages/meshbay-hub/tests/test_bundle_kdf_parity.py
parent28c88908b88c58cba4de08710f6bb268ede02d57 (diff)
downloadmeshbay-422647e7898b8df67cd11f8fb060aec23dec84aa.tar.gz
feat(cli): meshbay-cli, a command-line client for members
Signs in to the hub, lists the account's groups and their folders, and downloads and uploads files directly with each group's node, end-to-end encrypted. With no argument it opens a two-pane interactive mode (Textual). Session and device keys are kept encrypted under the system's key storage, or a 0600 key file where there is none; node keys are pinned at first contact; a new machine links as a device. Packaged on its own on Linux (deb and rpm, on meshbay-common's venv, with a man page generated from the parser) and frozen next to meshbay-node in the full Windows installer, not the light one, with a self-test the build runs. client/keys.py ports keyderive.js and is LGPL like it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/tests/test_bundle_kdf_parity.py')
-rw-r--r--packages/meshbay-hub/tests/test_bundle_kdf_parity.py36
1 files changed, 35 insertions, 1 deletions
diff --git a/packages/meshbay-hub/tests/test_bundle_kdf_parity.py b/packages/meshbay-hub/tests/test_bundle_kdf_parity.py
index a4bee43..358d6ce 100644
--- a/packages/meshbay-hub/tests/test_bundle_kdf_parity.py
+++ b/packages/meshbay-hub/tests/test_bundle_kdf_parity.py
@@ -158,7 +158,7 @@ const fp = async (key) => Buffer.from(await webcrypto.subtle.encrypt(
Buffer.from('ed-private'), Buffer.from('x-private'), kNode,
{ userId: v.user_id, nodePk: v.node_pk, pepperVersion: 1 });
out.push({ node: await fp(kNode), playlists: await fp(await derivePlaylistKey(sk.v3)),
- bundle });
+ bundle, auth: await K.deriveAuthKey(v.password, v.username) });
}
process.stdout.write(JSON.stringify(out));
})().catch((e) => { console.error(e); process.exit(1); });
@@ -231,3 +231,37 @@ def test_an_mbk3_bundle_opens_from_the_specification(chain):
plain = json.loads(AESGCM(_reference(v)[0]).decrypt(raw[5:17], raw[17:], aad))
assert base64.b64decode(plain["skEd"]) == b"ed-private"
assert base64.b64decode(plain["skX"]) == b"x-private"
+
+
+# ── The command-line client derives the same keys ──────────────────────────
+#
+# `meshbay-cli` runs its own Python copy of the chain (it is a client, not a
+# test reference), so it is held here to the page, as the reference is.
+
+def test_the_cli_derives_what_the_page_derives(chain):
+ cli_keys = pytest.importorskip("meshbay_cli.client.keys")
+ import base64
+ vectors, js = chain
+ for v, got in zip(vectors, js):
+ assert cli_keys.auth_key(v["username"], v["password"]) == got["auth"], (
+ f"auth_key disagrees for {v['username']!r}: the CLI could not sign in")
+ a = cli_keys.passphrase_half(v["username"], v["password"])
+ assert a.hex() == _python_hash(v["username"], v["password"])
+ m = cli_keys.bundle_master_key(a, base64.b64decode(v["pepper"]), v["user_id"])
+ assert _fp(cli_keys.node_bundle_key(m, v["node_pk"])) == got["node"]
+
+
+def test_the_cli_opens_a_bundle_the_page_sealed(chain):
+ cli_keys = pytest.importorskip("meshbay_cli.client.keys")
+ import base64
+ vectors, js = chain
+ for v, got in zip(vectors, js):
+ a = cli_keys.passphrase_half(v["username"], v["password"])
+ m = cli_keys.bundle_master_key(a, base64.b64decode(v["pepper"]), v["user_id"])
+ keys = cli_keys.open_bundle(got["bundle"], master=m, legacy=None,
+ user_id=v["user_id"], node_pk=v["node_pk"])
+ assert base64.b64decode(keys["skEd"]) == b"ed-private"
+ # Bound to its account: served for another, it does not open.
+ with pytest.raises(cli_keys.BundleError):
+ cli_keys.open_bundle(got["bundle"], master=m, legacy=None,
+ user_id="someone-else", node_pk=v["node_pk"])