#!/usr/bin/env node import { createCipheriv, createHmac, generateKeyPairSync, pbkdf2Sync, randomBytes, } from "node:crypto"; function hkdfExpand(key, info, length) { const output = []; let previous = Buffer.alloc(0); let counter = 1; while (Buffer.concat(output).length < length) { previous = createHmac("sha256", key) .update(Buffer.concat([previous, Buffer.from(info, "utf8"), Buffer.from([counter])])) .digest(); output.push(previous); counter += 1; } return Buffer.concat(output).subarray(0, length); } function encrypt(plaintext, key) { const encryptionKey = key.subarray(0, 32); const macKey = key.subarray(32, 64); const iv = randomBytes(16); const cipher = createCipheriv("aes-256-cbc", encryptionKey, iv); const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]); const mac = createHmac("sha256", macKey).update(Buffer.concat([iv, ciphertext])).digest(); return `2.${iv.toString("base64")}|${ciphertext.toString("base64")}|${mac.toString("base64")}`; } async function main() { const [server, rawEmail, password] = process.argv.slice(2); if (!server || !rawEmail || !password) { throw new Error("Usage: create_test_vault_account.mjs "); } const email = rawEmail.trim().toLowerCase(); const iterations = 600_000; const masterKey = pbkdf2Sync(password, email, iterations, 32, "sha256"); const masterPasswordHash = pbkdf2Sync(masterKey, password, 1, 32, "sha256").toString( "base64", ); const stretchedMasterKey = Buffer.concat([ hkdfExpand(masterKey, "enc", 32), hkdfExpand(masterKey, "mac", 32), ]); const userKey = randomBytes(64); const key = encrypt(userKey, stretchedMasterKey); const { publicKey, privateKey } = generateKeyPairSync("rsa", { modulusLength: 2048, publicKeyEncoding: { type: "spki", format: "der" }, privateKeyEncoding: { type: "pkcs8", format: "der" }, }); const response = await fetch(`${server.replace(/\/$/, "")}/identity/accounts/register`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email, name: "MemRelay Stage Zero", masterPasswordHash, masterPasswordHint: null, key, keys: { encryptedPrivateKey: encrypt(privateKey, userKey), publicKey: publicKey.toString("base64"), }, kdf: 0, kdfIterations: iterations, kdfMemory: null, kdfParallelism: null, }), }); const body = await response.text(); if (!response.ok) { throw new Error(`Vault registration failed (${response.status}): ${body}`); } console.log(body); } await main();