aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/keyderive.js
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/keyderive.js')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/keyderive.js109
1 files changed, 36 insertions, 73 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/keyderive.js b/packages/meshbay-hub/src/meshbay_hub/static/keyderive.js
index af119c7..a27522d 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/keyderive.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/keyderive.js
@@ -189,34 +189,43 @@ async function decryptBundle(bundleB64, password, username) {
* Returns the raw private keys for immediate use after registration.
*/
async function registerUser(username, email, password) {
- const { skEdRaw, pkEdRaw, skXRaw, pkXRaw } = await generateKeypairs();
-
- const pkEdCrypto = await crypto.subtle.importKey('spki', pkEdRaw, 'Ed25519', true, ['verify']);
- const pkXCrypto = await crypto.subtle.importKey('spki', pkXRaw, 'X25519', true, []);
- const pkEdBytes = new Uint8Array(await crypto.subtle.exportKey('raw', pkEdCrypto));
- const pkXBytes = new Uint8Array(await crypto.subtle.exportKey('raw', pkXCrypto));
-
- const encBundle = await encryptBundle(skEdRaw, skXRaw, password, username);
+ // No keypair here any more. Identity keys are per node: one is generated the
+ // first time this account joins a given node, encrypted under the passphrase,
+ // and left with that node. So an operator who cracks what sits on their own
+ // disk holds a key that is worthless anywhere else — and on their own node,
+ // one that unlocks nothing they did not already have.
+ //
+ // It also means the hub stores no user key to publish, which is what H3 read.
const authKey = await deriveAuthKey(password, username);
const resp = await fetch(`${HUB}/v1/users/register`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({
- username,
- email,
- auth_key: authKey,
- pk_user_ed25519: btoa(String.fromCharCode(...pkEdBytes)),
- pk_user_x25519: btoa(String.fromCharCode(...pkXBytes)),
- }),
+ body: JSON.stringify({ username, email, auth_key: authKey }),
});
if (!resp.ok) throw new Error(`Registration failed: ${await resp.text()}`);
+ return { registered: true };
+}
- // Store encrypted bundle locally — will be backed up to node on first group connect
- try { localStorage.setItem(`meshbay_kp_${username}`, encBundle); } catch {}
-
- return { skEdRaw, skXRaw, pkEdBytes, pkXBytes, keypairBundleEnc: encBundle };
+/**
+ * A fresh identity for one node, encrypted under the passphrase-derived key.
+ *
+ * Returns { skEdB64, skXB64, pkXB64, bundleEnc } — the bundle goes to that node
+ * and nowhere else, and is what any other browser fetches to become the same
+ * person there.
+ */
+async function generateNodeIdentity(bundleKey) {
+ const { skEdRaw, pkEdRaw, skXRaw, pkXRaw } = await generateKeypairs();
+ const b64 = (buf) => btoa(String.fromCharCode(...new Uint8Array(buf)));
+ const pkXCrypto = await crypto.subtle.importKey('spki', pkXRaw, { name: 'X25519' }, true, []);
+ const pkXBytes = new Uint8Array(await crypto.subtle.exportKey('raw', pkXCrypto));
+ return {
+ skEdB64: b64(skEdRaw),
+ skXB64: b64(skXRaw),
+ pkXB64: b64(pkXBytes),
+ bundleEnc: await encryptBundleWithKey(skEdRaw, skXRaw, bundleKey.v2 || bundleKey),
+ };
}
/**
@@ -269,63 +278,17 @@ async function loginAndRecover(username, password) {
},
};
- // localStorage bundle = new registration, not yet pushed to node
- const bundleEnc = (typeof localStorage !== 'undefined'
- && localStorage.getItem(`meshbay_kp_${username}`)) || null;
-
- if (bundleEnc) {
- // Reuse the keys just derived — decryptBundle() would run the KDF again,
- // and at these parameters that is another 0.6 s for nothing.
- const keys = await decryptBundleWithKey(bundleEnc, result.bundleKey);
- result.skEdB64 = keys.skEd;
- result.skXB64 = keys.skX;
- result.keypairBundleEnc = bundleEnc;
- }
-
+ // Nothing else to recover at sign-in. Identity keys belong to a node, so they
+ // are fetched from the node being connected to (or generated there on a first
+ // join) — see transport.js. All that is needed here is the key that opens them.
return result;
}
-async function regenerateKeys(token, username, password) {
- const { skEdRaw, pkEdRaw, skXRaw, pkXRaw } = await generateKeypairs();
+// regenerateKeys() removed. Rotating an identity is now per node: the operator
+// runs `meshbay-node member unpin <user>` and issues a fresh code. A hub call
+// that silently changed what every node believed about someone was the wrong
+// shape for this.
- const pkEdCrypto = await crypto.subtle.importKey('spki', pkEdRaw, 'Ed25519', true, ['verify']);
- const pkXCrypto = await crypto.subtle.importKey('spki', pkXRaw, 'X25519', true, []);
- const pkEdBytes = new Uint8Array(await crypto.subtle.exportKey('raw', pkEdCrypto));
- const pkXBytes = new Uint8Array(await crypto.subtle.exportKey('raw', pkXCrypto));
-
- const resp = await fetch(`${HUB}/v1/users/me/keys`, {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': `Bearer ${token}`,
- },
- body: JSON.stringify({
- pk_user_ed25519: btoa(String.fromCharCode(...pkEdBytes)),
- pk_user_x25519: btoa(String.fromCharCode(...pkXBytes)),
- }),
- });
-
- if (!resp.ok) throw new Error(`Key rotation failed: ${await resp.text()}`);
-
- const encBundle = await encryptBundle(skEdRaw, skXRaw, password, username);
- try { localStorage.setItem(`meshbay_kp_${username}`, encBundle); } catch {}
-
- return {
- skEdB64: btoa(String.fromCharCode(...new Uint8Array(skEdRaw))),
- skXB64: btoa(String.fromCharCode(...new Uint8Array(skXRaw))),
- pkEdB64: btoa(String.fromCharCode(...pkEdBytes)),
- pkXB64: btoa(String.fromCharCode(...pkXBytes)),
- keypairBundleEnc: encBundle,
- };
-}
-
-/**
- * Sign an explicit byte string with the user's Ed25519 identity key.
- *
- * Takes bytes rather than a base64 blob from the wire: callers are expected to
- * build the message themselves (see MeshBayCrypto.adminTranscript) so that the
- * user's identity key is never applied to content the peer chose. Finding H5.
- */
async function signBytes(skEdPkcs8B64, message) {
const skRaw = Uint8Array.from(atob(skEdPkcs8B64), c => c.charCodeAt(0));
const sk = await crypto.subtle.importKey(
@@ -335,6 +298,6 @@ async function signBytes(skEdPkcs8B64, message) {
}
window.MeshBayKeys = {
- registerUser, loginAndRecover, regenerateKeys, generateKeypairs, signBytes,
+ registerUser, loginAndRecover, generateNodeIdentity, generateKeypairs, signBytes,
deriveAuthKey, decryptBundleWithKey, encryptBundleWithKey, bundleVersion,
};