mirror of
https://github.com/silicoflare/confidant.git
synced 2026-05-26 12:45:24 +05:30
init: inital commit
This commit is contained in:
178
.gitignore
vendored
Normal file
178
.gitignore
vendored
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
|
||||||
|
logs
|
||||||
|
_.log
|
||||||
|
npm-debug.log_
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# Caches
|
||||||
|
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
|
||||||
|
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
|
||||||
|
pids
|
||||||
|
_.pid
|
||||||
|
_.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# vuepress v2.x temp and cache directory
|
||||||
|
|
||||||
|
.temp
|
||||||
|
|
||||||
|
# Docusaurus cache and generated files
|
||||||
|
|
||||||
|
.docusaurus
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
||||||
|
|
||||||
|
# IntelliJ based IDEs
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Finder (MacOS) folder config
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# confidant
|
||||||
|
env.ts
|
||||||
62
README.md
Normal file
62
README.md
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
# Confidant
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
1. [Introduction](#introduction)
|
||||||
|
2. [Installation](#installation)
|
||||||
|
a. [Linux](#linux)
|
||||||
|
b. [Windows](#windows)
|
||||||
|
c. [MacOS](#macos)
|
||||||
|
3. [Usage](#usage)
|
||||||
|
4. [Build from source](#build-from-source)
|
||||||
|
---
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
Confidant is a CLI tool used to create a triple-layer protected vault, written in TypeScript. It makes use of a combination of ECDH, AES256 and HMAC-SHA256 to create the vault, which can be acessible only if 3 particular files, namely `data.con`, `key.fid` and `vault.ant` (and a `config.toml`), are present. It also requires a password to start the decryption process. In case the password is lost, the vault can be recovered using the recovery phrase, which is a 12-word phrase that is generated during the vault creation process.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
### Linux
|
||||||
|
1. Download the latest release from the releases page.
|
||||||
|
2. Give it executable permissions by running `chmod +x confidant`.
|
||||||
|
3. Move it to a directory in your PATH, like `/usr/local/bin`.
|
||||||
|
4. Run `confidant --help` to verify the installation.
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
1. Download the latest release from the releases page.
|
||||||
|
2. Move it to a directory in your PATH.
|
||||||
|
3. Run `confidant --help` to verify the installation.
|
||||||
|
|
||||||
|
### MacOS
|
||||||
|
1. Download the latest release from the releases page.
|
||||||
|
2. Give it executable permissions by running `chmod +x confidant`.
|
||||||
|
3. Move it to a directory in your PATH, like `/usr/local/bin`.
|
||||||
|
4. Run `confidant --help` to verify the installation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
### Create a new vault
|
||||||
|
To create a new vault, run the following command:
|
||||||
|
```bash
|
||||||
|
confidant init
|
||||||
|
```
|
||||||
|
This will show a list of directories in your current directory. Select the directory where you want to create the vault. Also specify a password to encrypt the vault. The recovery phrase will be shown after the vault is created. Save it in a safe place. The following files will be created:
|
||||||
|
- `data.con`: Primary key
|
||||||
|
- `key.fid`: Secondary key
|
||||||
|
- `vault.ant`: Encrypted vault
|
||||||
|
- `config.toml`: Configuration file
|
||||||
|
- `.gitignore`: To ignore the vault files
|
||||||
|
After this, you can push the vault files to a remote repository. The `.gitignore` file will make sure the key files are not pushed to the repository. Make sure to never store the key files in the same place as the vault files.
|
||||||
|
|
||||||
|
### Decrypt a vault
|
||||||
|
To decrypt a vault, run the following command:
|
||||||
|
```bash
|
||||||
|
confidant decrypt
|
||||||
|
```
|
||||||
|
Make sure all the files `data.con`, `key.fid` and `vault.ant` are present in the current directory. Also make sure you have the password and the recovery phrase. The vault will be decrypted and the contents will be shown.
|
||||||
|
|
||||||
|
### Encrypt a vault
|
||||||
|
To encrypt a vault, run the following command:
|
||||||
|
```bash
|
||||||
|
confidant encrypt
|
||||||
|
```
|
||||||
|
Make sure the files `data.con`, `key.fid` and `vault.ant` are present in the current directory. The vault will be encrypted and the files will be updated, after which you can move them to a safe place.
|
||||||
114
confidant.ts
Executable file
114
confidant.ts
Executable file
@@ -0,0 +1,114 @@
|
|||||||
|
#!/bin/env bun
|
||||||
|
|
||||||
|
import { input, password, select } from "@inquirer/prompts";
|
||||||
|
import { $ } from "bun";
|
||||||
|
import chalk from "chalk-template";
|
||||||
|
import { Command } from "commander";
|
||||||
|
import { decrypt_diary, encrypt_diary, initialize, recovery } from "./src/main";
|
||||||
|
import { existsSync } from "fs";
|
||||||
|
import checkForFiles, { log, panic } from "./src/utils";
|
||||||
|
|
||||||
|
const program = new Command();
|
||||||
|
const { exit } = process;
|
||||||
|
$.nothrow();
|
||||||
|
|
||||||
|
program.name("confidant").description("Creates a very secure file vault.");
|
||||||
|
|
||||||
|
program
|
||||||
|
.command("init")
|
||||||
|
.description("initialize a confidant vault")
|
||||||
|
.action(async () => {
|
||||||
|
// check if a vault already exists
|
||||||
|
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({
|
||||||
|
message: chalk`{reset {yellow Enter a password to use:}}`,
|
||||||
|
mask: "•",
|
||||||
|
});
|
||||||
|
const confpass = await password({
|
||||||
|
message: chalk`{reset {yellow Enter the password again:}}`,
|
||||||
|
mask: "•",
|
||||||
|
});
|
||||||
|
if (pass !== confpass) {
|
||||||
|
console.log(chalk`Passwords don't math.`);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
await initialize(pass, dirname);
|
||||||
|
console.log(chalk`{green Initialized a new Confidant vault sucessfully!}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
program
|
||||||
|
.command("decrypt")
|
||||||
|
.description("decrypt the vault")
|
||||||
|
.option("-l, --live", "decrypt in live mode")
|
||||||
|
.action(async (args) => {
|
||||||
|
checkForFiles();
|
||||||
|
|
||||||
|
const pass = await password({
|
||||||
|
message: chalk`{reset {yellow Enter the password:}}`,
|
||||||
|
mask: "•",
|
||||||
|
});
|
||||||
|
await decrypt_diary(pass);
|
||||||
|
if (args.live) {
|
||||||
|
await input({
|
||||||
|
message: chalk`{yellow Live mode started. Press ENTER to encrypt}`,
|
||||||
|
});
|
||||||
|
await encrypt_diary();
|
||||||
|
console.log(chalk`{green Successfully encrypted!}`);
|
||||||
|
} else {
|
||||||
|
console.log(chalk`{green Decrypted sucessfully!}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
program
|
||||||
|
.command("encrypt")
|
||||||
|
.description("encrypt the vault")
|
||||||
|
.action(async () => {
|
||||||
|
checkForFiles();
|
||||||
|
|
||||||
|
if (!existsSync(".confidant")) {
|
||||||
|
panic`The vault was not decrypted yet!`;
|
||||||
|
}
|
||||||
|
|
||||||
|
await encrypt_diary();
|
||||||
|
console.log(chalk`{green Successfully encrypted!}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
program
|
||||||
|
.command("recover")
|
||||||
|
.description("recover vault when password is forgotten")
|
||||||
|
.action(async () => {
|
||||||
|
checkForFiles();
|
||||||
|
|
||||||
|
const recphrase = await input({
|
||||||
|
message: chalk`{reset {yellow Enter the recovery phrase:}}`,
|
||||||
|
});
|
||||||
|
recovery(recphrase);
|
||||||
|
});
|
||||||
|
|
||||||
|
await program.parseAsync();
|
||||||
36
package.json
Normal file
36
package.json
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"name": "confidant",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"module": "confidant.ts",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"init": "bun ./scripts/init.ts",
|
||||||
|
"build": "bun ./scripts/build.ts"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"confidant": "./confidant.ts"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bun": "latest"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "^5.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@iarna/toml": "^2.2.5",
|
||||||
|
"@inquirer/prompts": "^6.0.1",
|
||||||
|
"@types/commander": "^2.12.2",
|
||||||
|
"@types/crypto-js": "^4.2.2",
|
||||||
|
"@types/eccrypto": "^1.1.6",
|
||||||
|
"argon2": "^0.41.1",
|
||||||
|
"chalk": "^5.3.0",
|
||||||
|
"chalk-template": "^1.1.0",
|
||||||
|
"commander": "^12.1.0",
|
||||||
|
"crypto": "^1.0.1",
|
||||||
|
"crypto-js": "^4.2.0",
|
||||||
|
"dotenv": "^16.4.5",
|
||||||
|
"eccrypto": "^1.1.6",
|
||||||
|
"fs": "^0.0.1-security",
|
||||||
|
"random-words": "^2.0.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
11
scripts/build.ts
Normal file
11
scripts/build.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
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-x64 ./confidant.ts --outfile ./dist/confidant_${ver}_darwin_x64
|
||||||
|
`;
|
||||||
22
scripts/init.ts
Normal file
22
scripts/init.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { $ } from "bun";
|
||||||
|
import { stringy } from "../src/utils";
|
||||||
|
import { randomBytes } from "crypto";
|
||||||
|
import chalkTemplate from "chalk-template";
|
||||||
|
|
||||||
|
const envstring = `// env.ts
|
||||||
|
const env = {
|
||||||
|
// Re-run to get a new value
|
||||||
|
AUTH_KEY: "${stringy(randomBytes(32))}",
|
||||||
|
|
||||||
|
// Re-run to get a new value
|
||||||
|
AUTH_SALT: "${stringy(randomBytes(64))}",
|
||||||
|
|
||||||
|
// Can be literally anything!
|
||||||
|
PHRASE: "May the Force be with you!",
|
||||||
|
}
|
||||||
|
|
||||||
|
export default env;
|
||||||
|
`;
|
||||||
|
|
||||||
|
await $`echo ${envstring} > env.ts`;
|
||||||
|
console.log(chalkTemplate`{green env.ts initialized sucessfully!}`);
|
||||||
217
src/main.ts
Normal file
217
src/main.ts
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
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 {
|
||||||
|
buffer,
|
||||||
|
decrypt,
|
||||||
|
decrypt_file,
|
||||||
|
ECDH,
|
||||||
|
encrypt,
|
||||||
|
encrypt_file,
|
||||||
|
generate_recovery_phrase,
|
||||||
|
hmac,
|
||||||
|
log,
|
||||||
|
panic,
|
||||||
|
random,
|
||||||
|
stringy,
|
||||||
|
} from "./utils";
|
||||||
|
import env from "../env";
|
||||||
|
import { AES, enc, HmacSHA256 } from "crypto-js";
|
||||||
|
import chalk from "chalk-template";
|
||||||
|
import type { ConData, Config, FidData } from "../types";
|
||||||
|
import { password } from "@inquirer/prompts";
|
||||||
|
|
||||||
|
console.info = function () {};
|
||||||
|
const { exit } = process;
|
||||||
|
|
||||||
|
export async function initialize(password: string, dirname: string) {
|
||||||
|
// compressing and encrypting diary
|
||||||
|
const [K_C, P_C] = ECDH();
|
||||||
|
const [K_F, P_F] = ECDH();
|
||||||
|
const S_CF = await derive(K_C, P_F);
|
||||||
|
const fidsalt = randomBytes(32);
|
||||||
|
const fidcode = random(10000, 100000);
|
||||||
|
const D = pbkdf2(S_CF, fidsalt, fidcode, 64, "sha256");
|
||||||
|
await $`zip -r9 confidant.zip ${dirname} > /dev/null`;
|
||||||
|
await $`rm -rf ${dirname}`;
|
||||||
|
const Z = readFileSync("confidant.zip");
|
||||||
|
const E_Z = encrypt_file(Z, D);
|
||||||
|
writeFileSync(`vault.ant`, E_Z);
|
||||||
|
|
||||||
|
// creating key.fid
|
||||||
|
const fidData = {
|
||||||
|
privateKey: stringy(K_F),
|
||||||
|
salt: stringy(fidsalt),
|
||||||
|
code: fidcode,
|
||||||
|
};
|
||||||
|
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 conData = {
|
||||||
|
privateKey: stringy(K_C),
|
||||||
|
fidkey: stringy(fidkey),
|
||||||
|
consalt: stringy(consalt),
|
||||||
|
};
|
||||||
|
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);
|
||||||
|
|
||||||
|
const recovery_phrase = generate_recovery_phrase();
|
||||||
|
writeFileSync("recovery.txt", recovery_phrase + "\n");
|
||||||
|
const recovery_auth_key = HmacSHA256(recovery_phrase, env.AUTH_KEY).toString(
|
||||||
|
enc.Base64,
|
||||||
|
);
|
||||||
|
const password_auth_key = HmacSHA256(password, env.AUTH_KEY).toString(
|
||||||
|
enc.Base64,
|
||||||
|
);
|
||||||
|
|
||||||
|
// create config.toml
|
||||||
|
const configData = {
|
||||||
|
config: {
|
||||||
|
con: "data.con",
|
||||||
|
fid: "key.fid",
|
||||||
|
ant: "vault.ant",
|
||||||
|
dir: dirname,
|
||||||
|
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"));
|
||||||
|
console.log(`Recovery phrase:`);
|
||||||
|
console.log(chalk`{magenta ${recovery_phrase}}`);
|
||||||
|
await $`rm confidant.zip`;
|
||||||
|
|
||||||
|
const gitignore = `# .gitignore
|
||||||
|
|
||||||
|
data.con
|
||||||
|
key.fid
|
||||||
|
recovery.txt
|
||||||
|
confidant.zip
|
||||||
|
.confidant
|
||||||
|
${dirname}
|
||||||
|
.env
|
||||||
|
`;
|
||||||
|
writeFileSync(".gitignore", gitignore);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function decrypt_diary(password: string) {
|
||||||
|
// 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(
|
||||||
|
enc.Base64,
|
||||||
|
);
|
||||||
|
const dec = AES.decrypt(
|
||||||
|
config.config.phrasestore,
|
||||||
|
password_auth_key,
|
||||||
|
).toString(enc.Utf8);
|
||||||
|
if (dec !== env.PHRASE) {
|
||||||
|
panic`Wrong password. Try again or reset it.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypt data.con
|
||||||
|
const auth_secret = decrypt(
|
||||||
|
buffer(config.config.keystore),
|
||||||
|
buffer(password_auth_key),
|
||||||
|
);
|
||||||
|
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 { consalt, fidkey, privateKey: conK } = conData;
|
||||||
|
const D_C = hmac(buffer(consalt), buffer(fidkey));
|
||||||
|
const fidData = Object(
|
||||||
|
parse(decrypt_file(readFileSync(config.config.fid), D_C).toString("utf8")),
|
||||||
|
) as FidData;
|
||||||
|
const { privateKey: fidK, salt, code } = fidData;
|
||||||
|
|
||||||
|
// Decrypt diary.ant
|
||||||
|
const S_CF = await derive(buffer(conK), getPublic(buffer(fidK)));
|
||||||
|
const D = pbkdf2(S_CF, buffer(salt), code, 64, "sha256");
|
||||||
|
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`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function encrypt_diary() {
|
||||||
|
const { dirname, key }: { dirname: string; key: string } = JSON.parse(
|
||||||
|
decrypt(readFileSync(".confidant"), buffer(env.AUTH_KEY)).toString("utf8"),
|
||||||
|
);
|
||||||
|
const D = buffer(key);
|
||||||
|
await $`zip -r9 confidant.zip ${dirname} > /dev/null`;
|
||||||
|
await $`rm -rf ${dirname}`;
|
||||||
|
const Z = readFileSync("confidant.zip");
|
||||||
|
const E_Z = encrypt_file(Z, D);
|
||||||
|
writeFileSync(`vault.ant`, E_Z);
|
||||||
|
await $`rm confidant.zip .confidant`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function recovery(recoverystring: string) {
|
||||||
|
// Check if recovery phrase is correct
|
||||||
|
let config = Object(parse(readFileSync("config.toml").toString("utf8")));
|
||||||
|
let recovery_auth_key = HmacSHA256(recoverystring, env.AUTH_KEY).toString(
|
||||||
|
enc.Base64,
|
||||||
|
);
|
||||||
|
const dec = AES.decrypt(
|
||||||
|
config.config.recphrasestore,
|
||||||
|
recovery_auth_key,
|
||||||
|
).toString(enc.Utf8);
|
||||||
|
if (dec !== env.PHRASE) {
|
||||||
|
panic`Wrong recovery string. Please try again.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a fresh config file
|
||||||
|
const auth_secret = decrypt(
|
||||||
|
buffer(config.config.recoverystore),
|
||||||
|
buffer(recovery_auth_key),
|
||||||
|
);
|
||||||
|
const newpass = await password({
|
||||||
|
message: chalk`{reset {yellow Enter a new password:}}`,
|
||||||
|
mask: "•",
|
||||||
|
});
|
||||||
|
const recstring = generate_recovery_phrase();
|
||||||
|
recovery_auth_key = HmacSHA256(recstring, env.AUTH_KEY).toString(enc.Base64);
|
||||||
|
const password_auth_key = HmacSHA256(newpass, env.AUTH_KEY).toString(
|
||||||
|
enc.Base64,
|
||||||
|
);
|
||||||
|
|
||||||
|
config.config = {
|
||||||
|
...config.config,
|
||||||
|
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(config), "utf8"));
|
||||||
|
|
||||||
|
writeFileSync("recovery.txt", recstring + "\n");
|
||||||
|
console.log(`New recovery phrase:`);
|
||||||
|
log`{magenta ${recstring}}`;
|
||||||
|
}
|
||||||
94
src/utils.ts
Normal file
94
src/utils.ts
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import { AES, enc } from "crypto-js";
|
||||||
|
import { createHmac } from "crypto";
|
||||||
|
import { generatePrivate, getPublic } from "eccrypto";
|
||||||
|
import { generate } from "random-words";
|
||||||
|
import chalk from "chalk-template";
|
||||||
|
import { $ } from "bun";
|
||||||
|
import { existsSync } from "fs";
|
||||||
|
|
||||||
|
export function buffer(input: string): Buffer;
|
||||||
|
export function buffer(input: string, encoding: BufferEncoding): Buffer;
|
||||||
|
export function buffer(input: string, encoding?: BufferEncoding): Buffer {
|
||||||
|
return Buffer.from(input, encoding || "base64");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stringy(input: Buffer): string {
|
||||||
|
return input.toString("base64");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function encrypt(input: Buffer, key: Buffer) {
|
||||||
|
const result = AES.encrypt(stringy(input), stringy(key));
|
||||||
|
return buffer(result.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decrypt(input: Buffer, key: Buffer) {
|
||||||
|
const result = AES.decrypt(stringy(input), stringy(key));
|
||||||
|
return buffer(result.toString(enc.Utf8));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function random(start: number, end: number) {
|
||||||
|
return Math.floor((end - start) * Math.random() + start);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ECDH() {
|
||||||
|
const privkey = generatePrivate();
|
||||||
|
const pubkey = getPublic(privkey);
|
||||||
|
return [privkey, pubkey];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hmac(message: Buffer, key: Buffer): Buffer {
|
||||||
|
return buffer(
|
||||||
|
createHmac("sha256", stringy(key))
|
||||||
|
.update(stringy(message))
|
||||||
|
.digest("base64"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generate_recovery_phrase(): string {
|
||||||
|
return (
|
||||||
|
generate({ exactly: 12, minLength: 5, maxLength: 7 }) as string[]
|
||||||
|
).join(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function encrypt_file(input: Buffer, key: Buffer) {
|
||||||
|
const result = AES.encrypt(input.toString("base64"), key.toString("base64"));
|
||||||
|
return Buffer.from(result.toString(), "base64");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decrypt_file(input: Buffer, key: Buffer) {
|
||||||
|
const result = AES.decrypt(input.toString("base64"), key.toString("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(str: TemplateStringsArray, ...placeholders: unknown[]) {
|
||||||
|
console.log(chalk(str, placeholders));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function panic(str: TemplateStringsArray) {
|
||||||
|
console.log(chalk`{red ${str}}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function checkForFiles() {
|
||||||
|
const missing: string[] = [];
|
||||||
|
["data.con", "key.fid", "vault.ant", "config.toml"].forEach((val) => {
|
||||||
|
if (!existsSync(val)) {
|
||||||
|
missing.push(val);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (missing.length > 0) {
|
||||||
|
missing.forEach((val) => {
|
||||||
|
console.log(chalk`{red File "${val}" not found!}`);
|
||||||
|
});
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
27
tsconfig.json
Normal file
27
tsconfig.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
// Enable latest features
|
||||||
|
"lib": ["ESNext", "DOM"],
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"allowJs": true,
|
||||||
|
|
||||||
|
// Bundler mode
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
// Best practices
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
|
||||||
|
// Some stricter flags (disabled by default)
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
"noUnusedParameters": false,
|
||||||
|
"noPropertyAccessFromIndexSignature": false
|
||||||
|
}
|
||||||
|
}
|
||||||
24
types.d.ts
vendored
Normal file
24
types.d.ts
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
export interface Config {
|
||||||
|
config: {
|
||||||
|
con: string;
|
||||||
|
fid: string;
|
||||||
|
ant: string;
|
||||||
|
dir: string;
|
||||||
|
keystore: string;
|
||||||
|
recoverystore: string;
|
||||||
|
phrasestore: string;
|
||||||
|
recphrasestore: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ConData {
|
||||||
|
privateKey: string;
|
||||||
|
fidkey: string;
|
||||||
|
consalt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FidData {
|
||||||
|
privateKey: string;
|
||||||
|
salt: string;
|
||||||
|
code: number;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user