89 lines
2.6 KiB
JavaScript
89 lines
2.6 KiB
JavaScript
#!/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 <server> <email> <password>");
|
|
}
|
|
|
|
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();
|