add and run prettier

pull/177/head
robinmoisson 2023-04-19 11:18:27 +02:00
rodzic 4db6e90b8c
commit f5721f079a
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 9419716500078583
11 zmienionych plików z 1163 dodań i 1131 usunięć

15
.prettierrc.json 100644
Wyświetl plik

@ -0,0 +1,15 @@
{
"printWidth": 120,
"semi": true,
"singleQuote": false,
"tabWidth": 4,
"trailingComma": "es5",
"overrides": [
{
"files": "*.{js,html}",
"options": {
"proseWrap": "preserve"
}
}
]
}

Wyświetl plik

@ -1,14 +1,13 @@
const path = require("path");
const fs = require("fs");
const readline = require('readline');
const readline = require("readline");
const { generateRandomSalt, generateRandomString} = require("../lib/cryptoEngine.js");
const { generateRandomSalt, generateRandomString } = require("../lib/cryptoEngine.js");
const { renderTemplate } = require("../lib/formater.js");
const Yargs = require("yargs");
const PASSWORD_TEMPLATE_DEFAULT_PATH = path.join(__dirname, "..", "lib", "password_template.html");
/**
* @param {string} message
*/
@ -43,8 +42,9 @@ function isOptionSetByUser(option, yargs) {
for (let aliasIndex in yargs.parsed.aliases[option]) {
const alias = yargs.parsed.aliases[option][aliasIndex];
if (searchForOption(`-${alias}`) || searchForOption(`--${alias}`))
if (searchForOption(`-${alias}`) || searchForOption(`--${alias}`)) {
return true;
}
}
return false;
@ -57,7 +57,7 @@ exports.isOptionSetByUser = isOptionSetByUser;
* @param {string} question
* @returns {Promise<string>}
*/
function prompt (question) {
function prompt(question) {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
@ -77,11 +77,11 @@ async function getValidatedPassword(passwordArgument, isShortAllowed) {
if (password.length < 14 && !isShortAllowed) {
const shouldUseShort = await prompt(
`WARNING: Your password is less than 14 characters (length: ${password.length})` +
" and it's easy to try brute-forcing on public files. For better security we recommend using a longer one, for example: "
+ generateRandomString(21)
+ "\nYou can hide this warning by increasing your password length or adding the '--short' flag." +
" Do you want to use the short password? [y/N] "
)
" and it's easy to try brute-forcing on public files. For better security we recommend using a longer one, for example: " +
generateRandomString(21) +
"\nYou can hide this warning by increasing your password length or adding the '--short' flag." +
" Do you want to use the short password? [y/N] "
);
if (!shouldUseShort.match(/^\s*(y|yes)\s*$/i)) {
console.log("Aborting.");
@ -135,7 +135,7 @@ async function getPassword(passwordArgument) {
}
// prompt the user for their password
return prompt('Enter your long, unusual password: ');
return prompt("Enter your long, unusual password: ");
}
/**
@ -162,8 +162,9 @@ function getValidatedSalt(namedArgs, config) {
// validate the salt
if (salt.length !== 32 || /[^a-f0-9]/.test(salt)) {
exitWithError(
"the salt should be a 32 character long hexadecimal string (only [0-9a-f] characters allowed)"
+ "\nDetected salt: " + salt
"the salt should be a 32 character long hexadecimal string (only [0-9a-f] characters allowed)" +
"\nDetected salt: " +
salt
);
}
@ -199,16 +200,14 @@ function getSalt(namedArgs, config) {
* @param {string} modulePath - path from staticrypt root directory
*/
function convertCommonJSToBrowserJS(modulePath) {
const rootDirectory = path.join(__dirname, '..');
const rootDirectory = path.join(__dirname, "..");
const resolvedPath = path.join(rootDirectory, ...modulePath.split("/")) + ".js";
if (!fs.existsSync(resolvedPath)) {
exitWithError(`could not find module to convert at path "${resolvedPath}"`);
}
const moduleText = fs
.readFileSync(resolvedPath, "utf8")
.replace(/^.*\brequire\(.*$\n/gm, "");
const moduleText = fs.readFileSync(resolvedPath, "utf8").replace(/^.*\brequire\(.*$\n/gm, "");
return `
((function(){
@ -289,107 +288,110 @@ function isCustomPasswordTemplateDefault(templatePathParameter) {
exports.isCustomPasswordTemplateDefault = isCustomPasswordTemplateDefault;
function parseCommandLineArguments() {
return Yargs.usage("Usage: staticrypt <filename> [<filename> ...] [options]")
.option("c", {
alias: "config",
type: "string",
describe: 'Path to the config file. Set to "false" to disable.',
default: ".staticrypt.json",
})
.option("d", {
alias: "directory",
type: "string",
describe: "Name of the directory where the encrypted files will be saved.",
default: "encrypted/",
})
.option("p", {
alias: "password",
type: "string",
describe: "The password to encrypt your file with. Leave empty to be prompted for it. If STATICRYPT_PASSWORD" +
" is set in the env, we'll use that instead.",
default: null,
})
.option("r", {
alias: "recursive",
type: "boolean",
describe: "Whether to recursively encrypt the input directory.",
default: false,
})
.option("remember", {
type: "number",
describe:
'Expiration in days of the "Remember me" checkbox that will save the (salted + hashed) password ' +
'in localStorage when entered by the user. Set to "false" to hide the box. Default: "0", no expiration.',
default: 0,
})
// 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("s", {
alias: "salt",
describe:
'Generate a config file or set the salt manually. Pass a 32-character-long hexadecimal string ' +
'to use as salt, or leave empty to generate, display and save to config a random salt. This won\'t' +
' overwrite an existing config file.',
type: "string",
})
// 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",
})
.option("short", {
describe: 'Hide the "short password" warning.',
type: "boolean",
default: false,
})
.option("t", {
alias: "template",
type: "string",
describe: "Path to custom HTML template with password prompt.",
default: PASSWORD_TEMPLATE_DEFAULT_PATH,
})
.option("template-button", {
type: "string",
describe: 'Label to use for the decrypt button. Default: "DECRYPT".',
default: "DECRYPT",
})
.option("template-color-primary", {
type: "string",
describe: "Primary color (button...)",
default: "#4CAF50",
})
.option("template-color-secondary", {
type: "string",
describe: "Secondary color (page background...)",
default: "#76B852",
})
.option("template-instructions", {
type: "string",
describe: "Special instructions to display to the user.",
default: "",
})
.option("template-error", {
type: "string",
describe: "Error message to display on entering wrong password.",
default: "Bad password!",
})
.option("template-placeholder", {
type: "string",
describe: "Placeholder to use for the password input.",
default: "Password",
})
.option("template-remember", {
type: "string",
describe: 'Label to use for the "Remember me" checkbox.',
default: "Remember me",
})
.option("template-title", {
type: "string",
describe: "Title for the output HTML page.",
default: "Protected Page",
});
return (
Yargs.usage("Usage: staticrypt <filename> [<filename> ...] [options]")
.option("c", {
alias: "config",
type: "string",
describe: 'Path to the config file. Set to "false" to disable.',
default: ".staticrypt.json",
})
.option("d", {
alias: "directory",
type: "string",
describe: "Name of the directory where the encrypted files will be saved.",
default: "encrypted/",
})
.option("p", {
alias: "password",
type: "string",
describe:
"The password to encrypt your file with. Leave empty to be prompted for it. If STATICRYPT_PASSWORD" +
" is set in the env, we'll use that instead.",
default: null,
})
.option("r", {
alias: "recursive",
type: "boolean",
describe: "Whether to recursively encrypt the input directory.",
default: false,
})
.option("remember", {
type: "number",
describe:
'Expiration in days of the "Remember me" checkbox that will save the (salted + hashed) password ' +
'in localStorage when entered by the user. Set to "false" to hide the box. Default: "0", no expiration.',
default: 0,
})
// 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("s", {
alias: "salt",
describe:
"Generate a config file or set the salt manually. Pass a 32-character-long hexadecimal string " +
"to use as salt, or leave empty to generate, display and save to config a random salt. This won't" +
" overwrite an existing config file.",
type: "string",
})
// 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",
})
.option("short", {
describe: 'Hide the "short password" warning.',
type: "boolean",
default: false,
})
.option("t", {
alias: "template",
type: "string",
describe: "Path to custom HTML template with password prompt.",
default: PASSWORD_TEMPLATE_DEFAULT_PATH,
})
.option("template-button", {
type: "string",
describe: 'Label to use for the decrypt button. Default: "DECRYPT".',
default: "DECRYPT",
})
.option("template-color-primary", {
type: "string",
describe: "Primary color (button...)",
default: "#4CAF50",
})
.option("template-color-secondary", {
type: "string",
describe: "Secondary color (page background...)",
default: "#76B852",
})
.option("template-instructions", {
type: "string",
describe: "Special instructions to display to the user.",
default: "",
})
.option("template-error", {
type: "string",
describe: "Error message to display on entering wrong password.",
default: "Bad password!",
})
.option("template-placeholder", {
type: "string",
describe: "Placeholder to use for the password input.",
default: "Password",
})
.option("template-remember", {
type: "string",
describe: 'Label to use for the "Remember me" checkbox.',
default: "Remember me",
})
.option("template-title", {
type: "string",
describe: "Title for the output HTML page.",
default: "Protected Page",
})
);
}
exports.parseCommandLineArguments = parseCommandLineArguments;

Wyświetl plik

@ -10,7 +10,7 @@ if (nodeVersion[0] < 16) {
}
// parse .env file into process.env
require('dotenv').config();
require("dotenv").config();
const fs = require("fs");
@ -18,9 +18,16 @@ const cryptoEngine = require("../lib/cryptoEngine.js");
const codec = require("../lib/codec.js");
const { generateRandomSalt } = cryptoEngine;
const { encodeWithHashedPassword } = codec.init(cryptoEngine);
const { parseCommandLineArguments, buildStaticryptJS, isOptionSetByUser, genFile, getFileContent,
const {
parseCommandLineArguments,
buildStaticryptJS,
isOptionSetByUser,
genFile,
getFileContent,
getValidatedSalt,
getValidatedPassword, getConfig, writeConfig
getValidatedPassword,
getConfig,
writeConfig,
} = require("./helpers.js");
// parse arguments
@ -100,27 +107,26 @@ async function runStatiCrypt() {
const hashedPassword = await cryptoEngine.hashPassword(password, salt);
positionalArguments.forEach(path => encodeAndGenerateFile(
path.toString(),
hashedPassword,
salt,
baseTemplateData,
isRememberEnabled,
namedArgs
));
positionalArguments.forEach((path) =>
encodeAndGenerateFile(path.toString(), hashedPassword, salt, baseTemplateData, isRememberEnabled, namedArgs)
);
}
async function encodeAndGenerateFile(path, hashedPassword, salt, baseTemplateData, isRememberEnabled, namedArgs) {
// if the path is a directory, get into it and process all files
if (fs.statSync(path).isDirectory()) {
if (!namedArgs.recursive) {
console.log("ERROR: The path '" + path + "' is a directory. Use the -r|--recursive flag to process all files in the directory.");
console.log(
"ERROR: The path '" +
path +
"' is a directory. Use the -r|--recursive flag to process all files in the directory."
);
// just return instead of exiting the process, that way all other files can be processed
return;
}
fs.readdirSync(path).forEach(filePath => {
fs.readdirSync(path).forEach((filePath) => {
const fullPath = `${path}/${filePath}`;
encodeAndGenerateFile(fullPath, hashedPassword, salt, baseTemplateData, isRememberEnabled, namedArgs);
@ -145,7 +151,7 @@ async function encodeAndGenerateFile(path, hashedPassword, salt, baseTemplateDat
staticrypt_config: staticryptConfig,
};
const outputFilepath = namedArgs.directory.replace(/\/+$/, '') + "/" + path;
const outputFilepath = namedArgs.directory.replace(/\/+$/, "") + "/" + path;
genFile(templateData, outputFilepath, namedArgs.template);
}

Wyświetl plik

@ -1,188 +1,188 @@
<!doctype html>
<!DOCTYPE html>
<html class="staticrypt-html">
<head>
<meta charset="utf-8">
<title>Protected Page</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<head>
<meta charset="utf-8" />
<title>Protected Page</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- do not cache this page -->
<meta http-equiv="cache-control" content="max-age=0"/>
<meta http-equiv="cache-control" content="no-cache"/>
<meta http-equiv="expires" content="0"/>
<meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT"/>
<meta http-equiv="pragma" content="no-cache"/>
<!-- do not cache this page -->
<meta http-equiv="cache-control" content="max-age=0" />
<meta http-equiv="cache-control" content="no-cache" />
<meta http-equiv="expires" content="0" />
<meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" />
<meta http-equiv="pragma" content="no-cache" />
<style>
.staticrypt-hr {
margin-top: 20px;
margin-bottom: 20px;
border: 0;
border-top: 1px solid #eee;
}
.staticrypt-page {
width: 360px;
padding: 8% 0 0;
margin: auto;
box-sizing: border-box;
}
.staticrypt-form {
position: relative;
z-index: 1;
background: #FFFFFF;
max-width: 360px;
margin: 0 auto 100px;
padding: 45px;
text-align: center;
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.2), 0 5px 5px 0 rgba(0, 0, 0, 0.24);
}
.staticrypt-form input[type="password"] {
outline: 0;
background: #f2f2f2;
width: 100%;
border: 0;
margin: 0 0 15px;
padding: 15px;
box-sizing: border-box;
font-size: 14px;
}
.staticrypt-form .staticrypt-decrypt-button {
text-transform: uppercase;
outline: 0;
background: #4CAF50;
width: 100%;
border: 0;
padding: 15px;
color: #FFFFFF;
font-size: 14px;
cursor: pointer;
}
.staticrypt-form .staticrypt-decrypt-button:hover, .staticrypt-form .staticrypt-decrypt-button:active, .staticrypt-form .staticrypt-decrypt-button:focus {
background: #4CAF50;
filter: brightness(92%);
}
.staticrypt-html {
height: 100%;
}
.staticrypt-body {
height: 100%;
margin: 0;
}
.staticrypt-content {
height: 100%;
margin-bottom: 1em;
background: #76B852;
font-family: "Arial", sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.staticrypt-instructions {
margin-top: -1em;
margin-bottom: 1em;
}
.staticrypt-title {
font-size: 1.5em;
}
label.staticrypt-remember {
display: flex;
align-items: center;
margin-bottom: 1em;
}
.staticrypt-remember input[type=checkbox] {
transform: scale(1.5);
margin-right: 1em;
}
.hidden {
display: none !important;
}
.staticrypt-spinner-container {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.staticrypt-spinner {
display: inline-block;
width: 2rem;
height: 2rem;
vertical-align: text-bottom;
border: 0.25em solid gray;
border-right-color: transparent;
border-radius: 50%;
-webkit-animation: spinner-border .75s linear infinite;
animation: spinner-border .75s linear infinite;
animation-duration: 0.75s;
animation-timing-function: linear;
animation-delay: 0s;
animation-iteration-count: infinite;
animation-direction: normal;
animation-fill-mode: none;
animation-play-state: running;
animation-name: spinner-border;
}
@keyframes spinner-border {
100% {
transform: rotate(360deg);
<style>
.staticrypt-hr {
margin-top: 20px;
margin-bottom: 20px;
border: 0;
border-top: 1px solid #eee;
}
}
</style>
</head>
<body class="staticrypt-body">
.staticrypt-page {
width: 360px;
padding: 8% 0 0;
margin: auto;
box-sizing: border-box;
}
<div id="staticrypt_loading" class="staticrypt-spinner-container">
<div class="staticrypt-spinner"></div>
</div>
.staticrypt-form {
position: relative;
z-index: 1;
background: #ffffff;
max-width: 360px;
margin: 0 auto 100px;
padding: 45px;
text-align: center;
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.2), 0 5px 5px 0 rgba(0, 0, 0, 0.24);
}
<div id="staticrypt_content" class="staticrypt-content hidden">
<div class="staticrypt-page">
<div class="staticrypt-form">
<div class="staticrypt-instructions">
<p class="staticrypt-title">Protected Page</p>
<p>Enter "test" to unlock the page</p>
</div>
.staticrypt-form input[type="password"] {
outline: 0;
background: #f2f2f2;
width: 100%;
border: 0;
margin: 0 0 15px;
padding: 15px;
box-sizing: border-box;
font-size: 14px;
}
<hr class="staticrypt-hr">
.staticrypt-form .staticrypt-decrypt-button {
text-transform: uppercase;
outline: 0;
background: #4CAF50;
width: 100%;
border: 0;
padding: 15px;
color: #ffffff;
font-size: 14px;
cursor: pointer;
}
<form id="staticrypt-form" action="#" method="post">
<input id="staticrypt-password"
type="password"
name="password"
placeholder="Password"
autofocus/>
.staticrypt-form .staticrypt-decrypt-button:hover,
.staticrypt-form .staticrypt-decrypt-button:active,
.staticrypt-form .staticrypt-decrypt-button:focus {
background: #4CAF50;
filter: brightness(92%);
}
<label id="staticrypt-remember-label" class="staticrypt-remember hidden">
<input id="staticrypt-remember"
type="checkbox"
name="remember"/>
Remember me
</label>
.staticrypt-html {
height: 100%;
}
<input type="submit" class="staticrypt-decrypt-button" value="DECRYPT"/>
</form>
.staticrypt-body {
height: 100%;
margin: 0;
}
.staticrypt-content {
height: 100%;
margin-bottom: 1em;
background: #76B852;
font-family: "Arial", sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.staticrypt-instructions {
margin-top: -1em;
margin-bottom: 1em;
}
.staticrypt-title {
font-size: 1.5em;
}
label.staticrypt-remember {
display: flex;
align-items: center;
margin-bottom: 1em;
}
.staticrypt-remember input[type="checkbox"] {
transform: scale(1.5);
margin-right: 1em;
}
.hidden {
display: none !important;
}
.staticrypt-spinner-container {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.staticrypt-spinner {
display: inline-block;
width: 2rem;
height: 2rem;
vertical-align: text-bottom;
border: 0.25em solid gray;
border-right-color: transparent;
border-radius: 50%;
-webkit-animation: spinner-border 0.75s linear infinite;
animation: spinner-border 0.75s linear infinite;
animation-duration: 0.75s;
animation-timing-function: linear;
animation-delay: 0s;
animation-iteration-count: infinite;
animation-direction: normal;
animation-fill-mode: none;
animation-play-state: running;
animation-name: spinner-border;
}
@keyframes spinner-border {
100% {
transform: rotate(360deg);
}
}
</style>
</head>
<body class="staticrypt-body">
<div id="staticrypt_loading" class="staticrypt-spinner-container">
<div class="staticrypt-spinner"></div>
</div>
</div>
</div>
<div id="staticrypt_content" class="staticrypt-content hidden">
<div class="staticrypt-page">
<div class="staticrypt-form">
<div class="staticrypt-instructions">
<p class="staticrypt-title">Protected Page</p>
<p>Enter "test" to unlock the page</p>
</div>
<script>
// these variables will be filled when generating the file - the template format is 'variable_name'
const staticryptInitiator = ((function(){
<hr class="staticrypt-hr" />
<form id="staticrypt-form" action="#" method="post">
<input
id="staticrypt-password"
type="password"
name="password"
placeholder="Password"
autofocus
/>
<label id="staticrypt-remember-label" class="staticrypt-remember hidden">
<input id="staticrypt-remember" type="checkbox" name="remember" />
Remember me
</label>
<input type="submit" class="staticrypt-decrypt-button" value="DECRYPT" />
</form>
</div>
</div>
</div>
<script>
// these variables will be filled when generating the file - the template format is 'variable_name'
const staticryptInitiator = ((function(){
const exports = {};
const cryptoEngine = ((function(){
const exports = {};
@ -256,13 +256,7 @@ async function encrypt(msg, hashedPassword) {
// https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/encrypt#parameters
const iv = crypto.getRandomValues(new Uint8Array(IV_BITS / 8));
const key = await subtle.importKey(
"raw",
HexEncoder.parse(hashedPassword),
ENCRYPTION_ALGO,
false,
["encrypt"]
);
const key = await subtle.importKey("raw", HexEncoder.parse(hashedPassword), ENCRYPTION_ALGO, false, ["encrypt"]);
const encrypted = await subtle.encrypt(
{
@ -290,13 +284,7 @@ async function decrypt(encryptedMsg, hashedPassword) {
const iv = HexEncoder.parse(encryptedMsg.substring(0, ivLength));
const encrypted = encryptedMsg.substring(ivLength);
const key = await subtle.importKey(
"raw",
HexEncoder.parse(hashedPassword),
ENCRYPTION_ALGO,
false,
["decrypt"]
);
const key = await subtle.importKey("raw", HexEncoder.parse(hashedPassword), ENCRYPTION_ALGO, false, ["decrypt"]);
const outBuffer = await subtle.decrypt(
{
@ -378,13 +366,7 @@ exports.hashThirdRound = hashThirdRound;
* @returns {Promise<string>}
*/
async function pbkdf2(password, salt, iterations, hashAlgorithm) {
const key = await subtle.importKey(
"raw",
UTF8Encoder.parse(password),
"PBKDF2",
false,
["deriveBits"]
);
const key = await subtle.importKey("raw", UTF8Encoder.parse(password), "PBKDF2", false, ["deriveBits"]);
const keyBytes = await subtle.deriveBits(
{
@ -424,7 +406,6 @@ async function signMessage(hashedPassword, message) {
}
exports.signMessage = signMessage;
function getRandomAlphanum() {
const possibleCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
@ -452,7 +433,7 @@ function getRandomAlphanum() {
* @returns {string}
*/
function generateRandomString(length) {
let randomString = '';
let randomString = "";
for (let i = 0; i < length; i++) {
randomString += getRandomAlphanum();
@ -462,9 +443,8 @@ function generateRandomString(length) {
}
exports.generateRandomString = generateRandomString;
return exports;
})())
})());
const codec = ((function(){
const exports = {};
/**
@ -473,109 +453,102 @@ const codec = ((function(){
* @param cryptoEngine - the engine to use for encryption / decryption
*/
function init(cryptoEngine) {
const exports = {};
const exports = {};
/**
* Top-level function for encoding a message.
* Includes password hashing, encryption, and signing.
*
* @param {string} msg
* @param {string} password
* @param {string} salt
*
* @returns {string} The encoded text
*/
async function encode(msg, password, salt) {
const hashedPassword = await cryptoEngine.hashPassword(password, salt);
/**
* Top-level function for encoding a message.
* Includes password hashing, encryption, and signing.
*
* @param {string} msg
* @param {string} password
* @param {string} salt
*
* @returns {string} The encoded text
*/
async function encode(msg, password, salt) {
const hashedPassword = await cryptoEngine.hashPassword(password, salt);
const encrypted = await cryptoEngine.encrypt(msg, hashedPassword);
const encrypted = await cryptoEngine.encrypt(msg, hashedPassword);
// we use the hashed password in the HMAC because this is effectively what will be used a password (so we can store
// it in localStorage safely, we don't use the clear text password)
const hmac = await cryptoEngine.signMessage(hashedPassword, encrypted);
// we use the hashed password in the HMAC because this is effectively what will be used a password (so we can store
// it in localStorage safely, we don't use the clear text password)
const hmac = await cryptoEngine.signMessage(hashedPassword, encrypted);
return hmac + encrypted;
}
exports.encode = encode;
/**
* Encode using a password that has already been hashed. This is useful to encode multiple messages in a row, that way
* we don't need to hash the password multiple times.
*
* @param {string} msg
* @param {string} hashedPassword
*
* @returns {string} The encoded text
*/
async function encodeWithHashedPassword(msg, hashedPassword) {
const encrypted = await cryptoEngine.encrypt(msg, hashedPassword);
// we use the hashed password in the HMAC because this is effectively what will be used a password (so we can store
// it in localStorage safely, we don't use the clear text password)
const hmac = await cryptoEngine.signMessage(hashedPassword, encrypted);
return hmac + encrypted;
}
exports.encodeWithHashedPassword = encodeWithHashedPassword;
/**
* Top-level function for decoding a message.
* Includes signature check and decryption.
*
* @param {string} signedMsg
* @param {string} hashedPassword
* @param {string} salt
* @param {int} backwardCompatibleAttempt
* @param {string} originalPassword
*
* @returns {Object} {success: true, decoded: string} | {success: false, message: string}
*/
async function decode(
signedMsg,
hashedPassword,
salt,
backwardCompatibleAttempt = 0,
originalPassword = ''
) {
const encryptedHMAC = signedMsg.substring(0, 64);
const encryptedMsg = signedMsg.substring(64);
const decryptedHMAC = await cryptoEngine.signMessage(hashedPassword, encryptedMsg);
if (decryptedHMAC !== encryptedHMAC) {
// we have been raising the number of iterations in the hashing algorithm multiple times, so to support the old
// remember-me/autodecrypt links we need to try bringing the old hashes up to speed.
originalPassword = originalPassword || hashedPassword;
if (backwardCompatibleAttempt === 0) {
const updatedHashedPassword = await cryptoEngine.hashThirdRound(originalPassword, salt);
return decode(signedMsg, updatedHashedPassword, salt, backwardCompatibleAttempt + 1, originalPassword);
}
if (backwardCompatibleAttempt === 1) {
let updatedHashedPassword = await cryptoEngine.hashSecondRound(originalPassword, salt);
updatedHashedPassword = await cryptoEngine.hashThirdRound(updatedHashedPassword, salt);
return decode(signedMsg, updatedHashedPassword, salt, backwardCompatibleAttempt + 1, originalPassword);
}
return { success: false, message: "Signature mismatch" };
return hmac + encrypted;
}
exports.encode = encode;
return {
success: true,
decoded: await cryptoEngine.decrypt(encryptedMsg, hashedPassword),
};
}
exports.decode = decode;
/**
* Encode using a password that has already been hashed. This is useful to encode multiple messages in a row, that way
* we don't need to hash the password multiple times.
*
* @param {string} msg
* @param {string} hashedPassword
*
* @returns {string} The encoded text
*/
async function encodeWithHashedPassword(msg, hashedPassword) {
const encrypted = await cryptoEngine.encrypt(msg, hashedPassword);
return exports;
// we use the hashed password in the HMAC because this is effectively what will be used a password (so we can store
// it in localStorage safely, we don't use the clear text password)
const hmac = await cryptoEngine.signMessage(hashedPassword, encrypted);
return hmac + encrypted;
}
exports.encodeWithHashedPassword = encodeWithHashedPassword;
/**
* Top-level function for decoding a message.
* Includes signature check and decryption.
*
* @param {string} signedMsg
* @param {string} hashedPassword
* @param {string} salt
* @param {int} backwardCompatibleAttempt
* @param {string} originalPassword
*
* @returns {Object} {success: true, decoded: string} | {success: false, message: string}
*/
async function decode(signedMsg, hashedPassword, salt, backwardCompatibleAttempt = 0, originalPassword = "") {
const encryptedHMAC = signedMsg.substring(0, 64);
const encryptedMsg = signedMsg.substring(64);
const decryptedHMAC = await cryptoEngine.signMessage(hashedPassword, encryptedMsg);
if (decryptedHMAC !== encryptedHMAC) {
// we have been raising the number of iterations in the hashing algorithm multiple times, so to support the old
// remember-me/autodecrypt links we need to try bringing the old hashes up to speed.
originalPassword = originalPassword || hashedPassword;
if (backwardCompatibleAttempt === 0) {
const updatedHashedPassword = await cryptoEngine.hashThirdRound(originalPassword, salt);
return decode(signedMsg, updatedHashedPassword, salt, backwardCompatibleAttempt + 1, originalPassword);
}
if (backwardCompatibleAttempt === 1) {
let updatedHashedPassword = await cryptoEngine.hashSecondRound(originalPassword, salt);
updatedHashedPassword = await cryptoEngine.hashThirdRound(updatedHashedPassword, salt);
return decode(signedMsg, updatedHashedPassword, salt, backwardCompatibleAttempt + 1, originalPassword);
}
return { success: false, message: "Signature mismatch" };
}
return {
success: true,
decoded: await cryptoEngine.decrypt(encryptedMsg, hashedPassword),
};
}
exports.decode = decode;
return exports;
}
exports.init = init;
return exports;
})())
})());
const decode = codec.init(cryptoEngine).decode;
/**
* Initialize the staticrypt module, that exposes functions callbable by the password_template.
*
@ -613,7 +586,7 @@ function init(staticryptConfig, templateConfig) {
const plainHTML = result.decoded;
// if the user configured a callback call it, otherwise just replace the whole HTML
if (typeof replaceHtmlCallback === 'function') {
if (typeof replaceHtmlCallback === "function") {
replaceHtmlCallback(plainHTML);
} else {
document.write(plainHTML);
@ -674,7 +647,7 @@ function init(staticryptConfig, templateConfig) {
function clearLocalStorage() {
const { clearLocalStorageCallback, rememberExpirationKey, rememberPassphraseKey } = templateConfig;
if (typeof clearLocalStorageCallback === 'function') {
if (typeof clearLocalStorageCallback === "function") {
clearLocalStorageCallback();
} else {
localStorage.removeItem(rememberPassphraseKey);
@ -786,55 +759,56 @@ function init(staticryptConfig, templateConfig) {
return exports;
}
exports.init = init;
return exports;
})())
const templateError = 'Bad password!',
isRememberEnabled = true,
staticryptConfig = {"encryptedMsg":"eaad887f76cba7e862577a8d69c79acfa062d4a1af097faf1fa2d5c56ca2383ea696b3a2f62c6507bebe2f6497d84bcbf7f989136c9ea7f6a95191c1da90a92c330615d9643f382beb9bc3eecaef67a1e2243913e3bd7165820eef4518ac95287e12c1af22bcdaf6190167da3215800ca6809cf5b011847b4618f36e53b40b39d77d174b52a5c8ffaf47f9309478f0d3739f29d606ebe5a2a2334bf6c0f06ed53cdf7f4d703a4a97a41e47b34c50ac4103b7c4a8f0ba92f31010fe097f57bf1c","isRememberEnabled":true,"rememberDurationInDays":0,"salt":"b93bbaf35459951c47721d1f3eaeb5b9"};
})());
const templateError = "Bad password!",
isRememberEnabled = true,
staticryptConfig = {"encryptedMsg":"bf0f821fe4fdab54ffde29fd6d812fdf7dd423a0e4e36369c41864d369b71aa6a826caf47e563ba5e4bdfc2b55ea323c5d9fabc6ece3e99cdacc30afd560ec57b6bdda0beef1b2b220e934f214202134e8eb284a5a58f94418c970ca2172622b9286eb5931fff69e345d737c00832ecfbb77057a4e814dd633ab1ef294514d28ef7cb047bb541000b12b8f4c15851fa8ca1e6cd0cbee64c67b4467a46e2bc154896bb4deed9987e222f0c280c8d766ea16769515f9d337592e6807dfa07534ef","isRememberEnabled":true,"rememberDurationInDays":0,"salt":"b93bbaf35459951c47721d1f3eaeb5b9"};
// you can edit these values to customize some of the behavior of StatiCrypt
const templateConfig = {
rememberExpirationKey: 'staticrypt_expiration',
rememberPassphraseKey: 'staticrypt_passphrase',
replaceHtmlCallback: null,
clearLocalStorageCallback: null,
};
// you can edit these values to customize some of the behavior of StatiCrypt
const templateConfig = {
rememberExpirationKey: "staticrypt_expiration",
rememberPassphraseKey: "staticrypt_passphrase",
replaceHtmlCallback: null,
clearLocalStorageCallback: null,
};
// init the staticrypt engine
const staticrypt = staticryptInitiator.init(staticryptConfig, templateConfig);
// init the staticrypt engine
const staticrypt = staticryptInitiator.init(staticryptConfig, templateConfig);
// try to automatically decrypt on load if there is a saved password
window.onload = async function () {
const { isSuccessful } = await staticrypt.handleDecryptOnLoad();
// try to automatically decrypt on load if there is a saved password
window.onload = async function () {
const { isSuccessful } = await staticrypt.handleDecryptOnLoad();
// if we didn't decrypt anything on load, show the password prompt. Otherwise the content has already been
// replaced, no need to do anything
if (!isSuccessful) {
// hide loading screen
document.getElementById("staticrypt_loading").classList.add("hidden");
document.getElementById("staticrypt_content").classList.remove("hidden");
document.getElementById("staticrypt-password").focus();
// if we didn't decrypt anything on load, show the password prompt. Otherwise the content has already been
// replaced, no need to do anything
if (!isSuccessful) {
// hide loading screen
document.getElementById("staticrypt_loading").classList.add("hidden");
document.getElementById("staticrypt_content").classList.remove("hidden");
document.getElementById("staticrypt-password").focus();
// show the remember me checkbox
if (isRememberEnabled) {
document.getElementById('staticrypt-remember-label').classList.remove('hidden');
}
}
}
// show the remember me checkbox
if (isRememberEnabled) {
document.getElementById("staticrypt-remember-label").classList.remove("hidden");
}
}
};
// handle password form submission
document.getElementById('staticrypt-form').addEventListener('submit', async function (e) {
e.preventDefault();
// handle password form submission
document.getElementById("staticrypt-form").addEventListener("submit", async function (e) {
e.preventDefault();
const password = document.getElementById('staticrypt-password').value,
isRememberChecked = document.getElementById('staticrypt-remember').checked;
const password = document.getElementById("staticrypt-password").value,
isRememberChecked = document.getElementById("staticrypt-remember").checked;
const { isSuccessful } = await staticrypt.handleDecryptionOfPage(password, isRememberChecked);
const { isSuccessful } = await staticrypt.handleDecryptionOfPage(password, isRememberChecked);
if (!isSuccessful) {
alert(templateError);
}
});
</script>
</body>
if (!isSuccessful) {
alert(templateError);
}
});
</script>
</body>
</html>

Wyświetl plik

@ -4,100 +4,94 @@
* @param cryptoEngine - the engine to use for encryption / decryption
*/
function init(cryptoEngine) {
const exports = {};
const exports = {};
/**
* Top-level function for encoding a message.
* Includes password hashing, encryption, and signing.
*
* @param {string} msg
* @param {string} password
* @param {string} salt
*
* @returns {string} The encoded text
*/
async function encode(msg, password, salt) {
const hashedPassword = await cryptoEngine.hashPassword(password, salt);
/**
* Top-level function for encoding a message.
* Includes password hashing, encryption, and signing.
*
* @param {string} msg
* @param {string} password
* @param {string} salt
*
* @returns {string} The encoded text
*/
async function encode(msg, password, salt) {
const hashedPassword = await cryptoEngine.hashPassword(password, salt);
const encrypted = await cryptoEngine.encrypt(msg, hashedPassword);
const encrypted = await cryptoEngine.encrypt(msg, hashedPassword);
// we use the hashed password in the HMAC because this is effectively what will be used a password (so we can store
// it in localStorage safely, we don't use the clear text password)
const hmac = await cryptoEngine.signMessage(hashedPassword, encrypted);
// we use the hashed password in the HMAC because this is effectively what will be used a password (so we can store
// it in localStorage safely, we don't use the clear text password)
const hmac = await cryptoEngine.signMessage(hashedPassword, encrypted);
return hmac + encrypted;
}
exports.encode = encode;
/**
* Encode using a password that has already been hashed. This is useful to encode multiple messages in a row, that way
* we don't need to hash the password multiple times.
*
* @param {string} msg
* @param {string} hashedPassword
*
* @returns {string} The encoded text
*/
async function encodeWithHashedPassword(msg, hashedPassword) {
const encrypted = await cryptoEngine.encrypt(msg, hashedPassword);
// we use the hashed password in the HMAC because this is effectively what will be used a password (so we can store
// it in localStorage safely, we don't use the clear text password)
const hmac = await cryptoEngine.signMessage(hashedPassword, encrypted);
return hmac + encrypted;
}
exports.encodeWithHashedPassword = encodeWithHashedPassword;
/**
* Top-level function for decoding a message.
* Includes signature check and decryption.
*
* @param {string} signedMsg
* @param {string} hashedPassword
* @param {string} salt
* @param {int} backwardCompatibleAttempt
* @param {string} originalPassword
*
* @returns {Object} {success: true, decoded: string} | {success: false, message: string}
*/
async function decode(
signedMsg,
hashedPassword,
salt,
backwardCompatibleAttempt = 0,
originalPassword = ''
) {
const encryptedHMAC = signedMsg.substring(0, 64);
const encryptedMsg = signedMsg.substring(64);
const decryptedHMAC = await cryptoEngine.signMessage(hashedPassword, encryptedMsg);
if (decryptedHMAC !== encryptedHMAC) {
// we have been raising the number of iterations in the hashing algorithm multiple times, so to support the old
// remember-me/autodecrypt links we need to try bringing the old hashes up to speed.
originalPassword = originalPassword || hashedPassword;
if (backwardCompatibleAttempt === 0) {
const updatedHashedPassword = await cryptoEngine.hashThirdRound(originalPassword, salt);
return decode(signedMsg, updatedHashedPassword, salt, backwardCompatibleAttempt + 1, originalPassword);
}
if (backwardCompatibleAttempt === 1) {
let updatedHashedPassword = await cryptoEngine.hashSecondRound(originalPassword, salt);
updatedHashedPassword = await cryptoEngine.hashThirdRound(updatedHashedPassword, salt);
return decode(signedMsg, updatedHashedPassword, salt, backwardCompatibleAttempt + 1, originalPassword);
}
return { success: false, message: "Signature mismatch" };
return hmac + encrypted;
}
exports.encode = encode;
return {
success: true,
decoded: await cryptoEngine.decrypt(encryptedMsg, hashedPassword),
};
}
exports.decode = decode;
/**
* Encode using a password that has already been hashed. This is useful to encode multiple messages in a row, that way
* we don't need to hash the password multiple times.
*
* @param {string} msg
* @param {string} hashedPassword
*
* @returns {string} The encoded text
*/
async function encodeWithHashedPassword(msg, hashedPassword) {
const encrypted = await cryptoEngine.encrypt(msg, hashedPassword);
return exports;
// we use the hashed password in the HMAC because this is effectively what will be used a password (so we can store
// it in localStorage safely, we don't use the clear text password)
const hmac = await cryptoEngine.signMessage(hashedPassword, encrypted);
return hmac + encrypted;
}
exports.encodeWithHashedPassword = encodeWithHashedPassword;
/**
* Top-level function for decoding a message.
* Includes signature check and decryption.
*
* @param {string} signedMsg
* @param {string} hashedPassword
* @param {string} salt
* @param {int} backwardCompatibleAttempt
* @param {string} originalPassword
*
* @returns {Object} {success: true, decoded: string} | {success: false, message: string}
*/
async function decode(signedMsg, hashedPassword, salt, backwardCompatibleAttempt = 0, originalPassword = "") {
const encryptedHMAC = signedMsg.substring(0, 64);
const encryptedMsg = signedMsg.substring(64);
const decryptedHMAC = await cryptoEngine.signMessage(hashedPassword, encryptedMsg);
if (decryptedHMAC !== encryptedHMAC) {
// we have been raising the number of iterations in the hashing algorithm multiple times, so to support the old
// remember-me/autodecrypt links we need to try bringing the old hashes up to speed.
originalPassword = originalPassword || hashedPassword;
if (backwardCompatibleAttempt === 0) {
const updatedHashedPassword = await cryptoEngine.hashThirdRound(originalPassword, salt);
return decode(signedMsg, updatedHashedPassword, salt, backwardCompatibleAttempt + 1, originalPassword);
}
if (backwardCompatibleAttempt === 1) {
let updatedHashedPassword = await cryptoEngine.hashSecondRound(originalPassword, salt);
updatedHashedPassword = await cryptoEngine.hashThirdRound(updatedHashedPassword, salt);
return decode(signedMsg, updatedHashedPassword, salt, backwardCompatibleAttempt + 1, originalPassword);
}
return { success: false, message: "Signature mismatch" };
}
return {
success: true,
decoded: await cryptoEngine.decrypt(encryptedMsg, hashedPassword),
};
}
exports.decode = decode;
return exports;
}
exports.init = init;

Wyświetl plik

@ -69,13 +69,7 @@ async function encrypt(msg, hashedPassword) {
// https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/encrypt#parameters
const iv = crypto.getRandomValues(new Uint8Array(IV_BITS / 8));
const key = await subtle.importKey(
"raw",
HexEncoder.parse(hashedPassword),
ENCRYPTION_ALGO,
false,
["encrypt"]
);
const key = await subtle.importKey("raw", HexEncoder.parse(hashedPassword), ENCRYPTION_ALGO, false, ["encrypt"]);
const encrypted = await subtle.encrypt(
{
@ -103,13 +97,7 @@ async function decrypt(encryptedMsg, hashedPassword) {
const iv = HexEncoder.parse(encryptedMsg.substring(0, ivLength));
const encrypted = encryptedMsg.substring(ivLength);
const key = await subtle.importKey(
"raw",
HexEncoder.parse(hashedPassword),
ENCRYPTION_ALGO,
false,
["decrypt"]
);
const key = await subtle.importKey("raw", HexEncoder.parse(hashedPassword), ENCRYPTION_ALGO, false, ["decrypt"]);
const outBuffer = await subtle.decrypt(
{
@ -191,13 +179,7 @@ exports.hashThirdRound = hashThirdRound;
* @returns {Promise<string>}
*/
async function pbkdf2(password, salt, iterations, hashAlgorithm) {
const key = await subtle.importKey(
"raw",
UTF8Encoder.parse(password),
"PBKDF2",
false,
["deriveBits"]
);
const key = await subtle.importKey("raw", UTF8Encoder.parse(password), "PBKDF2", false, ["deriveBits"]);
const keyBytes = await subtle.deriveBits(
{
@ -237,7 +219,6 @@ async function signMessage(hashedPassword, message) {
}
exports.signMessage = signMessage;
function getRandomAlphanum() {
const possibleCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
@ -265,7 +246,7 @@ function getRandomAlphanum() {
* @returns {string}
*/
function generateRandomString(length) {
let randomString = '';
let randomString = "";
for (let i = 0; i < length; i++) {
randomString += getRandomAlphanum();
@ -274,4 +255,3 @@ function generateRandomString(length) {
return randomString;
}
exports.generateRandomString = generateRandomString;

Wyświetl plik

@ -3,7 +3,7 @@
* break this comment), with the provided data.
*
* This weird format is so that we have something that doesn't break JS parser in the template files (it understands it
* as '0'), so we can still use auto-formatting.
* as '0'), so we can still use auto-formatting. The auto-formatter might add a space before the '0', we accept both.
*
* @param {string} templateString
* @param {Object} data
@ -11,12 +11,12 @@
* @returns string
*/
function renderTemplate(templateString, data) {
return templateString.replace(/\/\*\[\|\s*(\w+)\s*\|]\*\/0/g, function (_, key) {
return templateString.replace(/\/\*\[\|\s*(\w+)\s*\|]\*\/\s*0/g, function (_, key) {
if (!data || data[key] === undefined) {
return key;
}
if (typeof data[key] === 'object') {
if (typeof data[key] === "object") {
return JSON.stringify(data[key]);
}
@ -24,4 +24,3 @@ function renderTemplate(templateString, data) {
});
}
exports.renderTemplate = renderTemplate;

Wyświetl plik

@ -1,235 +1,235 @@
<!doctype html>
<!DOCTYPE html>
<html class="staticrypt-html">
<head>
<meta charset="utf-8">
<title>/*[|template_title|]*/0</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<head>
<meta charset="utf-8" />
<title>/*[|template_title|]*/0</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- do not cache this page -->
<meta http-equiv="cache-control" content="max-age=0"/>
<meta http-equiv="cache-control" content="no-cache"/>
<meta http-equiv="expires" content="0"/>
<meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT"/>
<meta http-equiv="pragma" content="no-cache"/>
<!-- do not cache this page -->
<meta http-equiv="cache-control" content="max-age=0" />
<meta http-equiv="cache-control" content="no-cache" />
<meta http-equiv="expires" content="0" />
<meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" />
<meta http-equiv="pragma" content="no-cache" />
<style>
.staticrypt-hr {
margin-top: 20px;
margin-bottom: 20px;
border: 0;
border-top: 1px solid #eee;
}
.staticrypt-page {
width: 360px;
padding: 8% 0 0;
margin: auto;
box-sizing: border-box;
}
.staticrypt-form {
position: relative;
z-index: 1;
background: #FFFFFF;
max-width: 360px;
margin: 0 auto 100px;
padding: 45px;
text-align: center;
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.2), 0 5px 5px 0 rgba(0, 0, 0, 0.24);
}
.staticrypt-form input[type="password"] {
outline: 0;
background: #f2f2f2;
width: 100%;
border: 0;
margin: 0 0 15px;
padding: 15px;
box-sizing: border-box;
font-size: 14px;
}
.staticrypt-form .staticrypt-decrypt-button {
text-transform: uppercase;
outline: 0;
background: /*[|template_color_primary|]*/0;
width: 100%;
border: 0;
padding: 15px;
color: #FFFFFF;
font-size: 14px;
cursor: pointer;
}
.staticrypt-form .staticrypt-decrypt-button:hover, .staticrypt-form .staticrypt-decrypt-button:active, .staticrypt-form .staticrypt-decrypt-button:focus {
background: /*[|template_color_primary|]*/0;
filter: brightness(92%);
}
.staticrypt-html {
height: 100%;
}
.staticrypt-body {
height: 100%;
margin: 0;
}
.staticrypt-content {
height: 100%;
margin-bottom: 1em;
background: /*[|template_color_secondary|]*/0;
font-family: "Arial", sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.staticrypt-instructions {
margin-top: -1em;
margin-bottom: 1em;
}
.staticrypt-title {
font-size: 1.5em;
}
label.staticrypt-remember {
display: flex;
align-items: center;
margin-bottom: 1em;
}
.staticrypt-remember input[type=checkbox] {
transform: scale(1.5);
margin-right: 1em;
}
.hidden {
display: none !important;
}
.staticrypt-spinner-container {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.staticrypt-spinner {
display: inline-block;
width: 2rem;
height: 2rem;
vertical-align: text-bottom;
border: 0.25em solid gray;
border-right-color: transparent;
border-radius: 50%;
-webkit-animation: spinner-border .75s linear infinite;
animation: spinner-border .75s linear infinite;
animation-duration: 0.75s;
animation-timing-function: linear;
animation-delay: 0s;
animation-iteration-count: infinite;
animation-direction: normal;
animation-fill-mode: none;
animation-play-state: running;
animation-name: spinner-border;
}
@keyframes spinner-border {
100% {
transform: rotate(360deg);
<style>
.staticrypt-hr {
margin-top: 20px;
margin-bottom: 20px;
border: 0;
border-top: 1px solid #eee;
}
}
</style>
</head>
<body class="staticrypt-body">
.staticrypt-page {
width: 360px;
padding: 8% 0 0;
margin: auto;
box-sizing: border-box;
}
<div id="staticrypt_loading" class="staticrypt-spinner-container">
<div class="staticrypt-spinner"></div>
</div>
.staticrypt-form {
position: relative;
z-index: 1;
background: #ffffff;
max-width: 360px;
margin: 0 auto 100px;
padding: 45px;
text-align: center;
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.2), 0 5px 5px 0 rgba(0, 0, 0, 0.24);
}
<div id="staticrypt_content" class="staticrypt-content hidden">
<div class="staticrypt-page">
<div class="staticrypt-form">
<div class="staticrypt-instructions">
<p class="staticrypt-title">/*[|template_title|]*/0</p>
<p>/*[|template_instructions|]*/0</p>
</div>
.staticrypt-form input[type="password"] {
outline: 0;
background: #f2f2f2;
width: 100%;
border: 0;
margin: 0 0 15px;
padding: 15px;
box-sizing: border-box;
font-size: 14px;
}
<hr class="staticrypt-hr">
.staticrypt-form .staticrypt-decrypt-button {
text-transform: uppercase;
outline: 0;
background: /*[|template_color_primary|]*/ 0;
width: 100%;
border: 0;
padding: 15px;
color: #ffffff;
font-size: 14px;
cursor: pointer;
}
<form id="staticrypt-form" action="#" method="post">
<input id="staticrypt-password"
type="password"
name="password"
placeholder="/*[|template_placeholder|]*/0"
autofocus/>
.staticrypt-form .staticrypt-decrypt-button:hover,
.staticrypt-form .staticrypt-decrypt-button:active,
.staticrypt-form .staticrypt-decrypt-button:focus {
background: /*[|template_color_primary|]*/ 0;
filter: brightness(92%);
}
<label id="staticrypt-remember-label" class="staticrypt-remember hidden">
<input id="staticrypt-remember"
type="checkbox"
name="remember"/>
/*[|template_remember|]*/0
</label>
.staticrypt-html {
height: 100%;
}
<input type="submit" class="staticrypt-decrypt-button" value="/*[|template_button|]*/0"/>
</form>
.staticrypt-body {
height: 100%;
margin: 0;
}
.staticrypt-content {
height: 100%;
margin-bottom: 1em;
background: /*[|template_color_secondary|]*/ 0;
font-family: "Arial", sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.staticrypt-instructions {
margin-top: -1em;
margin-bottom: 1em;
}
.staticrypt-title {
font-size: 1.5em;
}
label.staticrypt-remember {
display: flex;
align-items: center;
margin-bottom: 1em;
}
.staticrypt-remember input[type="checkbox"] {
transform: scale(1.5);
margin-right: 1em;
}
.hidden {
display: none !important;
}
.staticrypt-spinner-container {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.staticrypt-spinner {
display: inline-block;
width: 2rem;
height: 2rem;
vertical-align: text-bottom;
border: 0.25em solid gray;
border-right-color: transparent;
border-radius: 50%;
-webkit-animation: spinner-border 0.75s linear infinite;
animation: spinner-border 0.75s linear infinite;
animation-duration: 0.75s;
animation-timing-function: linear;
animation-delay: 0s;
animation-iteration-count: infinite;
animation-direction: normal;
animation-fill-mode: none;
animation-play-state: running;
animation-name: spinner-border;
}
@keyframes spinner-border {
100% {
transform: rotate(360deg);
}
}
</style>
</head>
<body class="staticrypt-body">
<div id="staticrypt_loading" class="staticrypt-spinner-container">
<div class="staticrypt-spinner"></div>
</div>
</div>
</div>
<div id="staticrypt_content" class="staticrypt-content hidden">
<div class="staticrypt-page">
<div class="staticrypt-form">
<div class="staticrypt-instructions">
<p class="staticrypt-title">/*[|template_title|]*/0</p>
<p>/*[|template_instructions|]*/0</p>
</div>
<script>
// these variables will be filled when generating the file - the template format is '/*[|variable_name|]*/0'
const staticryptInitiator = /*[|js_staticrypt|]*/0
const templateError = '/*[|template_error|]*/0',
isRememberEnabled = /*[|is_remember_enabled|]*/0,
staticryptConfig = /*[|staticrypt_config|]*/0;
<hr class="staticrypt-hr" />
// you can edit these values to customize some of the behavior of StatiCrypt
const templateConfig = {
rememberExpirationKey: 'staticrypt_expiration',
rememberPassphraseKey: 'staticrypt_passphrase',
replaceHtmlCallback: null,
clearLocalStorageCallback: null,
};
<form id="staticrypt-form" action="#" method="post">
<input
id="staticrypt-password"
type="password"
name="password"
placeholder="/*[|template_placeholder|]*/0"
autofocus
/>
// init the staticrypt engine
const staticrypt = staticryptInitiator.init(staticryptConfig, templateConfig);
<label id="staticrypt-remember-label" class="staticrypt-remember hidden">
<input id="staticrypt-remember" type="checkbox" name="remember" />
/*[|template_remember|]*/0
</label>
// try to automatically decrypt on load if there is a saved password
window.onload = async function () {
const { isSuccessful } = await staticrypt.handleDecryptOnLoad();
<input type="submit" class="staticrypt-decrypt-button" value="/*[|template_button|]*/0" />
</form>
</div>
</div>
</div>
// if we didn't decrypt anything on load, show the password prompt. Otherwise the content has already been
// replaced, no need to do anything
if (!isSuccessful) {
// hide loading screen
document.getElementById("staticrypt_loading").classList.add("hidden");
document.getElementById("staticrypt_content").classList.remove("hidden");
document.getElementById("staticrypt-password").focus();
<script>
// these variables will be filled when generating the file - the template format is '/*[|variable_name|]*/0'
const staticryptInitiator = /*[|js_staticrypt|]*/ 0;
const templateError = "/*[|template_error|]*/0",
isRememberEnabled = /*[|is_remember_enabled|]*/ 0,
staticryptConfig = /*[|staticrypt_config|]*/ 0;
// show the remember me checkbox
if (isRememberEnabled) {
document.getElementById('staticrypt-remember-label').classList.remove('hidden');
}
}
}
// you can edit these values to customize some of the behavior of StatiCrypt
const templateConfig = {
rememberExpirationKey: "staticrypt_expiration",
rememberPassphraseKey: "staticrypt_passphrase",
replaceHtmlCallback: null,
clearLocalStorageCallback: null,
};
// handle password form submission
document.getElementById('staticrypt-form').addEventListener('submit', async function (e) {
e.preventDefault();
// init the staticrypt engine
const staticrypt = staticryptInitiator.init(staticryptConfig, templateConfig);
const password = document.getElementById('staticrypt-password').value,
isRememberChecked = document.getElementById('staticrypt-remember').checked;
// try to automatically decrypt on load if there is a saved password
window.onload = async function () {
const { isSuccessful } = await staticrypt.handleDecryptOnLoad();
const { isSuccessful } = await staticrypt.handleDecryptionOfPage(password, isRememberChecked);
// if we didn't decrypt anything on load, show the password prompt. Otherwise the content has already been
// replaced, no need to do anything
if (!isSuccessful) {
// hide loading screen
document.getElementById("staticrypt_loading").classList.add("hidden");
document.getElementById("staticrypt_content").classList.remove("hidden");
document.getElementById("staticrypt-password").focus();
if (!isSuccessful) {
alert(templateError);
}
});
</script>
</body>
// show the remember me checkbox
if (isRememberEnabled) {
document.getElementById("staticrypt-remember-label").classList.remove("hidden");
}
}
};
// handle password form submission
document.getElementById("staticrypt-form").addEventListener("submit", async function (e) {
e.preventDefault();
const password = document.getElementById("staticrypt-password").value,
isRememberChecked = document.getElementById("staticrypt-remember").checked;
const { isSuccessful } = await staticrypt.handleDecryptionOfPage(password, isRememberChecked);
if (!isSuccessful) {
alert(templateError);
}
});
</script>
</body>
</html>

Wyświetl plik

@ -1,8 +1,7 @@
const cryptoEngine = /*[|js_crypto_engine|]*/0
const codec = /*[|js_codec|]*/0
const cryptoEngine = /*[|js_crypto_engine|]*/ 0;
const codec = /*[|js_codec|]*/ 0;
const decode = codec.init(cryptoEngine).decode;
/**
* Initialize the staticrypt module, that exposes functions callbable by the password_template.
*
@ -40,7 +39,7 @@ function init(staticryptConfig, templateConfig) {
const plainHTML = result.decoded;
// if the user configured a callback call it, otherwise just replace the whole HTML
if (typeof replaceHtmlCallback === 'function') {
if (typeof replaceHtmlCallback === "function") {
replaceHtmlCallback(plainHTML);
} else {
document.write(plainHTML);
@ -101,7 +100,7 @@ function init(staticryptConfig, templateConfig) {
function clearLocalStorage() {
const { clearLocalStorageCallback, rememberExpirationKey, rememberPassphraseKey } = templateConfig;
if (typeof clearLocalStorageCallback === 'function') {
if (typeof clearLocalStorageCallback === "function") {
clearLocalStorageCallback();
} else {
localStorage.removeItem(rememberPassphraseKey);
@ -212,4 +211,4 @@ function init(staticryptConfig, templateConfig) {
return exports;
}
exports.init = init;
exports.init = init;

Wyświetl plik

@ -1,4 +1,4 @@
const { convertCommonJSToBrowserJS, genFile, buildStaticryptJS} = require("../cli/helpers.js");
const { convertCommonJSToBrowserJS, genFile, buildStaticryptJS } = require("../cli/helpers.js");
const data = {
js_codec: convertCommonJSToBrowserJS("lib/codec"),

Wyświetl plik

@ -1,358 +1,421 @@
<!doctype html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>StatiCrypt: Password protect static HTML</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet"
type="text/css"
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
crossorigin="anonymous">
<style>
a.no-style {
color: inherit;
text-decoration: inherit;
}
<head>
<meta charset="utf-8" />
<title>StatiCrypt: Password protect static HTML</title>
<meta name="description" content="" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="stylesheet"
type="text/css"
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
crossorigin="anonymous"
/>
<style>
a.no-style {
color: inherit;
text-decoration: inherit;
}
body {
font-size: 16px;
}
body {
font-size: 16px;
}
label.no-style {
font-weight: normal;
}
</style>
</head>
label.no-style {
font-weight: normal;
}
</style>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-xs-12">
<h1>
StatiCrypt
<div class="pull-right">
<iframe src="https://ghbtns.com/github-btn.html?user=robinmoisson&repo=staticrypt&type=star&size=large"
frameborder="0" scrolling="0" width="80px" height="30px"></iframe>
<iframe src="https://ghbtns.com/github-btn.html?user=robinmoisson&repo=staticrypt&type=fork&size=large"
frameborder="0" scrolling="0" width="80px" height="30px"></iframe>
<body>
<div class="container">
<div class="row">
<div class="col-xs-12">
<h1>
StatiCrypt
<div class="pull-right">
<iframe
src="https://ghbtns.com/github-btn.html?user=robinmoisson&repo=staticrypt&type=star&size=large"
frameborder="0"
scrolling="0"
width="80px"
height="30px"
></iframe>
<iframe
src="https://ghbtns.com/github-btn.html?user=robinmoisson&repo=staticrypt&type=fork&size=large"
frameborder="0"
scrolling="0"
width="80px"
height="30px"
></iframe>
</div>
<br />
<small>Password protect a static HTML page</small>
</h1>
<p>
StatiCrypt uses AES-256 with WebCrypto to encrypt your html string with your long password, in
your browser (client side).
</p>
<p>
Download your encrypted string in a HTML page with a password prompt you can upload anywhere
(see <a target="_blank" href="example/encrypted/example.html">example</a>).
</p>
<p>
The tool is also available as
<a href="https://npmjs.com/package/staticrypt">a CLI on NPM</a> and is
<a href="https://github.com/robinmoisson/staticrypt">open source on GitHub</a>.
</p>
<br />
<h4>
<a class="no-style" id="toggle-concept" href="#">
<span id="toggle-concept-sign"></span> HOW IT WORKS
</a>
</h4>
<div id="concept" class="hidden">
<p>
<b class="text-danger">Disclaimer</b> if you are an at-risk activist, or have extra
sensitive banking data, you should probably use something else!
</p>
<p>
StatiCrypt generates a static, password protected page that can be decrypted in-browser:
just send or upload the generated page to a place serving static content (github pages, for
example) and you're done: the javascript will prompt users for password, decrypt the page
and load your HTML.
</p>
<p>
The page is encrypted with AES-256 in CBC mode (see why this mode is appropriate for
StatiCrypt in
<a href="https://github.com/robinmoisson/staticrypt/issues/19">#19</a>). The password is
hashed with PBKDF2 (599k iterations with SHA-256, plus 1k with SHA-1 for legacy reasons (see
<a href="https://github.com/robinmoisson/staticrypt/issues/159">#159</a>), for the added
<a
href="https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2"
>recommended total</a
>
of 600k) and used to encrypt the page.
</p>
<p>
It basically encrypts your page and puts everything with a user-friendly way to use a
password in the new file. AES-256 is state of the art but
<b
>brute-force/dictionary attacks would be easy to do at a really fast pace: use a long,
unusual password!</b
>
<br />
=> To be safe, we recommend 16+ alphanum characters, and using a password manager like the
open-source <a href="http://bitwarden.com">Bitwarden</a>.
</p>
<p>
Feel free to contribute or report any thought to the
<a href="https://github.com/robinmoisson/staticrypt">GitHub project</a>.
</p>
</div>
<br />
</div>
</div>
<div class="row">
<div class="col-xs-12">
<form id="encrypt_form">
<div class="form-group">
<label for="password">Password</label>
<input
type="password"
class="form-control"
id="password"
placeholder="Password (choose a long one!)"
/>
</div>
<div class="form-group">
<label for="unencrypted_html">HTML/string to encrypt</label>
<textarea
class="form-control"
id="unencrypted_html"
placeholder="<html><head>..."
rows="5"
></textarea>
</div>
<div class="form-group">
<label class="no-style">
<input type="checkbox" id="remember" checked />
Add "Remember me" checkbox (append <code>#staticrypt_logout</code> to your URL to
logout)
<small>
<abbr
class="text-muted"
title='The password will be stored in clear text in the browser&apos;s localStorage upon entry by the user. See "More options" to set the expiration (default: none)'
>
(?)
</abbr>
</small>
</label>
</div>
<p>
<a href="#" id="toggle-extra-option">+ More options</a>
</p>
<div id="extra-options" class="hidden">
<div class="form-group">
<label for="template_title">Page title</label>
<input
type="text"
class="form-control"
id="template_title"
placeholder="Default: 'Protected Page'"
/>
</div>
<div class="form-group">
<label for="template_instructions">Instructions to display the user</label>
<textarea
class="form-control"
id="template_instructions"
placeholder="Default: nothing."
></textarea>
</div>
<div class="form-group">
<label for="template_placeholder">Password input placeholder</label>
<input
type="text"
class="form-control"
id="template_placeholder"
placeholder="Default: 'Password'"
/>
</div>
<div class="form-group">
<label for="template_remember">"Remember me" checkbox label</label>
<input
type="text"
class="form-control"
id="template_remember"
placeholder="Default: 'Remember me'"
/>
</div>
<div class="form-group">
<label for="remember_in_days">"Remember me" expiration in days</label>
<input
type="number"
class="form-control"
id="remember_in_days"
step="any"
placeholder="Default: 0 (no expiration)"
/>
<small class="form-text text-muted">
After this many days, the user will have to enter the password again. Leave empty or
set to 0 for no expiration.
</small>
</div>
<div class="form-group">
<label for="template_button">Decrypt button label</label>
<input
type="text"
class="form-control"
id="template_button"
placeholder="Default: 'DECRYPT'"
/>
</div>
</div>
<button class="btn btn-primary pull-right" type="submit">
Generate password protected HTML
</button>
</form>
</div>
</div>
<div class="row mb-5">
<div class="col-xs-12">
<h2>Encrypted HTML</h2>
<p>
<a
class="btn btn-success download"
download="encrypted.html"
id="download-link"
disabled="disabled"
>Download html file with password prompt</a
>
</p>
<pre id="encrypted_html_display">Your encrypted string</pre>
</div>
<br>
<small>Password protect a static HTML page</small>
</h1>
<p>
StatiCrypt uses AES-256 with WebCrypto to encrypt your html string with your long password, in your browser (client side).
</p>
<p>
Download your encrypted string in a HTML page with a password prompt you can upload anywhere (see <a
target="_blank" href="example/encrypted/example.html">example</a>).
</p>
<p>
The tool is also available as <a href="https://npmjs.com/package/staticrypt">a CLI on NPM</a> and is <a
href="https://github.com/robinmoisson/staticrypt">open source on GitHub</a>.
</p>
<br>
<h4>
<a class="no-style" id="toggle-concept" href="#">
<span id="toggle-concept-sign"></span> HOW IT WORKS
</a>
</h4>
<div id="concept" class="hidden">
<p>
<b class="text-danger">Disclaimer</b> if you are an at-risk activist, or have extra sensitive
banking data, you should probably use something else!
</p>
<p>
StatiCrypt generates a static, password protected page that can be decrypted in-browser:
just send or upload the generated page to a place serving static content (github pages, for example)
and you're done: the javascript will prompt users for password, decrypt the page and load your HTML.
</p>
<p>
The page is encrypted with AES-256 in CBC mode (see why this mode is appropriate for StatiCrypt in
<a href="https://github.com/robinmoisson/staticrypt/issues/19">#19</a>). The password is hashed with
PBKDF2 (599k iterations with SHA-256, plus 1k with SHA-1 for legacy reasons (see
<a href="https://github.com/robinmoisson/staticrypt/issues/159">#159</a>), for the added
<a href="https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2">recommended
total</a> of 600k) and used to encrypt the page.
</p>
<p>
It basically encrypts your page and puts everything with a user-friendly way to use a password
in the new file. AES-256 is state of the art but <b>brute-force/dictionary attacks would be easy to
do at a really fast pace: use a long, unusual password!</b>
<br/> => To be safe, we recommend 16+ alphanum characters, and using a password manager like the
open-source <a href="http://bitwarden.com">Bitwarden</a>.
</p>
<p>
Feel free to contribute or report any thought to the
<a href="https://github.com/robinmoisson/staticrypt">GitHub project</a>.
</p>
</div>
<br>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<form id="encrypt_form">
<div class="form-group">
<label for="password">Password</label>
<input type="password" class="form-control" id="password"
placeholder="Password (choose a long one!)">
</div>
<div class="form-group">
<label for="unencrypted_html">HTML/string to encrypt</label>
<textarea class="form-control"
id="unencrypted_html"
placeholder="<html><head>..."
rows="5"></textarea>
</div>
<script src="https://cdn.ckeditor.com/4.7.0/standard/ckeditor.js"></script>
<div class="form-group">
<label class="no-style">
<input type="checkbox" id="remember" checked>
Add "Remember me" checkbox (append <code>#staticrypt_logout</code> to your URL to logout)
<small>
<abbr class="text-muted"
title="The password will be stored in clear text in the browser's localStorage upon entry by the user. See &quot;More options&quot; to set the expiration (default: none)">
(?)
</abbr>
</small>
</label>
</div>
<script id="cryptoEngine">
window.cryptoEngine = /*[|js_crypto_engine|]*/ 0;
</script>
<p>
<a href="#" id="toggle-extra-option">+ More options</a>
</p>
<div id="extra-options" class="hidden">
<div class="form-group">
<label for="template_title">Page title</label>
<input type="text" class="form-control" id="template_title" placeholder="Default: 'Protected Page'">
</div>
<script id="codec">
window.codec = /*[|js_codec|]*/ 0;
</script>
<div class="form-group">
<label for="template_instructions">Instructions to display the user</label>
<textarea class="form-control" id="template_instructions" placeholder="Default: nothing."></textarea>
</div>
<script id="formater">
window.formater = /*[|js_formater|]*/ 0;
</script>
<div class="form-group">
<label for="template_placeholder">Password input placeholder</label>
<input type="text" class="form-control" id="template_placeholder"
placeholder="Default: 'Password'">
</div>
<script id="staticrypt">
window.staticrypt = /*[|js_staticrypt|]*/ 0;
</script>
<div class="form-group">
<label for="template_remember">"Remember me" checkbox label</label>
<input type="text" class="form-control" id="template_remember" placeholder="Default: 'Remember me'">
</div>
<script>
const encode = codec.init(cryptoEngine).encode;
<div class="form-group">
<label for="remember_in_days">"Remember me" expiration in days</label>
<input type="number"
class="form-control"
id="remember_in_days"
step="any"
placeholder="Default: 0 (no expiration)">
<small class="form-text text-muted">
After this many days, the user will have to enter the password again. Leave empty or set
to 0 for no expiration.
</small>
</div>
// enable CKEDIRTOR
CKEDITOR.replace("template_instructions");
<div class="form-group">
<label for="template_button">Decrypt button label</label>
<input type="text" class="form-control" id="template_button" placeholder="Default: 'DECRYPT'">
</div>
</div>
let htmlToDownload;
<button class="btn btn-primary pull-right" type="submit">Generate password protected HTML</button>
</form>
</div>
</div>
<div class="row mb-5">
<div class="col-xs-12">
<h2>Encrypted HTML</h2>
<p><a class="btn btn-success download"
download="encrypted.html"
id="download-link"
disabled="disabled">Download html file with password prompt</a></p>
<pre id="encrypted_html_display">
Your encrypted string</pre>
</div>
</div>
</div>
<script src="https://cdn.ckeditor.com/4.7.0/standard/ckeditor.js"></script>
<script id="cryptoEngine">
window.cryptoEngine = /*[|js_crypto_engine|]*/0
</script>
<script id="codec">
window.codec = /*[|js_codec|]*/0
</script>
<script id="formater">
window.formater = /*[|js_formater|]*/0
</script>
<script id="staticrypt">
window.staticrypt = /*[|js_staticrypt|]*/0
</script>
<script>
const encode = codec.init(cryptoEngine).encode;
// enable CKEDIRTOR
CKEDITOR.replace('template_instructions');
let htmlToDownload;
/**
* Extract js code from <script> tag and return it as a string
*
* @param {string} id
* @returns {string}
*/
function getScriptAsString(id) {
return document.getElementById(id)
.innerText.replace(/window\.\w+ = /, '');
}
/**
* Register something happened - this uses a simple Supabase function to implement a counter, and allows to drop
* google analytics. We don't store any personal data or IP.
*
* @param {string} action
*/
function trackEvent(action) {
const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://zlgpaemmniviswibzuwt.supabase.co/rest/v1/rpc/increment_analytics', true);
xhr.setRequestHeader('Content-type', 'application/json; charset=UTF-8')
xhr.setRequestHeader('apikey', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InpsZ3BhZW1tbml2aXN3aWJ6dXd0Iiwicm9sZSI6ImFub24iLCJpYXQiOjE2NjkxMjM0OTcsImV4cCI6MTk4NDY5OTQ5N30.wNoVDHG7F6INx-IPotMs3fL1nudfaF2qvQDgG-1PhNI')
xhr.setRequestHeader('Authorization', 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InpsZ3BhZW1tbml2aXN3aWJ6dXd0Iiwicm9sZSI6ImFub24iLCJpYXQiOjE2NjkxMjM0OTcsImV4cCI6MTk4NDY5OTQ5N30.wNoVDHG7F6INx-IPotMs3fL1nudfaF2qvQDgG-1PhNI')
xhr.send(
JSON.stringify({
action_input: action
}
));
}
/**
* Fill the password prompt template with data provided.
* @param data
*/
function setFileToDownload (data) {
const request = new XMLHttpRequest();
request.open('GET', 'lib/password_template.html', true);
request.onload = function () {
const renderedTmpl = formater.renderTemplate(request.responseText, data);
const downloadLink = document.querySelector('a.download');
downloadLink.href = 'data:text/html,' + encodeURIComponent(renderedTmpl);
downloadLink.removeAttribute('disabled');
htmlToDownload = renderedTmpl;
};
request.send();
}
// register page load
window.onload = function () {
trackEvent('show_index');
};
/**
* Handle form submission.
*/
document.getElementById('encrypt_form').addEventListener('submit', async function (e) {
e.preventDefault();
trackEvent('generate_encrypted');
// update instruction textarea value with CKEDITOR content
// (see https://stackoverflow.com/questions/3147670/ckeditor-update-textarea)
CKEDITOR.instances['template_instructions'].updateElement();
const unencrypted = document.getElementById('unencrypted_html').value,
password = document.getElementById('password').value;
const salt = cryptoEngine.generateRandomSalt();
const encryptedMsg = await encode(unencrypted, password, salt);
const templateButton = document.getElementById('template_button').value,
templateInstructions = document.getElementById('template_instructions').value,
isRememberEnabled = document.getElementById('remember').checked,
templateTitle = document.getElementById('template_title').value.trim(),
templatePlaceholder = document.getElementById('template_placeholder').value.trim(),
rememberDurationInDays = document.getElementById('remember_in_days').value || 0,
templateRemember = document.getElementById('template_remember').value;
const data = {
staticrypt_config: {
encryptedMsg,
isRememberEnabled,
rememberDurationInDays,
salt,
},
is_remember_enabled: JSON.stringify(isRememberEnabled),
js_staticrypt: getScriptAsString('staticrypt'),
template_button: templateButton ? templateButton : 'DECRYPT',
template_instructions: templateInstructions || '',
template_placeholder: templatePlaceholder || 'Password',
template_remember: templateRemember || 'Remember me',
template_title: templateTitle || 'Protected Page',
};
document.getElementById('encrypted_html_display').textContent = encryptedMsg;
setFileToDownload(data);
});
document.getElementById('toggle-extra-option')
.addEventListener('click', function (e) {
e.preventDefault();
document.getElementById('extra-options').classList.toggle('hidden');
});
let isConceptShown = false;
document.getElementById('toggle-concept')
.addEventListener('click', function (e) {
e.preventDefault();
isConceptShown = !isConceptShown;
document.getElementById('toggle-concept-sign').innerText = isConceptShown ? '▼' : '►';
document.getElementById('concept').classList.toggle('hidden');
});
/**
* Browser specific download code.
*/
document.getElementById('download-link')
.addEventListener('click', function (e) {
// only register the click event if there is actually a generated file
if (htmlToDownload) {
trackEvent('download_encrypted');
/**
* Extract js code from <script> tag and return it as a string
*
* @param {string} id
* @returns {string}
*/
function getScriptAsString(id) {
return document.getElementById(id).innerText.replace(/window\.\w+ = /, "");
}
const isIE = (navigator.userAgent.indexOf("MSIE") !== -1) || (!!document.documentMode === true); // >= 10
const isEdge = navigator.userAgent.indexOf("Edge") !== -1;
/**
* Register something happened - this uses a simple Supabase function to implement a counter, and allows to drop
* google analytics. We don't store any personal data or IP.
*
* @param {string} action
*/
function trackEvent(action) {
const xhr = new XMLHttpRequest();
xhr.open("POST", "https://zlgpaemmniviswibzuwt.supabase.co/rest/v1/rpc/increment_analytics", true);
xhr.setRequestHeader("Content-type", "application/json; charset=UTF-8");
xhr.setRequestHeader(
"apikey",
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InpsZ3BhZW1tbml2aXN3aWJ6dXd0Iiwicm9sZSI6ImFub24iLCJpYXQiOjE2NjkxMjM0OTcsImV4cCI6MTk4NDY5OTQ5N30.wNoVDHG7F6INx-IPotMs3fL1nudfaF2qvQDgG-1PhNI"
);
xhr.setRequestHeader(
"Authorization",
"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InpsZ3BhZW1tbml2aXN3aWJ6dXd0Iiwicm9sZSI6ImFub24iLCJpYXQiOjE2NjkxMjM0OTcsImV4cCI6MTk4NDY5OTQ5N30.wNoVDHG7F6INx-IPotMs3fL1nudfaF2qvQDgG-1PhNI"
);
xhr.send(
JSON.stringify({
action_input: action,
})
);
}
// download with MS specific feature
if (htmlToDownload && (isIE || isEdge)) {
/**
* Fill the password prompt template with data provided.
* @param data
*/
function setFileToDownload(data) {
const request = new XMLHttpRequest();
request.open("GET", "lib/password_template.html", true);
request.onload = function () {
const renderedTmpl = formater.renderTemplate(request.responseText, data);
const downloadLink = document.querySelector("a.download");
downloadLink.href = "data:text/html," + encodeURIComponent(renderedTmpl);
downloadLink.removeAttribute("disabled");
htmlToDownload = renderedTmpl;
};
request.send();
}
// register page load
window.onload = function () {
trackEvent("show_index");
};
/**
* Handle form submission.
*/
document.getElementById("encrypt_form").addEventListener("submit", async function (e) {
e.preventDefault();
const blobObject = new Blob([htmlToDownload]);
window.navigator.msSaveOrOpenBlob(blobObject, 'encrypted.html');
}
return true;
})
</script>
trackEvent("generate_encrypted");
</body>
// update instruction textarea value with CKEDITOR content
// (see https://stackoverflow.com/questions/3147670/ckeditor-update-textarea)
CKEDITOR.instances["template_instructions"].updateElement();
const unencrypted = document.getElementById("unencrypted_html").value,
password = document.getElementById("password").value;
const salt = cryptoEngine.generateRandomSalt();
const encryptedMsg = await encode(unencrypted, password, salt);
const templateButton = document.getElementById("template_button").value,
templateInstructions = document.getElementById("template_instructions").value,
isRememberEnabled = document.getElementById("remember").checked,
templateTitle = document.getElementById("template_title").value.trim(),
templatePlaceholder = document.getElementById("template_placeholder").value.trim(),
rememberDurationInDays = document.getElementById("remember_in_days").value || 0,
templateRemember = document.getElementById("template_remember").value;
const data = {
staticrypt_config: {
encryptedMsg,
isRememberEnabled,
rememberDurationInDays,
salt,
},
is_remember_enabled: JSON.stringify(isRememberEnabled),
js_staticrypt: getScriptAsString("staticrypt"),
template_button: templateButton ? templateButton : "DECRYPT",
template_instructions: templateInstructions || "",
template_placeholder: templatePlaceholder || "Password",
template_remember: templateRemember || "Remember me",
template_title: templateTitle || "Protected Page",
};
document.getElementById("encrypted_html_display").textContent = encryptedMsg;
setFileToDownload(data);
});
document.getElementById("toggle-extra-option").addEventListener("click", function (e) {
e.preventDefault();
document.getElementById("extra-options").classList.toggle("hidden");
});
let isConceptShown = false;
document.getElementById("toggle-concept").addEventListener("click", function (e) {
e.preventDefault();
isConceptShown = !isConceptShown;
document.getElementById("toggle-concept-sign").innerText = isConceptShown ? "▼" : "►";
document.getElementById("concept").classList.toggle("hidden");
});
/**
* Browser specific download code.
*/
document.getElementById("download-link").addEventListener("click", function (e) {
// only register the click event if there is actually a generated file
if (htmlToDownload) {
trackEvent("download_encrypted");
}
const isIE = navigator.userAgent.indexOf("MSIE") !== -1 || !!document.documentMode === true; // >= 10
const isEdge = navigator.userAgent.indexOf("Edge") !== -1;
// download with MS specific feature
if (htmlToDownload && (isIE || isEdge)) {
e.preventDefault();
const blobObject = new Blob([htmlToDownload]);
window.navigator.msSaveOrOpenBlob(blobObject, "encrypted.html");
}
return true;
});
</script>
</body>
</html>