diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-10-14 22:20:15 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-10-14 22:20:15 +0200 |
| commit | 422647e7898b8df67cd11f8fb060aec23dec84aa (patch) | |
| tree | 7ecca0e8df1bc8a7d26cf4b471bd44906dd9cfd1 /packages/meshbay-hub | |
| parent | 28c88908b88c58cba4de08710f6bb268ede02d57 (diff) | |
| download | meshbay-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')
| -rw-r--r-- | packages/meshbay-hub/tests/test_bundle_kdf_parity.py | 36 |
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"]) |