mirror of
https://github.com/silicoflare/confidant.git
synced 2026-05-26 12:45:24 +05:30
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fb254e97b5 | |||
| d5669b2988 | |||
| f008eea843 | |||
| 50d377d6bd | |||
| 8acfc09ea5 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -177,3 +177,4 @@ dist
|
||||
# confidant
|
||||
env.ts
|
||||
test
|
||||
executables
|
||||
|
||||
22
LICENSE
Normal file
22
LICENSE
Normal 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.
|
||||
|
||||
@@ -5,9 +5,6 @@
|
||||
1. [Introduction](#introduction)
|
||||
2. [Security](#security)
|
||||
3. [Installation](#installation)
|
||||
a. [Linux](#linux)
|
||||
b. [Windows](#windows)
|
||||
c. [MacOS](#macos)
|
||||
4. [Usage](#usage)
|
||||
5. [Build from source](#build-from-source)
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
#!/bin/env bun
|
||||
#!/bin/env node
|
||||
|
||||
import { input, password } from "@inquirer/prompts";
|
||||
import { $ } from "bun";
|
||||
import chalk from "chalk-template";
|
||||
import { Command } from "commander";
|
||||
import { decrypt_vault, encrypt_vault, initialize, reset } from "./src/main";
|
||||
import { existsSync, readdirSync } from "fs";
|
||||
import checkForFiles, {
|
||||
import { readdirSync } from "fs";
|
||||
import {
|
||||
Files,
|
||||
getDecryptedName,
|
||||
getDirectoryNames,
|
||||
@@ -15,11 +14,9 @@ import checkForFiles, {
|
||||
log,
|
||||
panic,
|
||||
} from "./src/utils";
|
||||
import { randomBytes } from "crypto";
|
||||
|
||||
const program = new Command();
|
||||
const { exit } = process;
|
||||
$.nothrow();
|
||||
|
||||
program.name("confidant").description("Creates a very secure file vault.");
|
||||
|
||||
|
||||
10
package.json
10
package.json
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"name": "confidant",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"module": "confidant.ts",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"init": "bun ./scripts/init.ts",
|
||||
"sh": "bun ./scripts/sh.ts",
|
||||
"build": "bun ./scripts/build.ts"
|
||||
"build": "bun build --minify ./confidant.ts --target node --outfile ./dist/confidant.mjs",
|
||||
"build-exec": "bun build --compile --minify ./confidant.ts --outfile ./dist/confidant"
|
||||
},
|
||||
"bin": {
|
||||
"confidant": "./confidant.ts"
|
||||
"confidant": "./dist/confidant.mjs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest"
|
||||
@@ -20,9 +20,11 @@
|
||||
"dependencies": {
|
||||
"@iarna/toml": "^2.2.5",
|
||||
"@inquirer/prompts": "^6.0.1",
|
||||
"@types/adm-zip": "^0.5.5",
|
||||
"@types/commander": "^2.12.2",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/eccrypto": "^1.1.6",
|
||||
"adm-zip": "^0.5.16",
|
||||
"argon2": "^0.41.1",
|
||||
"chalk": "^5.3.0",
|
||||
"chalk-template": "^1.1.0",
|
||||
|
||||
@@ -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
|
||||
`;
|
||||
@@ -1,7 +1,7 @@
|
||||
import { $ } from "bun";
|
||||
import { stringy } from "../src/utils";
|
||||
import { randomBytes } from "crypto";
|
||||
import chalkTemplate from "chalk-template";
|
||||
import { writeFileSync } from "fs";
|
||||
|
||||
const envstring = `// env.ts
|
||||
const env = {
|
||||
@@ -15,5 +15,5 @@ const env = {
|
||||
export default env;
|
||||
`;
|
||||
|
||||
await $`echo ${envstring} > env.ts`;
|
||||
writeFileSync("./env.ts", envstring);
|
||||
console.log(chalkTemplate`{green env.ts initialized sucessfully!}`);
|
||||
|
||||
30
src/main.ts
30
src/main.ts
@@ -1,8 +1,7 @@
|
||||
import { parse, stringify } from "@iarna/toml";
|
||||
import { $ } from "bun";
|
||||
import { pbkdf2Sync as pbkdf2, randomBytes } from "crypto";
|
||||
import { derive, getPublic } from "eccrypto";
|
||||
import { readFileSync, writeFileSync } from "fs";
|
||||
import { readFileSync, rmSync, writeFileSync } from "fs";
|
||||
import {
|
||||
buffer,
|
||||
decrypt,
|
||||
@@ -24,6 +23,7 @@ import env from "../env";
|
||||
import { AES, enc, HmacSHA256 } from "crypto-js";
|
||||
import chalk from "chalk-template";
|
||||
import { input, password } from "@inquirer/prompts";
|
||||
import AdmZip from "adm-zip";
|
||||
|
||||
console.info = function () {};
|
||||
const { exit } = process;
|
||||
@@ -36,8 +36,10 @@ export async function initialize(password: string, dirname: string) {
|
||||
const salt = randomBytes(32);
|
||||
const code = random(10000, 100000);
|
||||
const D = pbkdf2(S_AB, salt, code, 64, "sha256");
|
||||
await $`zip -r9 confidant.zip ${dirname} > /dev/null`;
|
||||
await $`rm -rf ${dirname}`;
|
||||
const zip = new AdmZip();
|
||||
zip.addLocalFolder("./" + dirname);
|
||||
zip.writeZip("./confidant.zip");
|
||||
rmSync(dirname, { recursive: true });
|
||||
const Z = readFileSync("confidant.zip");
|
||||
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(chalk`{magenta ${recovery_phrase}}`);
|
||||
await $`rm confidant.zip`;
|
||||
rmSync("confidant.zip");
|
||||
|
||||
// create .gitignore
|
||||
const gitignore = `# .gitignore
|
||||
|
||||
*.con
|
||||
*.fid
|
||||
*.key
|
||||
*_recovery.txt
|
||||
confidant.zip
|
||||
*.confidant
|
||||
@@ -132,7 +133,9 @@ export async function decrypt_vault(password: string, dirname: string) {
|
||||
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`;
|
||||
const unzip = new AdmZip("./confidant.zip");
|
||||
unzip.extractAllTo(`./${dirname}`, true);
|
||||
rmSync("./confidant.zip");
|
||||
} catch (e) {
|
||||
panic`Error decrypting vault. The files could be corrupted.`;
|
||||
}
|
||||
@@ -145,8 +148,10 @@ export async function encrypt_vault(dirname: string) {
|
||||
buffer(env.AUTH_KEY),
|
||||
);
|
||||
// create zip file and encrypt it
|
||||
await $`zip -r9 confidant.zip ${dirname} > /dev/null`;
|
||||
await $`rm -rf ${dirname}`;
|
||||
const zip = new AdmZip();
|
||||
zip.addLocalFolder("./" + dirname);
|
||||
zip.writeZip("./confidant.zip");
|
||||
rmSync(dirname, { recursive: true });
|
||||
const Z = readFileSync("confidant.zip");
|
||||
const E_Z = encrypt_file(Z, D);
|
||||
|
||||
@@ -159,7 +164,8 @@ export async function encrypt_vault(dirname: string) {
|
||||
`${dirname}.vault`,
|
||||
Buffer.concat([vaultfile.subarray(0, index), separator, E_Z]),
|
||||
);
|
||||
await $`rm confidant.zip .${dirname}.confidant`;
|
||||
rmSync("./confidant.zip");
|
||||
rmSync(`./.${dirname}.confidant`);
|
||||
} catch (e) {
|
||||
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(chalk`{magenta ${newrecphrase}}`);
|
||||
} catch (e) {
|
||||
|
||||
94
src/utils.ts
94
src/utils.ts
@@ -3,7 +3,6 @@ import { createHmac } from "crypto";
|
||||
import { generatePrivate, getPublic } from "eccrypto";
|
||||
import { generate } from "random-words";
|
||||
import chalk from "chalk-template";
|
||||
import { $ } from "bun";
|
||||
import { existsSync, readdirSync } from "fs";
|
||||
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");
|
||||
}
|
||||
|
||||
export async function exists(dir: string) {
|
||||
try {
|
||||
await $`test -d ${dir}`;
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function log(
|
||||
str: TemplateStringsArray,
|
||||
...placeholders: unknown[]
|
||||
@@ -109,13 +99,17 @@ export function print(key: string, value: string) {
|
||||
export class Files {
|
||||
data: string[] | null = null;
|
||||
|
||||
constructor(regex?: RegExp | string) {
|
||||
constructor(regex?: RegExp | string | string[]) {
|
||||
if (!regex) {
|
||||
return this;
|
||||
}
|
||||
try {
|
||||
const data = readdirSync(".").filter((x) => x.match(regex));
|
||||
this.data = data ? data.map((x) => x.replace(regex, "$1")) : null;
|
||||
if (Array.isArray(regex)) {
|
||||
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) {
|
||||
console.error("Error executing command:", error);
|
||||
this.data = null;
|
||||
@@ -130,51 +124,57 @@ export class Files {
|
||||
return `[ ${this.data ? this.data.join(", ") : "empty"} ]`;
|
||||
}
|
||||
|
||||
intersection(a: Files) {
|
||||
intersection(a: Files): 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;
|
||||
}
|
||||
|
||||
difference(a: Files) {
|
||||
difference(a: Files): 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;
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
panic`No directories found in current directory, please create one and run "init" again.`;
|
||||
return;
|
||||
if (dirlist.length === 0) {
|
||||
panic`No directories found in current directory, please create one and run "init" again.`;
|
||||
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() {
|
||||
@@ -252,7 +252,7 @@ export async function getDecryptedName() {
|
||||
|
||||
export function getRandomPassword(length: number) {
|
||||
const STRING =
|
||||
"0123456789abcdefghijklmnopqrstuvwxyz!@#$%^&*()ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
"0123456789abcdefghijklmnopqrstuvwxyz!@#$%^&*ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
let pass = "";
|
||||
|
||||
while (pass.length < length) {
|
||||
|
||||
Reference in New Issue
Block a user