From 91297944791a36f30302ef8c86dd69ebeb177671 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Wed, 30 Sep 2026 15:06:14 +0200 Subject: feat: bundles sealed per node under the passphrase and the hub's pepper The session key is M = HKDF(Argon2(passphrase) || pepper, account id); each node's bundle key and the playlist key derive from it. Bundles are MBK3, bound to account and node; MBK1/MBK2 are refused by name, never replaced silently. Playlists move to key v2 and are re-sealed over unreadable node copies. Co-Authored-By: Claude Opus 5.5 --- .../src/meshbay_hub/static/playlist-crypto.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) (limited to 'packages/meshbay-hub/src/meshbay_hub/static/playlist-crypto.js') diff --git a/packages/meshbay-hub/src/meshbay_hub/static/playlist-crypto.js b/packages/meshbay-hub/src/meshbay_hub/static/playlist-crypto.js index 0f41d1e..6323b96 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/playlist-crypto.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/playlist-crypto.js @@ -47,21 +47,23 @@ const PAD_TO = 4096; const NONCE_BYTES = 12; /** - * The playlist key, from the HKDF handle over the bundle key. + * The playlist key, from the session's bundle master key `M` + * (keyderive.js `deriveBundleSessionKey`). * - * A purpose-separated subkey rather than the bundle key reused with a different + * A purpose-separated subkey rather than a bundle key reused with a different * AAD — the rule `groupbox.py` writes down for chunk keys, for the same reason. - * v2 only: playlists are new, so there is no legacy blob and no v1 branch to - * take by mistake. + * `v2`: the v1 key came from the passphrase alone, so a blob sealed under it + * was a second offline oracle for the passphrase on every node. Such a blob no + * longer opens, and the local copy is sealed again over it (playlists.js). */ -async function derivePlaylistKey(hkdfHandle) { +async function derivePlaylistKey(masterKey) { return crypto.subtle.deriveKey( { name: 'HKDF', hash: 'SHA-256', salt: new Uint8Array(0), - info: new TextEncoder().encode('meshbay:playlists:v1'), + info: new TextEncoder().encode('meshbay:playlists:v2'), }, - hkdfHandle, { name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt']); + masterKey, { name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt']); } /** -- cgit v1.2.3