Showing preview only (223K chars total). Download the full file or copy to clipboard to get everything.
Repository: HR/Crypter
Branch: master
Commit: e2a95eb10feb
Files: 63
Total size: 208.0 KB
Directory structure:
gitextract_a8kqtnjq/
├── .babelrc
├── .codeclimate.yml
├── .github/
│ └── ISSUE_TEMPLATE.md
├── .gitignore
├── .nvmrc
├── .travis.yml
├── SECURITY.md
├── app/
│ ├── config.js
│ ├── core/
│ │ ├── Db.js
│ │ ├── MasterPass.js
│ │ ├── MasterPassKey.js
│ │ └── crypto.js
│ ├── index.js
│ ├── package.json
│ ├── src/
│ │ ├── crypter.js
│ │ ├── mainMenu.js
│ │ ├── masterPassPrompt.js
│ │ ├── menu.js
│ │ ├── settings.js
│ │ └── setup.js
│ ├── static/
│ │ ├── crypter.html
│ │ ├── fonts/
│ │ │ └── LICENSE.txt
│ │ ├── js/
│ │ │ ├── common.js
│ │ │ ├── crypter.js
│ │ │ ├── masterpassprompt.js
│ │ │ ├── settings.js
│ │ │ └── setup.js
│ │ ├── masterpassprompt.html
│ │ ├── settings.html
│ │ ├── setup.html
│ │ └── styles/
│ │ ├── crypter.css
│ │ ├── crypter.less
│ │ ├── masterpassprompt.css
│ │ ├── masterpassprompt.less
│ │ ├── mixins.css
│ │ ├── mixins.less
│ │ ├── settings.css
│ │ ├── settings.less
│ │ ├── setup.css
│ │ └── setup.less
│ └── utils/
│ ├── logger.js
│ ├── update.js
│ └── utils.js
├── appveyor.yml
├── build/
│ ├── background.tif
│ ├── crypto.icns
│ ├── icon.icns
│ ├── license.txt
│ └── vicon.icns
├── gulpfile.js
├── license
├── package.json
├── readme.md
├── script/
│ ├── appveyor-build.cmd
│ ├── build.cmd
│ ├── build_ml.sh
│ ├── darwin-build.sh
│ ├── resolveNodeV.js
│ ├── test.cmd
│ ├── travis-build.sh
│ └── win-build.sh
└── test/
├── test.js
└── ui/
└── test.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .babelrc
================================================
{
"presets": ["@babel/preset-env"]
}
================================================
FILE: .codeclimate.yml
================================================
engines:
csslint:
enabled: false
duplication:
enabled: true
config:
languages:
- javascript
exclude_fingerprints:
- faca6a53a5a4ae580279527e261dd1da
- 555c80119b06560cd2e96eb4e4e91f26
- a81ed3d8cb6b36bacb95a0ab40d09d78
- 9af23dc9a75d6c11f28122e9f83e7309
- faca6a53a5a4ae580279527e261dd1da
- fcc5dd7fd381cab75901402345e66511
- 144b552ca7471e150abb4ece59240e71
- 63a2186b2e759c9b5f6afb31965ab290
- 77a83a6a06048f2bda07f8c15c65c9ea
eslint:
enabled: true
fixme:
enabled: true
ratings:
paths:
- "**.css"
- "**.js"
exclude_paths:
- test/**/*
- "static/js/jquery.marquee.min.js"
================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
### Prerequisites
* [ ] Can you reproduce the problem?
* [ ] Did you read the entire [README.md](https://github.com/HR/Crypter/blob/master/readme.md)?
* [ ] Did you read the [FAQs](https://github.com/HR/Crypter/blob/master/readme.md#faqs)?
* [ ] Do you fully understand how Crypter works and how to use it?
* [ ] Did you check the [issues](https://github.com/HR/Crypter/issues) to see if your bug or enhancement is already reported?
### Description
[Description of the bug or feature]
### Steps to Reproduce
1. [First Step]
2. [Second Step]
3. [and so on...]
**Expected behavior:** [What you expected to happen]
**Actual behavior:** [What actually happened]
### Versions
Crypter:
Release (full release name):
Operating System (name and version):
You can find the full release name under https://github.com/HR/Crypter/releases. For version ```3.0.0``` and up, the version is found under ```Crypter > About``` or just in the menu ```Version X.Y.Z```.
================================================
FILE: .gitignore
================================================
### OSX ###
.DS_Store
.AppleDouble
.LSOverride
# dev
dest/
.cred
test/specs
test/render.js
.env
spec/
deprecated/
screens/
.crypting/
.decrypting
*.token
# Icon must end with two
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### Node ###
# Logs
logs
*.log
npm-debug.log*
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
app/node_modules
node_modules
jspm_packages
# Optional npm cache directory
.npm
# Optional REPL history
.node_repl_history
### Bower ###
bower_components
.bower-cache
.bower-registry
.bower-tmp
# Other stuff to ignore
dist/
*.log
.editorconfig
.Debug
core/elements/*.js
debug
backups/
*.app
chromedriver*
.nyc_output/
================================================
FILE: .nvmrc
================================================
12.14.1
================================================
FILE: .travis.yml
================================================
branches:
only:
- master
matrix:
include:
- os: linux
services: docker
sudo: required
dist: xenial
language: generic
env:
- ELECTRON_CACHE=$HOME/.cache/electron
- ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder
- os: osx
osx_image: xcode12
env:
- ELECTRON_CACHE=$HOME/.cache/electron
- ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder
language: node_js
node_js:
before_script:
- chmod +x ./script/*.sh
- npm install npm -g
script: ./script/travis-build.sh
after_success:
# Cleanup
- git reset --hard HEAD
# Remove untracked and ignored files
- git clean -dfXn && git clean -dfX
- chmod +x ./script/*.sh
# Check if build triggered ([build] in commit message)
- lgcm=$(git log -1 --pretty=%B | xargs echo);
if [[ $lgcm == *"[build]"* ]]; then
./script/build_ml.sh;
fi
before_cache:
- rm -rf $HOME/.cache/electron-builder/wine
cache:
yarn: true
apt: true
directories:
- node_modules
- $HOME/.cache/electron
- $HOME/.cache/electron-builder
notifications:
email:
on_success: never
on_failure: change
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- libgnome-keyring-dev
- icnsutils
- xvfb
================================================
FILE: SECURITY.md
================================================
# Security Policy
## Supported Versions
The following versions will receive security updates.
| Version | Supported |
| ------- | ------------------ |
| 4.x | :white_check_mark: |
| < 4.0 | :x: |
## Reporting a Vulnerability
Please report a vulnerbility directly to me at h@rehman.email
================================================
FILE: app/config.js
================================================
'use strict'
/**
* config.js
* Provides all essential config constants
******************************/
// Fixed constants
const VIEWS_BASE_URI = `file://${__dirname}/static`
module.exports = {
REPO: {
URL: 'https://github.com/HR/Crypter/',
RELEASES_API_URL: 'https://api.github.com/repos/HR/Crypter/releases/latest',
FORK: 'https://github.com/HR/Crypter/fork',
DOCS: 'https://github.com/HR/Crypter/blob/master/readme.md',
REPORT_ISSUE: 'https://github.com/HR/Crypter/issues/new'
},
WINDOW_OPTS: {
center: true,
show: true,
titleBarStyle: 'hiddenInset',
resizable: false,
maximizable: false,
movable: true,
webPreferences: {
nodeIntegration: true
}
},
VIEWS: {
BASE_URI: VIEWS_BASE_URI,
MASTERPASSPROMPT: `${VIEWS_BASE_URI}/masterpassprompt.html`,
SETUP: `${VIEWS_BASE_URI}/setup.html`,
CRYPTER: `${VIEWS_BASE_URI}/crypter.html`,
SETTINGS: `${VIEWS_BASE_URI}/settings.html`
},
CRYPTO: {
ENCRYPTION_TMP_DIR: '.crypting',
DECRYPTION_TMP_DIR: '.decrypting',
FILE_DATA: 'data',
FILE_CREDS: 'creds',
MASTERPASS_CREDS_FILE: 'credentials.crypter',
MASTERPASS_CREDS_PROPS: ['mpkhash', 'mpksalt', 'mpsalt'],
DECRYPT_OP: 'Decrypted',
DECRYPT_TITLE_PREPEND: 'Decrypted ',
ENCRYPT_OP: 'Encrypted',
EXT: '.crypto',
DEFAULTS: {
// Crypto default constants
ITERATIONS: 50000, // file encryption key derivation iterations
KEYLENGTH: 32, // encryption key length
IVLENGTH: 12, // initialisation vector length
ALGORITHM: 'aes-256-gcm', // encryption algorithm
DIGEST: 'sha256', // digest function
HASH_ALG: 'sha256', // hashing function
MPK_ITERATIONS: 100000 // MasterPassKey derivation iterations
}
},
REGEX: {
APP_EVENT: /^app:[\w-]+$/i,
ENCRYPTION_CREDS: /^Crypter(.*)$/gim,
MASTERPASS: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@!%*#?&]).{8,}$/
},
RESPONSES: {
invalid:
'MUST AT LEAST CONTAIN 1 UPPER & LOWER CASE ALPHABET, 1 NUMBER, 1 SYMBOL AND BE 8 CHARACTERS',
correct: 'CORRECT MASTERPASS',
incorrect: 'INCORRECT MASTERPASS',
setSuccess: 'MASTERPASS SUCCESSFULLY SET',
empty: 'PLEASE ENTER THE MASTERPASS',
resetSuccess:
"You have successfully reset your MasterPass. You'll be redirected to verify it shortly.",
exportSuccess: 'Successfully exported the credentials',
importSuccess:
'Successfully imported the credentials. You will need to verify the MasterPass for the credentials imported after Crypter relaunches.'
},
ERRORS: {
INVALID_MP_CREDS_FILE: 'Not a valid or corrupted Crypter credentials file!',
INVALID_FILE: 'Not a valid or corrupted CRYPTO file!',
AUTH_FAIL:
'Corrupted Crypter file or trying to decrypt on a different machine. See git.io/Crypter.info#faqs',
PROMISE: 'Oops, we encountered a problem...',
DECRYPT: 'Not a Crypter file (can not get salt, iv and authTag)',
MS: {
INVALID_FILE:
'Invalid tar header. Maybe the tar is corrupted or it needs to be gunzipped?',
AUTH_FAIL: 'Unsupported state or unable to authenticate data'
}
},
COLORS: {
bad: '#dc3545',
good: '#28a745',
highlight: '#333333'
},
SETTINGS: {
RELAUNCH_TIMEOUT: 4000
}
}
================================================
FILE: app/core/Db.js
================================================
'use strict'
/**
* Db.js
* Custom JSON DB API implementation
******************************/
const _ = require('lodash')
const logger = require('electron-log')
const fs = require('fs-extra')
/**
* Constructor
* An alias for the levelup constructor
* @param {location}:string the location at which the Db to create or is at
*/
// Db class constructor
function Db (path, cb) {
// If Db already exists at the path, it opens the Db
// If no Db exists at the path, it creates a new Db
// Set db path
this.path = path
// Save context to instance
const self = this
// Create db if does not exist
fs.ensureFile(path, (err) => {
if (err) {
throw err
return
}
// file has now been created or exists
// either way, read the json now
fs.readFile(path, (err, db) => {
if (err) {
throw err
return
}
if (_.isEmpty(db)) {
logger.verbose('db is empty so initialising it...')
self._db = {}
self.open = true
} else {
try {
logger.verbose('db is not empty so json parsing it...')
// Get original JSON object or create new if does not exist
self._db = JSON.parse(db)
self.open = true
} catch (err) {
throw err
return
}
}
// call callback with newly created object
cb(self)
})
})
}
// Flush db to filesystem
Db.prototype.flush = function () {
logger.verbose('flushing _db to fs')
const self = this
return new Promise((resolve, reject) => {
fs.outputJson(self.path, self._db, (err) => {
if (err) reject(err)
// successfully flushed db to disk
resolve()
})
})
}
// Add items to db
Db.prototype.put = function (key, value) {
logger.verbose(`Putting ${key} in _db`)
const self = this
return new Promise((resolve, reject) => {
// set value with key in the internal db
self._db[key] = value
// then flush db to fs
self
.flush()
.then(() => {
resolve()
})
.catch((err) => {
reject(err)
})
})
}
// Get items from db
Db.prototype.get = function (key) {
logger.verbose(`Getting ${key} from _db`)
const self = this
return new Promise((resolve, reject) => {
// Return the object if it exists otherwise return false
resolve(_.has(self._db, key) ? self._db[key] : false)
})
}
Db.prototype.close = function () {
logger.verbose(`Closing _db`)
const self = this
return new Promise((resolve, reject) => {
// Check if db is open
if (self.open) {
self
.flush()
.then(() => {
self.open = false
resolve()
})
.catch((err) => {
reject(err)
})
}
})
}
// save a global object of a name (objName)
Db.prototype.saveGlobalObj = function (objName) {
logger.verbose(`Saving global obj ${objName} to _db`)
const self = this // get reference to the class instance
return new Promise((resolve, reject) => {
// If object not empty then save it in db
if (!_.isEmpty(global[objName])) {
// stringify object and store as a string with objName as key
try {
// wrap serialization of object around try catch as it could throw error
const serializedObj = JSON.stringify(global[objName])
self
.put(objName, serializedObj)
.then(() => {
resolve()
})
.catch((err) => {
reject(err) // db save error
})
} catch (err) {
reject(err)
}
} else {
// If object empty then do not save it
resolve()
}
})
}
// restores a global object of a name (objName)
Db.prototype.restoreGlobalObj = function (objName) {
logger.verbose(`Restoring global obj ${objName} from _db`)
const self = this // get reference to th class instance
return new Promise((resolve, reject) => {
// get serialized object from db
self
.get(objName)
.then((serializedObj) => {
try {
// deserialize object and set as global
global[objName] = JSON.parse(serializedObj) // try parsing JSON
resolve()
} catch (err) {
// if error occurs while parsing, reject promise
reject(err)
}
})
.catch((err) => {
// I/O or other error, pass it up the callback
reject(err)
})
})
}
module.exports = Db
================================================
FILE: app/core/MasterPass.js
================================================
'use strict'
/**
* MasterPass.js
* MasterPass functionality
******************************/
const crypto = require('./crypto'),
keytar = require('keytar'),
SERVICE = 'Crypter',
ACCOUNT = 'MasterPass'
// TODO: Make independent from global obj! use param instead
exports.init = async () => {
const masterpass = await keytar.getPassword(SERVICE, ACCOUNT)
if (!masterpass) return false
const mpk = await crypto.deriveKey(masterpass, global.creds.mpsalt)
return mpk
}
exports.save = masterpass => keytar.setPassword(SERVICE, ACCOUNT, masterpass)
// Check MasterPass
exports.check = masterpass => {
return new Promise((resolve, reject) => {
// deriveKey using the salt originally used to generate the
// MasterPassKey
crypto
.deriveKey(masterpass, global.creds.mpsalt)
.then(mpk => {
// generate the hash for the MasterPassKey
return crypto.genPassHash(mpk.key, global.creds.mpksalt)
})
.then(mpk => {
// check if MasterPassKey hash is equal to the MasterPassKey hash in mdb
// Use timingSafeEqual to protect against timing attacks
const match = crypto.timingSafeEqual(global.creds.mpkhash, mpk.hash)
// return the match and derived key
resolve({ match, key: mpk.key })
})
.catch(err => {
reject(err)
})
})
}
// Set MasterPass
exports.set = masterpass => {
return new Promise((resolve, reject) => {
// Derive the MasterPassKey from the supplied masterpass
crypto
.deriveKey(masterpass, null)
.then(mp => exports.save(masterpass).then(() => mp))
.then(mp => {
// Save the salt used to generate the MasterPassKey
global.creds.mpsalt = mp.salt
// generate the hash for the MasterPassKey
return crypto.genPassHash(mp.key, null)
})
.then(mpk => {
// Save the salt used to generate the masterpass
global.creds.mpkhash = mpk.hash
global.creds.mpksalt = mpk.salt
// return the derived mpkey
resolve(mpk.key)
})
.catch(err => {
// reject if error occurs
reject(err)
})
})
}
================================================
FILE: app/core/MasterPassKey.js
================================================
'use strict'
/**
* MasterPass.js
* Provides a way to securely set and retrieve the MasterPass globally
* MasterPassKey is protected (private var) and only exist in Main memory
******************************/
// Uses closure to securely store MasterPassKey in MasterPassKey object
const MasterPassKey = (function () {
// Private mpk variable that stores the MasterPassKey
const mpk = new WeakMap()
// Class constructor
function MasterPassKey (key) {
// Initialise the new instance of class with the MasterPassKey
mpk.set(this, key)
}
// Public get method the for mpk
MasterPassKey.prototype.get = function () {
if (mpk.get(this) === undefined) {
// If MasterPassKey not set or delected
return new Error('MasterPassKey is not set')
} else {
// If MasterPassKey is set then return it
return mpk.get(this)
}
}
// Public set method the for mpk
MasterPassKey.prototype.set = function (key) {
if (key instanceof Buffer) {
// If the key is a Buffer then set it
mpk.set(this, key)
} else {
// If the key is not a Buffer return an erro
return new Error('MasterPassKey not a Buffer')
}
}
// Delete the MasterPassKey
MasterPassKey.prototype.delete = function (key) {
mpk.delete(this)
}
return MasterPassKey
}())
module.exports = MasterPassKey
================================================
FILE: app/core/crypto.js
================================================
'use strict'
/**
* crypto.js
* Provides the crypto functionality required
******************************/
const fs = require('fs-extra')
const path = require('path')
const scrypto = require('crypto')
const logger = require('electron-log')
const Readable = require('stream').Readable
const tar = require('tar-fs')
const { CRYPTO, REGEX, ERRORS } = require('../config')
// Helper functions
let readFile = path => {
return new Promise((resolve, reject) => {
fs.readFile(path, 'utf-8', (err, data) => {
if (err) reject(err)
resolve(data)
})
})
}
// Exports
exports.crypt = (origpath, masterpass) => {
return new Promise((resolve, reject) => {
logger.verbose(`Encrypting ${origpath}...`)
// Resolve the destination path for encrypted file
exports
.encrypt(origpath, masterpass)
.then(creds => {
resolve({
op: CRYPTO.ENCRYPT_OP, // Crypter operation
name: path.basename(origpath), // filename
path: origpath, // path of the (unencrypted) file
cryptPath: creds.cryptpath, // path of the encrypted file
salt: creds.salt.toString('hex'), // salt used to derivekey in hex
key: creds.key.toString('hex'), // dervived key in hex
iv: creds.iv.toString('hex'), // iv in hex
authTag: creds.tag.toString('hex') // authTag in hex
})
})
.catch(err => {
reject(err)
})
})
}
exports.encrypt = (origpath, mpkey) => {
// Encrypts any arbitrary data passed with the pass
return new Promise((resolve, reject) => {
// derive the encryption key
exports
.deriveKey(mpkey, null, CRYPTO.DEFAULTS.ITERATIONS)
.then(dcreds => {
let tag
let isDirectory = fs.lstatSync(origpath).isDirectory()
let tempd = `${path.dirname(origpath)}/${CRYPTO.ENCRYPTION_TMP_DIR}`
let dataDestPath = `${tempd}/data`
let credsDestPath = `${tempd}/creds`
logger.verbose(
`tempd: ${tempd}, dataDestPath: ${dataDestPath}, credsDestPath: ${credsDestPath}, isDirectory: ${isDirectory}`
)
// create tempd temporary directory
fs.mkdirs(tempd, err => {
if (err) reject(err)
logger.verbose(`Created ${tempd} successfully`)
// readstream to read the (unencrypted) file
const origin = isDirectory
? tar.pack(origpath)
: fs.createReadStream(origpath)
// create data and creds file
const dataDest = fs.createWriteStream(dataDestPath)
const credsDest = fs.createWriteStream(credsDestPath)
// generate a cryptographically secure random iv
const iv = scrypto.randomBytes(CRYPTO.DEFAULTS.IVLENGTH)
// create the AES-256-GCM cipher with iv and derive encryption key
const cipher = scrypto.createCipheriv(
CRYPTO.DEFAULTS.ALGORITHM,
dcreds.key,
iv
)
// Read file, apply tranformation (encryption) to stream and
// then write stream to filesystem
origin
.on('error', err => reject(err))
.pipe(cipher)
.on('error', err => reject(err))
.pipe(dataDest)
.on('error', err => reject(err))
.on('finish', () => {
// get the generated Message Authentication Code
tag = cipher.getAuthTag()
// Write credentials used to encrypt in creds file
const creds = {
type: 'CRYPTO',
iv: iv.toString('hex'),
authTag: tag.toString('hex'),
salt: dcreds.salt.toString('hex'),
isDir: isDirectory
}
credsDest.end(JSON.stringify(creds))
})
// writestream finish handler
credsDest.on('finish', () => {
let tarDestPath = origpath + CRYPTO.EXT
const tarDest = fs.createWriteStream(tarDestPath)
const tarPack = tar.pack(tempd)
// Pack directory and zip into a .crypto file
tarPack
.on('error', err => reject(err))
.pipe(tarDest)
.on('error', err => reject(err))
.on('finish', () => {
// Remove temporary dir tempd
fs.remove(tempd, err => {
if (err) reject(err)
// return all the credentials and parameters used for encryption
logger.verbose('Successfully deleted tempd!')
resolve({
salt: dcreds.salt,
key: dcreds.key,
cryptpath: tarDestPath,
tag: tag,
iv: iv
})
})
})
})
})
})
.catch(err => reject(err))
})
}
exports.decrypt = (origpath, mpkey) => {
// Decrypts a crypto format file passed with the pass
return new Promise((resolve, reject) => {
logger.verbose(`Decrypting ${origpath}...`)
// Extract a directory
let tempd = `${path.dirname(origpath)}/${CRYPTO.DECRYPTION_TMP_DIR}`
let dataOrigPath = `${tempd}/${CRYPTO.FILE_DATA}`
let credsOrigPath = `${tempd}/${CRYPTO.FILE_CREDS}`
let dataDestPath = origpath.replace(CRYPTO.EXT, '')
dataDestPath = dataDestPath.replace(
path.basename(dataDestPath),
path.basename(dataDestPath)
)
let tarOrig = fs.createReadStream(origpath)
let tarExtr = tar.extract(tempd)
// Extract tar to CRYPTO.DECRYPTION_TMP_DIR directory
tarOrig
.on('error', err => reject(err))
.pipe(tarExtr)
.on('error', err => reject(err))
.on('finish', () => {
// Now read creds and use to decrypt data
logger.verbose('Finished extracting')
readFile(credsOrigPath)
.then(credsData => {
let creds
try {
// Try creds v2
creds = JSON.parse(credsData)
creds = [creds.iv, creds.authTag, creds.salt, creds.isDir]
} catch (error) {
// Try creds v1
let credsLine = credsData.trim().match(REGEX.ENCRYPTION_CREDS)
if (!credsLine) {
return reject(new Error(ERRORS.DECRYPT))
}
creds = credsLine[0].split('#').slice(1)
}
const iv = Buffer.from(creds[0], 'hex')
const authTag = Buffer.from(creds[1], 'hex')
const salt = Buffer.from(creds[2], 'hex')
const isDir = creds[3]
logger.verbose(
`Extracted data, iv: ${iv}, authTag: ${authTag}, salt: ${salt}`
)
// Read encrypted data stream
const dataOrig = fs.createReadStream(dataOrigPath)
// derive the original encryption key for the file
exports
.deriveKey(mpkey, salt, CRYPTO.DEFAULTS.ITERATIONS)
.then(dcreds => {
try {
let decipher = scrypto.createDecipheriv(
CRYPTO.DEFAULTS.ALGORITHM,
dcreds.key,
iv
)
decipher.setAuthTag(authTag)
let dataDest = isDir
? tar.extract(dataDestPath)
: fs.createWriteStream(dataDestPath)
dataOrig
.on('error', err => reject(err))
.pipe(decipher)
.on('error', err => reject(err))
.pipe(dataDest)
.on('error', err => reject(err))
.on('finish', () => {
logger.verbose(`Encrypted to ${dataDestPath}`)
// Now delete tempd (temporary directory)
fs.remove(tempd, err => {
if (err) reject(err)
logger.verbose(`Removed temp dir ${tempd}`)
resolve({
op: CRYPTO.DECRYPT_OP,
name: path.basename(origpath),
path: origpath,
cryptPath: dataDestPath,
salt: salt.toString('hex'),
key: dcreds.key.toString('hex'),
iv: iv.toString('hex'),
authTag: authTag.toString('hex')
})
})
})
} catch (err) {
reject(err)
}
})
})
.catch(err => {
reject(err)
})
})
})
}
exports.deriveKey = (pass, psalt) => {
return new Promise((resolve, reject) => {
// reject with error if pass not provided
if (!pass) reject(new Error('Pass to derive key from not provided'))
// If psalt is provided and is a Buffer then assign it
// If psalt is provided and is not a Buffer then coerce it and assign it
// If psalt is not provided then generate a cryptographically secure salt
// and assign it
const salt = psalt
? Buffer.isBuffer(psalt)
? psalt
: Buffer.from(psalt)
: scrypto.randomBytes(CRYPTO.DEFAULTS.KEYLENGTH)
// derive the key using the salt, password and default crypto setup
scrypto.pbkdf2(
pass,
salt,
CRYPTO.DEFAULTS.MPK_ITERATIONS,
CRYPTO.DEFAULTS.KEYLENGTH,
CRYPTO.DEFAULTS.DIGEST,
(err, key) => {
if (err) reject(err)
// return the key and the salt
resolve({ key, salt })
}
)
})
}
// create a sha256 hash of the MasterPassKey
exports.genPassHash = (masterpass, salt) => {
return new Promise((resolve, reject) => {
// convert the masterpass (of type Buffer) to a hex encoded string
// if it is not already one
const pass = Buffer.isBuffer(masterpass)
? masterpass.toString('hex')
: masterpass
// if salt provided then the MasterPass is being checked
// if salt not provided then the MasterPass is being set
if (salt) {
// create hash from the contanation of the pass and salt
// assign the hex digest of the created hash
const hash = scrypto
.createHash(CRYPTO.DEFAULTS.HASH_ALG)
.update(`${pass}${salt}`)
.digest('hex')
resolve({ hash, key: masterpass })
} else {
// generate a cryptographically secure salt and use it as the salt
const salt = scrypto
.randomBytes(CRYPTO.DEFAULTS.KEYLENGTH)
.toString('hex')
// create hash from the contanation of the pass and salt
// assign the hex digest of the created hash
const hash = scrypto
.createHash(CRYPTO.DEFAULTS.HASH_ALG)
.update(`${pass}${salt}`)
.digest('hex')
resolve({ hash, salt, key: masterpass })
}
})
}
// Converts a buffer array to a hex string
exports.buf2hex = arr => {
const buf = Buffer.from(arr)
return buf.toString('hex')
}
// Compares vars in a constant time (protects against timing attacks)
exports.timingSafeEqual = (a, b) => {
// convert args to buffers if not already
a = Buffer.isBuffer(a) ? a : Buffer.from(a)
b = Buffer.isBuffer(b) ? b : Buffer.from(b)
var result = 0
var l = a.length
while (l--) {
// bitwise comparison
result |= a[l] ^ b[l]
}
return result === 0
}
================================================
FILE: app/index.js
================================================
'use strict'
/**
* index.js
* Entry point for app execution
******************************/
const { app, dialog, BrowserWindow } = require('electron'),
{ openNewGitHubIssue, debugInfo } = require('electron-util'),
debug = require('electron-debug'),
unhandled = require('electron-unhandled')
unhandled({
reportButton: error => {
openNewGitHubIssue({
user: 'HR',
repo: 'Crypter',
body: `\`\`\`\n${error.stack}\n\`\`\`\n\n---\n\n${debugInfo()}`
})
}
})
debug()
app.setAppUserModelId('com.github.hr.crypter')
// MasterPass credentials global
global.creds = {}
// User settings global
global.settings = {}
// Paths global (only resolved at runtime)
global.paths = {
mdb: `${app.getPath('userData')}/mdb`,
userData: app.getPath('userData'),
home: app.getPath('home'),
documents: app.getPath('documents')
}
let fileToCrypt
let settingsWindowNotOpen = true
const logger = require('electron-log')
const { existsSync } = require('fs-extra')
const { checkUpdate } = require('./utils/update')
// Core
const Db = require('./core/Db')
const MasterPass = require('./core/MasterPass')
const MasterPassKey = require('./core/MasterPassKey')
// Windows
const crypter = require('./src/crypter')
const masterPassPrompt = require('./src/masterPassPrompt')
const setup = require('./src/setup')
const settings = require('./src/settings')
const { ERRORS } = require('./config')
// Debug info
logger.info(`Crypter v${app.getVersion()}`)
logger.info(`Process args: ${process.argv}`)
logger.info(`AppPath: ${app.getAppPath()}`)
logger.info(`UseData Path: ${app.getPath('userData')}`)
// Change exec path
process.chdir(app.getAppPath())
logger.info(`Changed cwd to: ${process.cwd()}`)
logger.info(`Electron v${process.versions.electron}`)
logger.info(`Electron node v${process.versions.node}`)
// Prevent second instance
const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
app.quit()
}
/**
* Promisification of initialisation
**/
const init = function () {
return new Promise(function (resolve, reject) {
// initialise mdb
global.mdb = new Db(global.paths.mdb, function (mdb) {
// Get the credentials serialized object from mdb
resolve(mdb.get('creds'))
})
})
}
const initMain = function () {
logger.verbose(`initialising Main...`)
return global.mdb
.restoreGlobalObj('creds')
.then(() => MasterPass.init())
.then(mpk => mpk && (global.MasterPassKey = new MasterPassKey(mpk.key)))
}
/**
* Event handlers
**/
// Main event handler
app.on('ready', function () {
// Check for updates, silently
checkUpdate().catch(err => {
logger.warn(err)
})
// Check synchronously whether paths exist
init()
.then(mainRun => {
logger.info(`Init done.`)
// If the credentials not find in mdb, run setup
// otherwise run main
if (mainRun) {
// Run main
logger.info(`Main run. Creating CrypterWindow...`)
// Initialise (open mdb and get creds)
initMain()
.then(mpLoaded => {
logger.verbose(
'INIT: MasterPass',
mpLoaded ? 'loaded' : 'not saved'
)
// Obtain MasterPass, derive MasterPassKey and set globally
return mpLoaded || createWindow(masterPassPrompt, false)
})
.then(() => {
// Create the Crypter window and open it
return createWindow(crypter, fileToCrypt)
})
.then(() => {
// Quit app after crypterWindow is closed
app.quit()
})
.catch(function (error) {
if (error) {
// Catch any fatal errors and exit
logger.error(`PROMISE ERR: ${error.stack}`)
dialog.showErrorBox(ERRORS.PROMISE, error.message)
}
app.quit()
})
} else {
// Run Setup
logger.info('Setup run. Creating Setup wizard...')
createWindow(setup)
.then(() => {
logger.info('MAIN Setup successfully completed. quitting...')
// setup successfully completed
app.quit()
})
.catch(function (error) {
logger.error(`PROMISE ERR: ${error.stack}`)
// Display error to user
dialog.showErrorBox(ERRORS.PROMISE, error.message)
app.quit()
})
}
})
.catch(function (error) {
logger.error(`PROMISE ERR: ${error.stack}`)
// Display error to user
dialog.showErrorBox(ERRORS.PROMISE, error.message)
app.quit()
})
})
/**
* Electron events
**/
app.on('will-finish-launching', () => {
// Check if launched with a file (opened with app in macOS)
app.on('open-file', (event, file) => {
if (app.isReady() === false) {
// Opening when not launched yet
logger.info('Launching with open-file ' + file)
fileToCrypt = file
}
event.preventDefault()
})
// Check if launched with a file (opened with app in Windows)
if (
process.argv[1] &&
process.argv[1].length > 1 &&
existsSync(process.argv[1])
) {
fileToCrypt = process.argv[1]
}
})
app.on('window-all-closed', () => {
logger.verbose('APP: window-all-closed event emitted')
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
})
app.on('quit', () => {
logger.info('APP: quit event emitted')
global.mdb.close().catch(err => {
console.error(err)
throw err
})
})
app.on('will-quit', event => {
// will exit program once exit procedures have been run
logger.info(`APP.ON('will-quit'): will-quit event emitted`)
global.mdb.close().catch(err => {
console.error(err)
throw err
})
})
/**
* Custom events
**/
app.on('app:quit', () => {
logger.verbose('APP: app:quit event emitted')
app.quit()
})
app.on('app:open-settings', () => {
logger.verbose('APP: app:open-settings event emitted')
createWindow(settings)
})
app.on('app:check-update', () => {
logger.verbose('APP: app:check-updates event emitted')
// Check for updates
checkUpdate()
.then(updateAvailable => {
if (!updateAvailable) {
dialog.showMessageBox({
type: 'info',
message: 'No update available.',
detail: `You have the latest version Crypter ${app.getVersion()} :)`
})
}
})
.catch(err => {
logger.warn(err)
dialog.showErrorBox(
'Failed to check for update',
`An error occured while checking for update:\n ${err.message}`
)
})
})
app.on('app:relaunch', () => {
logger.verbose('APP: app:relaunch event emitted')
// Relaunch Crypter
app.relaunch()
// Exit successfully
app.quit(0)
// app.exit(0)
})
app.on('app:reset-masterpass', () => {
logger.verbose('APP: app:reset-masterpass event emitted')
createWindow(masterPassPrompt)
})
/**
* Promisification of windows
**/
function createWindow (window, ...args) {
const winInst = BrowserWindow.getAllWindows().find(
win => win.getTitle() === window.title
)
// Focus on existing instance
if (winInst) return winInst.focus()
return new Promise((resolve, reject) =>
window.window(global, ...args, err => {
if (err) reject(err)
resolve()
})
)
}
================================================
FILE: app/package.json
================================================
{
"name": "Crypter",
"productName": "Crypter",
"version": "5.0.0",
"description": "An innovative, convenient and secure cross-platform crypto app",
"license": "MIT",
"repository": "https://github.com/HR/Crypter",
"homepage": "https://github.com/HR/Crypter",
"bugs": "https://github.com/HR/Crypter/issues",
"private": true,
"main": "index.js",
"author": {
"name": "Habib Rehman",
"email": "H@Rehman.email",
"url": "https://git.io/HR"
},
"dependencies": {
"electron-debug": "^3.1.0",
"electron-log": "^4.2.2",
"electron-unhandled": "^3.0.2",
"electron-util": "^0.14.2",
"estraverse": "5.1.0",
"fs-extra": "9.0.1",
"handlebars": "4.7.6",
"jquery": "3.5.1",
"jquery.marquee": "1.5.0",
"keytar": "^6.0.1",
"lodash": "4.17.19",
"moment": "2.27.0",
"normalize.css": "8.0.1",
"tar-fs": "2.1.0"
}
}
================================================
FILE: app/src/crypter.js
================================================
const { app, ipcMain, Menu, BrowserWindow } = require('electron')
const { VIEWS, ERRORS, WINDOW_OPTS } = require('../config')
const crypto = require('../core/crypto')
const menuTemplate = require('./mainMenu')
const { isCryptoFile } = require('../utils/utils')
const logger = require('electron-log')
const title = 'Crypter'
exports.title = title
exports.window = function (global, fileToCrypt, callback) {
// creates a new BrowserWindow
let win = new BrowserWindow({
width: 350,
height: 460,
title,
...WINDOW_OPTS
})
// create menu from menuTemplate and set
Menu.setApplicationMenu(Menu.buildFromTemplate(menuTemplate))
// loads crypt.html view into the BrowserWindow
win.loadURL(VIEWS.CRYPTER)
let webContents = win.webContents
webContents.once('did-finish-load', () => {
// Process any file opened with app before this window
if (fileToCrypt) {
logger.info('Got a file to crypt', fileToCrypt)
cryptFile(fileToCrypt)
}
})
function encrypt(filePath) {
// Update UI
webContents.send('encryptingFile', filePath)
crypto.crypt(filePath, global.MasterPassKey.get())
.then((file) => {
webContents.send('cryptedFile', file)
})
.catch((err) => {
logger.info(`cryptFile error`)
logger.error(err)
webContents.send('cryptErr', err.message)
})
}
function decrypt(filePath) {
// Update UI
webContents.send('decryptingFile', filePath)
crypto.decrypt(filePath, global.MasterPassKey.get())
.then((file) => {
logger.info('decrypted')
webContents.send('decryptedFile', file)
})
.catch((err) => {
logger.info(`decryptFile error`)
logger.error(err)
switch (err.message.trim()) {
case ERRORS.MS.INVALID_FILE:
webContents.send('cryptErr', ERRORS.INVALID_FILE)
break;
case ERRORS.MS.AUTH_FAIL:
webContents.send('cryptErr', ERRORS.AUTH_FAIL)
break;
default:
webContents.send('cryptErr', err.message)
}
})
}
function cryptFile(file) {
if (isCryptoFile(file)) {
decrypt(file)
} else {
encrypt(file)
}
}
ipcMain.on('app:open-settings', (event) => {
logger.verbose('CRYPTER: app:open-settings emitted.')
app.emit('app:open-settings')
})
// Process any file opened with app while this window is active
ipcMain.on('cryptFile', (event, file) => cryptFile(file))
app.on('open-file', (event, file) => {
if (app.isReady()) {
// Opening when already launched
logger.info('Opening file ' + file)
cryptFile(file)
}
event.preventDefault()
})
win.on('closed', function () {
logger.info('win.closed event emitted for PromptWindow')
win = null
callback()
})
return win
}
================================================
FILE: app/src/mainMenu.js
================================================
const {app, shell} = require('electron')
const menu = require('./menu')
if (process.platform === 'darwin') {
menu.unshift({
label: 'Crypter',
submenu: [
{ label: 'About Crypter', role: 'about' },
{ label: `Version ${app.getVersion()}`, enabled: false },
{ label: 'Check for Update', click() { app.emit('app:check-update') } },
{ type: 'separator' },
{ label: 'Preferences…', click() { app.emit('app:open-settings') } },
{ type: 'separator' },
{ label: 'Quit', click() { app.emit('app:quit') } }
]
})
}
module.exports = menu
================================================
FILE: app/src/masterPassPrompt.js
================================================
const { ipcMain, Menu, BrowserWindow } = require('electron')
const { VIEWS, WINDOW_OPTS } = require('../config')
const MasterPass = require('../core/MasterPass')
const MasterPassKey = require('../core/MasterPassKey')
const logger = require('electron-log')
const menuTemplate = require('./menu')
const title = 'MasterPass'
exports.title = title
exports.window = function (global, resetOnly, callback) {
let noMP = true // init noMP flag with false
let error = null
const CLOSE_TIMEOUT = 2000
// creates a new BrowserWindow
let win = new BrowserWindow({
width: 300,
height: 460,
title,
...WINDOW_OPTS
})
// create menu from menuTemplate and set
Menu.setApplicationMenu(Menu.buildFromTemplate(menuTemplate))
const qs = resetOnly ? '?reset=true' : ''
// loads masterpassprompt.html view into the BrowserWindow
win.loadURL(VIEWS.MASTERPASSPROMPT + qs)
let webContents = win.webContents
ipcMain.on('checkMasterPass', function (event, masterpass) {
logger.verbose('IPCMAIN: checkMasterPass emitted. Checking MasterPass...')
// Check user submitted MasterPass
MasterPass.check(masterpass)
.then(res => {
if (res.match) {
// Password matches
logger.info('IPCMAIN: PASSWORD MATCHES!')
// Save MasterPassKey (while program is running)
global.MasterPassKey = new MasterPassKey(res.key)
// Save for next time
MasterPass.save(masterpass)
// send result match result to masterpassprompt.html
webContents.send('checkMasterPassResult', {
err: null,
match: res.match
})
noMP = false
// Close after 1 second
setTimeout(function () {
// close window (invokes 'closed') event
win.close()
}, CLOSE_TIMEOUT)
} else {
logger.warn('IPCMAIN: PASSWORD DOES NOT MATCH!')
webContents.send('checkMasterPassResult', {
err: null,
match: res.match
})
}
})
.catch(err => {
// Inform user of error (on render side)
webContents.send('checkMasterPassResult', err.message)
// set error
error = err
// Close after 1 second
setTimeout(function () {
// close window (invokes 'closed') event
win.close()
}, CLOSE_TIMEOUT)
})
})
ipcMain.on('setMasterPass', function (event, masterpass) {
// setMasterPass event triggered by render proces
logger.verbose('IPCMAIN: setMasterPass emitted Setting Masterpass...')
// derive MasterPassKey, genPassHash and set creds globally
MasterPass.set(masterpass)
.then(mpkey => {
// set the derived MasterPassKey globally
global.MasterPassKey = new MasterPassKey(mpkey)
return
})
.then(() => {
// save the credentials used to derive the MasterPassKey
return global.mdb.saveGlobalObj('creds')
})
.then(() => {
// Inform user that the MasterPass has successfully been set
logger.verbose('IPCMAIN: Masterpass has been reset successfully')
webContents.send('setMasterPassResult', null)
})
.catch(err => {
// Inform user of the error that occured while setting the MasterPass
webContents.send('setMasterPassResult', err.message)
error = err
})
})
win.on('closed', function () {
logger.info('win.closed event emitted for PromptWindow')
// send error and noMP back to callee (masterPassPromptWindow Promise)
if (callback) callback(error || noMP)
// close window by setting it to nothing (null)
win = null
})
return win
}
================================================
FILE: app/src/menu.js
================================================
const {app, shell} = require('electron')
const {REPO} = require('../config')
module.exports = [
{
label: 'Edit',
submenu: [
{
role: 'undo'
},
{
role: 'redo'
},
{
type: 'separator'
},
{
role: 'cut'
},
{
role: 'copy'
},
{
role: 'paste'
},
{
role: 'pasteandmatchstyle'
},
{
role: 'delete'
},
{
role: 'selectall'
}
]
},
{
label: 'Help',
role: 'help',
submenu: [
{ label: 'Documentation', click() { shell.openExternal(REPO.DOCS)} },
{ type: 'separator' },
{ label: 'Report Issue', click() { shell.openExternal(REPO.REPORT_ISSUE)} },
{ label: 'Star Crypter', click() { shell.openExternal(REPO.URL)} },
{ label: 'Contribute', click() { shell.openExternal(REPO.FORK)} },
]
}
]
================================================
FILE: app/src/settings.js
================================================
const {app, ipcMain, Menu, BrowserWindow} = require('electron')
const menuTemplate = require('./menu')
const {CRYPTO, VIEWS, SETTINGS, ERRORS, WINDOW_OPTS} = require('../config')
const logger = require('electron-log')
const fs = require('fs-extra')
const title = 'Settings'
exports.title = title
exports.window = function (global, callback) {
// creates a new BrowserWindow
let win = new BrowserWindow({
width: 600,
height: 460,
title,
...WINDOW_OPTS
})
// create menu from menuTemplate and set
Menu.setApplicationMenu(Menu.buildFromTemplate(menuTemplate))
let webContents = win.webContents
// loads settings.html view into the BrowserWindow
win.loadURL(VIEWS.SETTINGS)
ipcMain.on('export', (event, dir) => {
logger.verbose(`SETTINGS: export event emitted, got ${dir}`)
const file = `${dir}/${CRYPTO.MASTERPASS_CREDS_FILE}`
fs.outputJson(file, global.creds, function (err) {
if (err) {
logger.error(err)
webContents.send('exportResult', err.message)
} else {
// Successfully exported
webContents.send('exportResult', null)
}
})
})
ipcMain.on('import', (event, file) => {
logger.verbose(`SETTINGS: import event emitted, got ${file}`)
fs.readJson(file, global.creds, function (err, credsObj) {
const invalidCredsErr = new Error(ERRORS.INVALID_MP_CREDS_FILE)
if (err) {
logger.error(err)
webContents.send('importResult', err.message)
} else {
let isCredsObjProp = function (prop) {
return credsObj.hasOwnProperty(prop)
}
let credsFileValid = CRYPTO.MASTERPASS_CREDS_PROPS.every(isCredsObjProp)
logger.verbose(`SETTINGS: Got for credsFileValid ${credsFileValid}`)
if (credsFileValid) {
// Is valid (i.e. has required properties)
// Save in-memory - global creds obj
global.creds = credsObj
// Save to fs - via global mdb
global.mdb.saveGlobalObj('creds')
// Successfully imported
webContents.send('importResult', null)
// Restart after timeout
setTimeout(function () {
app.emit('app:relaunch')
}, SETTINGS.RELAUNCH_TIMEOUT);
} else {
webContents.send('importResult', invalidCredsErr.message)
}
}
})
})
win.on('closed', function () {
logger.info('win.closed event emitted for SettingsWindow')
win = null
callback()
})
return win
}
================================================
FILE: app/src/setup.js
================================================
const {app, ipcMain, Menu, BrowserWindow} = require('electron')
const {VIEWS, WINDOW_OPTS} = require('../config')
const MasterPass = require('../core/MasterPass')
const MasterPassKey = require('../core/MasterPassKey')
const logger = require('electron-log')
const menuTemplate = require('./menu')
const title = 'Setup'
exports.title = title
exports.window = function (global, callback) {
// setup view controller
// creates the setup window
let win = new BrowserWindow({
width: 600,
height: 420,
title,
...WINDOW_OPTS
})
// create menu from menuTemplate and set
Menu.setApplicationMenu(Menu.buildFromTemplate(menuTemplate))
let webContents = win.webContents
let error
// loads setup.html view into the SetupWindow
win.loadURL(VIEWS.SETUP)
ipcMain.on('setMasterPass', function (event, masterpass) {
// setMasterPass event triggered by render proces
logger.verbose('IPCMAIN: setMasterPass emitted Setting Masterpass...')
// derive MasterPassKey, genPassHash and set creds globally
MasterPass.set(masterpass)
.then((mpkey) => {
// set the derived MasterPassKey globally
global.MasterPassKey = new MasterPassKey(mpkey)
return
})
.then(() => {
// save the credentials used to derive the MasterPassKey
return global.mdb.saveGlobalObj('creds')
})
.then(() => {
// Inform user that the MasterPass has successfully been set
webContents.send('setMasterPassResult', null)
})
.catch((err) => {
// Inform user of the error that occured while setting the MasterPass
logger.error(err)
webContents.send('setMasterPassResult', err.message)
error = err
})
})
ipcMain.on('done', function (event, masterpass) {
// Dond event emotted from render process
logger.info('IPCMAIN: done emitted setup complete. Closing...')
// Setup successfully finished
// therefore set error to nothing
error = null
// Relaunch Crypter
app.emit('app:relaunch')
})
win.on('closed', function () {
logger.verbose('IPCMAIN: win.closed event emitted for setupWindow.')
// close window by setting it to nothing (null)
win = null
// if error occured then send error back to callee else send null
callback((error) ? error : null)
})
}
================================================
FILE: app/static/crypter.html
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link rel="stylesheet" href="styles/crypter.css" />
<script src="js/common.js" type="text/javascript"></script>
<script src="js/crypter.js" type="text/javascript"></script>
</head>
<body>
<section id="crypt">
<div class="panel-container">
<div id="panel-crypt" class="current">
<a class="navigationLink" data-action="app:open-settings">
<img class="info" src="images/icons/settings.svg" alt="Settings" />
</a>
<header>
<img src="images/icons/Crypter.svg" alt="Crypter" class="header" />
<h1 class="title">Crypter</h1>
<p class="subtitle">Encrypt. Decrypt. Anything.</p>
<p class="leadinfo">
Encrypt unlimited bits. Remember only a bit.
</p>
<p id="errLabel" style="display: none;"></p>
</header>
<div id="fileInput" class="fancy">
<p id="fileInputText">
Select or Drop
</p>
</div>
</div>
<div id="panel-crypted">
<div id="crypted-container">
<!-- Crypted panel dynamically loaded here -->
</div>
<p class="intrfo">
Note these details down<br />
(for use with third-party apps)
</p>
<footer>
<a class="back navigationLink" data-panel="crypt">
<img src="images/icons/back.svg" alt="back" />
</a>
</footer>
</div>
</div>
</section>
<script id="crypted-template" type="text/x-handlebars-template">
<header>
<img src="images/icons/{{op}}.svg" class='header' />
</header>
<div id='finfo'>
<h3>
<div title="{{cryptPath}}">
{{op}} {{name}}
</div>
</h3>
<table>
<tr>
<td>
Original file
</td>
<td class='file-path'>
<input type='text' readonly='readonly' value="{{path}}" title="{{path}}" />
<a href='#' onclick="showFile('{{path}}')">
<img src='images/icons/eye.svg' />
</a>
</td>
</tr>
<tr>
<td>
{{op}} file
</td>
<td class='file-path'>
<input type='text' readonly='readonly' value="{{cryptPath}}" title="{{cryptPath}}" />
<a href='#' onclick="showFile('{{cryptPath}}')">
<img src='images/icons/eye.svg' />
</a>
</td>
</tr>
<tr>
<td>
Initialisation Vector
</td>
<td>
<input type='text' readonly='readonly' value="{{iv}}" title="{{iv}}" />
</td>
</tr>
<tr>
<td>
Auth Tag
</td>
<td>
<input type='text' readonly='readonly' value="{{authTag}}" title="{{authTag}}" />
</td>
</tr>
<tr>
<td>
Salt
</td>
<td>
<input type='text' readonly='readonly' value="{{salt}}" title="{{salt}}" />
</td>
</tr>
<tr>
<td>
Encryption Key
</td>
<td>
<input type='text' readonly='readonly' value="{{key}}" title="{{key}}" />
</td>
</tr>
</table>
</div>
</script>
</body>
</html>
================================================
FILE: app/static/fonts/LICENSE.txt
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: app/static/js/common.js
================================================
'use strict'
/**
* common.js
* Contains all the functionality that is common between the views
******************************/
/* Common variables */
// Load JQuery library and make accessible via $
window.$ = window.jQuery = require('jquery')
// Cross-view dependencies
const {ipcRenderer, remote, shell} = require('electron')
const { app } = remote
const logger = require('electron-log')
const {REGEX, RESPONSES, COLORS} = require('../config')
const Handlebars = require('handlebars')
/* Shared functions */
function getUrlParameter(name) {
name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
var results = regex.exec(location.search);
return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
};
function navigate (panel) {
let oldSel = $('.panel-container > div.current') // get current panel
let sel = $(`#panel-${panel}`) // get panel to navigate to
oldSel.removeClass('current') // apply hide styling
sel.addClass('current') // apply show styling
}
function validateMasterPass(field, errLabel) {
const MPel = $(`input#${field}Input`)
const masterpass = MPel.val()
if (!masterpass) {
// MP is empty
// set errLabel text to response for empty and show errLabel
errLabel.text(RESPONSES.empty).show()
// Clear MP input field
MPel.val('')
} else if (REGEX.MASTERPASS.test(masterpass)) {
// MP is valid
// Hide errLabel
errLabel.hide()
// Send valid MasterPass to controller function to be checked
ipcRenderer.send(field, masterpass)
} else {
// set errLabel text to response for invalid and show errLabel
errLabel.text(RESPONSES.invalid).show()
// Clear MP input field
MPel.val('')
}
}
/* Onload */
$(window).on('load', function() {
$(".navigationLink").each(function(index) {
let $this = $(this)
$this.on('click', function(event) {
let target = $this.data("target")
let panel = $this.data("panel")
let tab = $this.data("tab")
let action = $this.data("action")
if (action) {
// Is an action to perform
if (REGEX.APP_EVENT.test(action)) {
console.log(`Got main app event ${action}`)
// Emit event on app
app.emit(action)
} else {
console.log(`Got render event ${action}`)
// Emit event in render proc
ipcRenderer.emit(action)
}
} else if (tab) {
console.log(`Got tab ${tab}`)
// is a tab to navigate to
$(".item.active").first().removeClass("active")
$(`a[data-tab='${tab}']`).addClass("active")
navigate(tab)
} else if (target) {
console.log(`Got URL ${target}`)
// is a URL so open it
shell.openExternal(target)
} else if (panel) {
console.log(`Got panel ${panel}`)
// target is just a panel to navigate to
navigate(panel)
}
return false
})
})
})
================================================
FILE: app/static/js/crypter.js
================================================
'use strict'
/**
* crypter.js
* Contains scripts for crypter.html
******************************/
const dialog = remote.dialog
const paths = remote.getGlobal('paths')
const { basename } = require('path')
const os = require('os')
let errLabel,
fileInput,
fileInputD,
cryptedContainer,
fileInputText,
ifileInputText,
crypted_template
$(window).on('load', function () {
// Get DOM elements
errLabel = $('#errLabel')
fileInput = $('#fileInput')
fileInputD = document.getElementById('fileInput')
cryptedContainer = $('#crypted-container')
fileInputText = fileInput.find('#fileInputText')
ifileInputText = fileInputText.text()
// compile the crypted template
crypted_template = Handlebars.compile($('#crypted-template').html())
// attach event
fileInputD.ondragover = function () {
return false
}
fileInputD.ondragleave = fileInputD.ondragend = function () {
return false
}
enableFileInput()
})
/* Event listeners */
ipcRenderer.on('cryptedFile', function (event, file) {
logger.verbose(`IPCRENDER cryptedFile emitted`)
let fileHTML = crypted_template(file)
cryptedContainer.html(fileHTML)
enableUI()
navigate('crypted')
})
ipcRenderer.on('decryptedFile', function (event, file) {
logger.verbose(`IPCRENDER decryptedFile emitted`)
let fileHTML = crypted_template(file)
cryptedContainer.html(fileHTML)
enableUI()
navigate('crypted')
})
ipcRenderer.on('cryptErr', function (event, err) {
logger.verbose(`IPCRENDER cryptErr emitted`)
errLabel.text(`ERROR: ${err}`).show()
enableUI()
})
ipcRenderer.on('encryptingFile', function (event, file) {
logger.verbose(`IPCRENDER encryptingFile emitted`)
fileInputText.text(`Encrypting ${basename(file)}...`)
disableUI()
})
ipcRenderer.on('decryptingFile', function (event, file) {
logger.verbose(`IPCRENDER decryptingFile emitted`)
fileInputText.text(`Decrypting ${basename(file)}...`)
disableUI()
})
/* Helper functions */
function disableFileInput () {
fileInput.off('click', handler)
fileInput.ondrop = function () {
return false
}
}
function enableFileInput () {
fileInput.on('click', handler)
fileInputD.ondrop = function (e) {
e.preventDefault()
logger.info(`ONDROP fired!`)
if (e.dataTransfer.files[0].path) {
logger.info(`Got file: ${e.dataTransfer.files[0].path}`)
ipcRenderer.send('cryptFile', e.dataTransfer.files[0].path)
}
return false
}
}
function enableUI () {
fileInputText.text(ifileInputText)
enableFileInput()
}
function disableUI () {
disableFileInput()
errLabel.hide()
}
function showFile (path) {
shell.showItemInFolder(path)
}
function showOpenDialog (properties) {
// Create file input dialog
dialog
.showOpenDialog({
title: 'Choose a file to Encrypt',
defaultPath: paths.documents, // open dialog at home directory
properties: properties
})
.then(fileData => {
const filePath = fileData.filePaths
console.log(filePath)
// callback for selected file returns undefined if file not selected by user
if (filePath && filePath.length) {
// Prevent multiple input dialog
fileInput.off('click', handler)
// Select the first one
ipcRenderer.send('cryptFile', filePath[0])
} else {
fileInput.on('click', handler)
}
})
}
function handler () {
if (os.platform() === 'darwin') {
// macOS allows selecting files and folders so just show dialog
showOpenDialog(['openFile', 'openDirectory'])
} else {
// Windows/Linux only allow selecting either only files or folders so ask the user to choose
dialog.showMessageBox(
{
type: 'question',
message: 'What would you like to crypt?',
buttons: ['File', 'Folder', 'Cancel'],
defaultId: 1
},
function (response) {
switch (response) {
case 0:
// File
showOpenDialog(['openFile'])
break
case 1:
// Folder
showOpenDialog(['openDirectory'])
break
default:
}
}
)
}
return false
}
================================================
FILE: app/static/js/masterpassprompt.js
================================================
'use strict'
/**
* masterpassprompt.js
* Contains scripts for masterpassprompt.html
******************************/
const DONE_TIMEOUT = 2000
let errLabelCheckMP, errLabelSetMP, resetMasterPassInput
const reset = getUrlParameter('reset')
$(window).on('load', function () {
// Get error label element
errLabelCheckMP = $('#checkMasterPassLabel')
errLabelSetMP = $('#setMasterPassLabel')
resetMasterPassInput = $('#resetMasterPassInput')
if (reset) {
navigate('reset')
$('footer').remove()
} else {
navigate('default')
}
// hide error label and make red
errLabelCheckMP.hide().css('color', COLORS.bad)
$('#checkMasterPass').click(function () {
validateMasterPass('checkMasterPass', errLabelCheckMP)
})
$('#setMasterPass').click(function () {
validateMasterPass('setMasterPass', errLabelSetMP)
// Reset validation when reset
errLabelCheckMP.hide()
})
})
/* Event listeners */
ipcRenderer.on('setMasterPassResult', function (event, err) {
if (err) {
// If error occured
// Display the error
errLabelSetMP
.text(`ERROR: ${err.message}`.toUpperCase())
.css('color', COLORS.bad)
.show()
} else {
// Display the MasterPass set success
errLabelSetMP
.text(RESPONSES.setSuccess)
.css('color', COLORS.good)
.show()
// Change note text to give further instruction and higlight it
$('p.note')
.html(RESPONSES.resetSuccess)
.css('color', COLORS.highlight)
// Close navigate back to chechMP after 5 seconds
setTimeout(function () {
if (reset) {
return app.emit('app:relaunch')
}
navigate('default')
errLabelSetMP.hide()
}, DONE_TIMEOUT)
}
})
ipcRenderer.on('checkMasterPassResult', function (event, result) {
if (result.err) {
errLabelCheckMP.text(`ERROR: ${err.message}`).show()
} else if (result.match) {
resetMasterPassInput.hide()
errLabelCheckMP
.text(RESPONSES.correct)
.css('color', COLORS.good)
.show()
} else {
errLabelCheckMP.text(RESPONSES.incorrect).show()
}
})
================================================
FILE: app/static/js/settings.js
================================================
'use strict'
/**
* settings.js
* Contains scripts for settings.html
******************************/
const dialog = remote.dialog
const creds = remote.getGlobal('creds')
const paths = remote.getGlobal('paths')
const buf2hex = require('../core/crypto').buf2hex
let errLabel
// let settings = remote.getGlobal("settings")
$(window).on('load', function () {
errLabel = $('#errLabel')
// Render the credentials
let creds_temp = Handlebars.compile($('#creds-template').html())
$('#creds').prepend(
creds_temp({
mpsalt: buf2hex(creds.mpsalt),
mpkhash: creds.mpkhash,
mpksalt: creds.mpksalt
})
)
})
ipcRenderer.on('export', function () {
dialog
.showOpenDialog({
title: 'Choose a dir to export to',
defaultPath: paths.documents,
properties: ['openDirectory']
})
.then(function (fileData) {
const dirPath = fileData.filePaths
// callback for selected file
// returns undefined if file not selected by user
if (dirPath && dirPath.length === 1) {
errLabel.hide()
console.log(`Got dirpath ${dirPath[0]}`)
ipcRenderer.send('export', dirPath[0])
}
})
})
ipcRenderer.on('exportResult', function (event, err) {
if (err) {
errLabel
.text(`ERROR: ${err.message}`.toUpperCase())
.css('color', COLORS.bad)
.show()
} else {
errLabel
.text(RESPONSES.exportSuccess)
.css('color', COLORS.good)
.show()
}
})
ipcRenderer.on('import', function () {
dialog
.showOpenDialog({
title: 'Choose the crypter credentials file',
defaultPath: paths.documents,
properties: ['openFile']
})
.then(function (fileData) {
const filePath = fileData.filePaths
// callback for selected file
// returns undefined if file not selected by user
if (filePath && filePath.length === 1) {
errLabel.hide()
console.log(`Got filePath ${filePath[0]}`)
ipcRenderer.send('import', filePath[0])
}
})
})
ipcRenderer.on('importResult', function (event, err) {
window.erro = err
if (err) {
console.log(JSON.stringify(err))
errLabel
.text(`ERROR: ${err}`.toUpperCase())
.css('color', COLORS.bad)
.show()
} else {
errLabel
.text(RESPONSES.importSuccess)
.css('color', COLORS.good)
.show()
}
})
================================================
FILE: app/static/js/setup.js
================================================
'use strict'
/**
* setup.js
* Contains scripts for setup.html
******************************/
// Delay (in ms) after which it navigates to the done panel
const NAV2DONE_TIMEOUT = 2000
// Delay (in ms) after which the setup window is closed
const DONE_TIMEOUT = NAV2DONE_TIMEOUT + 5000
// Animation config
const SPEED = 3000
const OFFSET = SPEED * 1.1
let errLabel
$(window).on('load', function () {
// Load jQuery marquee plugin
window.$.marquee = window.jQuery.marquee = require('jquery.marquee')
// Get error label
errLabel = $('#setMasterPassLabel')
// When DOM has loaded... hide error label and make red
errLabel.hide().css('color', COLORS.bad)
// attach click event listener to setMasterPass button
$('#setMasterPass').click(function () {
// Event handler function
validateMasterPass('setMasterPass', errLabel)
})
$('#done').click(function () {
// Close setup window and restart app
ipcRenderer.send('done')
})
// navigate to welcome screen by default
navigate('welcome')
/* Encryption animation */
$('.marquee-1').marquee({direction: 'right', gap: 0, duplicated: true, duration: SPEED}).addClass('visible')
$('.marquee-2').marquee({direction: 'right', gap: 0, duplicated: true, duration: SPEED, delayBeforeStart: OFFSET})
setTimeout(function () {
$('.marquee-2').addClass('visible')
}, OFFSET)
})
/* Event listeners */
ipcRenderer.on('setMasterPassResult', function (event, err) {
if (err) {
// If error occured Display the error
errLabel.text(`ERROR: ${err.message}`.toUpperCase())
errLabel.show()
} else {
// Display the MasterPass set success
errLabel.text(RESPONSES.setSuccess).css('color', COLORS.good).show()
setTimeout(function () {
// Navigate to the done panel after 2 seconds
navigate('done')
}, NAV2DONE_TIMEOUT)
// Invoke (setup) done event in main after 5 seconds So that user has time to comprehend the above change setTimeout(function() { // Close setup window after 5 seconds ipcRenderer.send('done') }, DONE_TIMEOUT)
}
})
================================================
FILE: app/static/masterpassprompt.html
================================================
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles/masterpassprompt.css" charset="utf-8">
<script src="js/common.js" type="text/javascript"></script>
<script src="js/masterpassprompt.js" type="text/javascript"></script>
</head>
<body>
<section id="masterpassprompt">
<img class="header" src="images/icons/masterpass.svg"/>
<div class="panel-container">
<div id="panel-default" class="current">
<header>
<h1>Verify the MasterPass</h1>
<p>Please enter your Master Password
</p>
<img class="info" src="images/icons/info.svg" alt=""/>
<p class="info">A MasterPassKey will be derived from the MasterPass. This will then be used to derive the keys used to encrypt your data.</p>
</header>
<form onsubmit="return false;">
<div class="masterpass">
<input type="password" name="masterpass" id="checkMasterPassInput" placeholder="********"/>
<label for="checkMasterPassInput" id="checkMasterPassLabel"></label>
<a href="#" id="resetMasterPassInput" class="navigationLink" data-panel="reset">Forgot it?</a>
</div>
<button id="checkMasterPass">Verify</button>
</form>
</div>
<div id="panel-reset">
<header>
<h1>Reset the MasterPass</h1>
<p>Please enter a new secure MasterPass
</p>
<img class="info" src="images/icons/info.svg"/>
<p class="info">The MasterPass is used to derive the data encryption keys. So any encrypted data using former MasterPass would not be decryptable.</p>
</header>
<form onsubmit="return false;">
<div class="masterpass">
<input type="password" name="name" id="setMasterPassInput" placeholder="********" required/>
<label for="setMasterPassInput" id="setMasterPassLabel"></label>
</div>
<button id="setMasterPass">Reset</button>
</form>
<footer>
<a class="back navigationLink" data-panel="default">
<img src="images/icons/back.svg"/>
</a>
</footer>
</div>
</div>
</section>
</body>
</html>
================================================
FILE: app/static/settings.html
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link rel="stylesheet" href="styles/settings.css" />
<script src="js/common.js" type="text/javascript"></script>
<script src="js/settings.js" type="text/javascript"></script>
</head>
<body>
<section id="settings">
<div class="panel-container">
<div class="menu">
<a class="item right navigationLink active" data-tab="general">General</a>
<a class="item navigationLink" data-tab="contribute">
<img class="icon" src="images/icons/mark-github.svg" alt="" />
</a>
</div>
<div id="panel-general" class="current">
<section class="general">
<header>
<img class="header" src="images/icons/crypt.svg" alt="" />
<h3>General</h3>
</header>
<section class="category">
<h4>Credentials</h4>
<div class="options" id="creds">
<div class="option">
<button class="navigationLink" data-action="export">Export</button>
<button class="navigationLink" data-action="import">Import</button>
</div>
<div class="option">
<button class="navigationLink" data-action="app:reset-masterpass">Reset MasterPass</button>
</div>
</div>
<p id="errLabel" class="info"></p>
</section>
</section>
</div>
<div id="panel-contribute">
<section class="contribute">
<header>
<img class="header" src="images/icons/mark-github.svg" alt="" />
<h3>Contribute</h3>
</header>
<div class="list">
<div class="item">
<a class="navigationLink" data-target="https://github.com/HR/Crypter/issues/new">
<img src="images/icons/issue-opened.svg" class="icon" alt="" />
<div class="name">
Report an issue / Suggest improvements
</div>
</a>
</div>
<div class="item">
<a class="navigationLink" data-target="https://github.com/HR/Crypter">
<img src="images/icons/star.svg" alt="" class="icon" />
<div class="name">
Star the repo
</div>
</a>
</div>
<div class="item">
<a class="navigationLink" data-target="https://github.com/HR/Crypter/fork">
<img src="images/icons/repo-forked.svg" alt="" class="icon" />
<div class="name">
Fork the repo
</div>
</a>
</div>
<div class="item">
<a class="navigationLink" data-action="app:check-update">
<img src="images/icons/package.svg" alt="" class="icon" />
<div class="name">
Check for Updates
</div>
</a>
</div>
</div>
</section>
</div>
<footer>
<img src="images/icons/code.svg" alt="" class="icon" />
with
<img src="images/icons/heart.svg" alt="" class="icon" />
by
<a class="navigationLink" style="margin-left: 3px" data-target="https://github.com/HR">Habib Rehman</a>
</footer>
</div>
</section>
<script id="creds-template" type="text/x-handlebars-template">
<div class='option'>
<span>
MasterPass salt
</span>
<input class='right' type='text' value="{{mpsalt}}" />
</div>
<div class='option'>
<span>
MasterPassKey hash
</span>
<input class='right' type='text' value="{{mpkhash}}" />
</div>
<div class='option'>
<span>
MasterPassKey salt
</span>
<input class='right' type='text' value="{{mpksalt}}" />
</div>
</script>
</body>
</html>
================================================
FILE: app/static/setup.html
================================================
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles/setup.css" charset="utf-8">
<script src="js/common.js" type="text/javascript"></script>
<script src="js/setup.js" type="text/javascript"></script>
</head>
<body>
<section id="setup">
<div class="panel-container">
<div id="panel-welcome">
<header>
<div class="banner banner-left">
<div class="marquee marquee-1">
Information is the resolution of uncertainty.
</div>
<div class="marquee marquee-1">
Rather be without a state than without a voice.
</div>
<div class="marquee marquee-1">
Freedom is never voluntarily given by oppressors.
</div>
<div class="marquee marquee-1">
Everyone has secrets to conceal; Privacy to protect.
</div>
<div class="marquee marquee-1">
Crypto is the ultimate form of non-violent direct action.
</div>
</div>
<div class="banner banner-right">
<div class="marquee marquee-2">
aNdtYE1jkAF8i8e6LgqAHNvS5J3Jc9H6XcUlM6YUIDi8rj3a0DLl
</div>
<div class="marquee marquee-2">
zLAgN6Umn61buZC8I6D6+mjZQW2Ap0eXJYDmAi/uGKUQ5ezHI0GI
</div>
<div class="marquee marquee-2">
3j6Nh1XO7WTRFa+1F3i38+TKvdhLt8kS8NAJkS5U670gElWDSffT
</div>
<div class="marquee marquee-2">
yUuJBEfzkIv6t4dcGXSoX4nOh4KFjhrdIymLpnpOlw+7VGLxqWHl
</div>
<div class="marquee marquee-2">
fG2w7C7LKU9eoB4hKjvWJ7EhXPBbzY/+/14VqjCG/O4Kqqr2sWGb
</div>
</div>
<img src="images/icons/Crypter.svg" alt="Crypter Logo" class="himg"/>
<h1>Welcome to Crypter</h1>
<p>Encrypt unlimited bits. Remember only a bit.</p>
<p class="intrfo">An innovative, convenient and secure encryption app that simplifies password generation and management by requiring you to only remember one bit, the MasterPass.</p>
</header>
<button class="fancy navigationLink" data-panel="masterpass" id="getstarted">GET STARTED</button>
</div>
<div id="panel-masterpass">
<section id="masterpassprompt">
<header>
<div class="banner">
<img class="himg" src="images/icons/masterpass.svg"/>
</div>
<h1>Set a MasterPass for encryption</h1>
<img class="info" src="images/icons/info.svg" alt=""/>
<p class="info">A MasterPassKey will be derived from the MasterPass. This will then be used to derive the keys used to encrypt your data.</p>
</header>
<form onsubmit="return false;">
<div class="masterpass">
<input type="password" id="setMasterPassInput" placeholder="********"/>
<label for="password" id="setMasterPassLabel"></label>
</div>
<button class="fancy" id="setMasterPass">SET</button>
</form>
<p class="note">
NOTE: If lost, the MasterPass will be unverifiable! So, please store it safely.
</p>
</section>
</div>
<div id="panel-done">
<header>
<div class="banner">
<img src="images/icons/done.svg" class="himg"/>
</div>
<h1>All done!</h1>
<p>You have successfully setup Crypter.
<br/>
You just need to verify your MasterPass to start Crypting shortly.</p>
</header>
<button class="fancy done navigationLink" id="done">FINISH</button>
<footer>
<img src="images/icons/code.svg" alt="" class="icon"/>
with
<img src="images/icons/heart.svg" alt="" class="icon"/>
by
<a class="navigationLink" data-target="https://github.com/HR">Habib Rehman</a>
</footer>
</div>
</div>
</section>
</body>
</html>
================================================
FILE: app/static/styles/crypter.css
================================================
@import "../../node_modules/normalize.css/normalize.css";body,p{margin:0}.button,body,button{background-color:#FFF;width:100%}.fancy,button.fancy{animation:OrangeAnimGrad 3s ease infinite}.fancy:active,.fancy:hover,button.fancy:active,button.fancy:hover{opacity:.8}@keyframes colorTrans{from{border-bottom:1px solid #DDD}to{border-bottom:1px solid #333}}body{font-family:Roboto,HelveticaNeue-Light,"Helvetica Neue Light","Helvetica Neue",Helvetica,"Lucida Grande",sans-serif;font-weight:300;padding:0;height:100vh;overflow:hidden}div.none{display:none}.left{-webkit-align-self:flex-start;align-self:flex-start!important}.right{margin-left:auto!important}.button,button{border:1px solid #DDD;padding:.3rem;font-weight:400;outline:0}.button:active,.button:hover,button:active,button:hover{background-color:#DDD}button.fancy{margin-top:.2rem;border:none;color:#FFF;width:30%!important;background:linear-gradient(to right,#EEA849,#F46B45);background-size:200% 200%}.fancy{border:none;color:#FFF;background:linear-gradient(to right,#EEA849,#F46B45);background-size:200% 200%}input[type=text]{border:none;border-bottom:1px solid #DDD;outline:0;vertical-align:top}input[type=text]:focus{animation-name:colorTrans;animation-duration:2s;border-bottom:1px solid #333}img.header{width:7rem;height:auto;bottom:0}img.icon{width:1.4rem;height:1.4rem}img.info{padding-left:.2rem;height:.8rem}p.intrfo{color:#9D9D9D;font-size:.8rem;margin:.4rem}a,header>p{color:#222}p.info{display:block;width:100%;box-sizing:border-box;max-height:0;padding:0 .5rem;transition:max-height .5s,padding .3s;background-color:#DDD;font-size:.8rem}#crypt,footer{background-color:#FFF}img.info:hover+p.info{max-height:10rem;padding-top:.5rem;padding-bottom:.5rem}a{cursor:pointer;cursor:hand;font-size:.8rem;text-decoration:none;outline:0}a.back>img{padding:.2rem}footer{font-size:.8rem;align-items:center;justify-content:left;background:#FFF}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Italic.eot);src:url(../fonts/Roboto-Italic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Italic.woff) format('woff'),url(../fonts/Roboto-Italic.ttf) format('truetype');font-weight:400;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-BlackItalic.eot);src:url(../fonts/Roboto-BlackItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-BlackItalic.woff) format('woff'),url(../fonts/Roboto-BlackItalic.ttf) format('truetype');font-weight:900;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Bold.eot);src:url(../fonts/Roboto-Bold.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Bold.woff) format('woff'),url(../fonts/Roboto-Bold.ttf) format('truetype');font-weight:700;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Thin.eot);src:url(../fonts/Roboto-Thin.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Thin.woff) format('woff'),url(../fonts/Roboto-Thin.ttf) format('truetype');font-weight:100;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Medium.eot);src:url(../fonts/Roboto-Medium.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Medium.woff) format('woff'),url(../fonts/Roboto-Medium.ttf) format('truetype');font-weight:500;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Light.eot);src:url(../fonts/Roboto-Light.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Light.woff) format('woff'),url(../fonts/Roboto-Light.ttf) format('truetype');font-weight:300;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Regular.eot);src:url(../fonts/Roboto-Regular.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Regular.woff) format('woff'),url(../fonts/Roboto-Regular.ttf) format('truetype');font-weight:400;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-ThinItalic.eot);src:url(../fonts/Roboto-ThinItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-ThinItalic.woff) format('woff'),url(../fonts/Roboto-ThinItalic.ttf) format('truetype');font-weight:100;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-BoldItalic.eot);src:url(../fonts/Roboto-BoldItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-BoldItalic.woff) format('woff'),url(../fonts/Roboto-BoldItalic.ttf) format('truetype');font-weight:700;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Black.eot);src:url(../fonts/Roboto-Black.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Black.woff) format('woff'),url(../fonts/Roboto-Black.ttf) format('truetype');font-weight:900;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-MediumItalic.eot);src:url(../fonts/Roboto-MediumItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-MediumItalic.woff) format('woff'),url(../fonts/Roboto-MediumItalic.ttf) format('truetype');font-weight:500;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-LightItalic.eot);src:url(../fonts/Roboto-LightItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-LightItalic.woff) format('woff'),url(../fonts/Roboto-LightItalic.ttf) format('truetype');font-weight:300;font-style:italic}header{margin:0}h3{margin-top:0}#crypt{height:100%;min-height:20rem;width:100%;text-align:center;overflow:hidden}#crypted-container{height:84vh;overflow-y:scroll;overflow-x:hidden}.panel-container{position:relative}.panel-container>div{position:absolute;text-align:center;transform:translateX(-110%);transition:transform .5s}.panel-container>div>button{margin-top:1rem}.panel-container>div.current{width:100%;height:100vh;overflow:hidden;transform:none;position:relative}.panel-container>div.current~div{transform:translateX(110%)}.panel-container header{margin-top:2.5rem}#panel-crypt{display:flex;flex-direction:column;justify-content:space-between}#panel-crypt h1{margin:.4rem 0;font-size:1.4rem}#panel-crypt p.subtitle{margin-top:.6rem}#panel-crypt p.leadinfo{color:#9D9D9D;font-size:.8rem;margin:.4rem 1rem}#panel-crypt #fileInput{margin-top:6vh;width:100%;height:40vh;text-align:center;display:flex;flex-flow:column;cursor:pointer;align-items:center;justify-items:center}#panel-crypt #fileInput p{line-height:40vh;color:#FFF;width:70%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}#panel-crypted #finfo{margin:1rem}#panel-crypted #finfo h3>div{text-align:center;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}#panel-crypted #finfo table{width:100%;font-size:.8rem}#panel-crypted #finfo table tr{margin-top:.4rem}#panel-crypted #finfo table tr .bttline{vertical-align:inherit}#panel-crypted #finfo table tr .file-path{position:relative}#panel-crypted #finfo table tr .file-path img{height:16px;position:absolute;right:0;background-image:linear-gradient(to right,rgba(255,255,255,.5),#fff);padding-left:5px}#panel-crypted #finfo table tr td{vertical-align:top}#panel-crypted #finfo table tr td input[type=text]{display:table-cell;width:50vw;float:right;white-space:nowrap;text-overflow:ellipsis}#panel-crypted #finfo table tr td a>img{height:1rem;margin-left:.1rem}#panel-crypted #finfo table tr td:first-child{text-align:left;color:#7e7e7e}#panel-crypted #finfo table tr td:last-child{text-align:left;padding-left:.2rem}a[data-action='app:open-settings'] img.info{height:1.2rem;position:absolute;right:0;top:0;padding:.5rem}a.back{float:left}a.back>img{height:1rem;width:auto}footer{display:flex;clear:both;position:absolute;border-top:1px solid #DDD;width:100%;padding-bottom:0;bottom:0;left:0}footer>a{float:right;padding:.2rem}p#errLabel{display:none;display:inline-block;font-size:.8rem;padding:.2rem;color:#9f3a38}
================================================
FILE: app/static/styles/crypter.less
================================================
// out: ./crypter.css, compress: true
/*
MasterPassPrompt styles
==========================================================================
*/
@import (less) 'mixins.less';
/* Variable declarations*/
@invalid-color: #9f3a38;
@fileInput-height: 40vh;
/* Section styles */
header {
margin: 0;
}
header > p {
color: @blacker;
}
h3 {
margin-top: 0;
}
#crypt {
height: 100%;
min-height: 20rem;
width: 100%;
background-color: @white;
text-align: center;
overflow: hidden;
}
#crypted-container {
height: 84vh;
overflow-y: scroll;
overflow-x: hidden;
}
.panel-container {
position: relative;
& > div {
position: absolute;
text-align: center;
transform: translateX(-110%);
transition: transform 0.5s;
& > button {
margin-top: 1rem;
}
}
& > div.current {
width: 100%;
height: 100vh;
overflow: hidden;
transform: none;
position: relative;
}
& > div.current ~ div {
transform: translateX(110%);
}
header {
margin-top: 2.5rem;
}
}
#panel-crypt {
display: flex;
flex-direction: column;
justify-content: space-between;
h1 {
margin: 0.4rem 0;
font-size: 1.4rem;
}
p.subtitle {
margin-top: 0.6rem;
}
p.leadinfo {
color: @dark;
font-size: 0.8rem;
margin: 0.4rem 1rem;
}
#fileInput {
margin-top: 6vh;
width: 100%;
height: @fileInput-height;
text-align: center;
display: flex;
flex-flow: column;
cursor: pointer;
align-items: center;
justify-items: center;
p {
line-height: @fileInput-height;
color: @white;
width: 70%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
#panel-crypted {
#finfo {
margin: 1rem;
h3 > div {
text-align: center;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
table {
width: 100%;
font-size: 0.8rem;
tr {
margin-top: 0.4rem;
.bttline {
vertical-align: inherit;
}
.file-path {
position: relative;
img {
height: 16px;
position: absolute;
right: 0;
background-image: linear-gradient(to right, rgba(255,255,255,0.5), rgba(255,255,255,1));
padding-left: 5px;
}
}
td {
vertical-align: top;
input[type='text'] {
display: table-cell;
width: 50vw;
float: right;
white-space: nowrap;
text-overflow: ellipsis;
}
a > img {
height: 1rem;
margin-left: 0.1rem;
}
}
td:first-child {
text-align: left;
color: @dark * 0.8;
}
td:last-child {
text-align: left;
padding-left: 0.2rem;
}
}
}
}
}
a[data-action='app:open-settings'] {
img.info {
height: 1.2rem;
position: absolute;
right: 0;
top: 0;
padding: 0.5rem;
}
}
a.back {
float: left;
}
a.back > img {
height: 1rem;
width: auto;
}
footer {
display: flex;
clear: both;
position: absolute;
border-top: 1px solid @light;
width: 100%;
padding-bottom: 0;
background-color: @white;
bottom: 0;
left: 0;
}
footer > a {
float: right;
padding: 0.2rem;
}
/* INVALID STYLING */
p#errLabel {
display: none;
display: inline-block;
font-size: 0.8rem;
padding: 0.2rem;
color: @invalid-color;
}
================================================
FILE: app/static/styles/masterpassprompt.css
================================================
@import "../../node_modules/normalize.css/normalize.css";body,p{margin:0}.button,body,button{background-color:#FFF;width:100%}.fancy,button.fancy{animation:OrangeAnimGrad 3s ease infinite}.fancy:active,.fancy:hover,button.fancy:active,button.fancy:hover{opacity:.8}#masterpassprompt,body,p.info{overflow:hidden}@keyframes colorTrans{from{border-bottom:1px solid #DDD}to{border-bottom:1px solid #333}}body{font-family:Roboto,HelveticaNeue-Light,"Helvetica Neue Light","Helvetica Neue",Helvetica,"Lucida Grande",sans-serif;font-weight:300;padding:0;height:100vh}div.none{display:none}.left{-webkit-align-self:flex-start;align-self:flex-start!important}.right{margin-left:auto!important}.button,button{border:1px solid #DDD;padding:.3rem;font-weight:400;outline:0}.button:active,.button:hover,button:active,button:hover{background-color:#DDD}button.fancy{margin-top:.2rem;border:none;color:#FFF;width:30%!important;background:linear-gradient(to right,#EEA849,#F46B45);background-size:200% 200%}.fancy{border:none;color:#FFF;background:linear-gradient(to right,#EEA849,#F46B45);background-size:200% 200%}input[type=password]:focus,input[type=text]:focus{animation-name:colorTrans;animation-duration:2s;border-bottom:1px solid #333}input[type=text]{border:none;border-bottom:1px solid #DDD;outline:0;vertical-align:top}img.header{width:7rem;height:auto;bottom:0}img.icon{width:1.4rem;height:1.4rem}img.info{padding-left:.2rem}p.intrfo{color:#9D9D9D;font-size:.8rem;margin:.4rem}a,header>p{color:#222}a{cursor:pointer;cursor:hand;font-size:.8rem;text-decoration:none;outline:0}a.back{float:left}a.back>img{height:1rem;padding:.2rem;width:auto}footer{display:flex;position:absolute;border-top:1px solid #DDD;width:100%;bottom:0;left:0;font-size:.8rem;align-items:center;justify-content:left;background:#FFF}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Italic.eot);src:url(../fonts/Roboto-Italic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Italic.woff) format('woff'),url(../fonts/Roboto-Italic.ttf) format('truetype');font-weight:400;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-BlackItalic.eot);src:url(../fonts/Roboto-BlackItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-BlackItalic.woff) format('woff'),url(../fonts/Roboto-BlackItalic.ttf) format('truetype');font-weight:900;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Bold.eot);src:url(../fonts/Roboto-Bold.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Bold.woff) format('woff'),url(../fonts/Roboto-Bold.ttf) format('truetype');font-weight:700;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Thin.eot);src:url(../fonts/Roboto-Thin.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Thin.woff) format('woff'),url(../fonts/Roboto-Thin.ttf) format('truetype');font-weight:100;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Medium.eot);src:url(../fonts/Roboto-Medium.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Medium.woff) format('woff'),url(../fonts/Roboto-Medium.ttf) format('truetype');font-weight:500;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Light.eot);src:url(../fonts/Roboto-Light.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Light.woff) format('woff'),url(../fonts/Roboto-Light.ttf) format('truetype');font-weight:300;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Regular.eot);src:url(../fonts/Roboto-Regular.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Regular.woff) format('woff'),url(../fonts/Roboto-Regular.ttf) format('truetype');font-weight:400;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-ThinItalic.eot);src:url(../fonts/Roboto-ThinItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-ThinItalic.woff) format('woff'),url(../fonts/Roboto-ThinItalic.ttf) format('truetype');font-weight:100;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-BoldItalic.eot);src:url(../fonts/Roboto-BoldItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-BoldItalic.woff) format('woff'),url(../fonts/Roboto-BoldItalic.ttf) format('truetype');font-weight:700;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Black.eot);src:url(../fonts/Roboto-Black.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Black.woff) format('woff'),url(../fonts/Roboto-Black.ttf) format('truetype');font-weight:900;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-MediumItalic.eot);src:url(../fonts/Roboto-MediumItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-MediumItalic.woff) format('woff'),url(../fonts/Roboto-MediumItalic.ttf) format('truetype');font-weight:500;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-LightItalic.eot);src:url(../fonts/Roboto-LightItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-LightItalic.woff) format('woff'),url(../fonts/Roboto-LightItalic.ttf) format('truetype');font-weight:300;font-style:italic}header{margin:0}h1{font-size:1.4rem;margin-bottom:.1rem}button#setMasterPass{margin-top:.6rem;margin-bottom:.4rem;display:block!important}button#checkMasterPass{margin-top:2rem;display:block!important}#masterpassprompt{margin:2rem 0 0;height:100%;width:100%;background-color:#FFF;text-align:center}.panel-container{position:relative}.panel-container>div{display:block;position:absolute;text-align:center;padding:0 3rem 3rem;transform:translateX(-100%);transition:transform .3s}.panel-container>div.current~div{transform:translateX(100%)}.panel-container>div.current{transform:none;position:relative}p.info{display:block;width:100%;box-sizing:border-box;max-height:0;padding:0 .5rem;transition:max-height .3s,padding .3s;background-color:#efefef;font-size:.8rem}img.info{float:right;height:.9rem}img.info:hover+p.info{max-height:10rem;padding-top:.5rem;padding-bottom:.5rem}div.masterpass input{width:98%}div.masterpass .navigationLink{margin-top:.2rem;float:right}p.note{margin-top:.2rem;font-size:.8rem}input[type=password]{margin-top:1rem;border:none;border-bottom:1px solid #DDD;outline:0}.invalid{border-color:#9F3A38!important}div.forgotMP{padding:.2rem .2rem 0 0;text-align:right}#checkMasterPass{width:100%!important;margin-top:1rem}div.masterpass>label{display:inline-block;font-size:.8rem;margin:.2rem 0;color:#9F3A38}
================================================
FILE: app/static/styles/masterpassprompt.less
================================================
// out: ./masterpassprompt.css, compress: true
/*
MasterPassPrompt styles
==========================================================================
*/
@import (less) "mixins.less";
/* Variable declarations*/
@invalid-color: #9F3A38;
/* Section styles */
header {
margin: 0;
}
header > p {
color: @blacker;
}
h1 {
font-size : 1.4rem;
margin-bottom: 0.1rem;
}
button#setMasterPass {
margin-top: 0.6rem;
margin-bottom: 0.4rem;
display: block !important;
}
button#checkMasterPass {
margin-top: 2rem;
display: block !important;
}
#masterpassprompt {
margin: 2rem 0 0 0;
height : 100%;
width : 100%;
background-color: @white;
text-align : center;
overflow : hidden;
}
.panel-container {
position: relative;
}
.panel-container > div {
display : block;
position : absolute;
text-align: center;
padding : 0 3rem 3rem;
transform : translateX(-100%);
transition: transform 0.3s;
}
.panel-container > div.current ~ div {
transform: translateX(100%);
}
.panel-container > div.current {
transform: none;
position : relative;
}
p.info {
display : block;
width : 100%;
box-sizing : border-box;
max-height : 0;
overflow : hidden;
padding : 0 0.5rem;
transition : max-height 0.3s, padding 0.3s;
background-color: #efefef;
font-size : 0.8rem;
}
img.info {
float : right;
height: 0.9rem;
}
img.info:hover + p.info {
max-height : 10rem;
padding-top : 0.5rem;
padding-bottom: 0.5rem;
}
div.masterpass {
input {
width: 98%;
}
.navigationLink {
margin-top: 0.2rem;
float: right;
}
}
p.note {
margin-top: 0.2rem;
font-size: 0.8rem;
}
input[type=password] {
margin-top : 1rem;
border : none;
border-bottom: 1px solid @light;
outline : none;
&:focus {
animation-name : colorTrans;
animation-duration: 2s;
border-bottom : 1px solid @black;
}
}
.invalid {
border-color: @invalid-color !important;
}
div.forgotMP {
padding : 0.2rem 0.2rem 0 0;
text-align: right;
}
#checkMasterPass {
width: 100% !important;
margin-top : 1rem;
}
/* INVALID STYLING */
div.masterpass > label {
display : inline-block;
font-size: 0.8rem;
margin : 0.2rem 0;
color : @invalid-color;
}
================================================
FILE: app/static/styles/mixins.css
================================================
@import "../../node_modules/normalize.css/normalize.css";body,p{margin:0}.button,body,button{background-color:#FFF;width:100%}.fancy,button.fancy{animation:OrangeAnimGrad 3s ease infinite}.fancy:active,.fancy:hover,button.fancy:active,button.fancy:hover{opacity:.8}@keyframes colorTrans{from{border-bottom:1px solid #DDD}to{border-bottom:1px solid #333}}body{font-family:Roboto,HelveticaNeue-Light,"Helvetica Neue Light","Helvetica Neue",Helvetica,"Lucida Grande",sans-serif;font-weight:300;padding:0;height:100vh;overflow:hidden}div.none{display:none}.left{-webkit-align-self:flex-start;align-self:flex-start!important}.right{margin-left:auto!important}.button,button{border:1px solid #DDD;padding:.3rem;font-weight:400;outline:0}.button:active,.button:hover,button:active,button:hover{background-color:#DDD}button.fancy{margin-top:.2rem;border:none;color:#FFF;width:30%!important;background:linear-gradient(to right,#EEA849,#F46B45);background-size:200% 200%}.fancy{border:none;color:#FFF;background:linear-gradient(to right,#EEA849,#F46B45);background-size:200% 200%}input[type=text]{border:none;border-bottom:1px solid #DDD;outline:0;vertical-align:top}input[type=text]:focus{animation-name:colorTrans;animation-duration:2s;border-bottom:1px solid #333}img.header{width:7rem;height:auto;bottom:0}img.icon{width:1.4rem;height:1.4rem}img.info{padding-left:.2rem;height:.8rem}p.intrfo{color:#9D9D9D;font-size:.8rem;margin:.4rem}p.info{display:block;width:100%;box-sizing:border-box;max-height:0;padding:0 .5rem;transition:max-height .5s,padding .3s;background-color:#DDD;font-size:.8rem}img.info:hover+p.info{max-height:10rem;padding-top:.5rem;padding-bottom:.5rem}a{cursor:pointer;cursor:hand;font-size:.8rem;color:#222;text-decoration:none;outline:0}a.back{float:left}a.back>img{height:1rem;padding:.2rem;width:auto}footer{display:flex;position:absolute;border-top:1px solid #DDD;width:100%;bottom:0;left:0;font-size:.8rem;align-items:center;justify-content:left;background:#FFF}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Italic.eot);src:url(../fonts/Roboto-Italic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Italic.woff) format('woff'),url(../fonts/Roboto-Italic.ttf) format('truetype');font-weight:400;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-BlackItalic.eot);src:url(../fonts/Roboto-BlackItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-BlackItalic.woff) format('woff'),url(../fonts/Roboto-BlackItalic.ttf) format('truetype');font-weight:900;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Bold.eot);src:url(../fonts/Roboto-Bold.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Bold.woff) format('woff'),url(../fonts/Roboto-Bold.ttf) format('truetype');font-weight:700;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Thin.eot);src:url(../fonts/Roboto-Thin.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Thin.woff) format('woff'),url(../fonts/Roboto-Thin.ttf) format('truetype');font-weight:100;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Medium.eot);src:url(../fonts/Roboto-Medium.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Medium.woff) format('woff'),url(../fonts/Roboto-Medium.ttf) format('truetype');font-weight:500;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Light.eot);src:url(../fonts/Roboto-Light.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Light.woff) format('woff'),url(../fonts/Roboto-Light.ttf) format('truetype');font-weight:300;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Regular.eot);src:url(../fonts/Roboto-Regular.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Regular.woff) format('woff'),url(../fonts/Roboto-Regular.ttf) format('truetype');font-weight:400;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-ThinItalic.eot);src:url(../fonts/Roboto-ThinItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-ThinItalic.woff) format('woff'),url(../fonts/Roboto-ThinItalic.ttf) format('truetype');font-weight:100;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-BoldItalic.eot);src:url(../fonts/Roboto-BoldItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-BoldItalic.woff) format('woff'),url(../fonts/Roboto-BoldItalic.ttf) format('truetype');font-weight:700;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Black.eot);src:url(../fonts/Roboto-Black.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Black.woff) format('woff'),url(../fonts/Roboto-Black.ttf) format('truetype');font-weight:900;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-MediumItalic.eot);src:url(../fonts/Roboto-MediumItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-MediumItalic.woff) format('woff'),url(../fonts/Roboto-MediumItalic.ttf) format('truetype');font-weight:500;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-LightItalic.eot);src:url(../fonts/Roboto-LightItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-LightItalic.woff) format('woff'),url(../fonts/Roboto-LightItalic.ttf) format('truetype');font-weight:300;font-style:italic}
================================================
FILE: app/static/styles/mixins.less
================================================
// out: ./mixins.css, compress: true
@import (css) "../../node_modules/normalize.css/normalize.css";
/* theme colors */
@white: #FFFFFF;
@light: #DDDDDD;
@dark: #9D9D9D;
@darker: #555555;
@black: #333333;
@blacker: #222222;
@invalid-color: #F05A5C;
@orange: #EEA849;
@orange_d: #F46B45;
@ic-size: 1.4rem;
@max-height: 100vh;
/* Animations */
@keyframes colorTrans {
from {
border-bottom: 1px solid @light;
}
to {
border-bottom: 1px solid @black;
}
}
/* General (shared) styles */
body {
font-family : "Roboto", "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, "Lucida Grande", sans-serif;
font-weight : 300;
margin : 0;
padding : 0;
background-color: @white;
width : 100%;
height : 100vh;
overflow : hidden;
}
p {
margin: 0;
}
div.none {
display: none;
}
.left {
-webkit-align-self: flex-start;
align-self : flex-start !important;
}
.right {
margin-left: auto !important;
}
button, .button {
background-color: @white;
border : 1px solid @light;
width : 100%;
padding : 0.3rem;
font-weight : 400;
outline : none;
&:active,
&:hover {
background-color: @light;
}
}
button.fancy {
margin-top : 0.2rem;
border : none;
color : @white;
width : 30% !important;
background : linear-gradient(to right, @orange, @orange_d);
background-size: 200% 200%;
animation : OrangeAnimGrad 3s ease infinite;
&:active,
&:hover {
opacity: 0.8;
}
}
.fancy {
border : none;
color : @white;
background : linear-gradient(to right, @orange, @orange_d);
background-size: 200% 200%;
animation : OrangeAnimGrad 3s ease infinite;
&:active,
&:hover {
opacity: 0.8;
}
}
input[type=text] {
border: none;
border-bottom: 1px solid @light;
outline: none;
vertical-align: top;
&:focus {
animation-name: colorTrans;
animation-duration: 2s;
border-bottom: 1px solid @black;
}
}
img.header {
width: 7rem;
height: auto;
bottom: 0;
}
img.icon {
width : @ic-size;
height: @ic-size;
}
img.info {
padding-left: 0.2rem;
height : 0.8rem;
}
p.intrfo {
color: @dark;
font-size: 0.8rem;
margin: 0.4rem;
}
p.info {
display : block;
width : 100%;
box-sizing : border-box;
max-height : 0;
padding : 0 0.5rem;
transition : max-height 0.5s, padding 0.3s;
background-color: @light;
font-size : 0.8rem;
}
img.info:hover + p.info {
max-height : 10rem;
padding-top : 0.5rem;
padding-bottom: 0.5rem;
}
a {
cursor: pointer;
cursor: hand;
font-size : 0.8rem;
color : @blacker;
text-decoration: none;
outline : none;
}
a.back {
float: left;
}
a.back > img {
height: 1rem;
padding: 0.2rem;
width : auto;
}
footer {
display: flex;
position: absolute;
border-top: 1px solid @light;
width: 100%;
bottom: 0;
left: 0;
font-size: 0.8rem;
align-items: center;
justify-content: left;
background: @white;
}
/* font declarations */
@font-face {
font-family: 'Roboto';
src : url('../fonts/Roboto-Italic.eot');
src : url('../fonts/Roboto-Italic.eot?#iefix') format('embedded-opentype'), url('../fonts/Roboto-Italic.woff') format('woff'), url('../fonts/Roboto-Italic.ttf') format('truetype');
font-weight: normal;
font-style : italic;
}
@font-face {
font-family: 'Roboto';
src : url('../fonts/Roboto-BlackItalic.eot');
src : url('../fonts/Roboto-BlackItalic.eot?#iefix') format('embedded-opentype'), url('../fonts/Roboto-BlackItalic.woff') format('woff'), url('../fonts/Roboto-BlackItalic.ttf') format('truetype');
font-weight: 900;
font-style : italic;
}
@font-face {
font-family: 'Roboto';
src : url('../fonts/Roboto-Bold.eot');
src : url('../fonts/Roboto-Bold.eot?#iefix') format('embedded-opentype'), url('../fonts/Roboto-Bold.woff') format('woff'), url('../fonts/Roboto-Bold.ttf') format('truetype');
font-weight: bold;
font-style : normal;
}
@font-face {
font-family: 'Roboto';
src : url('../fonts/Roboto-Thin.eot');
src : url('../fonts/Roboto-Thin.eot?#iefix') format('embedded-opentype'), url('../fonts/Roboto-Thin.woff') format('woff'), url('../fonts/Roboto-Thin.ttf') format('truetype');
font-weight: 100;
font-style : normal;
}
@font-face {
font-family: 'Roboto';
src : url('../fonts/Roboto-Medium.eot');
src : url('../fonts/Roboto-Medium.eot?#iefix') format('embedded-opentype'), url('../fonts/Roboto-Medium.woff') format('woff'), url('../fonts/Roboto-Medium.ttf') format('truetype');
font-weight: 500;
font-style : normal;
}
@font-face {
font-family: 'Roboto';
src : url('../fonts/Roboto-Light.eot');
src : url('../fonts/Roboto-Light.eot?#iefix') format('embedded-opentype'), url('../fonts/Roboto-Light.woff') format('woff'), url('../fonts/Roboto-Light.ttf') format('truetype');
font-weight: 300;
font-style : normal;
}
@font-face {
font-family: 'Roboto';
src : url('../fonts/Roboto-Regular.eot');
src : url('../fonts/Roboto-Regular.eot?#iefix') format('embedded-opentype'), url('../fonts/Roboto-Regular.woff') format('woff'), url('../fonts/Roboto-Regular.ttf') format('truetype');
font-weight: normal;
font-style : normal;
}
@font-face {
font-family: 'Roboto';
src : url('../fonts/Roboto-ThinItalic.eot');
src : url('../fonts/Roboto-ThinItalic.eot?#iefix') format('embedded-opentype'), url('../fonts/Roboto-ThinItalic.woff') format('woff'), url('../fonts/Roboto-ThinItalic.ttf') format('truetype');
font-weight: 100;
font-style : italic;
}
@font-face {
font-family: 'Roboto';
src : url('../fonts/Roboto-BoldItalic.eot');
src : url('../fonts/Roboto-BoldItalic.eot?#iefix') format('embedded-opentype'), url('../fonts/Roboto-BoldItalic.woff') format('woff'), url('../fonts/Roboto-BoldItalic.ttf') format('truetype');
font-weight: bold;
font-style : italic;
}
@font-face {
font-family: 'Roboto';
src : url('../fonts/Roboto-Black.eot');
src : url('../fonts/Roboto-Black.eot?#iefix') format('embedded-opentype'), url('../fonts/Roboto-Black.woff') format('woff'), url('../fonts/Roboto-Black.ttf') format('truetype');
font-weight: 900;
font-style : normal;
}
@font-face {
font-family: 'Roboto';
src : url('../fonts/Roboto-MediumItalic.eot');
src : url('../fonts/Roboto-MediumItalic.eot?#iefix') format('embedded-opentype'), url('../fonts/Roboto-MediumItalic.woff') format('woff'), url('../fonts/Roboto-MediumItalic.ttf') format('truetype');
font-weight: 500;
font-style : italic;
}
@font-face {
font-family: 'Roboto';
src : url('../fonts/Roboto-LightItalic.eot');
src : url('../fonts/Roboto-LightItalic.eot?#iefix') format('embedded-opentype'), url('../fonts/Roboto-LightItalic.woff') format('woff'), url('../fonts/Roboto-LightItalic.ttf') format('truetype');
font-weight: 300;
font-style : italic;
}
================================================
FILE: app/static/styles/settings.css
================================================
@import "../../node_modules/normalize.css/normalize.css";body,p{margin:0}.fancy,button.fancy{animation:OrangeAnimGrad 3s ease infinite}.fancy:active,.fancy:hover,button.fancy:active,button.fancy:hover{opacity:.8}.button,body,button{background-color:#FFF;width:100%}@keyframes colorTrans{from{border-bottom:1px solid #DDD}to{border-bottom:1px solid #333}}body{font-family:Roboto,HelveticaNeue-Light,"Helvetica Neue Light","Helvetica Neue",Helvetica,"Lucida Grande",sans-serif;font-weight:300;padding:0;height:100vh;overflow:hidden}div.none{display:none}.left{align-self:flex-start!important;-webkit-align-self:flex-start}.right{margin-left:auto!important}.button,button{border:1px solid #DDD;padding:.3rem;font-weight:400;outline:0}.button:active,.button:hover,button:active,button:hover{background-color:#DDD}button.fancy{margin-top:.2rem;border:none;color:#FFF;width:30%!important;background:linear-gradient(to right,#EEA849,#F46B45);background-size:200% 200%}.fancy{border:none;color:#FFF;background:linear-gradient(to right,#EEA849,#F46B45);background-size:200% 200%}input[type=text]{border:none;border-bottom:1px solid #DDD;outline:0;vertical-align:top}input[type=text]:focus{animation-name:colorTrans;animation-duration:2s;border-bottom:1px solid #333}img.info{padding-left:.2rem;height:.8rem}p.intrfo{color:#9D9D9D;font-size:.8rem;margin:.4rem}a,a:not(.navigationLink){color:#222;outline:0;text-decoration:none;font-size:.8rem}p.info{display:block;width:100%;box-sizing:border-box;max-height:0;padding:0 .5rem;transition:max-height .5s,padding .3s;background-color:#DDD;font-size:.8rem}img.info:hover+p.info{max-height:10rem;padding-top:.5rem;padding-bottom:.5rem}a{cursor:pointer;cursor:hand}a.back{float:left}a.back>img{height:1rem;padding:.2rem;width:auto}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Italic.eot);src:url(../fonts/Roboto-Italic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Italic.woff) format('woff'),url(../fonts/Roboto-Italic.ttf) format('truetype');font-weight:400;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-BlackItalic.eot);src:url(../fonts/Roboto-BlackItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-BlackItalic.woff) format('woff'),url(../fonts/Roboto-BlackItalic.ttf) format('truetype');font-weight:900;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Bold.eot);src:url(../fonts/Roboto-Bold.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Bold.woff) format('woff'),url(../fonts/Roboto-Bold.ttf) format('truetype');font-weight:700;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Thin.eot);src:url(../fonts/Roboto-Thin.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Thin.woff) format('woff'),url(../fonts/Roboto-Thin.ttf) format('truetype');font-weight:100;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Medium.eot);src:url(../fonts/Roboto-Medium.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Medium.woff) format('woff'),url(../fonts/Roboto-Medium.ttf) format('truetype');font-weight:500;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Light.eot);src:url(../fonts/Roboto-Light.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Light.woff) format('woff'),url(../fonts/Roboto-Light.ttf) format('truetype');font-weight:300;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Regular.eot);src:url(../fonts/Roboto-Regular.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Regular.woff) format('woff'),url(../fonts/Roboto-Regular.ttf) format('truetype');font-weight:400;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-ThinItalic.eot);src:url(../fonts/Roboto-ThinItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-ThinItalic.woff) format('woff'),url(../fonts/Roboto-ThinItalic.ttf) format('truetype');font-weight:100;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-BoldItalic.eot);src:url(../fonts/Roboto-BoldItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-BoldItalic.woff) format('woff'),url(../fonts/Roboto-BoldItalic.ttf) format('truetype');font-weight:700;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Black.eot);src:url(../fonts/Roboto-Black.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Black.woff) format('woff'),url(../fonts/Roboto-Black.ttf) format('truetype');font-weight:900;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-MediumItalic.eot);src:url(../fonts/Roboto-MediumItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-MediumItalic.woff) format('woff'),url(../fonts/Roboto-MediumItalic.ttf) format('truetype');font-weight:500;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-LightItalic.eot);src:url(../fonts/Roboto-LightItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-LightItalic.woff) format('woff'),url(../fonts/Roboto-LightItalic.ttf) format('truetype');font-weight:300;font-style:italic}.panel-container{overflow:hidden;position:relative;height:100vh}.panel-container>div:not(.menu){position:absolute;text-align:center;transform:translateX(-110%);transition:transform .5s}.panel-container>div:not(.menu)>button{margin-top:1rem}.panel-container>div.current{width:100%;min-height:100vh;transform:none;position:relative;display:flex;flex-direction:column}.panel-container>div.current section{height:90vh}.panel-container>div.current~div{transform:translateX(110%)}section#settings{min-height:100%;position:relative;background-color:#FFF}p#errLabel{text-align:center}.menu{display:flex;margin-left:0;margin-right:0;align-self:flex-end;transition:color .1s ease}.menu:after{content:'';display:block;height:0;clear:both;visibility:hidden}.menu .item:after{display:none}.menu .item{position:relative;cursor:pointer;line-height:1.4rem;text-decoration:none;-webkit-tap-highlight-color:transparent;-webkit-box-flex:0;flex:0 0 auto;-webkit-user-select:none;user-select:none;padding:.7rem .8rem 0;margin-right:.4rem;text-transform:none;color:rgba(0,0,0,.87);transition:background .1s ease,box-shadow .1s ease,color .1s ease}div.item,footer{align-items:center;display:flex}.active.item,.menu .active.item{box-shadow:none;color:rgba(0,0,0,.95)}.menu .active.item{border-bottom:2px solid rgba(0,0,0,.8);font-weight:400}a.item:active{border-bottom:2px solid rgba(0,0,0,.1);margin-bottom:-2px}.active.item{background-color:transparent;border-color:#333;font-weight:700;margin-bottom:-2px}div.item,h4,section.contribute header{border-bottom:1px solid #DDD}.item.right{display:flex;margin-left:auto!important}img.icon{width:1.4rem;height:1.4rem}img.header{width:6rem;height:auto;bottom:0}h3{margin:.5rem 0;font-weight:400}h4,section.contribute div.list .item .name{font-weight:300}h4{margin-top:.5rem;margin-bottom:.5rem;text-align:left;padding-bottom:5px}header img.icon{width:6rem;height:6rem}footer{position:absolute;border-top:1px solid #DDD;width:100%;bottom:0;left:0;padding:.3rem 0;font-size:.8rem;justify-content:center;background:#FFF}footer>img{height:1rem!important}input[type=text]{width:15rem}section.category{text-align:left;padding:1rem}div.options{margin-left:1rem}div.option{display:flex;margin-top:.2rem;font-size:.8rem}div.list{height:10rem;width:100%;overflow-y:auto}div.item{padding:.4rem 0;text-align:left}section.general{width:90%;margin:auto}section.general button{margin:.4rem 0}section.general button:nth-child(1){margin-right:.4rem}section.contribute{width:70%;margin:auto}section.contribute div.list{width:100%;margin-bottom:3rem;height:100%}section.contribute div.list .item a{display:flex;align-items:center;text-align:left}section.contribute div.list .item .icon{padding:.4rem}section.crypto{width:90%;margin:1rem auto auto}section.crypto header>img{height:4rem!important;width:auto}section.crypto button{width:20%}
================================================
FILE: app/static/styles/settings.less
================================================
// out: ./settings.css, compress: true
/* Settings styles
==========================================================================
*/
@import (less) "mixins.less";
/* Variable declarations*/
@list-height: 10rem;
@border-width: 2px;
@spacing04: 0.4rem;
@ic-size: 1.4rem;
@header-ic-size: 6rem;
.panel-container {
overflow: hidden;
position: relative;
height: 100vh;
& > div:not(.menu) {
position: absolute;
text-align: center;
transform: translateX(-110%);
transition: transform 0.5s;
& > button {
margin-top: 1rem;
}
}
& > div.current {
width: 100%;
min-height: 100vh;
transform: none;
position: relative;
display: flex;
flex-direction: column;
section {
height: 90vh;
}
}
& > div.current ~ div {
transform: translateX(110%);
}
}
section#settings {
min-height: 100%;
position: relative;
background-color: @white;
}
p#errLabel {
text-align: center;
}
a:not(.navigationLink) {
font-size: 0.8rem;
color: @blacker;
text-decoration: none;
outline: none;
}
.left {
-webkit-align-self: flex-start;
align-self: flex-start;
}
.right {
margin-left: auto;
}
.menu {
display: flex;
margin-left: 0;
margin-right: 0;
align-self: flex-end;
transition: color 0.1s ease;
&:after {
content: '';
display: block;
height: 0;
clear: both;
visibility: hidden;
}
.item:after {
display: none;
}
.item {
position: relative;
cursor: pointer;
line-height: 1.4rem;
text-decoration: none;
-webkit-tap-highlight-color: transparent;
-webkit-box-flex: 0;
flex: 0 0 auto;
-webkit-user-select: none;
user-select: none;
padding: 0.7rem 0.8rem 0rem;
margin-right: 0.4rem;
text-transform: none;
color: rgba(0, 0, 0, 0.87);
transition: background 0.1s ease, box-shadow 0.1s ease, color 0.1s ease;
}
.active.item {
border-bottom: @border-width solid rgba(0, 0, 0, 0.8);
color: rgba(0, 0, 0, 0.95);
font-weight: normal;
box-shadow: none;
}
}
a.item:active {
border-bottom: @border-width solid rgba(0, 0, 0, 0.1);
margin-bottom: -@border-width;
}
.active.item {
background-color: transparent;
box-shadow: none;
border-color: @black;
font-weight: 700;
margin-bottom: -@border-width;
color: rgba(0,0,0,.95);
}
.item.right {
display: flex;
margin-left: auto !important;
}
img.icon {
width: @ic-size;
height: @ic-size;
}
img.header {
width: 6rem;
height: auto;
bottom: 0;
}
h3 {
margin: 0.5rem 0;
font-weight: normal;
}
h4 {
margin-top: 0.5rem;
margin-bottom: 0.5rem;
font-weight: 300;
text-align: left;
border-bottom: 1px solid @light;
padding-bottom: 5px;
}
header {
img.icon {
width: @header-ic-size;
height: @header-ic-size;
}
}
footer {
display: flex;
position: absolute;
border-top: 1px solid @light;
width: 100%;
bottom: 0;
left: 0;
padding: 0.3rem 0;
font-size: 0.8rem;
align-items: center;
justify-content: center;
background: @white;
& > img {
height: 1rem !important;
}
}
input[type=text] {
width: 15rem;
}
section.category {
text-align: left;
padding: 1rem;
}
div.options {
margin-left: 1rem;
}
div.option {
display: flex;
margin-top: 0.2rem;
font-size: 0.8rem;
}
div.list {
height: @list-height;
width: 100%;
overflow-y: auto;
}
div.item {
display: flex;
align-items: center;
border-bottom: 1px solid @light;
padding: @spacing04 0;
text-align: left;
}
/* Panel styles
* =================
*/
section.general {
width: 90%;
margin: auto;
button {
margin: 0.4rem 0;
}
button:nth-child(1) {
margin-right: 0.4rem;
}
}
section.contribute {
width: 70%;
margin: auto;
header {
border-bottom: 1px solid @light;
}
div.list {
width: 100%;
margin-bottom: 3rem;
height: 100%;
.item {
a {
display: flex;
align-items: center;
text-align: left;
}
.name {
font-weight: 300;
}
.icon {
padding: 0.4rem;
}
}
}
}
section.crypto {
width: 90%;
margin: 1rem auto auto;
header > img {
height: 4rem !important;
width: auto;
}
button {
width: 20%;
}
}
================================================
FILE: app/static/styles/setup.css
================================================
@import "../../node_modules/normalize.css/normalize.css";body,p{margin:0}.button,body,button{background-color:#FFF;width:100%}.fancy,button.fancy{color:#FFF;animation:OrangeAnimGrad 3s ease infinite}.fancy:active,.fancy:hover,button.fancy:active,button.fancy:hover{opacity:.8}.panel-container>div>button,div.none{display:none}@keyframes colorTrans{from{border-bottom:1px solid #DDD}to{border-bottom:1px solid #333}}body{font-family:Roboto,HelveticaNeue-Light,"Helvetica Neue Light","Helvetica Neue",Helvetica,"Lucida Grande",sans-serif;font-weight:300;padding:0;height:100vh;overflow:hidden}.left{-webkit-align-self:flex-start;align-self:flex-start!important}.button,button{border:1px solid #DDD;padding:.3rem;font-weight:400;outline:0}.button:active,.button:hover,button:active,button:hover{background-color:#DDD}button.fancy{margin-top:.2rem;border:none;width:30%!important;background:linear-gradient(to right,#EEA849,#F46B45);background-size:200% 200%}.fancy{border:none;background:linear-gradient(to right,#EEA849,#F46B45);background-size:200% 200%}p,p.intrfo{color:#9D9D9D}input[type=text]{border:none;border-bottom:1px solid #DDD;outline:0;vertical-align:top}input[type=text]:focus{animation-name:colorTrans;animation-duration:2s;border-bottom:1px solid #333}img.header{width:7rem;height:auto;bottom:0}img.icon{width:1.4rem;height:1.4rem}img.info{padding-left:.2rem}p.intrfo{font-size:.8rem;margin:.4rem}a,header p{color:#222}img.info:hover+p.info{max-height:10rem;padding-top:.5rem;padding-bottom:.5rem}a{cursor:pointer;cursor:hand;font-size:.8rem;text-decoration:none;outline:0}a.back{float:left}a.back>img{height:1rem;padding:.2rem;width:auto}footer{font-size:.8rem;align-items:center;justify-content:left;background:#FFF}#panel-done footer,.banner,div.item>a{align-items:center}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Italic.eot);src:url(../fonts/Roboto-Italic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Italic.woff) format('woff'),url(../fonts/Roboto-Italic.ttf) format('truetype');font-weight:400;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-BlackItalic.eot);src:url(../fonts/Roboto-BlackItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-BlackItalic.woff) format('woff'),url(../fonts/Roboto-BlackItalic.ttf) format('truetype');font-weight:900;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Bold.eot);src:url(../fonts/Roboto-Bold.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Bold.woff) format('woff'),url(../fonts/Roboto-Bold.ttf) format('truetype');font-weight:700;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Thin.eot);src:url(../fonts/Roboto-Thin.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Thin.woff) format('woff'),url(../fonts/Roboto-Thin.ttf) format('truetype');font-weight:100;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Medium.eot);src:url(../fonts/Roboto-Medium.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Medium.woff) format('woff'),url(../fonts/Roboto-Medium.ttf) format('truetype');font-weight:500;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Light.eot);src:url(../fonts/Roboto-Light.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Light.woff) format('woff'),url(../fonts/Roboto-Light.ttf) format('truetype');font-weight:300;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Regular.eot);src:url(../fonts/Roboto-Regular.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Regular.woff) format('woff'),url(../fonts/Roboto-Regular.ttf) format('truetype');font-weight:400;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-ThinItalic.eot);src:url(../fonts/Roboto-ThinItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-ThinItalic.woff) format('woff'),url(../fonts/Roboto-ThinItalic.ttf) format('truetype');font-weight:100;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-BoldItalic.eot);src:url(../fonts/Roboto-BoldItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-BoldItalic.woff) format('woff'),url(../fonts/Roboto-BoldItalic.ttf) format('truetype');font-weight:700;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Black.eot);src:url(../fonts/Roboto-Black.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Black.woff) format('woff'),url(../fonts/Roboto-Black.ttf) format('truetype');font-weight:900;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-MediumItalic.eot);src:url(../fonts/Roboto-MediumItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-MediumItalic.woff) format('woff'),url(../fonts/Roboto-MediumItalic.ttf) format('truetype');font-weight:500;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-LightItalic.eot);src:url(../fonts/Roboto-LightItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-LightItalic.woff) format('woff'),url(../fonts/Roboto-LightItalic.ttf) format('truetype');font-weight:300;font-style:italic}@keyframes colorTrans{from{background-color:#FFF}to{background-color:#DDD}}@-webkit-keyframes OrangeAnimGrad{0%,100%{background-position:0 51%}50%{background-position:100% 50%}}@keyframes OrangeAnimGrad{0%,100%{background-position:0 51%}50%{background-position:100% 50%}}#setup{height:100%;width:100%;text-align:center;overflow:hidden}form,header{margin:0}header .minimal h1{margin-top:1rem;font-weight:300}#setMasterPass,h1,h3,input[type=submit]{font-weight:400}h1{font-size:1.4rem;margin-bottom:.4rem}h1+p{margin:0 2rem}.panel-container{position:relative}.panel-container>div{position:absolute;text-align:center;transform:translateX(-110%);transition:transform .5s}.panel-container>div.current{width:100%;height:100vh;overflow:hidden;transform:none;position:relative}.panel-container>div.current>button{margin-top:1rem;display:inline-block}.panel-container>div.current~div{transform:translateX(110%)}.panel-container header{padding-top:2rem}.right{margin-left:auto!important}h3{margin:.5rem 0}a.csp{padding:.6rem 0!important}a.csp img{height:1.6rem;widht:auto}footer{display:flex;clear:both;position:absolute;border-top:1px solid #DDD;width:100%;padding-bottom:0;background-color:#FFF;bottom:0;left:0}footer>a{float:right;padding:.2rem}.himg{width:9rem;height:auto;z-index:1000}div.list{width:100%;overflow:auto}div.item>a{display:flex;border-bottom:1px solid #DDD;padding:.4rem 0;text-align:left}img.info{float:right;height:.9rem}p.info{display:block;width:100%;box-sizing:border-box;max-height:0;overflow:hidden;padding:0 .5rem;transition:max-height .3s,padding .3s;background-color:#efefef;font-size:.8rem}input{width:98%}input[type=password]{margin-top:1rem;border:none;border-bottom:1px solid #DDD;outline:0}input[type=password]:focus{border-bottom:1px solid #333}input[type=submit]{margin-top:1rem;height:2rem;background-color:#DDD;border:1px solid #DDD;width:100%;outline:0}input[type=submit]:focus{border:1px solid #333}div.submit{text-align:right;background-color:#FFF;width:100%}.none{display:none}.banner{margin:1rem auto 0;position:relative;justify-content:center;width:100%}.banner-left{margin-left:-3rem;float:left}.banner-right{margin-right:-3rem;float:right}#panel-welcome .marquee{overflow:hidden;position:relative}#panel-welcome .marquee-1,#panel-welcome .marquee-2{visibility:hidden}#panel-welcome .marquee.visible{visibility:visible}#panel-welcome>header .banner{margin:2rem auto 0;position:relative;display:inline-block;width:50%;z-index:-1;color:#DDD}#panel-welcome>header .banner+.himg{margin-top:-7.5rem!important}#panel-welcome>header .himg{z-index:1000}#panel-done footer{padding:.3rem 0;text-align:center;display:flex;font-size:.8rem;justify-content:center}#panel-done footer img{height:1rem!important}#masterpassprompt{height:100%;min-height:20rem;width:60%;margin:auto}#setMasterPass{margin:.5rem 0;width:100%}#masterpassprompt p.note{margin:none;font-size:.8rem}.forgotMP{padding:.2rem .2rem 0 0;text-align:right}.masterpass>label{display:inline-block;font-size:.8rem;margin:.2rem 0;color:#F05A5C}.invalid{border-color:#F05A5C!important}
================================================
FILE: app/static/styles/setup.less
================================================
// out: ./setup.css, compress: true
/* Setup styles
==========================================================================
*/
@import (less) "mixins.less";
/* Variable declarations*/
@border-width: 2px;
@side-margin: 0.8rem;
@section-width: 60%;
@spacing04: 0.4rem;
@spacing-ic-txt: @side-margin - 0.4rem;
/* TODO: ADD MARGIN RELATIVELY/CONTEXTUALLY TO CORE ELEMENTS I.E. for a button, use panel-container > button*/
/* Animations */
@keyframes colorTrans {
from {
background-color: @white;
}
to {
background-color: @light;
}
}
@-webkit-keyframes OrangeAnimGrad {
0% {
background-position: 0 51%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0 51%;
}
}
@keyframes OrangeAnimGrad {
0% {
background-position: 0 51%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0 51%;
}
}
/* General styles */
#setup {
height : 100%;
width : 100%;
text-align: center;
overflow : hidden;
}
form {
margin: 0;
}
header {
margin: 0;
p {
color: @blacker;
}
.minimal {
h1 {
margin-top : 1rem;
font-weight: 300;
}
}
}
h1 {
font-size : 1.4rem;
margin-bottom: 0.4rem;
font-weight : 400;
}
p {
margin: 0;
color : @dark;
}
h1 + p {
margin: 0 2rem;
}
.panel-container {
position: relative;
& > div {
position : absolute;
text-align: center;
transform : translateX(-110%);
transition: transform 0.5s;
& > button {
display: none;
}
}
& > div.current {
width : 100%;
height: 100vh;
overflow : hidden;
transform: none;
position : relative;
& > button {
margin-top: 1rem;
display: inline-block;
}
}
& > div.current ~ div {
transform: translateX(110%);
}
header {
padding-top: 2rem;
}
}
// .panel-container > div {
// position: absolute;
// text-align: center;
// transform: translateX(-110%);
// transition: transform 0.5s;
// }
// .panel-container > div > footer {
// transform: translateX(-110%);
// }
//
// .panel-container > div.current > footer {
// transform: translateX(100%);
// }
/* TODO DISABLE ANIMATION ON FIRST PANE */
// #panel-welcome ~ div {
// transform: none;
// }
// #panel-welcome > div {
// transform: none;
// transition: none;
// }
.right {
margin-left: auto !important;
}
h3 {
margin : 0.5rem 0;
font-weight: normal;
}
a.csp {
padding: 0.6rem 0 !important;
img {
height: 1.6rem;
widht : auto;
}
}
footer {
display : flex;
clear : both;
position : absolute;
border-top : 1px solid @light;
width : 100%;
padding-bottom : 0;
background-color: @white;
bottom : 0;
left : 0;
}
footer > a {
float : right;
padding: 0.2rem;
}
.himg {
width : 9rem;
height : auto;
z-index: 1000;
}
div.list {
width : 100%;
overflow: auto;
}
div.item > a {
display : flex;
align-items : center;
border-bottom: 1px solid @light;
padding : @spacing04 0;
text-align : left;
}
img.info {
float : right;
height: 0.9rem;
}
p.info {
display : block;
width : 100%;
box-sizing : border-box;
max-height : 0;
overflow : hidden;
padding : 0 0.5rem;
transition : max-height 0.3s, padding 0.3s;
background-color: #efefef;
font-size : 0.8rem;
}
input {
width: 98%;
}
input[type=password] {
margin-top : 1rem;
border : none;
border-bottom: 1px solid @light;
outline : none;
&:focus {
border-bottom: 1px solid @black;
}
}
input[type=submit] {
margin-top : 1rem;
height : 2rem;
font-weight : normal;
background-color: @light;
border : 1px solid @light;
width : 100%;
outline : none;
&:focus {
border: 1px solid @black;
}
}
div.submit {
text-align : right;
background-color: @white;
width : 100%;
}
.none {
display: none;
}
/* Panel styles
* =================
*/
.banner {
margin : 1rem auto 0;
position : relative;
align-items : center;
justify-content: center;
width : 100%;
}
.banner-left {
margin-left: -3rem;
float : left;
}
.banner-right {
margin-right: -3rem;
float : right;
}
/* Welcome Panel styles
* =================
*/
#panel-welcome {
.marquee {
overflow: hidden;
position: relative;
}
.marquee-1 {
visibility: hidden;
}
.marquee-2 {
visibility: hidden;
}
.marquee.visible {
visibility: visible;
}
& > header {
.banner {
margin : 2rem auto 0;
position: relative;
display : inline-block;
width : 50%;
z-index : -1;
color : @light;
}
.banner + .himg {
margin-top: -7.5rem !important;
}
.himg {
z-index: 1000;
}
}
}
/* Done Panel styles
* =================
*/
#panel-done {
footer {
padding: 0.3rem 0;
text-align : center;
display : flex;
align-items : center;
font-size : 0.8rem;
justify-content: center;
img {
height: 1rem !important;
}
}
}
/* MasterPass Panel styles
* =================
*/
#masterpassprompt {
height : 100%;
min-height: 20rem;
width : @section-width;
margin : auto;
}
#setMasterPass {
margin : 0.5rem 0;
font-weight: normal;
width : 100%;
}
#masterpassprompt p.note {
margin : none;
font-size: 0.8rem;
}
.forgotMP {
padding : 0.2rem 0.2rem 0 0;
text-align: right;
}
/* INVALID STYLING */
.masterpass > label {
display : inline-block;
font-size: 0.8rem;
margin : 0.2rem 0;
color : @invalid-color;
}
.invalid {
border-color: @invalid-color !important;
}
================================================
FILE: app/utils/logger.js
================================================
'use strict'
/**
* logger.js
* Custom logger for debugging
******************************/
const { createLogger, format, transports } = require('winston')
const { isRenderer } = require('./utils')
// const { app } = require('electron')
if (process.env.TEST_RUN || isRenderer()) {
module.exports = createLogger({
silent: true,
exitOnError: false
})
} else {
const moment = require('moment')
const fs = require('fs-extra')
// const IN_DEV = !app.isPackaged
let debugDir = `${global.paths.userData}/debug`
fs.ensureDirSync(debugDir)
const fileTransport = new transports.File({
filename: `${debugDir}/CS_debug_${moment().format('DD.MM@HH:MM').trim()}.log`,
handleExceptions: true,
colorize: false,
level: 'verbose'
})
const logFormat = format.combine(
format.colorize(),
format.timestamp(),
format.prettyPrint(),
format.align(),
format.splat(),
format.printf(info => `${info.timestamp} ${info.level}: ${info.message}`)
)
const logger = createLogger({
transports: [
new transports.Console(),
fileTransport
],
exitOnError: false,
format: logFormat,
level: 'verbose'
})
module.exports = logger
}
================================================
FILE: app/utils/update.js
================================================
const https = require('https')
const { app, dialog, shell } = require('electron')
const { REPO } = require('../config')
const USER_AGENT = 'Crypter/x Wubba Lubba Dub Dub'
const VERSION_REGEX = /[\.v]+/g
const VERSION = parseV(app.getVersion())
function parseV(str) {
return parseInt(str.replace(VERSION_REGEX, ''))
}
module.exports = {
checkUpdate: function () {
return new Promise((resolve, reject) => {
https.get(REPO.RELEASES_API_URL, {
headers: { 'User-Agent': USER_AGENT }
}, (res) => {
let data = ''
res.on('data', (chunk) => {
data += chunk
})
res.on('end', () => {
try {
release = JSON.parse(data.toString('utf8'))
const LATEST_VERSION = parseV(release.tag_name)
if (VERSION < LATEST_VERSION) {
dialog.showMessageBox({
type: 'info',
message: `Update is available.`,
detail: `A new version Crypter ${release.tag_name} is available.\nDo you want to get it?`,
buttons: ['Get update', 'Later'],
defaultId: 0,
cancelId: 1,
icon: null
}, (response) => {
if (response === 0) {
// Update button pressed
shell.openExternal(release.html_url)
}
})
resolve(true)
} else {
resolve(false)
}
} catch (err) {
reject(err)
}
})
})
.on('error', (err) => reject(err))
})
}
}
================================================
FILE: app/utils/utils.js
================================================
const { extname } = require('path')
const { CRYPTO } = require('../config')
module.exports = {
isRenderer: () => {
// running in a web browser
if (typeof process === 'undefined') return true
// node-integration is disabled
if (!process) return true
// We're in node.js somehow
if (!process.type) return false
return process.type === 'renderer'
},
isCryptoFile: (file) => extname(file).toLowerCase() === CRYPTO.EXT
}
================================================
FILE: appveyor.yml
================================================
version: "{build}"
platform:
- x64
image: Visual Studio 2019
cache:
- node_modules
- app\node_modules
- '%APPDATA%\npm-cache'
- '%USERPROFILE%\.electron'
init:
- git config --global core.autocrlf input
# Only build if commit message contains '[build]'
only_commits:
message: /\[build\]/
install:
- set /p NODE_VERSION=<.nvmrc
- ps: Install-Product node $env:NODE_VERSION $env:PLATFORM
- npm install npm -g
- npm install electron-builder@next -g
- npm install --production
- npm prune
build_script:
- node --version
- npm --version
- npm run build:win
# Post-install test scripts.
# test_script:
# - script\test.cmd
test: off
================================================
FILE: build/license.txt
================================================
The MIT License (MIT)
Copyright (c) Habib Rehman <H@Rehman.email> (https://git.io/HR)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished TODO so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================
FILE: gulpfile.js
================================================
const gulp = require('gulp')
const shell = require('child_process').exec
const spawn = require('child_process').spawn
const babel = require('gulp-babel')
const env = require('gulp-env')
const less = require('gulp-less')
const path = require('path')
const istanbul = require('gulp-babel-istanbul')
const injectModules = require('gulp-inject-modules')
const mocha = require('gulp-mocha')
const LessPluginCleanCSS = require('less-plugin-clean-css')
const cleancss = new LessPluginCleanCSS({ advanced: true })
const LESS_FILES = './app/static/styles/*.less'
const ELECTRON = __dirname + '/node_modules/.bin/electron'
const DEBUG = false
let p
let args = ['.']
// Start the electron process.
async function electron () {
// kill previous spawned process
if (p) {
p.kill()
}
if (DEBUG) args.unshift('--inspect=5858')
// `spawn` a child `gulp` process linked to the parent `stdio`
p = await spawn(ELECTRON, args, {
stdio: 'inherit',
env: {
...process.env
}
})
}
gulp.task('run', electron)
gulp.task('watch', function (done) {
gulp.watch(LESS_FILES, gulp.series('less'))
gulp.watch(['./app/*.js', './app/src/*.js', './app/core/*.js'], gulp.series('run'))
done()
})
gulp.task('nodev', function () {
return shell(
// start electron main and render process
`node_modules/.bin/electron script/resolveNodeV.js`,
function (err, stdout, stderr) {
console.log(stdout)
console.log(stderr)
}
)
})
gulp.task('less', function () {
return gulp
.src('./app/static/styles/*.less')
.pipe(
less({
paths: [path.join(__dirname, 'less', 'includes')],
plugins: [cleancss]
})
)
.pipe(gulp.dest('./app/static/styles/'))
})
/* TEST */
gulp.task('test', () => {
shell(
// Run test stuff
'mocha --require babel-core/register test/'
)
})
gulp.task('coverage', function (cb) {
const envs = env.set({
TEST_RUN: true
})
gulp
.src('./app/core/**/*.js')
.pipe(envs)
.pipe(istanbul())
.pipe(istanbul.hookRequire())
.on('finish', function () {
gulp
.src('test/*.js')
.pipe(babel())
.pipe(injectModules())
.pipe(mocha())
.pipe(istanbul.writeReports())
.on('end', cb)
})
})
/* BUILD */
gulp.task('rebuildni', () => {
shell(
// start node inspector server
'node_modules/.bin/node-pre-gyp --target=$(node_modules/.bin/electron -v | sed s/\v//g) --runtime=electron --fallback-to-build --directory node_modules/v8-debug/ --dist-url=https://atom.io/download/atom-shell reinstall && node_modules/.bin/node-pre-gyp --target=$(node_modules/.bin/electron -v | sed s/\v//g) --runtime=electron --fallback-to-build --directory node_modules/v8-profiler/ --dist-url=https://atom.io/download/atom-shell reinstall'
)
})
gulp.task('buildnative', () => {
shell(
// start build the native module
'./node_modules/.bin/electron-rebuild #1'
)
})
gulp.task('ni', () => {
shell(
// start node inspector server
'ELECTRON_RUN_AS_NODE=true node_modules/.bin/electron node_modules/node-inspector/bin/inspector.js'
)
})
gulp.task('driver', () => {
shell(
// Run chromedriver
'./node_modules/chromedriver/bin/chromedriver'
)
})
gulp.task('default', gulp.series('less', 'watch', 'run'))
================================================
FILE: license
================================================
The MIT License (MIT)
Copyright (c) Habib Rehman <H@Rehman.email> (https://git.io/HR)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished TODO so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================
FILE: package.json
================================================
{
"name": "Crypter",
"productName": "Crypter",
"version": "5.0.0",
"description": "An innovative, convenient and secure cross-platform crypto app",
"license": "MIT",
"repository": "https://github.com/HR/Crypter",
"homepage": "https://github.com/HR/Crypter",
"bugs": "https://github.com/HR/Crypter/issues",
"main": "./app/index.js",
"author": {
"name": "Habib Rehman",
"email": "H@Rehman.email",
"url": "https://git.io/HR"
},
"build": {
"appId": "com.github.hr.crypter",
"asar": false,
"files": [
"**/*",
"!**/node_modules/.bin",
"!**/._*",
"!**/{.*,.git,*.yml,*.md,README,readme,test*,*.less,gulpfile.js}",
"!script${/*}",
"!test${/*}"
],
"compression": "normal",
"fileAssociations": {
"ext": "crypto",
"name": "CRYPTO",
"role": "Editor",
"description": "The Crypter encryption format. More info at git.io/Crypter.info#crypto-file"
},
"mac": {
"category": "public.app-category.utilities",
"publish": "github",
"identity": null
},
"dmg": {
"background": "build/background.tif",
"icon": "vicon.icns",
"iconSize": 116,
"iconTextSize": 13,
"contents": [
{
"x": 240,
"y": 135
},
{
"x": 240,
"y": 400,
"type": "link",
"path": "/Applications"
}
],
"window": {
"width": "480",
"height": "540"
}
},
"linux": {
"category": "Utility",
"publish": "github"
},
"win": {
"icon": "icon.ico",
"target": "nsis",
"publish": "github"
},
"nsis": {
"oneClick": true,
"perMachine": true
},
"snap": {
"publish": "github"
}
},
"scripts": {
"pack": "electron-builder --dir",
"build:mac": "electron-builder -m -c.electronVersion $(npm info electron version)",
"build:lin": "electron-builder -l --x64 --ia32 -c.electronVersion $(npm info electron version)",
"build:win": "electron-builder -w --x64 --ia32",
"start": "electron .",
"postinstall": "electron-builder install-app-deps",
"nodeGypReBuild": "electron-rebuild .",
"electronV": "electron -v | sed s/\\v//g",
"test": "TEST_RUN=true mocha --require @babel/register test/test.js",
"coverage": "TEST_RUN=true nyc --reporter=lcov mocha -- test/test.js",
"coveralls": "coveralls < coverage/lcov.info",
"codeclimate": "node codeclimate-test-reporter < coverage/lcov.info",
"package": "electron-packager . $npm_package_productName --out=dest --ignore='(test|dest|coverage)' --prune --asar --all --version=$(npm run electronVersion)",
"winpackage": "chmod +x script/win-build.sh && script/win-build.sh",
"xtest": "npm run xtestbuild && npm run xltest",
"xpackage": "electron-packager . $npm_package_productName --out=dest --ignore='(test|dest|coverage|github)' --asar=false --platform=darwin --arch=x64 --version=$(npm run electronVersion) --icon=app/icons/Crypter.icns --app-copyright=Habib_Rehman --overwrite",
"xltest": "unset TEST_RUN && rm -rf ~/Library/Application\\ Support/CrypterTest/ && mocha --require @babel/register ./test/ui/*.js",
"xtestbuild": "npm_package_productName=CrypterTest && electron-packager . $npm_package_productName --out=dest --ignore='(test|dest|coverage|backups|github)' --asar=false --platform=darwin --arch=x64 --version=$(npm run electronVersion) --icon=app/icons/Crypter.icns --app-copyright=HR --overwrite && npm run productNameChange"
},
"keywords": [
"encryption",
"decryption",
"crypto",
"end-to-end",
"client",
"electron"
],
"devDependencies": {
"@babel/core": "^7.11.0",
"@babel/preset-env": "^7.11.0",
"@babel/register": "^7.10.5",
"babel-cli": "6.26.0",
"chai": "4.2.0",
"chai-as-promised": "7.1.1",
"codeclimate-test-reporter": "0.5.1",
"coveralls": "3.1.0",
"electron": "9.1.2",
"electron-builder": "^22.8.0",
"electron-packager": "15.0.0",
"electron-rebuild": "1.11.0",
"gulp": "4.0.2",
"gulp-babel": "8.0.0",
"gulp-babel-istanbul": "1.6.0",
"gulp-env": "0.4.0",
"gulp-inject-modules": "1.0.0",
"gulp-json-editor": "2.5.4",
"gulp-less": "4.0.1",
"gulp-mocha": "7.0.2",
"isparta": "4.1.1",
"less-plugin-clean-css": "1.5.1",
"mocha": "8.1.0",
"mocha-lcov-reporter": "1.3.0",
"nyc": "^15.1.0",
"spectron": "11.1.0"
}
}
================================================
FILE: readme.md
================================================
<h1 align="center">
<br>
<a href="https://github.com/HR/Crypter"><img src="./build/icons/256x256.png" alt="Crypter" width="180" style= "margin-bottom: 1rem"></a>
<br>
Crypter
<br>
<br>
</h1>
<h4 align="center">An innovative, convenient and secure crypto app.</h4>
<p align="center">
<a href="https://github.com/HR/Crypter/releases/latest">
<img src="https://img.shields.io/badge/Download-4.0-orange.svg"
alt="Download latest release" style= "margin-bottom: 0.5rem" height="25px">
</a>
</p>
<p align="center">
<a href="http://standardjs.com/">
<img src="https://img.shields.io/badge/code%20style-standard-brightgreen.svg"
alt="Standard JS Code Style">
</a>
<a href="https://travis-ci.org/HR/Crypter">
<img src="https://travis-ci.org/HR/Crypter.svg?branch=master"
alt="Travis Build">
</a>
<a href="https://ci.appveyor.com/project/HR/crypter">
<img src="https://ci.appveyor.com/api/projects/status/4pa8cnlb1qnyj1xd/branch/master?svg=true"
alt="AppVeyor Build">
</a>
<a href="https://coveralls.io/github/HR/Crypter?branch=master">
<img src="https://coveralls.io/repos/github/HR/Crypter/badge.svg?branch=master"
alt="Test Coverage">
</a>
<a href="https://codeclimate.com/github/HR/Crypter?branch=master">
<img src="https://codeclimate.com/github/HR/Crypter/badges/gpa.svg"
alt="CodeClimate GPA">
</a>
<a href="https://github.com/HR/Crypter/releases/latest">
<img src="https://img.shields.io/github/downloads/hr/crypter/total"
alt="GitHub all releases downloads" >
</a>
</p>
<br>
> Encrypt unlimited bits. Remember only a bit.
**Crypter** is a cross-platform crypto app that makes encryption and decryption
convenient while still upholding strong security. It tackles one of the weakest
links in most security systems today - weak passwords. It simplifies secure
password generation and management and requires you to only remember one bit -
your MasterPass.
[Crypter v4.0](https://github.com/HR/Crypter/releases/tag/v4.0.0) is a crypto
app that can decrypt and encrypt any arbitrary data this includes files and
folders. This version has been released and fully tested for macOS (OSX), Linux
(for all distros via [AppImage](http://appimage.org/)) and Windows (32 & 64
bit). All core modules (modules that provide the core functionality) are fully
tested.
[Crypter v5.0](https://github.com/HR/Crypter/milestone/4) will save your MasterPass
in your OS's Keychain so you won't have to enter it every time you open the app.
To help speed up the development please send a PR for what's left do https://github.com/HR/Crypter/milestone/4
Please open an issue if you have any suggestions and add improvements via PRs!
Also checkout [Ciphora](https://github.com/HR/ciphora) (https://github.com/HR/ciphora)
a decentralized end-to-end encrypted messaging app.
Link to this README: https://git.io/Crypter.info
---
# Contents
<!-- TOC depthFrom:2 depthTo:6 withLinks:1 updateOnSave:1 orderedList:0 -->
- [Installation](#installation)
- [Screens](#screens)
- [Crypto](#crypto)
- [Encryption process](#encryption-process)
- [Decryption process](#decryption-process)
- [Public credentials](#public-credentials)
- [CRYPTO file](#crypto-file)
- [Format](#format)
- [Reusing the same MasterPass](#reusing-the-same-masterpass)
- [Achieving portability and same MasterPass reuse](#achieving-portability-and-same-masterpass-reuse)
- [Security](#security)
- [Security-first practice](#security-first-practice)
- [MasterPass](#masterpass)
- [MasterPassKey](#masterpasskey)
- [FAQs](#faqs)
- [How do I encrypt a file or folder?](#how-do-i-encrypt-a-file-or-folder)
- [How do I decrypt a CRYPTO file?](#how-do-i-decrypt-a-crypto-file)
- [How do I encrypt multiple files?](#how-do-i-encrypt-multiple-files)
- [Why am I getting the "Corrupted Crypter file or trying to decrypt on a different machine." error?](#why-am-i-getting-the-corrupted-crypter-file-or-trying-to-decrypt-on-a-different-machine-error)
- [Why can't I decrypt a CRYPTO file on a different machine with the same MasterPass?](#why-cant-i-decrypt-a-crypto-file-on-a-different-machine-with-the-same-masterpass)
- [Why can't I decrypt a CRYPTO file with the same MasterPass?](#why-cant-i-decrypt-a-crypto-file-with-the-same-masterpass)
- [Where are my encrypted/decrypted files/folders placed?](#where-are-my-encrypteddecrypted-filesfolders-placed)
- [How can I access Crypter's preferences?](#how-can-i-access-crypters-preferences)
- [How can I reset my MasterPass?](#how-can-i-reset-my-masterpass)
- [What is a valid MasterPass?](#what-is-a-valid-masterpass)
- [What are MasterPass credentials?](#what-are-masterpass-credentials)
- [How can I export my MasterPass credentials?](#how-can-i-export-my-masterpass-credentials)
- [How can I import my MasterPass credentials?](#how-can-i-import-my-masterpass-credentials)
- [Development](#development)
- [Configurations](#configurations)
- [Install (dependencies)](#install-dependencies)
- [Run](#run)
- [Test](#test)
- [Build](#build)
- [License](#license)
## <!-- /TOC -->
## Installation
All prebuilt binaries for all major platforms are available under
[releases](https://github.com/HR/Crypter/releases/latest).
Crypter is also on [Homebrew Cask](https://formulae.brew.sh/cask/crypter) for
macOS. So to install it, simply run the following command in the Terminal:
```bash
$ brew install --cask crypter@4.0.0
```
<br/>
## Screens
<p align="center">
<img src="/.github/Welcome_screen.png?raw=true" alt="Welcome screen" width="100%">
<img src="/.github/Crypter_main_screen.png?raw=true" alt="Crypter screen" width="40%">
<img src="/.github/MasterPass_screen.png?raw=true" alt="MasterPass screen" width="40%">
<img src="/.github/Settings_screen.png?raw=true" alt="Settings screen" width="85%">
</p>
<br/>
## Crypto
> One key to derive them all!
Crypter derives a MasterPassKey from the MasterPass obtained at setup by using
the PBKDF2 key derivation algorithm (see below for the specification). It
then uses PBKDF2 to derive a number of encryption keys from the MasterPassKey
that can be used for the encryption of files. This method allows for the
generation of very secure encryption keys for data encryption. Moreover, by
publicly storing the credentials used to derive the MasterPassKey and the salts
used to derive the encryption keys, you are able to produce the encryption keys
at will and without needing to store them securely. Your MasterPass is the only
thing that you need to remember.
Crypter never directly encrypts anything with your MasterPass. Instead, it
derives a MasterPassKey from it, which it then uses to derive the
encryption key used to encrypt your file. Every time a file is decrypted,
the encryption key is re-derived from the MasterPassKey. Every time you set the
MasterPass through the setup or reset it through Verify MasterPass, the
MasterPassKey is derived from the MasterPass using a newly generated set of
(random) credentials. These credentials are used to re-derive the MasterPassKey
every time that Crypter is executed (i.e. the app is launched).
Authentication with the AES-256-GCM symmetric block cipher is used by default.
This ensures that data integrity is verified on decryption and allows the app
to detect tampering or data corruption.
The following are the crypto defaults and can be found under `app/config.js`:
```js
// Crypto defaults
{
ITERATIONS: 50000, // file encryption key derivation iterations
KEYLENGTH: 32, // encryption key length
IVLENGTH: 12, // initialisation vector length
ALGORITHM: 'aes-256-gcm', // encryption algorithm
DIGEST: 'sha256', // digest function
HASH_ALG: 'sha256', // hashing function
MPK_ITERATIONS: 100000 // MasterPassKey derivation iterations
}
```
### Encryption process
When encrypting a file, Crypter first creates a temporary hidden directory,
namely '.crypting'. It then encrypts the user-selected file with the crypto
defaults and flushes the encrypted data to a file in the directory, namely
'data'. If it is a directory then it is compressed first (tar). It also writes
the public credentials to a file within the same directory, namely 'creds'.
Finally, Crypter compresses the directory to a tar archive with the name of the
user-selected file and the '.crypto' extension appended to it.
### Decryption process
The decryption process is essentially the inverse of the encryption process.
During decryption, Crypter creates a temporary hidden directory named
'.decrypting'. It then reads the credentials from the creds file and decrypts
the data file into the original file or directory (after decompressing it) with
its original name and extension, as deduced from the CRYPTO file name (e.g. the
extension for "file.txt.crypto" would be ".txt").
### Public credentials
Certain credentials are required to decrypt the encrypted data. These are
needed to reconstruct the particular encryption key and to verify data integrity.
These can be stored publicly without compromising security since it is fairly
impossible (by current standards) to reconstruct the encryption key without the
MasterPass and its credentials. These credentials are stored in the creds file of
the [CRYPTO file](#crypto-file) archive (as delineated above) in the following
format:
#### v1
```
Crypter#iv#authTag#salt#dir
```
#### v2
Uses JSON
```json
{
"type": "CRYPTO",
"iv": "...",
"authTag": "...",
"salt": "...",
"isDir": true || false
}
```
The `dir` part is only included for directories
<br/>
## CRYPTO file
### Format
A CRYPTO file is the product of the Crypter encryption process. This file
stores both the encrypted version of the user file and the public credentials
needed to encrypt and decrypt it. It has a `.crypto` file extension, which
is appended to the full file name (including the extension) of the file
originally encrypted. The file itself is a tar archive in the following
structure:
```c
someFile.crypto
├── data // the encrypted version of the user selected file
└── creds // the public credentials used to encrypt it
```
### Reusing the same MasterPass
If you attempt to decrypt a CRYPTO file by _resetting to a specific
MasterPass_ or _setting an identical MasterPass on a different machine_,
you will likely encounter the following error:
```
ERROR: Unsupported state or unable to authenticate data
```
This issue occurs because the MasterPassKey that was originally used to
derive the encryption key on is **not the same** as the MasterPassKey
derived with the reused MasterPass. Since Crypter uses randomness to
generate secure credentials, this second set of credentials will be quite
**different** from the original set. As a result, the derived encryption key is
incorrect and yields this error.
See [Achieving portability and same MasterPass reuse](#achieving-portability-and-same-masterpass-reuse)
for instructions on how to successfully reuse the same MasterPass.
### Achieving portability and same MasterPass reuse
To achieve portability on Crypter, the set of MasterPassKey credentials
need to be exported from Crypter on the source machine<sup>[1](#source)</sup>
and imported into Crypter on the target machine<sup>[2](#target)</sup>.
This can be achieved in two simple steps:
<ol>
<li><a href="#how-can-i-export-my-masterpass-credentials">Export MasterPass credentials on the source machine</a><sup><a href="#source">1</a></sup></li>
<li><a href="#how-can-i-import-my-masterpass-credentials">Import MasterPass credentials on the target machine</a><sup><a href="#target">2</a></sup></li>
</ol>
Please refer to the FAQs for instructions on how to perform the above steps.
<hr>
<a name="source"></a> [1] The machine where the CRYPTO file was initially
encrypted.
<a name="target"></a> [2] The machine where you wish to decrypt the CRYPTO
file.
<br/>
## Security
### Security-first practice
Crypter follows a security-first practice. This means that security is its
highest priority and first consideration. Thus, while Crypter seeks
to make encryption more convenient, it always defers to maintaining
a high level of security.
### MasterPass
Crypter never stores your MasterPass in memory or on the filesystem. This
substantially improves the security of your MasterPass. You are only asked to
enter the MasterPass when you first set, reset or verify it. Whenever you enter
your MasterPass, Crypter derives a MasterPassKey (using a set of generated
credentials) and then immediately discards the MasterPass. The MasterPassKey is
then securely stored in memory and used to derive the encryption keys. Since
these credentials are derived via a one-way function, they cannot be used in
any way to derive the MasterPass.
### MasterPassKey
Crypter uses a WeakMap to store the MasterPassKey inside the MasterPassKey class
using a closure function. This makes the MasterPassKey data held in the object
(externally) inaccessible, consequently increasing the protection of the
MasterPassKey. The MasterPassKey is never flushed to the filesystem and is always
stored in (main) memory. Since JS does not give control over or allow such a
low-level operation as wiping memory, the program relies on the garbage
collection and volatility of the main memory for the permanent erasure of the
MasterPassKey stored in memory.
A decent number of iterations (see the above specifications) are used in the
derivation of the MasterPassKey to mitigate brute-force attacks. A good
amount of iterations are also used during the derivation of the encryption
keys from the MasterPassKey. Consequently, performance and speed are not
significantly compromised. For critical applications, you may choose to
select 10,000,000 iterations instead of the default number
(in app/core/crypto.js). Please refer to
http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-132.pdf for
more information.
Crypter generates a new set of random credentials for deriving the MasterPassKey
every time the MasterPass is set (at setup) or reset. Crypter employs randomness
to mitigate brute-force attacks and thus drastically improve security.
<br/>
## FAQs
### How do I encrypt a file or folder?
If you haven't already, walk through the setup and set your MasterPass.
To encrypt a file or folder, first launch Crypter and verify your MasterPass.
After doing so successfully, you will see the main Crypter window with an orange
area. Here, you can simply drag-and-drop or click to select the file/folder you
wish to encrypt. Once Crypter is done encrypting your file/folder, it will show
you the encryption information (i.e. the encryption key and the path of the
encrypted file) in a new window. To encrypt another file/folder simply click the
back arrow at the bottom left and start-over ;)
### How do I decrypt a CRYPTO file?
> The following instructions assume that the CRYPTO file that you wish to
> decrypt is being used with the same MasterPass that you set at setup and also
> that you have not reset it since that time. If this is not the case, please refer
> to [Reusing the same MasterPass](#reusing-the-same-masterpass).
To decrypt a CRYPTO file, first launch Crypter and verify your MasterPass. After
doing so successfully, you will see the main Crypter window with an orange area.
Here, you can simply drag-and-drop or click to select the CRYPTO file that you
wish to decrypt. After a few seconds, the process will complete and you will see
some information about the file and its original encryption in a new window. By
default, the decrypted file has the same name as the name of the original file
(i.e. the encrypted file name without the `.crypto` at the end).
### How do I encrypt multiple files?
Crypter can encrypt an entire folder so you can put them in a folder or,
alternatively, compress them into an archive (like a `.zip`) and then just pass
it to Crypter ;)
### Why am I getting the "Corrupted Crypter file or trying to decrypt on a different machine." error?
This error means that either your Crypter file (i.e. the `data` file) is
corrupt/tempered, that you are on a different machine than the one originally
used to encrypt the file or that you have previously reset your MasterPass.
For the last two cases, please refer to
[Reusing the same MasterPass](#reusing-the-same-masterpass) and
[Achieving portability and same MasterPass reuse](#achieving-portability-and-same-masterpass-reuse).
### Why can't I decrypt a CRYPTO file on a different machine with the same MasterPass?
Please refer to [Reusing the same MasterPass](#reusing-the-same-masterpass) and
[Achieving portability and same MasterPass reuse](#achieving-portability-and-same-masterpass-reuse)
### Why can't I decrypt a CRYPTO file with the same MasterPass?
Please refer to [Reusing the same MasterPass](#reusing-the-same-masterpass) and
[Achieving portability and same MasterPass reuse](#achieving-portability-and-same-masterpass-reuse)
### Where are my encrypted/decrypted files/folders placed?
By default, every source file that you encrypt/decrypt gets encrypted/decrypted
to the same directory where the source file is located.
### How can I access Crypter's preferences?
You can access Crypter's preferences by either clicking on the cog icon in the
main Crypter window or by going to `Crypter > Preferences...` from the menu.
### How can I reset my MasterPass?
You can reset your MasterPass by clicking on the "Forgot it" link in the Verify
MasterPass window. This takes you to a new screen where you can enter a new, valid
MasterPass. Once you've entered it, click the 'Reset' button and you'll be sent
back to the verify screen where you can verify your new MasterPass.
### What is a valid MasterPass?
Crypter will not allow you to set an invalid MasterPass. A MasterPass is valid
when it adheres to the following rules:
- It is at least 8 characters long
- It has at least one uppercase alphabetic character (A-Z)
- It has at least one lowercase alphabetic character (a-z)
- It has at least one numeric character (0-9)
- It has at least one special character (\$@!%\*#?&)
These rules are enforced via the following regular expression:
```javascript
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@!%*#?&]).{8,}$/;
```
### What are MasterPass credentials?
MasterPass credentials are a set of values that are required to derive the
MasterPassKey from the MasterPass. These values have a pseudo-random element and
are cryptographically linked. Every MasterPass that is set or reset with Crypter
has a unique set of MasterPass credentials. These yield a distinct MasterPassKey,
even when a MasterPass is reused.
### How can I export my MasterPass credentials?
To export your MasterPass credentials, you can first open Crypter's
preferences (see above). From this screen, click on the "Export" button. A
dialog will appear from which you can select the folder where you wish to export
the credentials. A success message will confirm a successful export. The
exported MasterPass credentials file is always named `credentials.crypter`.
### How can I import my MasterPass credentials?
To import a set of MasterPass credentials, you can first open Crypter's
preferences (see above). From this screen, click on the "Import" button. A
dialog will appear from which you can locate your `credentials.crypter` file.
After you select it, a success message will confirm a successful import and
you will have to verify the MasterPass for the credentials.
NOTE: while Crypter does not require the MasterPass credentials file to be
exactly named `credentials.crypter`, it does require the file's contents to
be unaltered from when it was exported from Crypter. If it has been altered,
the import may fail.
<br/>
## Development
Crypter is developed in the "dev" branch, which may be unstable at times.
This branch should typically be used for pull requests.
The "master" branch will always be kept stable.
### Configurations
All major configurations that you can apply are found under `app/config.js`.
This includes changes to certain cryptography settings. Please be advised
that altering these may break functionality and
[portability](#achieving-portability-and-same-masterpass-reuse).
### Install (dependencies)
To install all dependencies, run:
```
$ yarn install
```
### Run
Since Crypter uses gulp, please install it globally if you have not already
done so. To start Crypter, run:
```
$ gulp
```
### Test
Crypter primarily uses mocha and chai for testing. Since the project uses a
lot of JS ES6 syntax, babel is also used as a transpiler. To run all the tests,
execute:
```
$ yarn test
```
Crypter uses istanbul for coverage. To run test coverage, execute:
```
$ yarn run coverage
```
### Build
Crypter's binaries (available under releases) have been built using
Electron. Since Crypter uses electron-builder to build binaries,
you must install it globally:
```
$ npm install electron-builder@next -g
```
To build the app for **macOS**, run:
```
$ yarn run build:mac
```
To build the app for **Linux**, run:
```
$ sudo apt-get install --no-install-recommends -y icnsutils graphicsmagick xz-utils
$ yarn run build:lin
```
To build the app for **Windows** x84 and/or x64, run:
```
$ yarn run build:win
```
## License
The MIT License (MIT)
Copyright (c) Habib Rehman (https://git.io/HR)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished todo so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================
FILE: script/appveyor-build.cmd
================================================
@setlocal enableextensions enabledelayedexpansion
@echo off
get-host
set keyword=build
If NOT %APPVEYOR_REPO_COMMIT_MESSAGE:build=%==x%APPVEYOR_REPO_COMMIT_MESSAGE% (
REM Commit message contains '[build]' so build
echo Building Crypter
build.cmd
) else (
REM Commit message does not contain '[build]' so just test and skip build
echo Testing Crypter
test.cmd
)
endlocal
================================================
FILE: script/build.cmd
================================================
echo Building Crypter
REM cd %APPVEYOR_BUILD_FOLDER%
set NODE_ENV=production
npm install electron-builder@next -g
npm install --production
npm run build:win
================================================
FILE: script/build_ml.sh
================================================
#!/bin/bash
echo "Building for: $TRAVIS_OS_NAME"
echo "CWD: $PWD"
echo "Node $(node --version)"
echo "NPM $(npm --version)"
# make for production
export NODE_ENV=production
npm install electron-builder -g
npm prune
if [ "$TRAVIS_OS_NAME" == "linux" ]; then
# to build for linux
echo "Building for linux"
docker run --rm \
--env-file <(env | grep -iE 'DEBUG|NODE_|ELECTRON_|YARN_|NPM_|CI|CIRCLE|TRAVIS|APPVEYOR_|CSC_|_TOKEN|_KEY|AWS_|STRIP|BUILD_') \
-v ${PWD}:/project \
-v ~/.cache/electron:/root/.cache/electron \
-v ~/.cache/electron-builder:/root/.cache/electron-builder \
electronuserland/builder:wine \
/bin/bash -c "npm i -g electron-builder && yarn run build:lin"
else
echo "Building for mac"
yarn run build:mac
fi
# zip -r dist/**/*.zip ./github/RELEASE
================================================
FILE: script/darwin-build.sh
================================================
echo "pwd: "$PWD
export NODE_ENV=production
# remove any existing distribution
rm -rf dest
npm install --production
electron-packager . $npm_package_productName --out=dest --ignore='(test|backups|github)' --asar=false --platform=darwin --arch=x64 --version=$(npm run electronVersion) --icon=res/app-icons/Crypter.icns --app-copyright=Habib_Rehman --overwrite
cp ./github/RELEASE ./dest/Crypter-darwin-x64/RELEASE
cp ./license ./dest/Crypter-darwin-x64
zip -9r ./dest/Crypter-darwin-x64 ./dest/Crypter-darwin-x64
================================================
FILE: script/resolveNodeV.js
================================================
const {app} = require('electron')
const fs = require('fs-extra')
const FILE_PATH = '.nvmrc'
console.log("Electron node version is: "+process.versions.node)
fs.writeFile(FILE_PATH, process.versions.node, function (err) {
if (err) return console.log('Error writing file: ' + err)
app.quit()
})
================================================
FILE: script/test.cmd
================================================
node --version
npm --version
REM install all deps
npm install --no-optional
REM run tests
npm test
================================================
FILE: script/travis-build.sh
================================================
#!/bin/bash
# set -ev
# Just exit when fail dont print code to be exec
set +e
export TEST_RUN=true
echo "CC: $CC"
echo "CXX: $CXX"
echo "OS Name: $TRAVIS_OS_NAME"
echo "Node $(node --version)"
echo "NPM $(npm --version)"
# Install deps
yarn install --ignore-optional
yarn prune
# Test and get coverage
yarn run coverage
yarn run coveralls
# - npm run codeclimate
# End-to-end OSX testing
# if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
# export DISPLAY=:99.0
# sh -e /etc/init.d/xvfb start
# sleep 3
# unset TEST_RUN
# npm run xtest
# fi
unset TEST_RUN
================================================
FILE: script/win-build.sh
================================================
echo "pwd: "$PWD
export NODE_ENV=production
export DISPLAY=':99.0'
# remove any existing distribution
rm -rf dest
# install deps
sudo apt-get wine
sudo dpkg --add-architecture i386
sudo add-apt-repository ppa:wine/wine-builds
sudo apt-get update
sudo apt-get install --install-recommends winehq-devel
sudo apt-get install -y xvfb
# start Xvfb server
Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1
npm install --production
./node_modules/.bin/electron-packager . Crypter --out=dest --ignore='(test|github)' --asar=false --platform=win32 --arch=x64 --version=$(npm run electronVersion) --icon=res/app-icons/Crypter.ico --app-copyright=Habib_Rehman --overwrite
cp ./github/RELEASE ./dest/Crypter-win32-x64/RELEASE
cp ./license ./dest/Crypter-win32-x64
zip -r ./dest/Crypter-win32-x64 ./dest/Crypter-win32-x64
================================================
FILE: test/test.js
================================================
'use strict'
const assert = require('assert')
const path = require('path')
const expect = require('chai')
.expect
const crypto = require('../app/core/crypto.js')
const Db = require('../app/core/Db')
const MasterPassKey = require('../app/core/MasterPassKey')
const MasterPass = require('../app/core/MasterPass')
const scrypto = require('crypto')
const _ = require('lodash')
const fs = require('fs-extra')
const logger = require('../app/node_modules/electron-log/src')
logger.transports.file.level = false;
logger.transports.console.level = false;
console.log(`cwd: ${process.cwd()}`)
console.log(`__dirname: ${__dirname}`)
let checkFileSync = function (path) {
try {
fs.accessSync(path, fs.F_OK)
} catch (err) {
if (err.code === 'ENOENT') return false
}
return true
}
describe("Crypter Core Modules' tests", function () {
// Declare globals
global.creds = {}
global.paths = {
mdb: path.join(__dirname, '/tmp/mdb'),
tmp: path.join(__dirname, '/tmp')
}
global.MasterPassKey = new MasterPassKey(scrypto.randomBytes(32))
// Declare constants
const KEY_LENGTH = 32 // 32 bytes
const TEST_FOLDER_PATH = `${global.paths.tmp}/test`
const TEST_FOLDER_FILE_PATH = `${TEST_FOLDER_PATH}/test.txt`
const ENCRYTED_TEST_FOLDER_PATH = `${TEST_FOLDER_PATH}.crypto`
const DECRYTING_FOLDER_TEMP_DIR_PATH = `${path.dirname(ENCRYTED_TEST_FOLDER_PATH)}/.decrypting`
const TEST_FILE_PATH = `${global.paths.tmp}/test.txt`
const ENCRYTED_TEST_FILE_PATH = `${TEST_FILE_PATH}.crypto`
const DECRYTED_TEST_FILE_PATH = `${global.paths.tmp}/test.txt`
const TEST_FILE_CONTENTS = '#Crypter'
const TEST_CONTENTS_ENCODING = 'utf8'
const DECRYTING_TEMP_DIR_PATH = `${path.dirname(ENCRYTED_TEST_FILE_PATH)}/.decrypting`
const ENCRYTING_TEMP_DIR_PATH = `${path.dirname(ENCRYTED_TEST_FILE_PATH)}/.crypting`
const DB_TEST_FILE_PATH = `${global.paths.tmp}/db`
const BUF2HEX_TEST_ARR = [248, 27, 158, 201, 66, 216, 80, 254, 81, 104, 238, 9, 1, 231, 134, 106, 8, 202, 44, 89, 231, 61, 99, 139, 167, 162, 21, 216, 127, 85, 142, 86]
const BUF2HEX_TEST_HEX_EXPECTED = 'f81b9ec942d850fe5168ee0901e7866a08ca2c59e73d638ba7a215d87f558e56'
const SAFEEQ_TEST_HEX = 'f81b9ec942d850f35168ee0901e7866a08ca2c59e73d638ba7a215d87f558e56'
// Before all tests have run
before(() => {
// create temporary dir
fs.ensureDirSync(global.paths.tmp)
process.chdir(global.paths.tmp)
console.log(` > Changed cwd to: ${process.cwd()}`)
global.mdb = new Db(global.paths.mdb, function () {})
})
// After all tests have run
after(() => {
// remove temporary dir
fs.removeSync(global.paths.tmp)
})
/**
* Crypto module.js
******************************/
describe('Crypto module', function () {
it('should convert string buffer to hex string', function () {
const b2hr = crypto.buf2hex(BUF2HEX_TEST_ARR)
expect(b2hr)
.to.equal(BUF2HEX_TEST_HEX_EXPECTED)
})
it('should check if two strings are equal time safely', function () {
expect(crypto.timingSafeEqual(BUF2HEX_TEST_HEX_EXPECTED, BUF2HEX_TEST_HEX_EXPECTED))
.to.be.true
expect(crypto.timingSafeEqual(BUF2HEX_TEST_HEX_EXPECTED, SAFEEQ_TEST_HEX))
.to.be.false
expect(crypto.timingSafeEqual(SAFEEQ_TEST_HEX, BUF2HEX_TEST_HEX_EXPECTED))
.to.be.false
})
describe('deriveKey (and genPassHash)', function () {
// deriveKey uses genPassHash Promise internally. Testing the derived
// MasterPassKey before and after also suffienctly tests genPassHash
// (indirectly test the hash before and after)
const masterpass = 'crypto#101'
it('should deriveKey using a MasterPass correctly when salt is buffer', function () {
let mpkey
return crypto.deriveKey(masterpass, null)
.then((dmp) => {
// derived salt should be a Buffer
expect(dmp.salt instanceof Buffer)
.to.be.true
mpkey = dmp.key
return crypto.deriveKey(masterpass, dmp.salt)
})
.then((dmp) => {
// The newly derive MPK should equal to the originally derived MPK
expect(dmp.key.toString('hex'))
.to.equal(mpkey.toString('hex'))
})
})
it('should deriveKey using a MasterPass correctly with persistent salt', function () {
let mpkey
return crypto.deriveKey(masterpass, null)
.then((dmp) => {
// Serialize and deserialize the derived salt
const pdmpsalt = JSON.parse(JSON.stringify(dmp.salt))
mpkey = dmp.key
// Used deserialized salt to derive key
return crypto.deriveKey(masterpass, pdmpsalt)
})
.then((dmp) => {
// The deserialized salt should successfully create a Buffer
expect(Buffer.isBuffer(dmp.salt))
.to.be.true
// The deserialized salt should have been used to recreate the
// Buffer orginally used to derive the key correctly which should
// result in the newly derive MPK equalling to the originally
// derived MPK
expect(dmp.key.toString('hex'))
.to.equal(mpkey.toString('hex'))
})
})
})
describe('Encryption', function () {
// Before any tests are run in this suite
before(function () {
// create a test file (for encryption)
fs.writeFileSync(TEST_FILE_PATH, TEST_FILE_CONTENTS, TEST_CONTENTS_ENCODING)
})
after(function () {
fs.removeSync(ENCRYTING_TEMP_DIR_PATH)
})
describe('encrypt promise', function () {
it('should encrypt file with pass without errors & have all expected creds', function () {
return crypto.encrypt(TEST_FILE_PATH, global.MasterPassKey.get())
.then((creds) => {
// The encrypted file should exist at the ENCRYTED_TEST_FILE_PATH
expect(checkFileSync(ENCRYTED_TEST_FILE_PATH))
.to.be.true
expect(checkFileSync(ENCRYTING_TEMP_DIR_PATH))
.to.be.false
// Creds should have all the expected properties
expect(creds)
.not.be.empty
gitextract_a8kqtnjq/
├── .babelrc
├── .codeclimate.yml
├── .github/
│ └── ISSUE_TEMPLATE.md
├── .gitignore
├── .nvmrc
├── .travis.yml
├── SECURITY.md
├── app/
│ ├── config.js
│ ├── core/
│ │ ├── Db.js
│ │ ├── MasterPass.js
│ │ ├── MasterPassKey.js
│ │ └── crypto.js
│ ├── index.js
│ ├── package.json
│ ├── src/
│ │ ├── crypter.js
│ │ ├── mainMenu.js
│ │ ├── masterPassPrompt.js
│ │ ├── menu.js
│ │ ├── settings.js
│ │ └── setup.js
│ ├── static/
│ │ ├── crypter.html
│ │ ├── fonts/
│ │ │ └── LICENSE.txt
│ │ ├── js/
│ │ │ ├── common.js
│ │ │ ├── crypter.js
│ │ │ ├── masterpassprompt.js
│ │ │ ├── settings.js
│ │ │ └── setup.js
│ │ ├── masterpassprompt.html
│ │ ├── settings.html
│ │ ├── setup.html
│ │ └── styles/
│ │ ├── crypter.css
│ │ ├── crypter.less
│ │ ├── masterpassprompt.css
│ │ ├── masterpassprompt.less
│ │ ├── mixins.css
│ │ ├── mixins.less
│ │ ├── settings.css
│ │ ├── settings.less
│ │ ├── setup.css
│ │ └── setup.less
│ └── utils/
│ ├── logger.js
│ ├── update.js
│ └── utils.js
├── appveyor.yml
├── build/
│ ├── background.tif
│ ├── crypto.icns
│ ├── icon.icns
│ ├── license.txt
│ └── vicon.icns
├── gulpfile.js
├── license
├── package.json
├── readme.md
├── script/
│ ├── appveyor-build.cmd
│ ├── build.cmd
│ ├── build_ml.sh
│ ├── darwin-build.sh
│ ├── resolveNodeV.js
│ ├── test.cmd
│ ├── travis-build.sh
│ └── win-build.sh
└── test/
├── test.js
└── ui/
└── test.js
SYMBOL INDEX (40 symbols across 15 files)
FILE: app/config.js
constant VIEWS_BASE_URI (line 8) | const VIEWS_BASE_URI = `file://${__dirname}/static`
FILE: app/core/Db.js
function Db (line 18) | function Db (path, cb) {
FILE: app/core/MasterPass.js
constant SERVICE (line 7) | const crypto = require('./crypto'),
constant ACCOUNT (line 7) | const crypto = require('./crypto'),
FILE: app/core/MasterPassKey.js
function MasterPassKey (line 14) | function MasterPassKey (key) {
FILE: app/index.js
function createWindow (line 264) | function createWindow (window, ...args) {
FILE: app/src/crypter.js
function encrypt (line 34) | function encrypt(filePath) {
function decrypt (line 48) | function decrypt(filePath) {
function cryptFile (line 72) | function cryptFile(file) {
FILE: app/src/mainMenu.js
method click (line 10) | click() { app.emit('app:check-update') }
method click (line 12) | click() { app.emit('app:open-settings') }
method click (line 14) | click() { app.emit('app:quit') }
FILE: app/src/menu.js
method click (line 41) | click() { shell.openExternal(REPO.DOCS)}
method click (line 43) | click() { shell.openExternal(REPO.REPORT_ISSUE)}
method click (line 44) | click() { shell.openExternal(REPO.URL)}
method click (line 45) | click() { shell.openExternal(REPO.FORK)}
FILE: app/static/js/common.js
function getUrlParameter (line 19) | function getUrlParameter(name) {
function navigate (line 26) | function navigate (panel) {
function validateMasterPass (line 33) | function validateMasterPass(field, errLabel) {
FILE: app/static/js/crypter.js
function disableFileInput (line 75) | function disableFileInput () {
function enableFileInput (line 82) | function enableFileInput () {
function enableUI (line 95) | function enableUI () {
function disableUI (line 100) | function disableUI () {
function showFile (line 105) | function showFile (path) {
function showOpenDialog (line 109) | function showOpenDialog (properties) {
function handler (line 132) | function handler () {
FILE: app/static/js/masterpassprompt.js
constant DONE_TIMEOUT (line 7) | const DONE_TIMEOUT = 2000
FILE: app/static/js/setup.js
constant NAV2DONE_TIMEOUT (line 8) | const NAV2DONE_TIMEOUT = 2000
constant DONE_TIMEOUT (line 10) | const DONE_TIMEOUT = NAV2DONE_TIMEOUT + 5000
constant SPEED (line 12) | const SPEED = 3000
constant OFFSET (line 13) | const OFFSET = SPEED * 1.1
FILE: app/utils/update.js
constant USER_AGENT (line 4) | const USER_AGENT = 'Crypter/x Wubba Lubba Dub Dub'
constant VERSION_REGEX (line 5) | const VERSION_REGEX = /[\.v]+/g
constant VERSION (line 6) | const VERSION = parseV(app.getVersion())
function parseV (line 8) | function parseV(str) {
FILE: gulpfile.js
constant LESS_FILES (line 13) | const LESS_FILES = './app/static/styles/*.less'
constant ELECTRON (line 14) | const ELECTRON = __dirname + '/node_modules/.bin/electron'
constant DEBUG (line 15) | const DEBUG = false
function electron (line 19) | async function electron () {
FILE: script/resolveNodeV.js
constant FILE_PATH (line 3) | const FILE_PATH = '.nvmrc'
Condensed preview — 63 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (223K chars).
[
{
"path": ".babelrc",
"chars": 38,
"preview": "{\n \"presets\": [\"@babel/preset-env\"]\n}"
},
{
"path": ".codeclimate.yml",
"chars": 676,
"preview": "engines:\n csslint:\n enabled: false\n duplication:\n enabled: true\n config:\n languages:\n - javascript\n"
},
{
"path": ".github/ISSUE_TEMPLATE.md",
"chars": 961,
"preview": "### Prerequisites\n\n* [ ] Can you reproduce the problem?\n* [ ] Did you read the entire [README.md](https://github.com/HR/"
},
{
"path": ".gitignore",
"chars": 1247,
"preview": "### OSX ###\n.DS_Store\n.AppleDouble\n.LSOverride\n\n# dev\ndest/\n.cred\ntest/specs\ntest/render.js\n.env\nspec/\ndeprecated/\nscree"
},
{
"path": ".nvmrc",
"chars": 7,
"preview": "12.14.1"
},
{
"path": ".travis.yml",
"chars": 1310,
"preview": "branches:\n only:\n - master\n\nmatrix:\n include:\n - os: linux\n services: docker\n sudo: required\n dis"
},
{
"path": "SECURITY.md",
"chars": 322,
"preview": "# Security Policy\n\n## Supported Versions\n\nThe following versions will receive security updates.\n\n| Version | Supported "
},
{
"path": "app/config.js",
"chars": 3293,
"preview": "'use strict'\n/**\n * config.js\n * Provides all essential config constants\n ******************************/\n\n// Fixed cons"
},
{
"path": "app/core/Db.js",
"chars": 4430,
"preview": "'use strict'\n/**\n * Db.js\n * Custom JSON DB API implementation\n ******************************/\n\nconst _ = require('loda"
},
{
"path": "app/core/MasterPass.js",
"chars": 2152,
"preview": "'use strict'\n/**\n * MasterPass.js\n * MasterPass functionality\n ******************************/\n\nconst crypto = require('"
},
{
"path": "app/core/MasterPassKey.js",
"chars": 1357,
"preview": "'use strict'\n/**\n * MasterPass.js\n * Provides a way to securely set and retrieve the MasterPass globally\n * MasterPassKe"
},
{
"path": "app/core/crypto.js",
"chars": 11415,
"preview": "'use strict'\n/**\n * crypto.js\n * Provides the crypto functionality required\n ******************************/\n\nconst fs ="
},
{
"path": "app/index.js",
"chars": 7317,
"preview": "'use strict'\n/**\n * index.js\n * Entry point for app execution\n ******************************/\n\nconst { app, dialog, Bro"
},
{
"path": "app/package.json",
"chars": 886,
"preview": "{\n \"name\": \"Crypter\",\n \"productName\": \"Crypter\",\n \"version\": \"5.0.0\",\n \"description\": \"An innovative, convenient and"
},
{
"path": "app/src/crypter.js",
"chars": 2831,
"preview": "const { app, ipcMain, Menu, BrowserWindow } = require('electron')\nconst { VIEWS, ERRORS, WINDOW_OPTS } = require('../con"
},
{
"path": "app/src/mainMenu.js",
"chars": 584,
"preview": "const {app, shell} = require('electron')\nconst menu = require('./menu')\n\nif (process.platform === 'darwin') {\n menu.uns"
},
{
"path": "app/src/masterPassPrompt.js",
"chars": 3708,
"preview": "const { ipcMain, Menu, BrowserWindow } = require('electron')\nconst { VIEWS, WINDOW_OPTS } = require('../config')\nconst M"
},
{
"path": "app/src/menu.js",
"chars": 916,
"preview": "const {app, shell} = require('electron')\nconst {REPO} = require('../config')\n\nmodule.exports = [\n {\n label: 'Edit',\n"
},
{
"path": "app/src/settings.js",
"chars": 2498,
"preview": "const {app, ipcMain, Menu, BrowserWindow} = require('electron')\nconst menuTemplate = require('./menu')\nconst {CRYPTO, VI"
},
{
"path": "app/src/setup.js",
"chars": 2340,
"preview": "const {app, ipcMain, Menu, BrowserWindow} = require('electron')\nconst {VIEWS, WINDOW_OPTS} = require('../config')\nconst "
},
{
"path": "app/static/crypter.html",
"chars": 3574,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"utf-8\" />\n <link rel=\"stylesheet\" href=\"styles/crypter.css\" />\n "
},
{
"path": "app/static/fonts/LICENSE.txt",
"chars": 11560,
"preview": "\r\n Apache License\r\n Version 2.0, January 2004\r\n "
},
{
"path": "app/static/js/common.js",
"chars": 2987,
"preview": "'use strict'\n/**\n * common.js\n * Contains all the functionality that is common between the views\n **********************"
},
{
"path": "app/static/js/crypter.js",
"chars": 4140,
"preview": "'use strict'\n/**\n * crypter.js\n * Contains scripts for crypter.html\n ******************************/\nconst dialog = remo"
},
{
"path": "app/static/js/masterpassprompt.js",
"chars": 2092,
"preview": "'use strict'\n/**\n * masterpassprompt.js\n * Contains scripts for masterpassprompt.html\n ******************************/\n\n"
},
{
"path": "app/static/js/settings.js",
"chars": 2360,
"preview": "'use strict'\n/**\n * settings.js\n * Contains scripts for settings.html\n ******************************/\n\nconst dialog = r"
},
{
"path": "app/static/js/setup.js",
"chars": 2066,
"preview": "'use strict'\n/**\n * setup.js\n * Contains scripts for setup.html\n ******************************/\n\n// Delay (in ms) after"
},
{
"path": "app/static/masterpassprompt.html",
"chars": 2326,
"preview": "<!doctype html>\n<html>\n\n <head>\n <meta charset=\"utf-8\">\n <link rel=\"stylesheet\" href=\"styles/masterpassprompt.css"
},
{
"path": "app/static/settings.html",
"chars": 4117,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"utf-8\" />\n <link rel=\"stylesheet\" href=\"styles/settings.css\" />\n "
},
{
"path": "app/static/setup.html",
"chars": 4285,
"preview": "<!doctype html>\n<html>\n\n <head>\n <meta charset=\"utf-8\">\n <link rel=\"stylesheet\" href=\"styles/setup.css\" charset=\""
},
{
"path": "app/static/styles/crypter.css",
"chars": 7667,
"preview": "@import \"../../node_modules/normalize.css/normalize.css\";body,p{margin:0}.button,body,button{background-color:#FFF;width"
},
{
"path": "app/static/styles/crypter.less",
"chars": 3517,
"preview": "// out: ./crypter.css, compress: true\n/*\n\t MasterPassPrompt styles\n\t ==================================================="
},
{
"path": "app/static/styles/masterpassprompt.css",
"chars": 6443,
"preview": "@import \"../../node_modules/normalize.css/normalize.css\";body,p{margin:0}.button,body,button{background-color:#FFF;width"
},
{
"path": "app/static/styles/masterpassprompt.less",
"chars": 2320,
"preview": "// out: ./masterpassprompt.css, compress: true\n/*\n\t MasterPassPrompt styles\n\t =========================================="
},
{
"path": "app/static/styles/mixins.css",
"chars": 5298,
"preview": "@import \"../../node_modules/normalize.css/normalize.css\";body,p{margin:0}.button,body,button{background-color:#FFF;width"
},
{
"path": "app/static/styles/mixins.less",
"chars": 7100,
"preview": "// out: ./mixins.css, compress: true\n@import (css) \"../../node_modules/normalize.css/normalize.css\";\n\n/* theme colors */"
},
{
"path": "app/static/styles/settings.css",
"chars": 7921,
"preview": "@import \"../../node_modules/normalize.css/normalize.css\";body,p{margin:0}.fancy,button.fancy{animation:OrangeAnimGrad 3s"
},
{
"path": "app/static/styles/settings.less",
"chars": 4242,
"preview": "// out: ./settings.css, compress: true\n/* Settings styles\n\t ============================================================"
},
{
"path": "app/static/styles/setup.css",
"chars": 8173,
"preview": "@import \"../../node_modules/normalize.css/normalize.css\";body,p{margin:0}.button,body,button{background-color:#FFF;width"
},
{
"path": "app/static/styles/setup.less",
"chars": 5752,
"preview": "// out: ./setup.css, compress: true\n/* Setup styles\n\t =================================================================="
},
{
"path": "app/utils/logger.js",
"chars": 1203,
"preview": "'use strict'\n/**\n * logger.js\n * Custom logger for debugging\n ******************************/\nconst { createLogger, form"
},
{
"path": "app/utils/update.js",
"chars": 1685,
"preview": "const https = require('https')\nconst { app, dialog, shell } = require('electron')\nconst { REPO } = require('../config')\n"
},
{
"path": "app/utils/utils.js",
"chars": 453,
"preview": "const { extname } = require('path')\nconst { CRYPTO } = require('../config')\n\nmodule.exports = {\n isRenderer: () => {\n "
},
{
"path": "appveyor.yml",
"chars": 667,
"preview": "version: \"{build}\"\n\nplatform:\n - x64\n\nimage: Visual Studio 2019\n\n\ncache:\n - node_modules\n - app\\node_modules\n - '%AP"
},
{
"path": "build/license.txt",
"chars": 1110,
"preview": "The MIT License (MIT)\n\nCopyright (c) Habib Rehman <H@Rehman.email> (https://git.io/HR)\n\nPermission is hereby granted, fr"
},
{
"path": "gulpfile.js",
"chars": 3294,
"preview": "const gulp = require('gulp')\nconst shell = require('child_process').exec\nconst spawn = require('child_process').spawn\nco"
},
{
"path": "license",
"chars": 1110,
"preview": "The MIT License (MIT)\n\nCopyright (c) Habib Rehman <H@Rehman.email> (https://git.io/HR)\n\nPermission is hereby granted, fr"
},
{
"path": "package.json",
"chars": 4491,
"preview": "{\n \"name\": \"Crypter\",\n \"productName\": \"Crypter\",\n \"version\": \"5.0.0\",\n \"description\": \"An innovative, convenient and"
},
{
"path": "readme.md",
"chars": 22469,
"preview": "<h1 align=\"center\">\n <br>\n <a href=\"https://github.com/HR/Crypter\"><img src=\"./build/icons/256x256.png\" alt=\"Crypter\" "
},
{
"path": "script/appveyor-build.cmd",
"chars": 382,
"preview": "@setlocal enableextensions enabledelayedexpansion\n@echo off\nget-host\nset keyword=build\n\nIf NOT %APPVEYOR_REPO_COMMIT_MES"
},
{
"path": "script/build.cmd",
"chars": 156,
"preview": "echo Building Crypter\nREM cd %APPVEYOR_BUILD_FOLDER%\nset NODE_ENV=production\nnpm install electron-builder@next -g\nnpm in"
},
{
"path": "script/build_ml.sh",
"chars": 824,
"preview": "#!/bin/bash\n\necho \"Building for: $TRAVIS_OS_NAME\"\necho \"CWD: $PWD\"\necho \"Node $(node --version)\"\necho \"NPM $(npm --versi"
},
{
"path": "script/darwin-build.sh",
"chars": 515,
"preview": "echo \"pwd: \"$PWD\nexport NODE_ENV=production\n\n# remove any existing distribution\nrm -rf dest\n\nnpm install --production\n\ne"
},
{
"path": "script/resolveNodeV.js",
"chars": 296,
"preview": "const {app} = require('electron')\nconst fs = require('fs-extra')\nconst FILE_PATH = '.nvmrc'\nconsole.log(\"Electron node v"
},
{
"path": "script/test.cmd",
"chars": 98,
"preview": "node --version\nnpm --version\nREM install all deps\nnpm install --no-optional\nREM run tests\nnpm test"
},
{
"path": "script/travis-build.sh",
"chars": 565,
"preview": "#!/bin/bash\n\n# set -ev\n# Just exit when fail dont print code to be exec\nset +e\n\nexport TEST_RUN=true\necho \"CC: $CC\"\necho"
},
{
"path": "script/win-build.sh",
"chars": 812,
"preview": "echo \"pwd: \"$PWD\nexport NODE_ENV=production\nexport DISPLAY=':99.0'\n\n# remove any existing distribution\nrm -rf dest\n\n# in"
},
{
"path": "test/test.js",
"chars": 20832,
"preview": "'use strict'\nconst assert = require('assert')\nconst path = require('path')\nconst expect = require('chai')\n .expect\ncons"
},
{
"path": "test/ui/test.js",
"chars": 5754,
"preview": "'use strict'\nconst Application = require('spectron').Application\nconst assert = require('assert')\nconst chai = require('"
}
]
// ... and 4 more files (download for full content)
About this extraction
This page contains the full source code of the HR/Crypter GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 63 files (208.0 KB), approximately 56.4k tokens, and a symbol index with 40 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.