2017-12-15 21:37:02 +00:00
#!/usr/bin/env node
2017-12-14 22:38:34 +00:00
'use strict' ;
2017-12-14 22:00:11 +00:00
var CryptoJS = require ( "crypto-js" ) ;
var FileSystem = require ( "fs" ) ;
2018-01-17 11:31:57 +00:00
const path = require ( "path" ) ;
2017-12-27 03:35:24 +00:00
const Yargs = require ( 'yargs' ) ;
2017-12-14 22:38:34 +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
2019-06-28 14:23:13 +00:00
/ * *
* Salt and encrypt a msg with a password .
* Inspired by https : //github.com/adonespitogo
* /
2022-02-10 08:22:32 +00:00
function encrypt ( msg , hashedPassphrase ) {
var iv = CryptoJS . lib . WordArray . random ( 128 / 8 ) ;
2019-06-28 14:23:13 +00:00
2022-02-10 08:22:32 +00:00
var encrypted = CryptoJS . AES . encrypt ( msg , hashedPassphrase , {
2019-06-28 14:23:13 +00:00
iv : iv ,
padding : CryptoJS . pad . Pkcs7 ,
mode : CryptoJS . mode . CBC
} ) ;
2022-02-10 08:22:32 +00:00
// iv will be hex 16 in length (32 characters)
// we prepend it to the ciphertext for use in decryption
return iv . toString ( ) + encrypted . toString ( ) ;
}
/ * *
* Salt and hash the passphrase so it can be stored in localStorage without opening a password reuse vulnerability .
*
* @ param { string } passphrase
* @ returns { { salt : string , hashedPassphrase : string } }
* /
function hashPassphrase ( passphrase ) {
var salt = CryptoJS . lib . WordArray . random ( 128 / 8 ) . toString ( ) ;
var hashedPassphrase = CryptoJS . PBKDF2 ( passphrase , salt , {
keySize : 256 / 32 ,
iterations : 1000
} ) ;
return {
salt : salt ,
hashedPassphrase : hashedPassphrase . toString ( ) ,
} ;
}
2022-02-10 18:58:07 +00:00
const namedArgs = Yargs
2022-02-10 08:22:32 +00:00
. usage ( 'Usage: staticrypt <filename> <passphrase> [options]' )
. demandCommand ( 2 )
. 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 ( 'o' , {
alias : 'output' ,
type : 'string' ,
describe : 'File name / path for generated encrypted file.' ,
default : null
} )
. option ( 't' , {
alias : 'title' ,
type : 'string' ,
describe : "Title for output HTML page." ,
default : 'Protected Page'
} )
. option ( 'i' , {
alias : 'instructions' ,
type : 'string' ,
describe : 'Special instructions to display to the user.' ,
default : ''
} )
. option ( 'f' , {
alias : 'file-template' ,
type : 'string' ,
describe : 'Path to custom HTML template with passphrase prompt.' ,
default : path . join ( _ _dirname , 'password_template.html' )
} )
. option ( 'r' , {
alias : 'remember' ,
type : 'number' ,
describe : 'Show a "Remember me" checkbox that will save the (salted + hashed) passphrase in localStorage when entered by the user.\nYou can set the expiration in days as value (no value means "0", no expiration).' ,
2022-02-10 18:58:07 +00:00
default : 0 ,
} )
. option ( 'noremember' , {
type : 'boolean' ,
describe : 'Set this flag to remove the "Remember me" checkbox.' ,
default : false ,
2022-02-10 08:22:32 +00:00
} )
. option ( 'remember-label' , {
type : 'string' ,
describe : 'Label to use for the "Remember me" checkbox. Default: "Remember me".' ,
default : 'Remember me'
} )
. option ( 'passphrase-placeholder' , {
type : 'string' ,
describe : 'Placeholder to use for the passphrase input. Default: "Passphrase".' ,
default : 'Passphrase'
} )
. option ( 'decrypt-button' , {
type : 'string' ,
describe : 'Label to use for the decrypt button. Default: "DECRYPT".' ,
default : 'DECRYPT'
2022-02-10 18:58:07 +00:00
} ) . argv ;
2022-02-10 08:22:32 +00:00
if ( namedArgs . _ . length !== 2 ) {
Yargs . showHelp ( ) ;
process . exit ( 1 ) ;
}
// parse input
const input = namedArgs . _ [ 0 ] . toString ( ) ,
passphrase = namedArgs . _ [ 1 ] . toString ( ) ;
// get the file content
let contents ;
try {
contents = FileSystem . readFileSync ( input , 'utf8' ) ;
} catch ( e ) {
console . log ( "Failure: input file does not exist!" ) ;
process . exit ( 1 ) ;
2019-06-28 14:23:13 +00:00
}
2017-12-28 11:32:27 +00:00
// encrypt input
2022-02-10 08:22:32 +00:00
const hashed = hashPassphrase ( passphrase ) ;
const hashedPassphrase = hashed . hashedPassphrase ;
const salt = hashed . salt ;
const encrypted = encrypt ( contents , hashedPassphrase ) ;
// we use the hashed passphrase in the HMAC because this is effectively what will be used a passphrase (so we can store
// it in localStorage safely, we don't use the clear text passphrase)
const hmac = CryptoJS . HmacSHA256 ( encrypted , CryptoJS . SHA256 ( hashedPassphrase ) . toString ( ) ) . toString ( ) ;
const encryptedMessage = hmac + encrypted ;
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 ) {
try {
2022-02-10 08:22:32 +00:00
const embedContents = FileSystem . readFileSync ( path . join ( _ _dirname , 'crypto-js.min.js' ) , 'utf8' ) ;
cryptoTag = '<script>' + embedContents + '</script>' ;
} catch ( e ) {
2017-12-19 01:32:27 +00:00
console . log ( "Failure: embed file does not exist!" ) ;
2017-12-14 22:38:34 +00:00
process . exit ( 1 ) ;
2017-12-19 01:32:27 +00:00
}
2017-12-14 22:38:34 +00:00
}
2022-02-10 08:22:32 +00:00
const data = {
2017-12-28 11:32:27 +00:00
crypto _tag : cryptoTag ,
2022-02-10 08:22:32 +00:00
decrypt _button : namedArgs . decryptButton ,
2017-12-28 11:32:27 +00:00
embed : namedArgs . embed ,
2022-02-10 08:22:32 +00:00
encrypted : encryptedMessage ,
instructions : namedArgs . instructions ,
2022-02-10 18:58:07 +00:00
is _remember _enabled : namedArgs . noremember ? 'false' : 'true' ,
2022-02-10 08:22:32 +00:00
output _file _path : namedArgs . output !== null ? namedArgs . output : input . replace ( /\.html$/ , '' ) + "_encrypted.html" ,
passphrase _placeholder : namedArgs . passphrasePlaceholder ,
2022-02-10 18:58:07 +00:00
remember _duration _in _days : namedArgs . remember ,
2022-02-10 08:22:32 +00:00
remember _me : namedArgs . rememberLabel ,
salt : salt ,
title : namedArgs . title ,
2017-12-28 11:32:27 +00:00
} ;
genFile ( data ) ;
/ * *
* Fill the template with provided data and writes it to output file .
*
* @ param data
* /
2022-02-10 08:22:32 +00:00
function genFile ( data ) {
let templateContents ;
try {
templateContents = FileSystem . readFileSync ( namedArgs . f , 'utf8' ) ;
} catch ( e ) {
2017-12-14 22:38:34 +00:00
console . log ( "Failure: could not read template!" ) ;
process . exit ( 1 ) ;
}
2022-02-10 08:22:32 +00:00
const renderedTemplate = render ( templateContents , data ) ;
2017-12-14 22:38:34 +00:00
2022-02-10 08:22:32 +00:00
try {
FileSystem . writeFileSync ( data . output _file _path , renderedTemplate ) ;
} catch ( e ) {
2017-12-14 22:38:34 +00:00
console . log ( "Failure: could not generate output file!" ) ;
process . exit ( 1 ) ;
}
}
2017-12-28 11:32:27 +00:00
/ * *
* Replace the placeholder tags ( between '{tag}' ) in 'tpl' string with provided data .
*
* @ param tpl
* @ param data
* @ returns string
* /
2022-02-10 08:22:32 +00:00
function render ( tpl , data ) {
2017-12-14 22:38:34 +00:00
return tpl . replace ( /{(.*?)}/g , function ( _ , key ) {
2022-02-10 08:22:32 +00:00
if ( data && data [ key ] !== undefined ) {
return data [ key ] ;
}
return '' ;
2017-12-14 22:38:34 +00:00
} ) ;
}