mirror of
https://github.com/silicoflare/confidant.git
synced 2026-05-26 12:45:24 +05:30
feat: error correction and new features to init, decrypt and encrypt
This commit is contained in:
80
confidant.ts
80
confidant.ts
@@ -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!}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
`;
|
`;
|
||||||
|
|||||||
112
src/main.ts
112
src/main.ts
@@ -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,62 +99,70 @@ confidant.zip
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function decrypt_vault(password: string, dirname: string) {
|
export async function decrypt_vault(password: string, dirname: string) {
|
||||||
// Read and split the vault file
|
try {
|
||||||
const combined = readFileSync(`${dirname}.vault`);
|
// Read and split the vault file
|
||||||
const index = combined.indexOf(separator);
|
const combined = readFileSync(`${dirname}.vault`);
|
||||||
const D_C = combined.subarray(0, index).toString("utf8");
|
const index = combined.indexOf(separator);
|
||||||
const E_Z = combined.subarray(index + separator.length);
|
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 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,
|
||||||
);
|
);
|
||||||
if (phrase !== env.PHRASE) {
|
if (phrase !== env.PHRASE) {
|
||||||
panic`Incorrect password. Try again or reset it.`;
|
panic`Incorrect password. Try again or reset it.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auth_secret = decrypt(config.keystore, buffer(password_auth_key));
|
||||||
|
const K = hmac(auth_secret, config.confsalt);
|
||||||
|
const E_K = readFileSync(`${dirname}.key`);
|
||||||
|
const keyfile = JSON.parse(decrypt_file(E_K, K).toString("utf8"));
|
||||||
|
|
||||||
|
const K_A = buffer(keyfile.privateKey);
|
||||||
|
const K_B = buffer(config.privateKey);
|
||||||
|
const P_B = getPublic(K_B);
|
||||||
|
const S_AB = await derive(K_A, P_B);
|
||||||
|
const D = pbkdf2(S_AB, buffer(keyfile.salt), keyfile.code, 64, "sha256");
|
||||||
|
|
||||||
|
const Z = decrypt_file(E_Z, D);
|
||||||
|
writeFileSync(`confidant.zip`, Z);
|
||||||
|
writeFileSync(`.${dirname}.confidant`, encrypt(D, buffer(env.AUTH_KEY)));
|
||||||
|
await $`unzip confidant.zip > /dev/null && rm confidant.zip`;
|
||||||
|
} catch (e) {
|
||||||
|
panic`Error decrypting vault. The files could be corrupted.`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auth_secret = decrypt(config.keystore, buffer(password_auth_key));
|
|
||||||
const K = hmac(auth_secret, config.confsalt);
|
|
||||||
const E_K = readFileSync(`${dirname}.key`);
|
|
||||||
const keyfile = JSON.parse(decrypt_file(E_K, K).toString("utf8"));
|
|
||||||
|
|
||||||
const K_A = buffer(keyfile.privateKey);
|
|
||||||
const K_B = buffer(config.privateKey);
|
|
||||||
const P_B = getPublic(K_B);
|
|
||||||
const S_AB = await derive(K_A, P_B);
|
|
||||||
const D = pbkdf2(S_AB, buffer(keyfile.salt), keyfile.code, 64, "sha256");
|
|
||||||
|
|
||||||
const Z = decrypt_file(E_Z, D);
|
|
||||||
writeFileSync(`confidant.zip`, Z);
|
|
||||||
writeFileSync(`.${dirname}.confidant`, encrypt(D, buffer(env.AUTH_KEY)));
|
|
||||||
await $`unzip confidant.zip > /dev/null && rm confidant.zip`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function encrypt_vault(dirname: string) {
|
export async function encrypt_vault(dirname: string) {
|
||||||
const D = decrypt(
|
try {
|
||||||
readFileSync(`.${dirname}.confidant`),
|
const D = decrypt(
|
||||||
buffer(env.AUTH_KEY),
|
readFileSync(`.${dirname}.confidant`),
|
||||||
);
|
buffer(env.AUTH_KEY),
|
||||||
// create zip file and encrypt it
|
);
|
||||||
await $`zip -r9 confidant.zip ${dirname} > /dev/null`;
|
// create zip file and encrypt it
|
||||||
await $`rm -rf ${dirname}`;
|
await $`zip -r9 confidant.zip ${dirname} > /dev/null`;
|
||||||
const Z = readFileSync("confidant.zip");
|
await $`rm -rf ${dirname}`;
|
||||||
const E_Z = encrypt_file(Z, D);
|
const Z = readFileSync("confidant.zip");
|
||||||
|
const E_Z = encrypt_file(Z, D);
|
||||||
|
|
||||||
// read original vault file to get header data
|
// read original vault file to get header data
|
||||||
const vaultfile = readFileSync(`${dirname}.vault`);
|
const vaultfile = readFileSync(`${dirname}.vault`);
|
||||||
const index = vaultfile.indexOf(separator);
|
const index = vaultfile.indexOf(separator);
|
||||||
|
|
||||||
// write new data to vault
|
// write new data to vault
|
||||||
writeFileSync(
|
writeFileSync(
|
||||||
`${dirname}.vault`,
|
`${dirname}.vault`,
|
||||||
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) {
|
||||||
|
|||||||
76
src/utils.ts
76
src/utils.ts
@@ -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: [
|
||||||
name: x.replace(".vault", ""),
|
...files.map((x) => ({
|
||||||
value: x.replace(".vault", ""),
|
name: 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;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user