mirror of
https://github.com/silicoflare/confidant.git
synced 2026-05-26 12:45:24 +05:30
feat: new init and decrypt
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -176,3 +176,4 @@ dist
|
|||||||
|
|
||||||
# confidant
|
# confidant
|
||||||
env.ts
|
env.ts
|
||||||
|
test
|
||||||
|
|||||||
47
confidant.ts
47
confidant.ts
@@ -1,12 +1,17 @@
|
|||||||
#!/bin/env bun
|
#!/bin/env bun
|
||||||
|
|
||||||
import { input, password, select } from "@inquirer/prompts";
|
import { checkbox, input, password, select } from "@inquirer/prompts";
|
||||||
import { $ } from "bun";
|
import { $ } from "bun";
|
||||||
import chalk from "chalk-template";
|
import chalk from "chalk-template";
|
||||||
import { Command } from "commander";
|
import { Command } from "commander";
|
||||||
import { decrypt_diary, encrypt_diary, initialize, recovery } from "./src/main";
|
import { decrypt_diary, encrypt_diary, initialize, recovery } from "./src/main";
|
||||||
import { existsSync } from "fs";
|
import { existsSync } from "fs";
|
||||||
import checkForFiles, { log, panic } from "./src/utils";
|
import checkForFiles, {
|
||||||
|
getDirectoryNames,
|
||||||
|
getVaultName,
|
||||||
|
log,
|
||||||
|
panic,
|
||||||
|
} from "./src/utils";
|
||||||
|
|
||||||
const program = new Command();
|
const program = new Command();
|
||||||
const { exit } = process;
|
const { exit } = process;
|
||||||
@@ -18,33 +23,8 @@ program
|
|||||||
.command("init")
|
.command("init")
|
||||||
.description("initialize a confidant vault")
|
.description("initialize a confidant vault")
|
||||||
.action(async () => {
|
.action(async () => {
|
||||||
// check if a vault already exists
|
const dirname = (await getDirectoryNames()) as string;
|
||||||
if (await $`test -e config.toml`) {
|
|
||||||
console.log(
|
|
||||||
chalk`{red A Confidant vault already exists in this directory.}`,
|
|
||||||
);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// select a directory
|
|
||||||
let dirs;
|
|
||||||
try {
|
|
||||||
dirs = (await $`ls -d */`.text()).trim();
|
|
||||||
} catch (e) {
|
|
||||||
console.error(
|
|
||||||
'No directories found in current directory, please create one and run "init" again.',
|
|
||||||
);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let dirlist = dirs.split("\n");
|
|
||||||
const dirname = await select({
|
|
||||||
message: "Select a directory to use:",
|
|
||||||
choices: dirlist.map((x) => ({
|
|
||||||
name: x,
|
|
||||||
value: x,
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
const pass = await password({
|
const pass = await password({
|
||||||
message: chalk`{reset {yellow Enter a password to use:}}`,
|
message: chalk`{reset {yellow Enter a password to use:}}`,
|
||||||
mask: "•",
|
mask: "•",
|
||||||
@@ -54,8 +34,7 @@ program
|
|||||||
mask: "•",
|
mask: "•",
|
||||||
});
|
});
|
||||||
if (pass !== confpass) {
|
if (pass !== confpass) {
|
||||||
console.log(chalk`Passwords don't math.`);
|
panic`Passwords don't match. Exiting...`;
|
||||||
exit(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await initialize(pass, dirname);
|
await initialize(pass, dirname);
|
||||||
@@ -66,14 +45,18 @@ program
|
|||||||
.command("decrypt")
|
.command("decrypt")
|
||||||
.description("decrypt the vault")
|
.description("decrypt the vault")
|
||||||
.option("-l, --live", "decrypt in live mode")
|
.option("-l, --live", "decrypt in live mode")
|
||||||
|
.option("-v, --vault <name>", "name of the vault to decrypt")
|
||||||
.action(async (args) => {
|
.action(async (args) => {
|
||||||
checkForFiles();
|
let files: string[];
|
||||||
|
if (!args.vault) {
|
||||||
|
args.vault = await getVaultName();
|
||||||
|
}
|
||||||
|
|
||||||
const pass = await password({
|
const pass = await password({
|
||||||
message: chalk`{reset {yellow Enter the password:}}`,
|
message: chalk`{reset {yellow Enter the password:}}`,
|
||||||
mask: "•",
|
mask: "•",
|
||||||
});
|
});
|
||||||
await decrypt_diary(pass);
|
await decrypt_diary(pass, args.vault);
|
||||||
if (args.live) {
|
if (args.live) {
|
||||||
await input({
|
await input({
|
||||||
message: chalk`{yellow Live mode started. Press ENTER to encrypt}`,
|
message: chalk`{yellow Live mode started. Press ENTER to encrypt}`,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"init": "bun ./scripts/init.ts",
|
"init": "bun ./scripts/init.ts",
|
||||||
|
"sh": "bun ./scripts/sh.ts",
|
||||||
"build": "bun ./scripts/build.ts"
|
"build": "bun ./scripts/build.ts"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|||||||
162
src/main.ts
162
src/main.ts
@@ -14,7 +14,9 @@ import {
|
|||||||
hmac,
|
hmac,
|
||||||
log,
|
log,
|
||||||
panic,
|
panic,
|
||||||
|
print,
|
||||||
random,
|
random,
|
||||||
|
separator,
|
||||||
stringy,
|
stringy,
|
||||||
} from "./utils";
|
} from "./utils";
|
||||||
import env from "../env";
|
import env from "../env";
|
||||||
@@ -27,136 +29,104 @@ console.info = function () {};
|
|||||||
const { exit } = process;
|
const { exit } = process;
|
||||||
|
|
||||||
export async function initialize(password: string, dirname: string) {
|
export async function initialize(password: string, dirname: string) {
|
||||||
// compressing and encrypting diary
|
// compressing and encrypting vault
|
||||||
const [K_C, P_C] = ECDH();
|
const [K_A, P_A] = ECDH();
|
||||||
const [K_F, P_F] = ECDH();
|
const [K_B, P_B] = ECDH();
|
||||||
const S_CF = await derive(K_C, P_F);
|
const S_AB = await derive(K_A, P_B);
|
||||||
const fidsalt = randomBytes(32);
|
const salt = randomBytes(32);
|
||||||
const fidcode = random(10000, 100000);
|
const code = random(10000, 100000);
|
||||||
const D = pbkdf2(S_CF, fidsalt, fidcode, 64, "sha256");
|
const D = pbkdf2(S_AB, salt, code, 64, "sha256");
|
||||||
await $`zip -r9 confidant.zip ${dirname} > /dev/null`;
|
await $`zip -r9 confidant.zip ${dirname} > /dev/null`;
|
||||||
await $`rm -rf ${dirname}`;
|
await $`rm -rf ${dirname}`;
|
||||||
const Z = readFileSync("confidant.zip");
|
const Z = readFileSync("confidant.zip");
|
||||||
const E_Z = encrypt_file(Z, D);
|
const E_Z = encrypt_file(Z, D);
|
||||||
writeFileSync(`vault.ant`, E_Z);
|
|
||||||
|
|
||||||
// creating key.fid
|
// creating dirname.key
|
||||||
const fidData = {
|
const keyfile = {
|
||||||
privateKey: stringy(K_F),
|
salt: stringy(salt),
|
||||||
salt: stringy(fidsalt),
|
code: code,
|
||||||
code: fidcode,
|
privateKey: stringy(K_A),
|
||||||
};
|
};
|
||||||
const fidkey = randomBytes(32);
|
|
||||||
const consalt = randomBytes(32);
|
|
||||||
const D_C = hmac(consalt, fidkey);
|
|
||||||
const E_F = encrypt_file(buffer(stringify(fidData), "utf8"), D_C);
|
|
||||||
writeFileSync(`key.fid`, E_F);
|
|
||||||
|
|
||||||
// create data.con
|
|
||||||
const auth_secret = randomBytes(32);
|
const auth_secret = randomBytes(32);
|
||||||
const conData = {
|
const confsalt = randomBytes(64);
|
||||||
privateKey: stringy(K_C),
|
const K = hmac(auth_secret, confsalt);
|
||||||
fidkey: stringy(fidkey),
|
const E_K = encrypt_file(Buffer.from(JSON.stringify(keyfile), "utf8"), K);
|
||||||
consalt: stringy(consalt),
|
writeFileSync(`${dirname}.key`, E_K);
|
||||||
};
|
|
||||||
const D_U = hmac(auth_secret, buffer(env.AUTH_KEY));
|
|
||||||
const E_C = encrypt_file(buffer(stringify(conData), "utf8"), D_U);
|
|
||||||
writeFileSync("data.con", E_C);
|
|
||||||
|
|
||||||
|
// creating config data
|
||||||
const recovery_phrase = generate_recovery_phrase();
|
const recovery_phrase = generate_recovery_phrase();
|
||||||
writeFileSync("recovery.txt", recovery_phrase + "\n");
|
writeFileSync(`${dirname}_recovery.txt`, recovery_phrase + "\n");
|
||||||
const recovery_auth_key = HmacSHA256(recovery_phrase, env.AUTH_KEY).toString(
|
const recovery_auth_key = HmacSHA256(recovery_phrase, env.AUTH_KEY).toString(
|
||||||
enc.Base64,
|
enc.Base64,
|
||||||
);
|
);
|
||||||
const password_auth_key = HmacSHA256(password, env.AUTH_KEY).toString(
|
const password_auth_key = HmacSHA256(password, env.AUTH_KEY).toString(
|
||||||
enc.Base64,
|
enc.Base64,
|
||||||
);
|
);
|
||||||
|
const D_C = {
|
||||||
// create config.toml
|
dir: dirname,
|
||||||
const configData = {
|
confsalt: stringy(confsalt),
|
||||||
config: {
|
privateKey: stringy(K_B),
|
||||||
con: "data.con",
|
keystore: stringy(encrypt(auth_secret, buffer(password_auth_key))),
|
||||||
fid: "key.fid",
|
recoverystore: stringy(encrypt(auth_secret, buffer(recovery_auth_key))),
|
||||||
ant: "vault.ant",
|
phrasestore: AES.encrypt(env.PHRASE, password_auth_key).toString(),
|
||||||
dir: dirname,
|
recphrasestore: AES.encrypt(env.PHRASE, recovery_auth_key).toString(),
|
||||||
keystore: stringy(encrypt(auth_secret, buffer(password_auth_key))),
|
|
||||||
recoverystore: stringy(encrypt(auth_secret, buffer(recovery_auth_key))),
|
|
||||||
phrasestore: AES.encrypt(env.PHRASE, password_auth_key).toString(),
|
|
||||||
recphrasestore: AES.encrypt(env.PHRASE, recovery_auth_key).toString(),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
writeFileSync("config.toml", buffer(stringify(configData), "utf8"));
|
|
||||||
|
// create dirname.vault
|
||||||
|
writeFileSync(
|
||||||
|
`${dirname}.vault`,
|
||||||
|
Buffer.concat([Buffer.from(JSON.stringify(D_C)), separator, E_Z]),
|
||||||
|
);
|
||||||
|
|
||||||
console.log(`Recovery phrase:`);
|
console.log(`Recovery phrase:`);
|
||||||
console.log(chalk`{magenta ${recovery_phrase}}`);
|
console.log(chalk`{magenta ${recovery_phrase}}`);
|
||||||
await $`rm confidant.zip`;
|
await $`rm confidant.zip`;
|
||||||
|
|
||||||
|
// create .gitignore
|
||||||
const gitignore = `# .gitignore
|
const gitignore = `# .gitignore
|
||||||
|
|
||||||
data.con
|
*.con
|
||||||
key.fid
|
*.fid
|
||||||
recovery.txt
|
*_recovery.txt
|
||||||
confidant.zip
|
confidant.zip
|
||||||
.confidant
|
*.confidant
|
||||||
${dirname}
|
|
||||||
.env
|
|
||||||
`;
|
`;
|
||||||
writeFileSync(".gitignore", gitignore);
|
writeFileSync(`.gitignore`, gitignore);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function decrypt_diary(password: string) {
|
export async function decrypt_diary(password: string, dirname: string) {
|
||||||
|
// Read and split the vault file
|
||||||
|
const combined = readFileSync(`${dirname}.vault`);
|
||||||
|
const index = combined.indexOf(separator);
|
||||||
|
const D_C = combined.subarray(0, index).toString("utf8");
|
||||||
|
const E_Z = combined.subarray(index + separator.length);
|
||||||
|
|
||||||
// Check if password is correct
|
// Check if password is correct
|
||||||
const config = Object(
|
|
||||||
parse(readFileSync("config.toml").toString("utf8")),
|
|
||||||
) as Config;
|
|
||||||
const password_auth_key = HmacSHA256(password, env.AUTH_KEY).toString(
|
const password_auth_key = HmacSHA256(password, env.AUTH_KEY).toString(
|
||||||
enc.Base64,
|
enc.Base64,
|
||||||
);
|
);
|
||||||
const dec = AES.decrypt(
|
const config = JSON.parse(D_C);
|
||||||
config.config.phrasestore,
|
const phrase = AES.decrypt(config.phrasestore, password_auth_key).toString(
|
||||||
password_auth_key,
|
enc.Utf8,
|
||||||
).toString(enc.Utf8);
|
);
|
||||||
if (dec !== env.PHRASE) {
|
if (phrase !== env.PHRASE) {
|
||||||
panic`Wrong password. Try again or reset it.`;
|
panic`Incorrect password. Try again or reset it.`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decrypt data.con
|
const auth_secret = decrypt(config.keystore, buffer(password_auth_key));
|
||||||
const auth_secret = decrypt(
|
const K = hmac(auth_secret, config.confsalt);
|
||||||
buffer(config.config.keystore),
|
const E_K = readFileSync(`${dirname}.key`);
|
||||||
buffer(password_auth_key),
|
const keyfile = JSON.parse(decrypt_file(E_K, K).toString("utf8"));
|
||||||
);
|
|
||||||
const D_U = hmac(auth_secret, buffer(env.AUTH_KEY));
|
|
||||||
const conData = Object(
|
|
||||||
parse(decrypt_file(readFileSync(config.config.con), D_U).toString("utf8")),
|
|
||||||
) as ConData;
|
|
||||||
|
|
||||||
// Decrypt key.fid
|
const K_A = buffer(keyfile.privateKey);
|
||||||
const { consalt, fidkey, privateKey: conK } = conData;
|
const K_B = buffer(config.privateKey);
|
||||||
const D_C = hmac(buffer(consalt), buffer(fidkey));
|
const P_B = getPublic(K_B);
|
||||||
const fidData = Object(
|
const S_AB = await derive(K_A, P_B);
|
||||||
parse(decrypt_file(readFileSync(config.config.fid), D_C).toString("utf8")),
|
const D = pbkdf2(S_AB, buffer(keyfile.salt), keyfile.code, 64, "sha256");
|
||||||
) as FidData;
|
|
||||||
const { privateKey: fidK, salt, code } = fidData;
|
|
||||||
|
|
||||||
// Decrypt diary.ant
|
const Z = decrypt_file(E_Z, D);
|
||||||
const S_CF = await derive(buffer(conK), getPublic(buffer(fidK)));
|
writeFileSync(`confidant.zip`, Z);
|
||||||
const D = pbkdf2(S_CF, buffer(salt), code, 64, "sha256");
|
writeFileSync(`.${dirname}.confidant`, encrypt(D, buffer(env.AUTH_KEY)));
|
||||||
writeFileSync(
|
|
||||||
".confidant",
|
|
||||||
encrypt(
|
|
||||||
Buffer.from(
|
|
||||||
JSON.stringify({
|
|
||||||
dirname: config.config.dir,
|
|
||||||
key: stringy(D),
|
|
||||||
}),
|
|
||||||
"utf8",
|
|
||||||
),
|
|
||||||
buffer(env.AUTH_KEY),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
writeFileSync(
|
|
||||||
"confidant.zip",
|
|
||||||
decrypt_file(readFileSync(config.config.ant), D),
|
|
||||||
);
|
|
||||||
await $`unzip confidant.zip > /dev/null && rm confidant.zip`;
|
await $`unzip confidant.zip > /dev/null && rm confidant.zip`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
83
src/utils.ts
83
src/utils.ts
@@ -5,6 +5,7 @@ import { generate } from "random-words";
|
|||||||
import chalk from "chalk-template";
|
import chalk from "chalk-template";
|
||||||
import { $ } from "bun";
|
import { $ } from "bun";
|
||||||
import { existsSync } from "fs";
|
import { existsSync } from "fs";
|
||||||
|
import { select } from "@inquirer/prompts";
|
||||||
|
|
||||||
export function buffer(input: string): Buffer;
|
export function buffer(input: string): Buffer;
|
||||||
export function buffer(input: string, encoding: BufferEncoding): Buffer;
|
export function buffer(input: string, encoding: BufferEncoding): Buffer;
|
||||||
@@ -69,8 +70,11 @@ export async function exists(dir: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function log(str: TemplateStringsArray, ...placeholders: unknown[]) {
|
export function log(
|
||||||
console.log(chalk(str, placeholders));
|
str: TemplateStringsArray,
|
||||||
|
...placeholders: unknown[]
|
||||||
|
): void {
|
||||||
|
console.log(chalk(str as TemplateStringsArray, ...placeholders));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function panic(str: TemplateStringsArray) {
|
export function panic(str: TemplateStringsArray) {
|
||||||
@@ -92,3 +96,78 @@ export default function checkForFiles() {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const separator = Buffer.from([
|
||||||
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff,
|
||||||
|
]);
|
||||||
|
|
||||||
|
export function print(key: string, value: string) {
|
||||||
|
console.log(chalk`{green ${key}:} {blue ${value}}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getDirectoryNames() {
|
||||||
|
const dirlist = (await $`ls -d */`.text()).trim();
|
||||||
|
|
||||||
|
if (!dirlist) {
|
||||||
|
panic`No directories found in current directory, please create one and run "init" again.`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let dirs: string[];
|
||||||
|
const vaultList = (await $`ls *.vault`.text()).trim().match(/.*\.vault/g);
|
||||||
|
if (vaultList) {
|
||||||
|
const vaults = vaultList.map((x) => x.replace(".vault", ""));
|
||||||
|
dirs = Array.from(new Set(dirlist.split("\n")).difference(new Set(vaults)));
|
||||||
|
} else {
|
||||||
|
dirs = dirlist.split("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dirs.length === 0) {
|
||||||
|
panic`No usable directories found in current directory, please create one and run "init" again.`;
|
||||||
|
return;
|
||||||
|
} else if (dirs.length === 1) {
|
||||||
|
return dirs[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
const dirname = await select({
|
||||||
|
message: "Select a directory to use:",
|
||||||
|
choices: dirs.map((x) => ({
|
||||||
|
name: x,
|
||||||
|
value: x,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
|
||||||
|
return dirname;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getVaultName() {
|
||||||
|
const vaultList = (await $`ls *.vault`.text()).trim().match(/.*\.vault/g);
|
||||||
|
if (!vaultList) {
|
||||||
|
panic`No vaults found in the current directory.`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let files: string[];
|
||||||
|
const decryptedList = (await $`bash -c "ls .*.confidant"`.text())
|
||||||
|
.trim()
|
||||||
|
.match(/\.(.*)\.confidant/g);
|
||||||
|
if (decryptedList) {
|
||||||
|
const vaults = vaultList.map((x) => x.replace(".vault", ""));
|
||||||
|
const decs = decryptedList.map((x) => x.replace(/\.(.*)\.confidant/, "$1"));
|
||||||
|
files = Array.from(new Set(vaults).difference(new Set(decs)));
|
||||||
|
} else {
|
||||||
|
files = vaultList.map((x) => x.replace(".vault", ""));
|
||||||
|
}
|
||||||
|
if (files.length === 1) {
|
||||||
|
return files[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
const vault = await select({
|
||||||
|
message: "Select vault to decrypt:",
|
||||||
|
choices: files.map((x) => ({
|
||||||
|
name: x.replace(".vault", ""),
|
||||||
|
value: x.replace(".vault", ""),
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
return vault;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user