2017-12-15 21:37:02 +00:00
|
|
|
#!/usr/bin/env node
|
|
|
|
|
2022-11-05 10:58:21 +00:00
|
|
|
"use strict";
|
2017-12-14 22:38:34 +00:00
|
|
|
|
2022-04-23 10:00:18 +00:00
|
|
|
const fs = require("fs");
|
2018-01-17 11:31:57 +00:00
|
|
|
const path = require("path");
|
2022-11-05 10:58:21 +00:00
|
|
|
const Yargs = require("yargs");
|
2022-11-21 21:46:45 +00:00
|
|
|
|
|
|
|
// parse .env file into process.env
|
|
|
|
require('dotenv').config();
|
|
|
|
|
2022-11-20 14:25:16 +00:00
|
|
|
const cryptoEngine = require("../lib/cryptoEngine/cryptojsEngine");
|
|
|
|
const codec = require("../lib/codec");
|
2022-11-23 22:20:46 +00:00
|
|
|
const { convertCommonJSToBrowserJS, exitEarly, isOptionSetByUser, genFile, getPassword, getFileContent, getSalt} = require("./helpers");
|
2022-11-20 14:25:16 +00:00
|
|
|
const { generateRandomSalt } = cryptoEngine;
|
|
|
|
const { encode } = codec.init(cryptoEngine);
|
2017-12-14 22:38:34 +00:00
|
|
|
|
2022-11-05 10:58:21 +00:00
|
|
|
const SCRIPT_URL =
|
|
|
|
"https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/crypto-js.min.js";
|
|
|
|
const SCRIPT_TAG =
|
|
|
|
'<script src="' +
|
|
|
|
SCRIPT_URL +
|
|
|
|
'" integrity="sha384-lp4k1VRKPU9eBnPePjnJ9M2RF3i7PC30gXs70+elCVfgwLwx1tv5+ctxdtwxqZa7" crossorigin="anonymous"></script>';
|
2017-12-14 22:00:11 +00:00
|
|
|
|
2022-11-21 21:46:45 +00:00
|
|
|
const yargs = Yargs.usage("Usage: staticrypt <filename> [<passphrase>] [options]")
|
2022-11-05 10:58:21 +00:00
|
|
|
.option("c", {
|
|
|
|
alias: "config",
|
|
|
|
type: "string",
|
|
|
|
describe: 'Path to the config file. Set to "false" to disable.',
|
|
|
|
default: ".staticrypt.json",
|
|
|
|
})
|
|
|
|
.option("decrypt-button", {
|
|
|
|
type: "string",
|
|
|
|
describe: 'Label to use for the decrypt button. Default: "DECRYPT".',
|
|
|
|
default: "DECRYPT",
|
|
|
|
})
|
|
|
|
.option("e", {
|
|
|
|
alias: "embed",
|
|
|
|
type: "boolean",
|
|
|
|
describe:
|
|
|
|
"Whether or not to embed crypto-js in the page (or use an external CDN).",
|
|
|
|
default: true,
|
|
|
|
})
|
|
|
|
.option("f", {
|
|
|
|
alias: "file-template",
|
|
|
|
type: "string",
|
|
|
|
describe: "Path to custom HTML template with passphrase prompt.",
|
|
|
|
default: path.join(__dirname, "..", "lib", "password_template.html"),
|
|
|
|
})
|
|
|
|
.option("i", {
|
|
|
|
alias: "instructions",
|
|
|
|
type: "string",
|
|
|
|
describe: "Special instructions to display to the user.",
|
|
|
|
default: "",
|
|
|
|
})
|
2022-11-15 08:21:58 +00:00
|
|
|
.option("label-error", {
|
|
|
|
type: "string",
|
|
|
|
describe: "Error message to display on entering wrong passphrase.",
|
|
|
|
default: "Bad password!",
|
|
|
|
})
|
2022-11-05 10:58:21 +00:00
|
|
|
.option("noremember", {
|
|
|
|
type: "boolean",
|
|
|
|
describe: 'Set this flag to remove the "Remember me" checkbox.',
|
|
|
|
default: false,
|
|
|
|
})
|
|
|
|
.option("o", {
|
|
|
|
alias: "output",
|
|
|
|
type: "string",
|
2022-12-20 20:06:46 +00:00
|
|
|
describe: "File name/path for the generated encrypted file.",
|
2022-11-05 10:58:21 +00:00
|
|
|
default: null,
|
|
|
|
})
|
|
|
|
.option("passphrase-placeholder", {
|
|
|
|
type: "string",
|
|
|
|
describe: "Placeholder to use for the passphrase input.",
|
2022-11-15 08:21:58 +00:00
|
|
|
default: "Password",
|
2022-11-05 10:58:21 +00:00
|
|
|
})
|
|
|
|
.option("r", {
|
|
|
|
alias: "remember",
|
|
|
|
type: "number",
|
|
|
|
describe:
|
|
|
|
'Expiration in days of the "Remember me" checkbox that will save the (salted + hashed) passphrase ' +
|
|
|
|
'in localStorage when entered by the user. Default: "0", no expiration.',
|
|
|
|
default: 0,
|
|
|
|
})
|
|
|
|
.option("remember-label", {
|
|
|
|
type: "string",
|
|
|
|
describe: 'Label to use for the "Remember me" checkbox.',
|
|
|
|
default: "Remember me",
|
|
|
|
})
|
2022-11-20 18:16:27 +00:00
|
|
|
// do not give a default option to this parameter - we want to see when the flag is included with no
|
2022-11-05 10:58:21 +00:00
|
|
|
// value and when it's not included at all
|
|
|
|
.option("s", {
|
|
|
|
alias: "salt",
|
|
|
|
describe:
|
2022-12-20 20:06:46 +00:00
|
|
|
'Set the salt manually. It should be set if you want to use "Remember me" through multiple pages. It ' +
|
|
|
|
"needs to be a 32-character-long hexadecimal string.\nInclude the empty flag to generate a random salt you " +
|
2022-11-05 10:58:21 +00:00
|
|
|
'can use: "statycrypt -s".',
|
|
|
|
type: "string",
|
|
|
|
})
|
2022-11-20 18:16:27 +00:00
|
|
|
// do not give a default option to this parameter - we want to see when the flag is included with no
|
|
|
|
// value and when it's not included at all
|
|
|
|
.option("share", {
|
|
|
|
describe:
|
|
|
|
'Get a link containing your hashed password that will auto-decrypt the page. Pass your URL as a value to append '
|
|
|
|
+ '"?staticrypt_pwd=<hashed_pwd>", or leave empty to display the hash to append.',
|
|
|
|
type: "string",
|
|
|
|
})
|
2022-11-05 10:58:21 +00:00
|
|
|
.option("t", {
|
|
|
|
alias: "title",
|
|
|
|
type: "string",
|
2022-12-20 20:06:46 +00:00
|
|
|
describe: "Title for the output HTML page.",
|
2022-11-05 10:58:21 +00:00
|
|
|
default: "Protected Page",
|
|
|
|
});
|
2022-02-27 18:37:17 +00:00
|
|
|
const namedArgs = yargs.argv;
|
2022-02-10 08:22:32 +00:00
|
|
|
|
2022-04-23 10:00:18 +00:00
|
|
|
// if the 's' flag is passed without parameter, generate a salt, display & exit
|
2022-11-05 10:58:21 +00:00
|
|
|
if (isOptionSetByUser("s", yargs) && !namedArgs.salt) {
|
|
|
|
console.log(generateRandomSalt());
|
|
|
|
process.exit(0);
|
2022-02-27 18:37:17 +00:00
|
|
|
}
|
|
|
|
|
2022-04-23 10:00:18 +00:00
|
|
|
// validate the number of arguments
|
2022-11-21 21:46:45 +00:00
|
|
|
const positionalArguments = namedArgs._;
|
|
|
|
if (positionalArguments.length > 2 || positionalArguments.length === 0) {
|
2022-11-05 10:58:21 +00:00
|
|
|
Yargs.showHelp();
|
|
|
|
process.exit(1);
|
2022-02-10 08:22:32 +00:00
|
|
|
}
|
|
|
|
|
2022-11-21 21:46:45 +00:00
|
|
|
// parse input
|
|
|
|
const inputFilepath = positionalArguments[0].toString(),
|
|
|
|
password = getPassword(positionalArguments);
|
|
|
|
|
2022-04-23 10:00:18 +00:00
|
|
|
// get config file
|
2022-11-05 10:58:21 +00:00
|
|
|
const isUsingconfigFile = namedArgs.config.toLowerCase() !== "false";
|
|
|
|
const configPath = "./" + namedArgs.config;
|
2022-04-23 10:00:18 +00:00
|
|
|
let config = {};
|
|
|
|
if (isUsingconfigFile && fs.existsSync(configPath)) {
|
2022-11-05 10:58:21 +00:00
|
|
|
config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
2022-04-23 10:00:18 +00:00
|
|
|
}
|
|
|
|
|
2022-11-21 21:46:45 +00:00
|
|
|
// get the salt
|
|
|
|
const salt = getSalt(namedArgs, config);
|
2022-04-23 10:00:18 +00:00
|
|
|
|
|
|
|
// validate the salt
|
|
|
|
if (salt.length !== 32 || /[^a-f0-9]/.test(salt)) {
|
2022-11-20 14:25:16 +00:00
|
|
|
exitEarly(
|
2022-11-05 10:58:21 +00:00
|
|
|
"The salt should be a 32 character long hexadecimal string (only [0-9a-f] characters allowed)"
|
2022-11-20 14:25:16 +00:00
|
|
|
+ "\nDetected salt: " + salt
|
2022-11-05 10:58:21 +00:00
|
|
|
);
|
2022-04-23 10:00:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// write salt to config file
|
|
|
|
if (isUsingconfigFile && config.salt !== salt) {
|
2022-11-05 10:58:21 +00:00
|
|
|
config.salt = salt;
|
|
|
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 4));
|
2022-04-23 10:00:18 +00:00
|
|
|
}
|
|
|
|
|
2022-11-20 18:16:27 +00:00
|
|
|
// display the share link with the hashed password if the --share flag is set
|
|
|
|
if (isOptionSetByUser("share", yargs)) {
|
|
|
|
const url = namedArgs.share || "";
|
2022-11-21 21:46:45 +00:00
|
|
|
const hashedPassphrase = cryptoEngine.hashPassphrase(password, salt);
|
2022-11-20 18:16:27 +00:00
|
|
|
|
|
|
|
console.log(url + "?staticrypt_pwd=" + hashedPassphrase);
|
|
|
|
}
|
|
|
|
|
2022-02-10 08:22:32 +00:00
|
|
|
// get the file content
|
2022-11-21 21:46:45 +00:00
|
|
|
const contents = getFileContent(inputFilepath);
|
2019-06-28 14:23:13 +00:00
|
|
|
|
2017-12-28 11:32:27 +00:00
|
|
|
// encrypt input
|
2022-11-21 21:46:45 +00:00
|
|
|
const encryptedMessage = encode(contents, password, salt);
|
2017-12-14 22:38:34 +00:00
|
|
|
|
2017-12-28 11:32:27 +00:00
|
|
|
// create crypto-js tag (embedded or not)
|
2022-02-10 08:22:32 +00:00
|
|
|
let cryptoTag = SCRIPT_TAG;
|
2017-12-28 11:32:27 +00:00
|
|
|
if (namedArgs.embed) {
|
2022-11-05 10:58:21 +00:00
|
|
|
try {
|
|
|
|
const embedContents = fs.readFileSync(
|
2022-11-20 14:25:16 +00:00
|
|
|
path.join(__dirname, "..", "lib", "kryptojs-3.1.9-1.min.js"),
|
2022-11-05 10:58:21 +00:00
|
|
|
"utf8"
|
|
|
|
);
|
|
|
|
|
|
|
|
cryptoTag = "<script>" + embedContents + "</script>";
|
|
|
|
} catch (e) {
|
2022-11-20 14:25:16 +00:00
|
|
|
exitEarly("Failure: embed file does not exist!");
|
2022-11-05 10:58:21 +00:00
|
|
|
}
|
2017-12-14 22:38:34 +00:00
|
|
|
}
|
|
|
|
|
2022-02-10 08:22:32 +00:00
|
|
|
const data = {
|
2022-11-05 10:58:21 +00:00
|
|
|
crypto_tag: cryptoTag,
|
|
|
|
decrypt_button: namedArgs.decryptButton,
|
|
|
|
embed: namedArgs.embed,
|
|
|
|
encrypted: encryptedMessage,
|
|
|
|
instructions: namedArgs.instructions,
|
|
|
|
is_remember_enabled: namedArgs.noremember ? "false" : "true",
|
2022-11-23 22:20:46 +00:00
|
|
|
js_codec: convertCommonJSToBrowserJS("lib/codec"),
|
|
|
|
js_crypto_engine: convertCommonJSToBrowserJS("lib/cryptoEngine/cryptojsEngine"),
|
2022-11-15 08:21:58 +00:00
|
|
|
label_error: namedArgs.labelError,
|
2022-11-05 10:58:21 +00:00
|
|
|
passphrase_placeholder: namedArgs.passphrasePlaceholder,
|
|
|
|
remember_duration_in_days: namedArgs.remember,
|
|
|
|
remember_me: namedArgs.rememberLabel,
|
|
|
|
salt: salt,
|
|
|
|
title: namedArgs.title,
|
2017-12-28 11:32:27 +00:00
|
|
|
};
|
|
|
|
|
2022-11-20 14:25:16 +00:00
|
|
|
const outputFilePath = namedArgs.output !== null
|
|
|
|
? namedArgs.output
|
2022-11-21 21:46:45 +00:00
|
|
|
: inputFilepath.replace(/\.html$/, "") + "_encrypted.html";
|
2017-12-14 22:38:34 +00:00
|
|
|
|
2022-11-20 14:25:16 +00:00
|
|
|
genFile(data, outputFilePath, namedArgs.f);
|