diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-10-02 13:46:34 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-10-03 14:24:54 +0200 |
| commit | 78f3208db5e7285e62151cecb06c2d3c9eb4d2ae (patch) | |
| tree | 66598c28931761831363192e9411cf87215aee99 /packages/meshbay-hub | |
| parent | 3af2c0205071ea74fd7f2b1b1bbe4d47cdd1357b (diff) | |
| download | meshbay-78f3208db5e7285e62151cecb06c2d3c9eb4d2ae.tar.gz | |
feat(android): device key, bundle key and node identities held natively
Keystore-wrapped store, a Kotlin port of keyring.js and transcripts.js held
to the shared vectors, the same keys/device/secrets bridge as the desktop,
and a native confirmation before browser access is widened.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub')
| -rw-r--r-- | packages/meshbay-hub/tests/test_android_keys.py | 74 | ||||
| -rw-r--r-- | packages/meshbay-hub/tests/test_android_shell.py | 5 |
2 files changed, 77 insertions, 2 deletions
diff --git a/packages/meshbay-hub/tests/test_android_keys.py b/packages/meshbay-hub/tests/test_android_keys.py new file mode 100644 index 0000000..a00b909 --- /dev/null +++ b/packages/meshbay-hub/tests/test_android_keys.py @@ -0,0 +1,74 @@ +""" +The Android keyring against the desktop's, where the shared vectors cannot see. + +`tests/vectors/keyring.json` holds the bytes (and the Android unit tests read +it). What a vector cannot hold is a *list*: which admin operations may be +signed at all, and the Argon2 parameters a format change would move. Two +implementations of a list drift silently — an operation the desktop stopped +signing at MNP 6.0 and Android still signs is a script in the page driving an +older node into widening its sharing. So both are read from source and +compared. +""" + +import re +from pathlib import Path + +import pytest + +PACKAGES = Path(__file__).resolve().parents[2] +MAIN = PACKAGES / "meshbay-android" / "app" / "src" / "main" +KEYS = MAIN / "kotlin" / "org" / "meshbay" / "client" / "keys" +CLIENT = PACKAGES / "meshbay-client" / "src" +SHIM = MAIN / "assets" / "bridge" / "meshbay-bridge.js" + +pytestmark = pytest.mark.skipif(not KEYS.exists(), reason="android sources not present") + + +def _read(path: Path) -> str: + return path.read_text(encoding="utf-8") + + +def test_the_same_admin_operations_are_signed(): + js = _read(CLIENT / "transcripts.js") + js = js.split("const ADMIN_OPS = new Set([", 1)[1].split("]", 1)[0] + kt = _read(KEYS / "Transcripts.kt").split("val ADMIN_OPS = setOf(", 1)[1].split(")", 1)[0] + desktop = set(re.findall(r"'([a-z_]+)'", js)) + android = set(re.findall(r'"([a-z_]+)"', kt)) + assert desktop and android == desktop, android ^ desktop + # The ones MNP 6.0 took away must not come back on either side. + assert not {"root_add", "root_update", "group_attach"} & android + + +def test_the_argon2_parameters_are_the_desktops(): + js = _read(CLIENT / "keyring.js") + m = re.search(r"memory: (\d+), passes: (\d+), parallelism: (\d+), tagLength: (\d+)", js) + kt = _read(KEYS / "Kdf.kt") + want = dict(zip(("MEMORY_KIB", "PASSES", "PARALLELISM", "TAG"), m.groups())) + for name, value in want.items(): + assert re.search(rf"const val ARGON2_{name} = {value}\b", kt), name + + +def test_the_shim_offers_the_desktops_key_surface(): + preload = _read(CLIENT / "preload.js") + shim = _read(SHIM) + + def channels(text: str, call: str) -> set[str]: + return set(re.findall(rf"{call}\('((?:keys|device|secrets):[\w:-]+)'", text)) + + assert channels(shim, "call") == channels(preload, r"ipcRenderer\.invoke") + + +def test_signing_is_by_kind_and_no_private_key_is_returned(): + channels = _read(KEYS.parent / "bridge" / "KeyChannels.kt") + assert '"keys:sign" -> keyring.signAs(' in channels + # No channel hands the page a stored key: the store's slots are never a + # return value, and identity/mint/open answer with public keys. + assert "secrets.read()" in channels # the keyring's load, and nothing else + assert channels.count("secrets.read()") == 1 + assert '"pkEdB64"' in channels and '"skEd"' not in channels + + +def test_widening_browser_access_is_confirmed_natively(): + channels = _read(KEYS.parent / "bridge" / "KeyChannels.kt") + branch = channels.split('"keys:set-browser-access" ->', 1)[1].split('"keys:created-here"', 1)[0] + assert 'confirm("native.browser_access_confirm")' in branch diff --git a/packages/meshbay-hub/tests/test_android_shell.py b/packages/meshbay-hub/tests/test_android_shell.py index 53b3914..ef7355d 100644 --- a/packages/meshbay-hub/tests/test_android_shell.py +++ b/packages/meshbay-hub/tests/test_android_shell.py @@ -133,8 +133,9 @@ def _shim_channels() -> set[str]: def test_the_shim_offers_desktop_channels_and_native_answers_each(): preload_js = _read(CLIENT / "src" / "preload.js") preload = set(re.findall(r"ipcRenderer\.invoke\('([\w:-]+)'", preload_js)) - channels_kt = _read(SRC / "bridge" / "Channels.kt") - native = set(re.findall(r'^\s*"([\w:-]+)" ->', channels_kt, flags=re.M)) + native = set() + for name in ("Channels.kt", "KeyChannels.kt"): + native |= set(re.findall(r'^\s*"([\w:-]+)" ->', _read(SRC / "bridge" / name), flags=re.M)) shim = _shim_channels() assert shim, "no channel found in the shim" assert shim <= preload, f"channels the desktop does not have: {shim - preload}" |