5 Commits
1.0.0 ... main

10 changed files with 100 additions and 85 deletions

1
.gitignore vendored
View File

@@ -177,3 +177,4 @@ dist
# confidant # confidant
env.ts env.ts
test test
executables

22
LICENSE Normal file
View File

@@ -0,0 +1,22 @@
MIT License
Copyright (c) 2024, Suraj B M "SilicoFlare"
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -5,9 +5,6 @@
1. [Introduction](#introduction) 1. [Introduction](#introduction)
2. [Security](#security) 2. [Security](#security)
3. [Installation](#installation) 3. [Installation](#installation)
a. [Linux](#linux)
b. [Windows](#windows)
c. [MacOS](#macos)
4. [Usage](#usage) 4. [Usage](#usage)
5. [Build from source](#build-from-source) 5. [Build from source](#build-from-source)
6. [Disclaimer](#disclaimer) 6. [Disclaimer](#disclaimer)
@@ -28,6 +25,7 @@ The vault has several layers of security to ensure that the contents are safe. T
2. **AES256**: The vault is encrypted using the AES256 algorithm, which is a symmetric encryption algorithm. The key is generated using the ECDH algorithm. 2. **AES256**: The vault is encrypted using the AES256 algorithm, which is a symmetric encryption algorithm. The key is generated using the ECDH algorithm.
3. **PBKDF2**: The password is hashed using the PBKDF2 algorithm, which is a key derivation function. This ensures that the password is not stored in plain text. 3. **PBKDF2**: The password is hashed using the PBKDF2 algorithm, which is a key derivation function. This ensures that the password is not stored in plain text.
4. **HMAC-SHA256**: The keys are encrypted using the HMAC-SHA256 algorithm, which is a hash-based message authentication code. This ensures that the keys are unique and cannot be tampered with. 4. **HMAC-SHA256**: The keys are encrypted using the HMAC-SHA256 algorithm, which is a hash-based message authentication code. This ensures that the keys are unique and cannot be tampered with.
5. **Unique build parameters:** Every binary built by you from source has completely unique parameters in the `env.ts` file, which means ONLY that binary can be used to decrypt a vault made with the binary.
--- ---

BIN
bun.lockb

Binary file not shown.

View File

@@ -1,12 +1,11 @@
#!/bin/env bun #!/bin/env node
import { input, password } from "@inquirer/prompts"; import { input, password } from "@inquirer/prompts";
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, reset } from "./src/main"; import { decrypt_vault, encrypt_vault, initialize, reset } from "./src/main";
import { existsSync, readdirSync } from "fs"; import { readdirSync } from "fs";
import checkForFiles, { import {
Files, Files,
getDecryptedName, getDecryptedName,
getDirectoryNames, getDirectoryNames,
@@ -15,11 +14,9 @@ import checkForFiles, {
log, 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;
$.nothrow();
program.name("confidant").description("Creates a very secure file vault."); program.name("confidant").description("Creates a very secure file vault.");

View File

@@ -1,15 +1,15 @@
{ {
"name": "confidant", "name": "confidant",
"version": "1.0.0", "version": "1.1.0",
"module": "confidant.ts", "module": "confidant.ts",
"type": "module", "type": "module",
"scripts": { "scripts": {
"init": "bun ./scripts/init.ts", "init": "bun ./scripts/init.ts",
"sh": "bun ./scripts/sh.ts", "build": "bun build --minify ./confidant.ts --target node --outfile ./dist/confidant.mjs",
"build": "bun ./scripts/build.ts" "build-exec": "bun build --compile --minify ./confidant.ts --outfile ./dist/confidant"
}, },
"bin": { "bin": {
"confidant": "./confidant.ts" "confidant": "./dist/confidant.mjs"
}, },
"devDependencies": { "devDependencies": {
"@types/bun": "latest" "@types/bun": "latest"
@@ -20,9 +20,11 @@
"dependencies": { "dependencies": {
"@iarna/toml": "^2.2.5", "@iarna/toml": "^2.2.5",
"@inquirer/prompts": "^6.0.1", "@inquirer/prompts": "^6.0.1",
"@types/adm-zip": "^0.5.5",
"@types/commander": "^2.12.2", "@types/commander": "^2.12.2",
"@types/crypto-js": "^4.2.2", "@types/crypto-js": "^4.2.2",
"@types/eccrypto": "^1.1.6", "@types/eccrypto": "^1.1.6",
"adm-zip": "^0.5.16",
"argon2": "^0.41.1", "argon2": "^0.41.1",
"chalk": "^5.3.0", "chalk": "^5.3.0",
"chalk-template": "^1.1.0", "chalk-template": "^1.1.0",

View File

@@ -1,11 +0,0 @@
import { $ } from "bun";
import data from "../package.json";
const { version: ver } = data;
await $`
rm -rf ./dist
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-darwin-arm64 ./confidant.ts --outfile ./dist/confidant_${ver}_darwin_arm64
`;

View File

@@ -1,7 +1,7 @@
import { $ } from "bun";
import { stringy } from "../src/utils"; import { stringy } from "../src/utils";
import { randomBytes } from "crypto"; import { randomBytes } from "crypto";
import chalkTemplate from "chalk-template"; import chalkTemplate from "chalk-template";
import { writeFileSync } from "fs";
const envstring = `// env.ts const envstring = `// env.ts
const env = { const env = {
@@ -15,5 +15,5 @@ const env = {
export default env; export default env;
`; `;
await $`echo ${envstring} > env.ts`; writeFileSync("./env.ts", envstring);
console.log(chalkTemplate`{green env.ts initialized sucessfully!}`); console.log(chalkTemplate`{green env.ts initialized sucessfully!}`);

View File

@@ -1,8 +1,7 @@
import { parse, stringify } from "@iarna/toml"; import { parse, stringify } from "@iarna/toml";
import { $ } from "bun";
import { pbkdf2Sync as pbkdf2, randomBytes } from "crypto"; import { pbkdf2Sync as pbkdf2, randomBytes } from "crypto";
import { derive, getPublic } from "eccrypto"; import { derive, getPublic } from "eccrypto";
import { readFileSync, writeFileSync } from "fs"; import { readFileSync, rmSync, writeFileSync } from "fs";
import { import {
buffer, buffer,
decrypt, decrypt,
@@ -24,6 +23,7 @@ import env from "../env";
import { AES, enc, HmacSHA256 } from "crypto-js"; import { AES, enc, HmacSHA256 } from "crypto-js";
import chalk from "chalk-template"; import chalk from "chalk-template";
import { input, password } from "@inquirer/prompts"; import { input, password } from "@inquirer/prompts";
import AdmZip from "adm-zip";
console.info = function () {}; console.info = function () {};
const { exit } = process; const { exit } = process;
@@ -36,8 +36,10 @@ export async function initialize(password: string, dirname: string) {
const salt = randomBytes(32); const salt = randomBytes(32);
const code = random(10000, 100000); const code = random(10000, 100000);
const D = pbkdf2(S_AB, salt, code, 64, "sha256"); const D = pbkdf2(S_AB, salt, code, 64, "sha256");
await $`zip -r9 confidant.zip ${dirname} > /dev/null`; const zip = new AdmZip();
await $`rm -rf ${dirname}`; zip.addLocalFolder("./" + dirname);
zip.writeZip("./confidant.zip");
rmSync(dirname, { recursive: true });
const Z = readFileSync("confidant.zip"); const Z = readFileSync("confidant.zip");
const E_Z = encrypt_file(Z, D); const E_Z = encrypt_file(Z, D);
@@ -84,13 +86,12 @@ export async function initialize(password: string, dirname: string) {
console.log(`Recovery phrase:`); console.log(`Recovery phrase:`);
console.log(chalk`{magenta ${recovery_phrase}}`); console.log(chalk`{magenta ${recovery_phrase}}`);
await $`rm confidant.zip`; rmSync("confidant.zip");
// create .gitignore // create .gitignore
const gitignore = `# .gitignore const gitignore = `# .gitignore
*.con *.key
*.fid
*_recovery.txt *_recovery.txt
confidant.zip confidant.zip
*.confidant *.confidant
@@ -132,7 +133,9 @@ export async function decrypt_vault(password: string, dirname: string) {
const Z = decrypt_file(E_Z, D); const Z = decrypt_file(E_Z, D);
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`; const unzip = new AdmZip("./confidant.zip");
unzip.extractAllTo(`./${dirname}`, true);
rmSync("./confidant.zip");
} catch (e) { } catch (e) {
panic`Error decrypting vault. The files could be corrupted.`; panic`Error decrypting vault. The files could be corrupted.`;
} }
@@ -145,8 +148,10 @@ export async function encrypt_vault(dirname: string) {
buffer(env.AUTH_KEY), buffer(env.AUTH_KEY),
); );
// create zip file and encrypt it // create zip file and encrypt it
await $`zip -r9 confidant.zip ${dirname} > /dev/null`; const zip = new AdmZip();
await $`rm -rf ${dirname}`; zip.addLocalFolder("./" + dirname);
zip.writeZip("./confidant.zip");
rmSync(dirname, { recursive: true });
const Z = readFileSync("confidant.zip"); const Z = readFileSync("confidant.zip");
const E_Z = encrypt_file(Z, D); const E_Z = encrypt_file(Z, D);
@@ -159,7 +164,8 @@ export async function encrypt_vault(dirname: string) {
`${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`; rmSync("./confidant.zip");
rmSync(`./.${dirname}.confidant`);
} catch (e) { } catch (e) {
panic`Error encrypting vault. The files could be corrupted.`; panic`Error encrypting vault. The files could be corrupted.`;
} }
@@ -231,7 +237,7 @@ export async function reset(dirname: string, recoverystring: string) {
]), ]),
); );
writeFileSync(`${dirname}_recovery.txt`, newrecphrase); writeFileSync(`${dirname}_recovery.txt`, newrecphrase + "\n");
console.log(`New recovery phrase:`); console.log(`New recovery phrase:`);
console.log(chalk`{magenta ${newrecphrase}}`); console.log(chalk`{magenta ${newrecphrase}}`);
} catch (e) { } catch (e) {

View File

@@ -3,7 +3,6 @@ import { createHmac } from "crypto";
import { generatePrivate, getPublic } from "eccrypto"; import { generatePrivate, getPublic } from "eccrypto";
import { generate } from "random-words"; import { generate } from "random-words";
import chalk from "chalk-template"; import chalk from "chalk-template";
import { $ } from "bun";
import { existsSync, readdirSync } from "fs"; import { existsSync, readdirSync } from "fs";
import { select } from "@inquirer/prompts"; import { select } from "@inquirer/prompts";
@@ -61,15 +60,6 @@ export function decrypt_file(input: Buffer, key: Buffer) {
return Buffer.from(result.toString(enc.Utf8), "base64"); return Buffer.from(result.toString(enc.Utf8), "base64");
} }
export async function exists(dir: string) {
try {
await $`test -d ${dir}`;
return true;
} catch (e) {
return false;
}
}
export function log( export function log(
str: TemplateStringsArray, str: TemplateStringsArray,
...placeholders: unknown[] ...placeholders: unknown[]
@@ -109,13 +99,17 @@ 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 | string[]) {
if (!regex) { if (!regex) {
return this; return this;
} }
try { try {
const data = readdirSync(".").filter((x) => x.match(regex)); if (Array.isArray(regex)) {
this.data = data ? data.map((x) => x.replace(regex, "$1")) : null; this.data = regex;
} else {
const data = readdirSync(".").filter((x) => x.match(regex));
this.data = data ? data.map((x) => x.replace(regex, "$1")) : null;
}
} catch (error) { } catch (error) {
console.error("Error executing command:", error); console.error("Error executing command:", error);
this.data = null; this.data = null;
@@ -130,51 +124,57 @@ export class Files {
return `[ ${this.data ? this.data.join(", ") : "empty"} ]`; return `[ ${this.data ? this.data.join(", ") : "empty"} ]`;
} }
intersection(a: Files) { intersection(a: Files): Files {
const obj = new Files(); const obj = new Files();
obj.data = Array.from(new Set(this.data).intersection(new Set(a.data))); if (this.data && a.data) {
obj.data = this.data.filter((x) => a.data!.includes(x));
}
return obj; return obj;
} }
difference(a: Files) { difference(a: Files): Files {
const obj = new Files(); const obj = new Files();
obj.data = Array.from(new Set(this.data).difference(new Set(a.data))); if (this.data && a.data) {
obj.data = this.data.filter((x) => !a.data!.includes(x));
}
return obj; return obj;
} }
} }
export async function getDirectoryNames() { export async function getDirectoryNames() {
const dirlist = (await $`ls -d */`.text()).trim(); try {
const dirlist = readdirSync(".", { withFileTypes: true })
.filter((file) => file.isDirectory())
.map((file) => file.name);
if (!dirlist) { if (dirlist.length === 0) {
panic`No directories found in current directory, please create one and run "init" again.`; panic`No directories found in current directory, please create one and run "init" again.`;
return; return;
}
const dirs = new Files(dirlist);
const vaults = new Files(/.*\.vault/g);
const usableDirs = dirs.difference(vaults);
if (usableDirs.data && usableDirs.data.length === 0) {
panic`No usable directories found in current directory, please create one and run "init" again.`;
return;
} else if (usableDirs.data && usableDirs.data.length === 1) {
return usableDirs.data[0];
}
const dirname = await select({
message: "Select a directory to use:",
choices: usableDirs.data!.map((x) => ({
name: x,
value: x,
})),
});
return dirname;
} catch (e) {
console.log(chalk`{red ${e.message}}`);
process.exit(1);
} }
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() { export async function getVaultName() {
@@ -252,7 +252,7 @@ export async function getDecryptedName() {
export function getRandomPassword(length: number) { export function getRandomPassword(length: number) {
const STRING = const STRING =
"0123456789abcdefghijklmnopqrstuvwxyz!@#$%^&*()ABCDEFGHIJKLMNOPQRSTUVWXYZ"; "0123456789abcdefghijklmnopqrstuvwxyz!@#$%^&*ABCDEFGHIJKLMNOPQRSTUVWXYZ";
let pass = ""; let pass = "";
while (pass.length < length) { while (pass.length < length) {