feat: error correction and new features to init, decrypt and encrypt

This commit is contained in:
2024-09-30 19:42:56 +05:30
parent 009f16796c
commit 3a90ed0d61
4 changed files with 171 additions and 99 deletions

View File

@@ -5,13 +5,17 @@ import { $ } from "bun";
import chalk from "chalk-template"; import chalk from "chalk-template";
import { Command } from "commander"; import { Command } from "commander";
import { decrypt_vault, encrypt_vault, initialize, recovery } from "./src/main"; import { decrypt_vault, encrypt_vault, initialize, recovery } from "./src/main";
import { existsSync } from "fs"; import { existsSync, readdirSync } from "fs";
import checkForFiles, { import checkForFiles, {
Files,
getDecryptedName, getDecryptedName,
getDirectoryNames, getDirectoryNames,
getRandomPassword,
getVaultName, getVaultName,
log,
panic, panic,
} from "./src/utils"; } from "./src/utils";
import { randomBytes } from "crypto";
const program = new Command(); const program = new Command();
const { exit } = process; const { exit } = process;
@@ -22,16 +26,29 @@ program.name("confidant").description("Creates a very secure file vault.");
program program
.command("init") .command("init")
.description("initialize a confidant vault") .description("initialize a confidant vault")
.action(async () => { .argument("[directory]", "Directory to use to create a vault")
const dirname = (await getDirectoryNames()) as string; .action(async (dirname) => {
if (!dirname) {
dirname = (await getDirectoryNames()) as string;
}
const selectedDir = readdirSync(".").filter((x) => x.match(dirname))[0];
const pass = await password({ if (!selectedDir) {
console.log(
chalk`{red Directory "${dirname}" not found in current location.}`,
);
exit(1);
}
log`{blue Using "{green ${dirname}}" to create a vault...}`;
const genPass = getRandomPassword(14);
const pass = await input({
message: chalk`{reset {yellow Enter a password to use:}}`, message: chalk`{reset {yellow Enter a password to use:}}`,
mask: "•", default: genPass,
}); });
const confpass = await password({ const confpass = await input({
message: chalk`{reset {yellow Enter the password again:}}`, message: chalk`{reset {yellow Enter the password again:}}`,
mask: "•", default: genPass,
}); });
if (pass !== confpass) { if (pass !== confpass) {
panic`Passwords don't match. Exiting...`; panic`Passwords don't match. Exiting...`;
@@ -44,24 +61,35 @@ program
program program
.command("decrypt") .command("decrypt")
.description("decrypt the vault") .description("decrypt the vault")
.argument("[vault]", "Name of the vault to decrypt")
.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, opts) => {
.action(async (args) => { if (!args) {
if (!args.vault) { args = await getVaultName();
args.vault = await getVaultName(); } else {
const vaults = new Files(/(.*).vault/g).intersection(
new Files(/(.*).key/g),
).data as string[];
if (!vaults.includes(args)) {
console.log(
chalk`{red Vault "${args}" not found in current directory.}`,
);
exit(1);
}
} }
log`{blue Decrypting "{green ${args}}"...}`;
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_vault(pass, args.vault); await decrypt_vault(pass, args);
if (args.live) { if (opts.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:}`,
}); });
await encrypt_vault(args.vault); await encrypt_vault(args);
console.log(chalk`{green Successfully encrypted!}`); console.log(chalk`{green Encrypted successfully!}`);
} else { } else {
console.log(chalk`{green Decrypted sucessfully!}`); console.log(chalk`{green Decrypted sucessfully!}`);
} }
@@ -70,13 +98,23 @@ program
program program
.command("encrypt") .command("encrypt")
.description("encrypt the vault") .description("encrypt the vault")
.option("-v, --vault <name>", "name of the vault to decrypt") .argument("[vault]", "Name of the vault to decrypt")
.action(async (args) => { .action(async (vault) => {
if (!args.vault) { if (!vault) {
args.vault = await getDecryptedName(); vault = await getDecryptedName();
} else {
const vaults = new Files(/(.*).vault/g).intersection(
new Files(/\.(.*)\.confidant/g),
).data as string[];
if (!vaults.includes(vault)) {
console.log(
chalk`{red Vault "${vault}" not found in current directory.}`,
);
exit(1);
}
} }
await encrypt_vault(args.vault); await encrypt_vault(vault);
console.log(chalk`{green Successfully encrypted!}`); console.log(chalk`{green Successfully encrypted!}`);
}); });

View File

@@ -7,5 +7,5 @@ await $`
rm -rf ./dist rm -rf ./dist
bun build --compile --target=bun-linux-x64 ./confidant.ts --outfile ./dist/confidant_${ver}_linux_x64 bun build --compile --target=bun-linux-x64 ./confidant.ts --outfile ./dist/confidant_${ver}_linux_x64
bun build --compile --target=bun-windows-x64 ./confidant.ts --outfile ./dist/confidant_${ver}_win_x64.exe bun build --compile --target=bun-windows-x64 ./confidant.ts --outfile ./dist/confidant_${ver}_win_x64.exe
bun build --compile --target=bun-darwin-x64 ./confidant.ts --outfile ./dist/confidant_${ver}_darwin_x64 bun build --compile --target=bun-darwin-arm64 ./confidant.ts --outfile ./dist/confidant_${ver}_darwin_arm64
`; `;

View File

@@ -75,7 +75,11 @@ export async function initialize(password: string, dirname: string) {
// create dirname.vault // create dirname.vault
writeFileSync( writeFileSync(
`${dirname}.vault`, `${dirname}.vault`,
Buffer.concat([Buffer.from(JSON.stringify(D_C)), separator, E_Z]), Buffer.concat([
Buffer.from(Buffer.from(JSON.stringify(D_C)).toString("base64")),
separator,
E_Z,
]),
); );
console.log(`Recovery phrase:`); console.log(`Recovery phrase:`);
@@ -95,6 +99,7 @@ confidant.zip
} }
export async function decrypt_vault(password: string, dirname: string) { export async function decrypt_vault(password: string, dirname: string) {
try {
// Read and split the vault file // Read and split the vault file
const combined = readFileSync(`${dirname}.vault`); const combined = readFileSync(`${dirname}.vault`);
const index = combined.indexOf(separator); const index = combined.indexOf(separator);
@@ -105,7 +110,7 @@ export async function decrypt_vault(password: string, dirname: string) {
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 config = JSON.parse(D_C); const config = JSON.parse(Buffer.from(D_C, "base64").toString());
const phrase = AES.decrypt(config.phrasestore, password_auth_key).toString( const phrase = AES.decrypt(config.phrasestore, password_auth_key).toString(
enc.Utf8, enc.Utf8,
); );
@@ -128,9 +133,13 @@ export async function decrypt_vault(password: string, dirname: string) {
writeFileSync(`confidant.zip`, Z); writeFileSync(`confidant.zip`, Z);
writeFileSync(`.${dirname}.confidant`, encrypt(D, buffer(env.AUTH_KEY))); writeFileSync(`.${dirname}.confidant`, encrypt(D, buffer(env.AUTH_KEY)));
await $`unzip confidant.zip > /dev/null && rm confidant.zip`; await $`unzip confidant.zip > /dev/null && rm confidant.zip`;
} catch (e) {
panic`Error decrypting vault. The files could be corrupted.`;
}
} }
export async function encrypt_vault(dirname: string) { export async function encrypt_vault(dirname: string) {
try {
const D = decrypt( const D = decrypt(
readFileSync(`.${dirname}.confidant`), readFileSync(`.${dirname}.confidant`),
buffer(env.AUTH_KEY), buffer(env.AUTH_KEY),
@@ -151,6 +160,9 @@ export async function encrypt_vault(dirname: string) {
Buffer.concat([vaultfile.subarray(0, index), separator, E_Z]), Buffer.concat([vaultfile.subarray(0, index), separator, E_Z]),
); );
await $`rm confidant.zip .${dirname}.confidant`; await $`rm confidant.zip .${dirname}.confidant`;
} catch (e) {
panic`Error encrypting vault. The files could be corrupted.`;
}
} }
export async function recovery(recoverystring: string) { export async function recovery(recoverystring: string) {

View File

@@ -109,7 +109,10 @@ export function print(key: string, value: string) {
export class Files { export class Files {
data: string[] | null = null; data: string[] | null = null;
constructor(regex: RegExp | string) { constructor(regex?: RegExp | string) {
if (!regex) {
return this;
}
try { try {
const data = readdirSync(".").filter((x) => x.match(regex)); const data = readdirSync(".").filter((x) => x.match(regex));
this.data = data ? data.map((x) => x.replace(regex, "$1")) : null; this.data = data ? data.map((x) => x.replace(regex, "$1")) : null;
@@ -119,16 +122,24 @@ export class Files {
} }
} }
empty(): boolean {
return this.data === null || this.data.length === 0;
}
toString(): string { toString(): string {
return `[ ${this.data ? this.data.join(", ") : "empty"} ]`; return `[ ${this.data ? this.data.join(", ") : "empty"} ]`;
} }
intersection(a: Files) { intersection(a: Files) {
return Array.from(new Set(this.data).intersection(new Set(a.data))); const obj = new Files();
obj.data = Array.from(new Set(this.data).intersection(new Set(a.data)));
return obj;
} }
difference(a: Files) { difference(a: Files) {
return Array.from(new Set(this.data).difference(new Set(a.data))); const obj = new Files();
obj.data = Array.from(new Set(this.data).difference(new Set(a.data)));
return obj;
} }
} }
@@ -167,35 +178,25 @@ export async function getDirectoryNames() {
} }
export async function getVaultName() { export async function getVaultName() {
const vaultList = (await $`ls *.vault`.text()).trim().match(/.*\.vault/g); const vaultList = new Files(/(.*)\.vault/g);
if (!vaultList) { if (vaultList.empty()) {
panic`No vaults found in the current directory.`; panic`No vaults found in the current directory.`;
return; return;
} }
const keyList = (await $`ls *.key`.text()) const keyList = new Files(/(.*)\.key/g);
.trim() if (keyList.empty()) {
.match(/.*\.key/g) as string[];
if (!keyList) {
panic`No vaults with keys found in the current directory.`; panic`No vaults with keys found in the current directory.`;
return; return;
} }
const vaults = Array.from( const vaults = vaultList.intersection(keyList);
new Set(vaultList.map((x) => x.replace(".vault", ""))).intersection(
new Set(keyList.map((x) => x.replace(".key", ""))),
),
);
let files: string[]; let files: string[];
const decryptedList = (await $`bash -c "ls .*.confidant"`.text()) const decryptedList = new Files(/\.(.*)\.confidant/g);
.trim() if (decryptedList.empty()) {
.match(/\.(.*)\.confidant/g); files = vaults.difference(decryptedList).data as string[];
if (decryptedList) {
const decs = decryptedList.map((x) => x.replace(/\.(.*)\.confidant/, "$1"));
files = Array.from(new Set(vaults).difference(new Set(decs)));
} else { } else {
files = vaultList.map((x) => x.replace(".vault", "")); files = vaults.data as string[];
} }
if (files.length === 1) { if (files.length === 1) {
return files[0]; return files[0];
@@ -203,11 +204,21 @@ export async function getVaultName() {
const vault = await select({ const vault = await select({
message: "Select vault to decrypt:", message: "Select vault to decrypt:",
choices: files.map((x) => ({ choices: [
...files.map((x) => ({
name: x.replace(".vault", ""), name: x.replace(".vault", ""),
value: x.replace(".vault", ""), value: x.replace(".vault", ""),
})), })),
{
name: chalk`{red EXIT}`,
value: "exit",
},
],
}); });
if (vault === "exit") {
log`{yellow Exiting...}`;
process.exit(0);
}
return vault; return vault;
} }
@@ -222,7 +233,7 @@ export async function getDecryptedName() {
panic`No vaults found.`; panic`No vaults found.`;
} }
const decrypted = dec.intersection(vaults); const decrypted = dec.intersection(vaults).data as string[];
if (decrypted.length === 1) { if (decrypted.length === 1) {
return decrypted[0]; return decrypted[0];
@@ -238,3 +249,14 @@ export async function getDecryptedName() {
return vault; return vault;
} }
export function getRandomPassword(length: number) {
const STRING =
"0123456789abcdefghijklmnopqrstuvwxyz!@#$%^&*()ABCDEFGHIJKLMNOPQRSTUVWXYZ";
let pass = "";
while (pass.length < length) {
pass += STRING[Math.floor(Math.random() * STRING.length)];
}
return pass;
}