├── .nvmrc ├── .babelrc ├── .github ├── Settings_screen.png ├── Welcome_screen.png ├── MasterPass_screen.png ├── Crypter_main_screen.png ├── MasterPass_reset_screen.png ├── MasterPass_set_screen.png └── ISSUE_TEMPLATE.md ├── app ├── static │ ├── fonts │ │ ├── Roboto-Bold.eot │ │ ├── Roboto-Bold.ttf │ │ ├── Roboto-Thin.eot │ │ ├── Roboto-Thin.ttf │ │ ├── Roboto-Black.eot │ │ ├── Roboto-Black.ttf │ │ ├── Roboto-Black.woff │ │ ├── Roboto-Bold.woff │ │ ├── Roboto-Italic.eot │ │ ├── Roboto-Italic.ttf │ │ ├── Roboto-Italic.woff │ │ ├── Roboto-Light.eot │ │ ├── Roboto-Light.ttf │ │ ├── Roboto-Light.woff │ │ ├── Roboto-Medium.eot │ │ ├── Roboto-Medium.ttf │ │ ├── Roboto-Medium.woff │ │ ├── Roboto-Regular.eot │ │ ├── Roboto-Regular.ttf │ │ ├── Roboto-Thin.woff │ │ ├── Roboto-Regular.woff │ │ ├── Roboto-BlackItalic.eot │ │ ├── Roboto-BlackItalic.ttf │ │ ├── Roboto-BlackItalic.woff │ │ ├── Roboto-BoldItalic.eot │ │ ├── Roboto-BoldItalic.ttf │ │ ├── Roboto-BoldItalic.woff │ │ ├── Roboto-LightItalic.eot │ │ ├── Roboto-LightItalic.ttf │ │ ├── Roboto-LightItalic.woff │ │ ├── Roboto-MediumItalic.eot │ │ ├── Roboto-MediumItalic.ttf │ │ ├── Roboto-ThinItalic.eot │ │ ├── Roboto-ThinItalic.ttf │ │ ├── Roboto-ThinItalic.woff │ │ ├── Roboto-MediumItalic.woff │ │ └── LICENSE.txt │ ├── images │ │ └── icons │ │ │ ├── code.svg │ │ │ ├── star.svg │ │ │ ├── issue-opened.svg │ │ │ ├── heart.svg │ │ │ ├── back.svg │ │ │ ├── eye.svg │ │ │ ├── package.svg │ │ │ ├── done.svg │ │ │ ├── repo-forked.svg │ │ │ ├── mark-github.svg │ │ │ ├── masterpass.svg │ │ │ ├── info.svg │ │ │ ├── Decrypted.svg │ │ │ ├── crypto.svg │ │ │ ├── crypt.svg │ │ │ ├── settings.svg │ │ │ ├── Crypter.svg │ │ │ └── Encrypted.svg │ ├── js │ │ ├── setup.js │ │ ├── masterpassprompt.js │ │ ├── settings.js │ │ ├── common.js │ │ └── crypter.js │ ├── masterpassprompt.html │ ├── styles │ │ ├── masterpassprompt.less │ │ ├── crypter.less │ │ ├── mixins.css │ │ ├── settings.less │ │ ├── masterpassprompt.css │ │ ├── crypter.css │ │ ├── setup.less │ │ ├── settings.css │ │ ├── setup.css │ │ └── mixins.less │ ├── crypter.html │ ├── settings.html │ └── setup.html ├── utils │ ├── utils.js │ ├── logger.js │ └── update.js ├── src │ ├── mainMenu.js │ ├── menu.js │ ├── setup.js │ ├── settings.js │ ├── crypter.js │ └── masterPassPrompt.js ├── package.json ├── core │ ├── MasterPassKey.js │ ├── MasterPass.js │ ├── Db.js │ └── crypto.js ├── config.js └── index.js ├── script ├── test.cmd ├── build.cmd ├── resolveNodeV.js ├── appveyor-build.cmd ├── darwin-build.sh ├── travis-build.sh ├── win-build.sh └── build_ml.sh ├── SECURITY.md ├── .codeclimate.yml ├── appveyor.yml ├── license ├── .travis.yml ├── .gitignore ├── gulpfile.js ├── package.json └── test └── ui └── test.js /.nvmrc: -------------------------------------------------------------------------------- 1 | 12.14.1 -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"] 3 | } -------------------------------------------------------------------------------- /.github/Settings_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hr/Crypter/master/.github/Settings_screen.png -------------------------------------------------------------------------------- /.github/Welcome_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hr/Crypter/master/.github/Welcome_screen.png -------------------------------------------------------------------------------- /.github/MasterPass_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hr/Crypter/master/.github/MasterPass_screen.png -------------------------------------------------------------------------------- /.github/Crypter_main_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hr/Crypter/master/.github/Crypter_main_screen.png -------------------------------------------------------------------------------- /app/static/fonts/Roboto-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hr/Crypter/master/app/static/fonts/Roboto-Bold.eot -------------------------------------------------------------------------------- /app/static/fonts/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hr/Crypter/master/app/static/fonts/Roboto-Bold.ttf -------------------------------------------------------------------------------- /app/static/fonts/Roboto-Thin.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hr/Crypter/master/app/static/fonts/Roboto-Thin.eot -------------------------------------------------------------------------------- /app/static/fonts/Roboto-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hr/Crypter/master/app/static/fonts/Roboto-Thin.ttf -------------------------------------------------------------------------------- /.github/MasterPass_reset_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hr/Crypter/master/.github/MasterPass_reset_screen.png -------------------------------------------------------------------------------- /.github/MasterPass_set_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hr/Crypter/master/.github/MasterPass_set_screen.png -------------------------------------------------------------------------------- /app/static/fonts/Roboto-Black.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hr/Crypter/master/app/static/fonts/Roboto-Black.eot -------------------------------------------------------------------------------- /app/static/fonts/Roboto-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hr/Crypter/master/app/static/fonts/Roboto-Black.ttf -------------------------------------------------------------------------------- /app/static/fonts/Roboto-Black.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hr/Crypter/master/app/static/fonts/Roboto-Black.woff -------------------------------------------------------------------------------- /app/static/fonts/Roboto-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hr/Crypter/master/app/static/fonts/Roboto-Bold.woff -------------------------------------------------------------------------------- /app/static/fonts/Roboto-Italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hr/Crypter/master/app/static/fonts/Roboto-Italic.eot -------------------------------------------------------------------------------- /app/static/fonts/Roboto-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hr/Crypter/master/app/static/fonts/Roboto-Italic.ttf -------------------------------------------------------------------------------- /app/static/fonts/Roboto-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hr/Crypter/master/app/static/fonts/Roboto-Italic.woff -------------------------------------------------------------------------------- /app/static/fonts/Roboto-Light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hr/Crypter/master/app/static/fonts/Roboto-Light.eot -------------------------------------------------------------------------------- /app/static/fonts/Roboto-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hr/Crypter/master/app/static/fonts/Roboto-Light.ttf -------------------------------------------------------------------------------- /app/static/fonts/Roboto-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hr/Crypter/master/app/static/fonts/Roboto-Light.woff -------------------------------------------------------------------------------- /app/static/fonts/Roboto-Medium.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hr/Crypter/master/app/static/fonts/Roboto-Medium.eot -------------------------------------------------------------------------------- /app/static/fonts/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hr/Crypter/master/app/static/fonts/Roboto-Medium.ttf -------------------------------------------------------------------------------- /app/static/fonts/Roboto-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hr/Crypter/master/app/static/fonts/Roboto-Medium.woff -------------------------------------------------------------------------------- /app/static/fonts/Roboto-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hr/Crypter/master/app/static/fonts/Roboto-Regular.eot -------------------------------------------------------------------------------- /app/static/fonts/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hr/Crypter/master/app/static/fonts/Roboto-Regular.ttf -------------------------------------------------------------------------------- /app/static/fonts/Roboto-Thin.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hr/Crypter/master/app/static/fonts/Roboto-Thin.woff -------------------------------------------------------------------------------- /app/static/fonts/Roboto-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hr/Crypter/master/app/static/fonts/Roboto-Regular.woff -------------------------------------------------------------------------------- /app/static/fonts/Roboto-BlackItalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hr/Crypter/master/app/static/fonts/Roboto-BlackItalic.eot -------------------------------------------------------------------------------- /app/static/fonts/Roboto-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hr/Crypter/master/app/static/fonts/Roboto-BlackItalic.ttf -------------------------------------------------------------------------------- /app/static/fonts/Roboto-BlackItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hr/Crypter/master/app/static/fonts/Roboto-BlackItalic.woff -------------------------------------------------------------------------------- /app/static/fonts/Roboto-BoldItalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hr/Crypter/master/app/static/fonts/Roboto-BoldItalic.eot -------------------------------------------------------------------------------- /app/static/fonts/Roboto-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hr/Crypter/master/app/static/fonts/Roboto-BoldItalic.ttf -------------------------------------------------------------------------------- /app/static/fonts/Roboto-BoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hr/Crypter/master/app/static/fonts/Roboto-BoldItalic.woff -------------------------------------------------------------------------------- /app/static/fonts/Roboto-LightItalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hr/Crypter/master/app/static/fonts/Roboto-LightItalic.eot -------------------------------------------------------------------------------- /app/static/fonts/Roboto-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hr/Crypter/master/app/static/fonts/Roboto-LightItalic.ttf -------------------------------------------------------------------------------- /app/static/fonts/Roboto-LightItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hr/Crypter/master/app/static/fonts/Roboto-LightItalic.woff -------------------------------------------------------------------------------- /app/static/fonts/Roboto-MediumItalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hr/Crypter/master/app/static/fonts/Roboto-MediumItalic.eot -------------------------------------------------------------------------------- /app/static/fonts/Roboto-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hr/Crypter/master/app/static/fonts/Roboto-MediumItalic.ttf -------------------------------------------------------------------------------- /app/static/fonts/Roboto-ThinItalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hr/Crypter/master/app/static/fonts/Roboto-ThinItalic.eot -------------------------------------------------------------------------------- /app/static/fonts/Roboto-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hr/Crypter/master/app/static/fonts/Roboto-ThinItalic.ttf -------------------------------------------------------------------------------- /app/static/fonts/Roboto-ThinItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hr/Crypter/master/app/static/fonts/Roboto-ThinItalic.woff -------------------------------------------------------------------------------- /app/static/fonts/Roboto-MediumItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hr/Crypter/master/app/static/fonts/Roboto-MediumItalic.woff -------------------------------------------------------------------------------- /script/test.cmd: -------------------------------------------------------------------------------- 1 | node --version 2 | npm --version 3 | REM install all deps 4 | npm install --no-optional 5 | REM run tests 6 | npm test -------------------------------------------------------------------------------- /script/build.cmd: -------------------------------------------------------------------------------- 1 | echo Building Crypter 2 | REM cd %APPVEYOR_BUILD_FOLDER% 3 | set NODE_ENV=production 4 | npm install electron-builder@next -g 5 | npm install --production 6 | npm run build:win -------------------------------------------------------------------------------- /script/resolveNodeV.js: -------------------------------------------------------------------------------- 1 | const {app} = require('electron') 2 | const fs = require('fs-extra') 3 | const FILE_PATH = '.nvmrc' 4 | console.log("Electron node version is: "+process.versions.node) 5 | fs.writeFile(FILE_PATH, process.versions.node, function (err) { 6 | if (err) return console.log('Error writing file: ' + err) 7 | app.quit() 8 | }) 9 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | The following versions will receive security updates. 6 | 7 | | Version | Supported | 8 | | ------- | ------------------ | 9 | | 4.x | :white_check_mark: | 10 | | < 4.0 | :x: | 11 | 12 | ## Reporting a Vulnerability 13 | 14 | Please report a vulnerbility directly to me at h@rehman.email 15 | -------------------------------------------------------------------------------- /script/appveyor-build.cmd: -------------------------------------------------------------------------------- 1 | @setlocal enableextensions enabledelayedexpansion 2 | @echo off 3 | get-host 4 | set keyword=build 5 | 6 | If NOT %APPVEYOR_REPO_COMMIT_MESSAGE:build=%==x%APPVEYOR_REPO_COMMIT_MESSAGE% ( 7 | REM Commit message contains '[build]' so build 8 | echo Building Crypter 9 | build.cmd 10 | ) else ( 11 | REM Commit message does not contain '[build]' so just test and skip build 12 | echo Testing Crypter 13 | test.cmd 14 | ) 15 | endlocal -------------------------------------------------------------------------------- /app/utils/utils.js: -------------------------------------------------------------------------------- 1 | const { extname } = require('path') 2 | const { CRYPTO } = require('../config') 3 | 4 | module.exports = { 5 | isRenderer: () => { 6 | // running in a web browser 7 | if (typeof process === 'undefined') return true 8 | 9 | // node-integration is disabled 10 | if (!process) return true 11 | 12 | // We're in node.js somehow 13 | if (!process.type) return false 14 | 15 | return process.type === 'renderer' 16 | }, 17 | isCryptoFile: (file) => extname(file).toLowerCase() === CRYPTO.EXT 18 | } -------------------------------------------------------------------------------- /script/darwin-build.sh: -------------------------------------------------------------------------------- 1 | echo "pwd: "$PWD 2 | export NODE_ENV=production 3 | 4 | # remove any existing distribution 5 | rm -rf dest 6 | 7 | npm install --production 8 | 9 | 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 10 | cp ./github/RELEASE ./dest/Crypter-darwin-x64/RELEASE 11 | cp ./license ./dest/Crypter-darwin-x64 12 | zip -9r ./dest/Crypter-darwin-x64 ./dest/Crypter-darwin-x64 13 | -------------------------------------------------------------------------------- /app/static/images/icons/code.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/static/images/icons/star.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/mainMenu.js: -------------------------------------------------------------------------------- 1 | const {app, shell} = require('electron') 2 | const menu = require('./menu') 3 | 4 | if (process.platform === 'darwin') { 5 | menu.unshift({ 6 | label: 'Crypter', 7 | submenu: [ 8 | { label: 'About Crypter', role: 'about' }, 9 | { label: `Version ${app.getVersion()}`, enabled: false }, 10 | { label: 'Check for Update', click() { app.emit('app:check-update') } }, 11 | { type: 'separator' }, 12 | { label: 'Preferences…', click() { app.emit('app:open-settings') } }, 13 | { type: 'separator' }, 14 | { label: 'Quit', click() { app.emit('app:quit') } } 15 | ] 16 | }) 17 | } 18 | 19 | module.exports = menu 20 | -------------------------------------------------------------------------------- /app/static/images/icons/issue-opened.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /script/travis-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # set -ev 4 | # Just exit when fail dont print code to be exec 5 | set +e 6 | 7 | export TEST_RUN=true 8 | echo "CC: $CC" 9 | echo "CXX: $CXX" 10 | echo "OS Name: $TRAVIS_OS_NAME" 11 | echo "Node $(node --version)" 12 | echo "NPM $(npm --version)" 13 | 14 | # Install deps 15 | yarn install --ignore-optional 16 | yarn prune 17 | 18 | # Test and get coverage 19 | yarn run coverage 20 | yarn run coveralls 21 | # - npm run codeclimate 22 | 23 | # End-to-end OSX testing 24 | # if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then 25 | # export DISPLAY=:99.0 26 | # sh -e /etc/init.d/xvfb start 27 | # sleep 3 28 | # unset TEST_RUN 29 | # npm run xtest 30 | # fi 31 | 32 | unset TEST_RUN 33 | -------------------------------------------------------------------------------- /app/static/images/icons/heart.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | engines: 2 | csslint: 3 | enabled: false 4 | duplication: 5 | enabled: true 6 | config: 7 | languages: 8 | - javascript 9 | exclude_fingerprints: 10 | - faca6a53a5a4ae580279527e261dd1da 11 | - 555c80119b06560cd2e96eb4e4e91f26 12 | - a81ed3d8cb6b36bacb95a0ab40d09d78 13 | - 9af23dc9a75d6c11f28122e9f83e7309 14 | - faca6a53a5a4ae580279527e261dd1da 15 | - fcc5dd7fd381cab75901402345e66511 16 | - 144b552ca7471e150abb4ece59240e71 17 | - 63a2186b2e759c9b5f6afb31965ab290 18 | - 77a83a6a06048f2bda07f8c15c65c9ea 19 | eslint: 20 | enabled: true 21 | fixme: 22 | enabled: true 23 | ratings: 24 | paths: 25 | - "**.css" 26 | - "**.js" 27 | exclude_paths: 28 | - test/**/* 29 | - "static/js/jquery.marquee.min.js" 30 | -------------------------------------------------------------------------------- /app/static/images/icons/back.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/static/images/icons/eye.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: "{build}" 2 | 3 | platform: 4 | - x64 5 | 6 | image: Visual Studio 2019 7 | 8 | 9 | cache: 10 | - node_modules 11 | - app\node_modules 12 | - '%APPDATA%\npm-cache' 13 | - '%USERPROFILE%\.electron' 14 | 15 | init: 16 | - git config --global core.autocrlf input 17 | 18 | # Only build if commit message contains '[build]' 19 | only_commits: 20 | message: /\[build\]/ 21 | 22 | install: 23 | - set /p NODE_VERSION=<.nvmrc 24 | - ps: Install-Product node $env:NODE_VERSION $env:PLATFORM 25 | - npm install npm -g 26 | - npm install electron-builder@next -g 27 | - npm install --production 28 | - npm prune 29 | 30 | build_script: 31 | - node --version 32 | - npm --version 33 | - npm run build:win 34 | 35 | # Post-install test scripts. 36 | # test_script: 37 | # - script\test.cmd 38 | test: off -------------------------------------------------------------------------------- /app/static/images/icons/package.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /script/win-build.sh: -------------------------------------------------------------------------------- 1 | echo "pwd: "$PWD 2 | export NODE_ENV=production 3 | export DISPLAY=':99.0' 4 | 5 | # remove any existing distribution 6 | rm -rf dest 7 | 8 | # install deps 9 | sudo apt-get wine 10 | sudo dpkg --add-architecture i386 11 | sudo add-apt-repository ppa:wine/wine-builds 12 | sudo apt-get update 13 | sudo apt-get install --install-recommends winehq-devel 14 | sudo apt-get install -y xvfb 15 | 16 | # start Xvfb server 17 | Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 18 | 19 | npm install --production 20 | 21 | ./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 22 | cp ./github/RELEASE ./dest/Crypter-win32-x64/RELEASE 23 | cp ./license ./dest/Crypter-win32-x64 24 | zip -r ./dest/Crypter-win32-x64 ./dest/Crypter-win32-x64 25 | -------------------------------------------------------------------------------- /script/build_ml.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Building for: $TRAVIS_OS_NAME" 4 | echo "CWD: $PWD" 5 | echo "Node $(node --version)" 6 | echo "NPM $(npm --version)" 7 | 8 | # make for production 9 | export NODE_ENV=production 10 | npm install electron-builder -g 11 | npm prune 12 | 13 | if [ "$TRAVIS_OS_NAME" == "linux" ]; then 14 | # to build for linux 15 | echo "Building for linux" 16 | docker run --rm \ 17 | --env-file <(env | grep -iE 'DEBUG|NODE_|ELECTRON_|YARN_|NPM_|CI|CIRCLE|TRAVIS|APPVEYOR_|CSC_|_TOKEN|_KEY|AWS_|STRIP|BUILD_') \ 18 | -v ${PWD}:/project \ 19 | -v ~/.cache/electron:/root/.cache/electron \ 20 | -v ~/.cache/electron-builder:/root/.cache/electron-builder \ 21 | electronuserland/builder:wine \ 22 | /bin/bash -c "npm i -g electron-builder && yarn run build:lin" 23 | else 24 | echo "Building for mac" 25 | yarn run build:mac 26 | fi 27 | # zip -r dist/**/*.zip ./github/RELEASE 28 | -------------------------------------------------------------------------------- /app/static/images/icons/done.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | 15 | -------------------------------------------------------------------------------- /app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Crypter", 3 | "productName": "Crypter", 4 | "version": "5.0.0", 5 | "description": "An innovative, convenient and secure cross-platform crypto app", 6 | "license": "MIT", 7 | "repository": "https://github.com/HR/Crypter", 8 | "homepage": "https://github.com/HR/Crypter", 9 | "bugs": "https://github.com/HR/Crypter/issues", 10 | "private": true, 11 | "main": "index.js", 12 | "author": { 13 | "name": "Habib Rehman", 14 | "email": "H@Rehman.email", 15 | "url": "https://git.io/HR" 16 | }, 17 | "dependencies": { 18 | "electron-debug": "^3.1.0", 19 | "electron-log": "^4.2.2", 20 | "electron-unhandled": "^3.0.2", 21 | "electron-util": "^0.14.2", 22 | "estraverse": "5.1.0", 23 | "fs-extra": "9.0.1", 24 | "handlebars": "4.7.6", 25 | "jquery": "3.5.1", 26 | "jquery.marquee": "1.5.0", 27 | "keytar": "^6.0.1", 28 | "lodash": "4.17.19", 29 | "moment": "2.27.0", 30 | "normalize.css": "8.0.1", 31 | "tar-fs": "2.1.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/static/images/icons/repo-forked.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Prerequisites 2 | 3 | * [ ] Can you reproduce the problem? 4 | * [ ] Did you read the entire [README.md](https://github.com/HR/Crypter/blob/master/readme.md)? 5 | * [ ] Did you read the [FAQs](https://github.com/HR/Crypter/blob/master/readme.md#faqs)? 6 | * [ ] Do you fully understand how Crypter works and how to use it? 7 | * [ ] Did you check the [issues](https://github.com/HR/Crypter/issues) to see if your bug or enhancement is already reported? 8 | 9 | ### Description 10 | 11 | [Description of the bug or feature] 12 | 13 | ### Steps to Reproduce 14 | 15 | 1. [First Step] 16 | 2. [Second Step] 17 | 3. [and so on...] 18 | 19 | **Expected behavior:** [What you expected to happen] 20 | 21 | **Actual behavior:** [What actually happened] 22 | 23 | ### Versions 24 | 25 | Crypter: 26 | 27 | Release (full release name): 28 | 29 | Operating System (name and version): 30 | 31 | 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```. 32 | -------------------------------------------------------------------------------- /app/static/images/icons/mark-github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/menu.js: -------------------------------------------------------------------------------- 1 | const {app, shell} = require('electron') 2 | const {REPO} = require('../config') 3 | 4 | module.exports = [ 5 | { 6 | label: 'Edit', 7 | submenu: [ 8 | { 9 | role: 'undo' 10 | }, 11 | { 12 | role: 'redo' 13 | }, 14 | { 15 | type: 'separator' 16 | }, 17 | { 18 | role: 'cut' 19 | }, 20 | { 21 | role: 'copy' 22 | }, 23 | { 24 | role: 'paste' 25 | }, 26 | { 27 | role: 'pasteandmatchstyle' 28 | }, 29 | { 30 | role: 'delete' 31 | }, 32 | { 33 | role: 'selectall' 34 | } 35 | ] 36 | }, 37 | { 38 | label: 'Help', 39 | role: 'help', 40 | submenu: [ 41 | { label: 'Documentation', click() { shell.openExternal(REPO.DOCS)} }, 42 | { type: 'separator' }, 43 | { label: 'Report Issue', click() { shell.openExternal(REPO.REPORT_ISSUE)} }, 44 | { label: 'Star Crypter', click() { shell.openExternal(REPO.URL)} }, 45 | { label: 'Contribute', click() { shell.openExternal(REPO.FORK)} }, 46 | ] 47 | } 48 | ] 49 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Habib Rehman (https://git.io/HR) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished TODO so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/static/images/icons/masterpass.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 9 | 11 | 12 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/utils/logger.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /** 3 | * logger.js 4 | * Custom logger for debugging 5 | ******************************/ 6 | const { createLogger, format, transports } = require('winston') 7 | const { isRenderer } = require('./utils') 8 | 9 | // const { app } = require('electron') 10 | if (process.env.TEST_RUN || isRenderer()) { 11 | module.exports = createLogger({ 12 | silent: true, 13 | exitOnError: false 14 | }) 15 | } else { 16 | const moment = require('moment') 17 | const fs = require('fs-extra') 18 | // const IN_DEV = !app.isPackaged 19 | let debugDir = `${global.paths.userData}/debug` 20 | fs.ensureDirSync(debugDir) 21 | const fileTransport = new transports.File({ 22 | filename: `${debugDir}/CS_debug_${moment().format('DD.MM@HH:MM').trim()}.log`, 23 | handleExceptions: true, 24 | colorize: false, 25 | level: 'verbose' 26 | }) 27 | 28 | const logFormat = format.combine( 29 | format.colorize(), 30 | format.timestamp(), 31 | format.prettyPrint(), 32 | format.align(), 33 | format.splat(), 34 | format.printf(info => `${info.timestamp} ${info.level}: ${info.message}`) 35 | ) 36 | 37 | const logger = createLogger({ 38 | transports: [ 39 | new transports.Console(), 40 | fileTransport 41 | ], 42 | exitOnError: false, 43 | format: logFormat, 44 | level: 'verbose' 45 | }) 46 | 47 | module.exports = logger 48 | } -------------------------------------------------------------------------------- /app/static/images/icons/info.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 16 | 17 | -------------------------------------------------------------------------------- /app/core/MasterPassKey.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /** 3 | * MasterPass.js 4 | * Provides a way to securely set and retrieve the MasterPass globally 5 | * MasterPassKey is protected (private var) and only exist in Main memory 6 | ******************************/ 7 | 8 | // Uses closure to securely store MasterPassKey in MasterPassKey object 9 | const MasterPassKey = (function () { 10 | // Private mpk variable that stores the MasterPassKey 11 | const mpk = new WeakMap() 12 | 13 | // Class constructor 14 | function MasterPassKey (key) { 15 | // Initialise the new instance of class with the MasterPassKey 16 | mpk.set(this, key) 17 | } 18 | 19 | // Public get method the for mpk 20 | MasterPassKey.prototype.get = function () { 21 | if (mpk.get(this) === undefined) { 22 | // If MasterPassKey not set or delected 23 | return new Error('MasterPassKey is not set') 24 | } else { 25 | // If MasterPassKey is set then return it 26 | return mpk.get(this) 27 | } 28 | } 29 | 30 | // Public set method the for mpk 31 | MasterPassKey.prototype.set = function (key) { 32 | if (key instanceof Buffer) { 33 | // If the key is a Buffer then set it 34 | mpk.set(this, key) 35 | } else { 36 | // If the key is not a Buffer return an erro 37 | return new Error('MasterPassKey not a Buffer') 38 | } 39 | } 40 | 41 | // Delete the MasterPassKey 42 | MasterPassKey.prototype.delete = function (key) { 43 | mpk.delete(this) 44 | } 45 | 46 | return MasterPassKey 47 | }()) 48 | 49 | module.exports = MasterPassKey 50 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | branches: 2 | only: 3 | - master 4 | 5 | matrix: 6 | include: 7 | - os: linux 8 | services: docker 9 | sudo: required 10 | dist: xenial 11 | language: generic 12 | env: 13 | - ELECTRON_CACHE=$HOME/.cache/electron 14 | - ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder 15 | - os: osx 16 | osx_image: xcode12 17 | env: 18 | - ELECTRON_CACHE=$HOME/.cache/electron 19 | - ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder 20 | language: node_js 21 | node_js: 22 | 23 | before_script: 24 | - chmod +x ./script/*.sh 25 | - npm install npm -g 26 | 27 | script: ./script/travis-build.sh 28 | 29 | after_success: 30 | # Cleanup 31 | - git reset --hard HEAD 32 | # Remove untracked and ignored files 33 | - git clean -dfXn && git clean -dfX 34 | - chmod +x ./script/*.sh 35 | # Check if build triggered ([build] in commit message) 36 | - lgcm=$(git log -1 --pretty=%B | xargs echo); 37 | if [[ $lgcm == *"[build]"* ]]; then 38 | ./script/build_ml.sh; 39 | fi 40 | 41 | 42 | before_cache: 43 | - rm -rf $HOME/.cache/electron-builder/wine 44 | 45 | cache: 46 | yarn: true 47 | apt: true 48 | directories: 49 | - node_modules 50 | - $HOME/.cache/electron 51 | - $HOME/.cache/electron-builder 52 | 53 | 54 | notifications: 55 | email: 56 | on_success: never 57 | on_failure: change 58 | 59 | addons: 60 | apt: 61 | sources: 62 | - ubuntu-toolchain-r-test 63 | packages: 64 | - libgnome-keyring-dev 65 | - icnsutils 66 | - xvfb 67 | -------------------------------------------------------------------------------- /app/static/images/icons/Decrypted.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 10 | 11 | 13 | 15 | 17 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### OSX ### 2 | .DS_Store 3 | .AppleDouble 4 | .LSOverride 5 | 6 | # dev 7 | dest/ 8 | .cred 9 | test/specs 10 | test/render.js 11 | .env 12 | spec/ 13 | deprecated/ 14 | screens/ 15 | .crypting/ 16 | .decrypting 17 | *.token 18 | 19 | 20 | # Icon must end with two 21 | Icon 22 | 23 | # Thumbnails 24 | ._* 25 | 26 | # Files that might appear in the root of a volume 27 | .DocumentRevisions-V100 28 | .fseventsd 29 | .Spotlight-V100 30 | .TemporaryItems 31 | .Trashes 32 | .VolumeIcon.icns 33 | 34 | # Directories potentially created on remote AFP share 35 | .AppleDB 36 | .AppleDesktop 37 | Network Trash Folder 38 | Temporary Items 39 | .apdisk 40 | 41 | 42 | ### Node ### 43 | # Logs 44 | logs 45 | *.log 46 | npm-debug.log* 47 | 48 | # Runtime data 49 | pids 50 | *.pid 51 | *.seed 52 | 53 | # Directory for instrumented libs generated by jscoverage/JSCover 54 | lib-cov 55 | 56 | # Coverage directory used by tools like istanbul 57 | coverage 58 | 59 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 60 | .grunt 61 | 62 | # node-waf configuration 63 | .lock-wscript 64 | 65 | # Compiled binary addons (http://nodejs.org/api/addons.html) 66 | build/Release 67 | 68 | # Dependency directories 69 | app/node_modules 70 | node_modules 71 | jspm_packages 72 | 73 | # Optional npm cache directory 74 | .npm 75 | 76 | # Optional REPL history 77 | .node_repl_history 78 | 79 | ### Bower ### 80 | bower_components 81 | .bower-cache 82 | .bower-registry 83 | .bower-tmp 84 | 85 | # Other stuff to ignore 86 | dist/ 87 | *.log 88 | .editorconfig 89 | .Debug 90 | core/elements/*.js 91 | debug 92 | backups/ 93 | *.app 94 | chromedriver* 95 | 96 | .nyc_output/ 97 | -------------------------------------------------------------------------------- /app/static/images/icons/crypto.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 11 | 13 | 15 | 17 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/static/images/icons/crypt.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 8 | 10 | 12 | 14 | 18 | 19 | -------------------------------------------------------------------------------- /app/static/images/icons/settings.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 20 | 21 | -------------------------------------------------------------------------------- /app/utils/update.js: -------------------------------------------------------------------------------- 1 | const https = require('https') 2 | const { app, dialog, shell } = require('electron') 3 | const { REPO } = require('../config') 4 | const USER_AGENT = 'Crypter/x Wubba Lubba Dub Dub' 5 | const VERSION_REGEX = /[\.v]+/g 6 | const VERSION = parseV(app.getVersion()) 7 | 8 | function parseV(str) { 9 | return parseInt(str.replace(VERSION_REGEX, '')) 10 | } 11 | 12 | module.exports = { 13 | checkUpdate: function () { 14 | return new Promise((resolve, reject) => { 15 | https.get(REPO.RELEASES_API_URL, { 16 | headers: { 'User-Agent': USER_AGENT } 17 | }, (res) => { 18 | let data = '' 19 | 20 | res.on('data', (chunk) => { 21 | data += chunk 22 | }) 23 | 24 | res.on('end', () => { 25 | try { 26 | release = JSON.parse(data.toString('utf8')) 27 | const LATEST_VERSION = parseV(release.tag_name) 28 | if (VERSION < LATEST_VERSION) { 29 | dialog.showMessageBox({ 30 | type: 'info', 31 | message: `Update is available.`, 32 | detail: `A new version Crypter ${release.tag_name} is available.\nDo you want to get it?`, 33 | buttons: ['Get update', 'Later'], 34 | defaultId: 0, 35 | cancelId: 1, 36 | icon: null 37 | }, (response) => { 38 | if (response === 0) { 39 | // Update button pressed 40 | shell.openExternal(release.html_url) 41 | } 42 | }) 43 | resolve(true) 44 | } else { 45 | resolve(false) 46 | } 47 | } catch (err) { 48 | reject(err) 49 | } 50 | }) 51 | }) 52 | .on('error', (err) => reject(err)) 53 | }) 54 | } 55 | 56 | } -------------------------------------------------------------------------------- /app/static/js/setup.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /** 3 | * setup.js 4 | * Contains scripts for setup.html 5 | ******************************/ 6 | 7 | // Delay (in ms) after which it navigates to the done panel 8 | const NAV2DONE_TIMEOUT = 2000 9 | // Delay (in ms) after which the setup window is closed 10 | const DONE_TIMEOUT = NAV2DONE_TIMEOUT + 5000 11 | // Animation config 12 | const SPEED = 3000 13 | const OFFSET = SPEED * 1.1 14 | let errLabel 15 | 16 | $(window).on('load', function () { 17 | // Load jQuery marquee plugin 18 | window.$.marquee = window.jQuery.marquee = require('jquery.marquee') 19 | // Get error label 20 | errLabel = $('#setMasterPassLabel') 21 | // When DOM has loaded... hide error label and make red 22 | errLabel.hide().css('color', COLORS.bad) 23 | 24 | // attach click event listener to setMasterPass button 25 | $('#setMasterPass').click(function () { 26 | // Event handler function 27 | validateMasterPass('setMasterPass', errLabel) 28 | }) 29 | 30 | $('#done').click(function () { 31 | // Close setup window and restart app 32 | ipcRenderer.send('done') 33 | }) 34 | 35 | // navigate to welcome screen by default 36 | navigate('welcome') 37 | 38 | /* Encryption animation */ 39 | $('.marquee-1').marquee({direction: 'right', gap: 0, duplicated: true, duration: SPEED}).addClass('visible') 40 | $('.marquee-2').marquee({direction: 'right', gap: 0, duplicated: true, duration: SPEED, delayBeforeStart: OFFSET}) 41 | 42 | setTimeout(function () { 43 | $('.marquee-2').addClass('visible') 44 | }, OFFSET) 45 | }) 46 | 47 | /* Event listeners */ 48 | ipcRenderer.on('setMasterPassResult', function (event, err) { 49 | if (err) { 50 | // If error occured Display the error 51 | errLabel.text(`ERROR: ${err.message}`.toUpperCase()) 52 | errLabel.show() 53 | } else { 54 | // Display the MasterPass set success 55 | errLabel.text(RESPONSES.setSuccess).css('color', COLORS.good).show() 56 | setTimeout(function () { 57 | // Navigate to the done panel after 2 seconds 58 | navigate('done') 59 | }, NAV2DONE_TIMEOUT) 60 | 61 | // 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) 62 | } 63 | }) 64 | -------------------------------------------------------------------------------- /app/static/images/icons/Crypter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 20 | 21 | 22 | Crypter 23 | Created with Sketch. 24 | 25 | 27 | 29 | 31 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/static/js/masterpassprompt.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /** 3 | * masterpassprompt.js 4 | * Contains scripts for masterpassprompt.html 5 | ******************************/ 6 | 7 | const DONE_TIMEOUT = 2000 8 | let errLabelCheckMP, errLabelSetMP, resetMasterPassInput 9 | const reset = getUrlParameter('reset') 10 | 11 | $(window).on('load', function () { 12 | // Get error label element 13 | errLabelCheckMP = $('#checkMasterPassLabel') 14 | errLabelSetMP = $('#setMasterPassLabel') 15 | resetMasterPassInput = $('#resetMasterPassInput') 16 | 17 | if (reset) { 18 | navigate('reset') 19 | $('footer').remove() 20 | } else { 21 | navigate('default') 22 | } 23 | // hide error label and make red 24 | errLabelCheckMP.hide().css('color', COLORS.bad) 25 | 26 | $('#checkMasterPass').click(function () { 27 | validateMasterPass('checkMasterPass', errLabelCheckMP) 28 | }) 29 | 30 | $('#setMasterPass').click(function () { 31 | validateMasterPass('setMasterPass', errLabelSetMP) 32 | // Reset validation when reset 33 | errLabelCheckMP.hide() 34 | }) 35 | }) 36 | 37 | /* Event listeners */ 38 | ipcRenderer.on('setMasterPassResult', function (event, err) { 39 | if (err) { 40 | // If error occured 41 | // Display the error 42 | errLabelSetMP 43 | .text(`ERROR: ${err.message}`.toUpperCase()) 44 | .css('color', COLORS.bad) 45 | .show() 46 | } else { 47 | // Display the MasterPass set success 48 | errLabelSetMP 49 | .text(RESPONSES.setSuccess) 50 | .css('color', COLORS.good) 51 | .show() 52 | // Change note text to give further instruction and higlight it 53 | $('p.note') 54 | .html(RESPONSES.resetSuccess) 55 | .css('color', COLORS.highlight) 56 | 57 | // Close navigate back to chechMP after 5 seconds 58 | setTimeout(function () { 59 | if (reset) { 60 | return app.emit('app:relaunch') 61 | } 62 | navigate('default') 63 | errLabelSetMP.hide() 64 | }, DONE_TIMEOUT) 65 | } 66 | }) 67 | 68 | ipcRenderer.on('checkMasterPassResult', function (event, result) { 69 | if (result.err) { 70 | errLabelCheckMP.text(`ERROR: ${err.message}`).show() 71 | } else if (result.match) { 72 | resetMasterPassInput.hide() 73 | errLabelCheckMP 74 | .text(RESPONSES.correct) 75 | .css('color', COLORS.good) 76 | .show() 77 | } else { 78 | errLabelCheckMP.text(RESPONSES.incorrect).show() 79 | } 80 | }) 81 | -------------------------------------------------------------------------------- /app/core/MasterPass.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /** 3 | * MasterPass.js 4 | * MasterPass functionality 5 | ******************************/ 6 | 7 | const crypto = require('./crypto'), 8 | keytar = require('keytar'), 9 | SERVICE = 'Crypter', 10 | ACCOUNT = 'MasterPass' 11 | 12 | // TODO: Make independent from global obj! use param instead 13 | 14 | exports.init = async () => { 15 | const masterpass = await keytar.getPassword(SERVICE, ACCOUNT) 16 | if (!masterpass) return false 17 | const mpk = await crypto.deriveKey(masterpass, global.creds.mpsalt) 18 | return mpk 19 | } 20 | 21 | exports.save = masterpass => keytar.setPassword(SERVICE, ACCOUNT, masterpass) 22 | 23 | // Check MasterPass 24 | exports.check = masterpass => { 25 | return new Promise((resolve, reject) => { 26 | // deriveKey using the salt originally used to generate the 27 | // MasterPassKey 28 | crypto 29 | .deriveKey(masterpass, global.creds.mpsalt) 30 | .then(mpk => { 31 | // generate the hash for the MasterPassKey 32 | return crypto.genPassHash(mpk.key, global.creds.mpksalt) 33 | }) 34 | .then(mpk => { 35 | // check if MasterPassKey hash is equal to the MasterPassKey hash in mdb 36 | // Use timingSafeEqual to protect against timing attacks 37 | const match = crypto.timingSafeEqual(global.creds.mpkhash, mpk.hash) 38 | // return the match and derived key 39 | resolve({ match, key: mpk.key }) 40 | }) 41 | .catch(err => { 42 | reject(err) 43 | }) 44 | }) 45 | } 46 | 47 | // Set MasterPass 48 | exports.set = masterpass => { 49 | return new Promise((resolve, reject) => { 50 | // Derive the MasterPassKey from the supplied masterpass 51 | crypto 52 | .deriveKey(masterpass, null) 53 | .then(mp => exports.save(masterpass).then(() => mp)) 54 | .then(mp => { 55 | // Save the salt used to generate the MasterPassKey 56 | global.creds.mpsalt = mp.salt 57 | // generate the hash for the MasterPassKey 58 | return crypto.genPassHash(mp.key, null) 59 | }) 60 | .then(mpk => { 61 | // Save the salt used to generate the masterpass 62 | global.creds.mpkhash = mpk.hash 63 | global.creds.mpksalt = mpk.salt 64 | // return the derived mpkey 65 | resolve(mpk.key) 66 | }) 67 | .catch(err => { 68 | // reject if error occurs 69 | reject(err) 70 | }) 71 | }) 72 | } 73 | -------------------------------------------------------------------------------- /app/static/masterpassprompt.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 |
15 |
16 |
17 |

Verify the MasterPass

18 |

Please enter your Master Password 19 |

20 | 21 |

A MasterPassKey will be derived from the MasterPass. This will then be used to derive the keys used to encrypt your data.

22 |
23 |
24 |
25 | 26 | 27 | Forgot it? 28 |
29 | 30 |
31 |
32 |
33 |
34 |

Reset the MasterPass

35 |

Please enter a new secure MasterPass 36 |

37 | 38 |

The MasterPass is used to derive the data encryption keys. So any encrypted data using former MasterPass would not be decryptable.

39 |
40 |
41 |
42 | 43 | 44 |
45 | 46 |
47 | 52 |
53 |
54 |
55 | 56 | 57 | -------------------------------------------------------------------------------- /app/src/setup.js: -------------------------------------------------------------------------------- 1 | const {app, ipcMain, Menu, BrowserWindow} = require('electron') 2 | const {VIEWS, WINDOW_OPTS} = require('../config') 3 | const MasterPass = require('../core/MasterPass') 4 | const MasterPassKey = require('../core/MasterPassKey') 5 | const logger = require('electron-log') 6 | const menuTemplate = require('./menu') 7 | const title = 'Setup' 8 | 9 | exports.title = title 10 | 11 | exports.window = function (global, callback) { 12 | // setup view controller 13 | 14 | // creates the setup window 15 | let win = new BrowserWindow({ 16 | width: 600, 17 | height: 420, 18 | title, 19 | ...WINDOW_OPTS 20 | }) 21 | 22 | // create menu from menuTemplate and set 23 | Menu.setApplicationMenu(Menu.buildFromTemplate(menuTemplate)) 24 | 25 | let webContents = win.webContents 26 | let error 27 | // loads setup.html view into the SetupWindow 28 | win.loadURL(VIEWS.SETUP) 29 | 30 | ipcMain.on('setMasterPass', function (event, masterpass) { 31 | // setMasterPass event triggered by render proces 32 | logger.verbose('IPCMAIN: setMasterPass emitted Setting Masterpass...') 33 | // derive MasterPassKey, genPassHash and set creds globally 34 | MasterPass.set(masterpass) 35 | .then((mpkey) => { 36 | // set the derived MasterPassKey globally 37 | global.MasterPassKey = new MasterPassKey(mpkey) 38 | return 39 | }) 40 | .then(() => { 41 | // save the credentials used to derive the MasterPassKey 42 | return global.mdb.saveGlobalObj('creds') 43 | }) 44 | .then(() => { 45 | // Inform user that the MasterPass has successfully been set 46 | webContents.send('setMasterPassResult', null) 47 | }) 48 | .catch((err) => { 49 | // Inform user of the error that occured while setting the MasterPass 50 | logger.error(err) 51 | webContents.send('setMasterPassResult', err.message) 52 | error = err 53 | }) 54 | }) 55 | 56 | ipcMain.on('done', function (event, masterpass) { 57 | // Dond event emotted from render process 58 | logger.info('IPCMAIN: done emitted setup complete. Closing...') 59 | // Setup successfully finished 60 | // therefore set error to nothing 61 | error = null 62 | // Relaunch Crypter 63 | app.emit('app:relaunch') 64 | }) 65 | 66 | win.on('closed', function () { 67 | logger.verbose('IPCMAIN: win.closed event emitted for setupWindow.') 68 | // close window by setting it to nothing (null) 69 | win = null 70 | // if error occured then send error back to callee else send null 71 | callback((error) ? error : null) 72 | }) 73 | } -------------------------------------------------------------------------------- /app/static/js/settings.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /** 3 | * settings.js 4 | * Contains scripts for settings.html 5 | ******************************/ 6 | 7 | const dialog = remote.dialog 8 | const creds = remote.getGlobal('creds') 9 | const paths = remote.getGlobal('paths') 10 | const buf2hex = require('../core/crypto').buf2hex 11 | let errLabel 12 | // let settings = remote.getGlobal("settings") 13 | 14 | $(window).on('load', function () { 15 | errLabel = $('#errLabel') 16 | // Render the credentials 17 | let creds_temp = Handlebars.compile($('#creds-template').html()) 18 | $('#creds').prepend( 19 | creds_temp({ 20 | mpsalt: buf2hex(creds.mpsalt), 21 | mpkhash: creds.mpkhash, 22 | mpksalt: creds.mpksalt 23 | }) 24 | ) 25 | }) 26 | 27 | ipcRenderer.on('export', function () { 28 | dialog 29 | .showOpenDialog({ 30 | title: 'Choose a dir to export to', 31 | defaultPath: paths.documents, 32 | properties: ['openDirectory'] 33 | }) 34 | .then(function (fileData) { 35 | const dirPath = fileData.filePaths 36 | // callback for selected file 37 | // returns undefined if file not selected by user 38 | if (dirPath && dirPath.length === 1) { 39 | errLabel.hide() 40 | console.log(`Got dirpath ${dirPath[0]}`) 41 | ipcRenderer.send('export', dirPath[0]) 42 | } 43 | }) 44 | }) 45 | 46 | ipcRenderer.on('exportResult', function (event, err) { 47 | if (err) { 48 | errLabel 49 | .text(`ERROR: ${err.message}`.toUpperCase()) 50 | .css('color', COLORS.bad) 51 | .show() 52 | } else { 53 | errLabel 54 | .text(RESPONSES.exportSuccess) 55 | .css('color', COLORS.good) 56 | .show() 57 | } 58 | }) 59 | 60 | ipcRenderer.on('import', function () { 61 | dialog 62 | .showOpenDialog({ 63 | title: 'Choose the crypter credentials file', 64 | defaultPath: paths.documents, 65 | properties: ['openFile'] 66 | }) 67 | .then(function (fileData) { 68 | const filePath = fileData.filePaths 69 | // callback for selected file 70 | // returns undefined if file not selected by user 71 | if (filePath && filePath.length === 1) { 72 | errLabel.hide() 73 | console.log(`Got filePath ${filePath[0]}`) 74 | ipcRenderer.send('import', filePath[0]) 75 | } 76 | }) 77 | }) 78 | 79 | ipcRenderer.on('importResult', function (event, err) { 80 | window.erro = err 81 | if (err) { 82 | console.log(JSON.stringify(err)) 83 | errLabel 84 | .text(`ERROR: ${err}`.toUpperCase()) 85 | .css('color', COLORS.bad) 86 | .show() 87 | } else { 88 | errLabel 89 | .text(RESPONSES.importSuccess) 90 | .css('color', COLORS.good) 91 | .show() 92 | } 93 | }) 94 | -------------------------------------------------------------------------------- /app/src/settings.js: -------------------------------------------------------------------------------- 1 | const {app, ipcMain, Menu, BrowserWindow} = require('electron') 2 | const menuTemplate = require('./menu') 3 | const {CRYPTO, VIEWS, SETTINGS, ERRORS, WINDOW_OPTS} = require('../config') 4 | const logger = require('electron-log') 5 | const fs = require('fs-extra') 6 | const title = 'Settings' 7 | 8 | exports.title = title 9 | exports.window = function (global, callback) { 10 | // creates a new BrowserWindow 11 | let win = new BrowserWindow({ 12 | width: 600, 13 | height: 460, 14 | title, 15 | ...WINDOW_OPTS 16 | }) 17 | // create menu from menuTemplate and set 18 | Menu.setApplicationMenu(Menu.buildFromTemplate(menuTemplate)) 19 | 20 | let webContents = win.webContents 21 | // loads settings.html view into the BrowserWindow 22 | win.loadURL(VIEWS.SETTINGS) 23 | 24 | ipcMain.on('export', (event, dir) => { 25 | logger.verbose(`SETTINGS: export event emitted, got ${dir}`) 26 | const file = `${dir}/${CRYPTO.MASTERPASS_CREDS_FILE}` 27 | fs.outputJson(file, global.creds, function (err) { 28 | if (err) { 29 | logger.error(err) 30 | webContents.send('exportResult', err.message) 31 | } else { 32 | // Successfully exported 33 | webContents.send('exportResult', null) 34 | } 35 | }) 36 | }) 37 | 38 | ipcMain.on('import', (event, file) => { 39 | logger.verbose(`SETTINGS: import event emitted, got ${file}`) 40 | fs.readJson(file, global.creds, function (err, credsObj) { 41 | const invalidCredsErr = new Error(ERRORS.INVALID_MP_CREDS_FILE) 42 | if (err) { 43 | logger.error(err) 44 | webContents.send('importResult', err.message) 45 | } else { 46 | let isCredsObjProp = function (prop) { 47 | return credsObj.hasOwnProperty(prop) 48 | } 49 | let credsFileValid = CRYPTO.MASTERPASS_CREDS_PROPS.every(isCredsObjProp) 50 | logger.verbose(`SETTINGS: Got for credsFileValid ${credsFileValid}`) 51 | 52 | if (credsFileValid) { 53 | // Is valid (i.e. has required properties) 54 | // Save in-memory - global creds obj 55 | global.creds = credsObj 56 | // Save to fs - via global mdb 57 | global.mdb.saveGlobalObj('creds') 58 | // Successfully imported 59 | webContents.send('importResult', null) 60 | // Restart after timeout 61 | setTimeout(function () { 62 | app.emit('app:relaunch') 63 | }, SETTINGS.RELAUNCH_TIMEOUT); 64 | } else { 65 | webContents.send('importResult', invalidCredsErr.message) 66 | } 67 | 68 | } 69 | }) 70 | }) 71 | 72 | win.on('closed', function () { 73 | logger.info('win.closed event emitted for SettingsWindow') 74 | win = null 75 | callback() 76 | }) 77 | 78 | return win 79 | } 80 | -------------------------------------------------------------------------------- /app/static/styles/masterpassprompt.less: -------------------------------------------------------------------------------- 1 | // out: ./masterpassprompt.css, compress: true 2 | /* 3 | MasterPassPrompt styles 4 | ========================================================================== 5 | */ 6 | @import (less) "mixins.less"; 7 | 8 | /* Variable declarations*/ 9 | @invalid-color: #9F3A38; 10 | 11 | /* Section styles */ 12 | header { 13 | margin: 0; 14 | } 15 | header > p { 16 | color: @blacker; 17 | } 18 | h1 { 19 | font-size : 1.4rem; 20 | margin-bottom: 0.1rem; 21 | } 22 | 23 | button#setMasterPass { 24 | margin-top: 0.6rem; 25 | margin-bottom: 0.4rem; 26 | display: block !important; 27 | } 28 | button#checkMasterPass { 29 | margin-top: 2rem; 30 | display: block !important; 31 | } 32 | #masterpassprompt { 33 | margin: 2rem 0 0 0; 34 | height : 100%; 35 | width : 100%; 36 | background-color: @white; 37 | text-align : center; 38 | overflow : hidden; 39 | } 40 | .panel-container { 41 | position: relative; 42 | } 43 | .panel-container > div { 44 | display : block; 45 | position : absolute; 46 | text-align: center; 47 | padding : 0 3rem 3rem; 48 | transform : translateX(-100%); 49 | transition: transform 0.3s; 50 | } 51 | .panel-container > div.current ~ div { 52 | transform: translateX(100%); 53 | } 54 | .panel-container > div.current { 55 | transform: none; 56 | position : relative; 57 | } 58 | p.info { 59 | display : block; 60 | width : 100%; 61 | box-sizing : border-box; 62 | max-height : 0; 63 | overflow : hidden; 64 | padding : 0 0.5rem; 65 | transition : max-height 0.3s, padding 0.3s; 66 | background-color: #efefef; 67 | font-size : 0.8rem; 68 | } 69 | img.info { 70 | float : right; 71 | height: 0.9rem; 72 | } 73 | img.info:hover + p.info { 74 | max-height : 10rem; 75 | padding-top : 0.5rem; 76 | padding-bottom: 0.5rem; 77 | } 78 | div.masterpass { 79 | input { 80 | width: 98%; 81 | } 82 | .navigationLink { 83 | margin-top: 0.2rem; 84 | float: right; 85 | } 86 | } 87 | 88 | p.note { 89 | margin-top: 0.2rem; 90 | font-size: 0.8rem; 91 | } 92 | 93 | input[type=password] { 94 | margin-top : 1rem; 95 | border : none; 96 | border-bottom: 1px solid @light; 97 | outline : none; 98 | &:focus { 99 | animation-name : colorTrans; 100 | animation-duration: 2s; 101 | border-bottom : 1px solid @black; 102 | } 103 | } 104 | .invalid { 105 | border-color: @invalid-color !important; 106 | } 107 | div.forgotMP { 108 | padding : 0.2rem 0.2rem 0 0; 109 | text-align: right; 110 | } 111 | #checkMasterPass { 112 | width: 100% !important; 113 | margin-top : 1rem; 114 | } 115 | 116 | /* INVALID STYLING */ 117 | div.masterpass > label { 118 | display : inline-block; 119 | font-size: 0.8rem; 120 | margin : 0.2rem 0; 121 | color : @invalid-color; 122 | } 123 | -------------------------------------------------------------------------------- /app/static/images/icons/Encrypted.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 22 | 23 | 24 | 25 | 26 | 27 | 29 | 30 | 31 | 32 | 33 | 34 | 36 | 37 | 38 | 39 | 40 | 41 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /app/src/crypter.js: -------------------------------------------------------------------------------- 1 | const { app, ipcMain, Menu, BrowserWindow } = require('electron') 2 | const { VIEWS, ERRORS, WINDOW_OPTS } = require('../config') 3 | const crypto = require('../core/crypto') 4 | const menuTemplate = require('./mainMenu') 5 | const { isCryptoFile } = require('../utils/utils') 6 | const logger = require('electron-log') 7 | const title = 'Crypter' 8 | 9 | exports.title = title 10 | 11 | exports.window = function (global, fileToCrypt, callback) { 12 | // creates a new BrowserWindow 13 | let win = new BrowserWindow({ 14 | width: 350, 15 | height: 460, 16 | title, 17 | ...WINDOW_OPTS 18 | }) 19 | // create menu from menuTemplate and set 20 | Menu.setApplicationMenu(Menu.buildFromTemplate(menuTemplate)) 21 | // loads crypt.html view into the BrowserWindow 22 | win.loadURL(VIEWS.CRYPTER) 23 | let webContents = win.webContents 24 | 25 | webContents.once('did-finish-load', () => { 26 | // Process any file opened with app before this window 27 | if (fileToCrypt) { 28 | logger.info('Got a file to crypt', fileToCrypt) 29 | cryptFile(fileToCrypt) 30 | } 31 | }) 32 | 33 | 34 | function encrypt(filePath) { 35 | // Update UI 36 | webContents.send('encryptingFile', filePath) 37 | crypto.crypt(filePath, global.MasterPassKey.get()) 38 | .then((file) => { 39 | webContents.send('cryptedFile', file) 40 | }) 41 | .catch((err) => { 42 | logger.info(`cryptFile error`) 43 | logger.error(err) 44 | webContents.send('cryptErr', err.message) 45 | }) 46 | } 47 | 48 | function decrypt(filePath) { 49 | // Update UI 50 | webContents.send('decryptingFile', filePath) 51 | crypto.decrypt(filePath, global.MasterPassKey.get()) 52 | .then((file) => { 53 | logger.info('decrypted') 54 | webContents.send('decryptedFile', file) 55 | }) 56 | .catch((err) => { 57 | logger.info(`decryptFile error`) 58 | logger.error(err) 59 | switch (err.message.trim()) { 60 | case ERRORS.MS.INVALID_FILE: 61 | webContents.send('cryptErr', ERRORS.INVALID_FILE) 62 | break; 63 | case ERRORS.MS.AUTH_FAIL: 64 | webContents.send('cryptErr', ERRORS.AUTH_FAIL) 65 | break; 66 | default: 67 | webContents.send('cryptErr', err.message) 68 | } 69 | }) 70 | } 71 | 72 | function cryptFile(file) { 73 | if (isCryptoFile(file)) { 74 | decrypt(file) 75 | } else { 76 | encrypt(file) 77 | } 78 | } 79 | 80 | ipcMain.on('app:open-settings', (event) => { 81 | logger.verbose('CRYPTER: app:open-settings emitted.') 82 | app.emit('app:open-settings') 83 | }) 84 | 85 | // Process any file opened with app while this window is active 86 | ipcMain.on('cryptFile', (event, file) => cryptFile(file)) 87 | 88 | app.on('open-file', (event, file) => { 89 | if (app.isReady()) { 90 | // Opening when already launched 91 | logger.info('Opening file ' + file) 92 | cryptFile(file) 93 | } 94 | event.preventDefault() 95 | }) 96 | 97 | win.on('closed', function () { 98 | logger.info('win.closed event emitted for PromptWindow') 99 | win = null 100 | callback() 101 | }) 102 | 103 | return win 104 | } -------------------------------------------------------------------------------- /app/static/js/common.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /** 3 | * common.js 4 | * Contains all the functionality that is common between the views 5 | ******************************/ 6 | 7 | /* Common variables */ 8 | 9 | // Load JQuery library and make accessible via $ 10 | window.$ = window.jQuery = require('jquery') 11 | // Cross-view dependencies 12 | const {ipcRenderer, remote, shell} = require('electron') 13 | const { app } = remote 14 | const logger = require('electron-log') 15 | const {REGEX, RESPONSES, COLORS} = require('../config') 16 | const Handlebars = require('handlebars') 17 | 18 | /* Shared functions */ 19 | function getUrlParameter(name) { 20 | name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]'); 21 | var regex = new RegExp('[\\?&]' + name + '=([^&#]*)'); 22 | var results = regex.exec(location.search); 23 | return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' ')); 24 | }; 25 | 26 | function navigate (panel) { 27 | let oldSel = $('.panel-container > div.current') // get current panel 28 | let sel = $(`#panel-${panel}`) // get panel to navigate to 29 | oldSel.removeClass('current') // apply hide styling 30 | sel.addClass('current') // apply show styling 31 | } 32 | 33 | function validateMasterPass(field, errLabel) { 34 | const MPel = $(`input#${field}Input`) 35 | const masterpass = MPel.val() 36 | if (!masterpass) { 37 | // MP is empty 38 | // set errLabel text to response for empty and show errLabel 39 | errLabel.text(RESPONSES.empty).show() 40 | // Clear MP input field 41 | MPel.val('') 42 | } else if (REGEX.MASTERPASS.test(masterpass)) { 43 | // MP is valid 44 | // Hide errLabel 45 | errLabel.hide() 46 | // Send valid MasterPass to controller function to be checked 47 | ipcRenderer.send(field, masterpass) 48 | } else { 49 | // set errLabel text to response for invalid and show errLabel 50 | errLabel.text(RESPONSES.invalid).show() 51 | // Clear MP input field 52 | MPel.val('') 53 | } 54 | } 55 | 56 | /* Onload */ 57 | $(window).on('load', function() { 58 | $(".navigationLink").each(function(index) { 59 | let $this = $(this) 60 | $this.on('click', function(event) { 61 | let target = $this.data("target") 62 | let panel = $this.data("panel") 63 | let tab = $this.data("tab") 64 | let action = $this.data("action") 65 | 66 | if (action) { 67 | // Is an action to perform 68 | if (REGEX.APP_EVENT.test(action)) { 69 | console.log(`Got main app event ${action}`) 70 | // Emit event on app 71 | app.emit(action) 72 | } else { 73 | console.log(`Got render event ${action}`) 74 | // Emit event in render proc 75 | ipcRenderer.emit(action) 76 | } 77 | } else if (tab) { 78 | console.log(`Got tab ${tab}`) 79 | // is a tab to navigate to 80 | $(".item.active").first().removeClass("active") 81 | $(`a[data-tab='${tab}']`).addClass("active") 82 | navigate(tab) 83 | } else if (target) { 84 | console.log(`Got URL ${target}`) 85 | // is a URL so open it 86 | shell.openExternal(target) 87 | } else if (panel) { 88 | console.log(`Got panel ${panel}`) 89 | // target is just a panel to navigate to 90 | navigate(panel) 91 | } 92 | return false 93 | }) 94 | }) 95 | }) 96 | -------------------------------------------------------------------------------- /app/config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /** 3 | * config.js 4 | * Provides all essential config constants 5 | ******************************/ 6 | 7 | // Fixed constants 8 | const VIEWS_BASE_URI = `file://${__dirname}/static` 9 | 10 | module.exports = { 11 | REPO: { 12 | URL: 'https://github.com/HR/Crypter/', 13 | RELEASES_API_URL: 'https://api.github.com/repos/HR/Crypter/releases/latest', 14 | FORK: 'https://github.com/HR/Crypter/fork', 15 | DOCS: 'https://github.com/HR/Crypter/blob/master/readme.md', 16 | REPORT_ISSUE: 'https://github.com/HR/Crypter/issues/new' 17 | }, 18 | WINDOW_OPTS: { 19 | center: true, 20 | show: true, 21 | titleBarStyle: 'hiddenInset', 22 | resizable: false, 23 | maximizable: false, 24 | movable: true, 25 | webPreferences: { 26 | nodeIntegration: true 27 | } 28 | }, 29 | VIEWS: { 30 | BASE_URI: VIEWS_BASE_URI, 31 | MASTERPASSPROMPT: `${VIEWS_BASE_URI}/masterpassprompt.html`, 32 | SETUP: `${VIEWS_BASE_URI}/setup.html`, 33 | CRYPTER: `${VIEWS_BASE_URI}/crypter.html`, 34 | SETTINGS: `${VIEWS_BASE_URI}/settings.html` 35 | }, 36 | CRYPTO: { 37 | ENCRYPTION_TMP_DIR: '.crypting', 38 | DECRYPTION_TMP_DIR: '.decrypting', 39 | FILE_DATA: 'data', 40 | FILE_CREDS: 'creds', 41 | MASTERPASS_CREDS_FILE: 'credentials.crypter', 42 | MASTERPASS_CREDS_PROPS: ['mpkhash', 'mpksalt', 'mpsalt'], 43 | DECRYPT_OP: 'Decrypted', 44 | DECRYPT_TITLE_PREPEND: 'Decrypted ', 45 | ENCRYPT_OP: 'Encrypted', 46 | EXT: '.crypto', 47 | DEFAULTS: { 48 | // Crypto default constants 49 | ITERATIONS: 50000, // file encryption key derivation iterations 50 | KEYLENGTH: 32, // encryption key length 51 | IVLENGTH: 12, // initialisation vector length 52 | ALGORITHM: 'aes-256-gcm', // encryption algorithm 53 | DIGEST: 'sha256', // digest function 54 | HASH_ALG: 'sha256', // hashing function 55 | MPK_ITERATIONS: 100000 // MasterPassKey derivation iterations 56 | } 57 | }, 58 | REGEX: { 59 | APP_EVENT: /^app:[\w-]+$/i, 60 | ENCRYPTION_CREDS: /^Crypter(.*)$/gim, 61 | MASTERPASS: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@!%*#?&]).{8,}$/ 62 | }, 63 | RESPONSES: { 64 | invalid: 65 | 'MUST AT LEAST CONTAIN 1 UPPER & LOWER CASE ALPHABET, 1 NUMBER, 1 SYMBOL AND BE 8 CHARACTERS', 66 | correct: 'CORRECT MASTERPASS', 67 | incorrect: 'INCORRECT MASTERPASS', 68 | setSuccess: 'MASTERPASS SUCCESSFULLY SET', 69 | empty: 'PLEASE ENTER THE MASTERPASS', 70 | resetSuccess: 71 | "You have successfully reset your MasterPass. You'll be redirected to verify it shortly.", 72 | exportSuccess: 'Successfully exported the credentials', 73 | importSuccess: 74 | 'Successfully imported the credentials. You will need to verify the MasterPass for the credentials imported after Crypter relaunches.' 75 | }, 76 | ERRORS: { 77 | INVALID_MP_CREDS_FILE: 'Not a valid or corrupted Crypter credentials file!', 78 | INVALID_FILE: 'Not a valid or corrupted CRYPTO file!', 79 | AUTH_FAIL: 80 | 'Corrupted Crypter file or trying to decrypt on a different machine. See git.io/Crypter.info#faqs', 81 | PROMISE: 'Oops, we encountered a problem...', 82 | DECRYPT: 'Not a Crypter file (can not get salt, iv and authTag)', 83 | MS: { 84 | INVALID_FILE: 85 | 'Invalid tar header. Maybe the tar is corrupted or it needs to be gunzipped?', 86 | AUTH_FAIL: 'Unsupported state or unable to authenticate data' 87 | } 88 | }, 89 | COLORS: { 90 | bad: '#dc3545', 91 | good: '#28a745', 92 | highlight: '#333333' 93 | }, 94 | SETTINGS: { 95 | RELAUNCH_TIMEOUT: 4000 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp') 2 | const shell = require('child_process').exec 3 | const spawn = require('child_process').spawn 4 | const babel = require('gulp-babel') 5 | const env = require('gulp-env') 6 | const less = require('gulp-less') 7 | const path = require('path') 8 | const istanbul = require('gulp-babel-istanbul') 9 | const injectModules = require('gulp-inject-modules') 10 | const mocha = require('gulp-mocha') 11 | const LessPluginCleanCSS = require('less-plugin-clean-css') 12 | const cleancss = new LessPluginCleanCSS({ advanced: true }) 13 | const LESS_FILES = './app/static/styles/*.less' 14 | const ELECTRON = __dirname + '/node_modules/.bin/electron' 15 | const DEBUG = false 16 | let p 17 | let args = ['.'] 18 | // Start the electron process. 19 | async function electron () { 20 | // kill previous spawned process 21 | if (p) { 22 | p.kill() 23 | } 24 | 25 | if (DEBUG) args.unshift('--inspect=5858') 26 | // `spawn` a child `gulp` process linked to the parent `stdio` 27 | p = await spawn(ELECTRON, args, { 28 | stdio: 'inherit', 29 | env: { 30 | ...process.env 31 | } 32 | }) 33 | } 34 | 35 | gulp.task('run', electron) 36 | 37 | gulp.task('watch', function (done) { 38 | gulp.watch(LESS_FILES, gulp.series('less')) 39 | gulp.watch(['./app/*.js', './app/src/*.js', './app/core/*.js'], gulp.series('run')) 40 | done() 41 | }) 42 | 43 | gulp.task('nodev', function () { 44 | return shell( 45 | // start electron main and render process 46 | `node_modules/.bin/electron script/resolveNodeV.js`, 47 | function (err, stdout, stderr) { 48 | console.log(stdout) 49 | console.log(stderr) 50 | } 51 | ) 52 | }) 53 | 54 | gulp.task('less', function () { 55 | return gulp 56 | .src('./app/static/styles/*.less') 57 | .pipe( 58 | less({ 59 | paths: [path.join(__dirname, 'less', 'includes')], 60 | plugins: [cleancss] 61 | }) 62 | ) 63 | .pipe(gulp.dest('./app/static/styles/')) 64 | }) 65 | 66 | /* TEST */ 67 | 68 | gulp.task('test', () => { 69 | shell( 70 | // Run test stuff 71 | 'mocha --require babel-core/register test/' 72 | ) 73 | }) 74 | 75 | gulp.task('coverage', function (cb) { 76 | const envs = env.set({ 77 | TEST_RUN: true 78 | }) 79 | gulp 80 | .src('./app/core/**/*.js') 81 | .pipe(envs) 82 | .pipe(istanbul()) 83 | .pipe(istanbul.hookRequire()) 84 | .on('finish', function () { 85 | gulp 86 | .src('test/*.js') 87 | .pipe(babel()) 88 | .pipe(injectModules()) 89 | .pipe(mocha()) 90 | .pipe(istanbul.writeReports()) 91 | .on('end', cb) 92 | }) 93 | }) 94 | 95 | /* BUILD */ 96 | 97 | gulp.task('rebuildni', () => { 98 | shell( 99 | // start node inspector server 100 | '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' 101 | ) 102 | }) 103 | 104 | gulp.task('buildnative', () => { 105 | shell( 106 | // start build the native module 107 | './node_modules/.bin/electron-rebuild #1' 108 | ) 109 | }) 110 | 111 | gulp.task('ni', () => { 112 | shell( 113 | // start node inspector server 114 | 'ELECTRON_RUN_AS_NODE=true node_modules/.bin/electron node_modules/node-inspector/bin/inspector.js' 115 | ) 116 | }) 117 | 118 | gulp.task('driver', () => { 119 | shell( 120 | // Run chromedriver 121 | './node_modules/chromedriver/bin/chromedriver' 122 | ) 123 | }) 124 | 125 | gulp.task('default', gulp.series('less', 'watch', 'run')) 126 | -------------------------------------------------------------------------------- /app/static/crypter.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 |
13 | 14 | Settings 15 | 16 |
17 | Crypter 18 |

Crypter

19 |

Encrypt. Decrypt. Anything.

20 |

21 | Encrypt unlimited bits. Remember only a bit. 22 |

23 | 24 |
25 |
26 |

27 | Select or Drop 28 |

29 |
30 |
31 |
32 |
33 | 34 |
35 |

36 | Note these details down
37 | (for use with third-party apps) 38 |

39 | 44 |
45 |
46 |
47 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /app/src/masterPassPrompt.js: -------------------------------------------------------------------------------- 1 | const { ipcMain, Menu, BrowserWindow } = require('electron') 2 | const { VIEWS, WINDOW_OPTS } = require('../config') 3 | const MasterPass = require('../core/MasterPass') 4 | const MasterPassKey = require('../core/MasterPassKey') 5 | const logger = require('electron-log') 6 | const menuTemplate = require('./menu') 7 | const title = 'MasterPass' 8 | 9 | exports.title = title 10 | 11 | exports.window = function (global, resetOnly, callback) { 12 | let noMP = true // init noMP flag with false 13 | let error = null 14 | const CLOSE_TIMEOUT = 2000 15 | 16 | // creates a new BrowserWindow 17 | let win = new BrowserWindow({ 18 | width: 300, 19 | height: 460, 20 | title, 21 | ...WINDOW_OPTS 22 | }) 23 | // create menu from menuTemplate and set 24 | Menu.setApplicationMenu(Menu.buildFromTemplate(menuTemplate)) 25 | const qs = resetOnly ? '?reset=true' : '' 26 | // loads masterpassprompt.html view into the BrowserWindow 27 | win.loadURL(VIEWS.MASTERPASSPROMPT + qs) 28 | 29 | let webContents = win.webContents 30 | 31 | ipcMain.on('checkMasterPass', function (event, masterpass) { 32 | logger.verbose('IPCMAIN: checkMasterPass emitted. Checking MasterPass...') 33 | // Check user submitted MasterPass 34 | MasterPass.check(masterpass) 35 | .then(res => { 36 | if (res.match) { 37 | // Password matches 38 | logger.info('IPCMAIN: PASSWORD MATCHES!') 39 | // Save MasterPassKey (while program is running) 40 | global.MasterPassKey = new MasterPassKey(res.key) 41 | // Save for next time 42 | MasterPass.save(masterpass) 43 | // send result match result to masterpassprompt.html 44 | webContents.send('checkMasterPassResult', { 45 | err: null, 46 | match: res.match 47 | }) 48 | noMP = false 49 | // Close after 1 second 50 | setTimeout(function () { 51 | // close window (invokes 'closed') event 52 | win.close() 53 | }, CLOSE_TIMEOUT) 54 | } else { 55 | logger.warn('IPCMAIN: PASSWORD DOES NOT MATCH!') 56 | webContents.send('checkMasterPassResult', { 57 | err: null, 58 | match: res.match 59 | }) 60 | } 61 | }) 62 | .catch(err => { 63 | // Inform user of error (on render side) 64 | webContents.send('checkMasterPassResult', err.message) 65 | // set error 66 | error = err 67 | // Close after 1 second 68 | setTimeout(function () { 69 | // close window (invokes 'closed') event 70 | win.close() 71 | }, CLOSE_TIMEOUT) 72 | }) 73 | }) 74 | 75 | ipcMain.on('setMasterPass', function (event, masterpass) { 76 | // setMasterPass event triggered by render proces 77 | logger.verbose('IPCMAIN: setMasterPass emitted Setting Masterpass...') 78 | // derive MasterPassKey, genPassHash and set creds globally 79 | MasterPass.set(masterpass) 80 | .then(mpkey => { 81 | // set the derived MasterPassKey globally 82 | global.MasterPassKey = new MasterPassKey(mpkey) 83 | return 84 | }) 85 | .then(() => { 86 | // save the credentials used to derive the MasterPassKey 87 | return global.mdb.saveGlobalObj('creds') 88 | }) 89 | .then(() => { 90 | // Inform user that the MasterPass has successfully been set 91 | logger.verbose('IPCMAIN: Masterpass has been reset successfully') 92 | webContents.send('setMasterPassResult', null) 93 | }) 94 | .catch(err => { 95 | // Inform user of the error that occured while setting the MasterPass 96 | webContents.send('setMasterPassResult', err.message) 97 | error = err 98 | }) 99 | }) 100 | 101 | win.on('closed', function () { 102 | logger.info('win.closed event emitted for PromptWindow') 103 | // send error and noMP back to callee (masterPassPromptWindow Promise) 104 | if (callback) callback(error || noMP) 105 | // close window by setting it to nothing (null) 106 | win = null 107 | }) 108 | 109 | return win 110 | } 111 | -------------------------------------------------------------------------------- /app/static/settings.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 | 19 |
20 |
21 |
22 | 23 |

General

24 |
25 |
26 |

Credentials

27 |
28 |
29 | 30 | 31 |
32 |
33 | 34 |
35 |
36 |

37 |
38 |
39 |
40 | 82 | 89 |
90 |
91 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /app/static/styles/crypter.less: -------------------------------------------------------------------------------- 1 | // out: ./crypter.css, compress: true 2 | /* 3 | MasterPassPrompt styles 4 | ========================================================================== 5 | */ 6 | @import (less) 'mixins.less'; 7 | /* Variable declarations*/ 8 | @invalid-color: #9f3a38; 9 | @fileInput-height: 40vh; 10 | /* Section styles */ 11 | header { 12 | margin: 0; 13 | } 14 | 15 | header > p { 16 | color: @blacker; 17 | } 18 | 19 | h3 { 20 | margin-top: 0; 21 | } 22 | 23 | #crypt { 24 | height: 100%; 25 | min-height: 20rem; 26 | width: 100%; 27 | background-color: @white; 28 | text-align: center; 29 | overflow: hidden; 30 | } 31 | 32 | #crypted-container { 33 | height: 84vh; 34 | overflow-y: scroll; 35 | overflow-x: hidden; 36 | } 37 | 38 | .panel-container { 39 | position: relative; 40 | 41 | & > div { 42 | position: absolute; 43 | text-align: center; 44 | transform: translateX(-110%); 45 | transition: transform 0.5s; 46 | 47 | & > button { 48 | margin-top: 1rem; 49 | } 50 | } 51 | 52 | & > div.current { 53 | width: 100%; 54 | height: 100vh; 55 | overflow: hidden; 56 | transform: none; 57 | position: relative; 58 | } 59 | 60 | & > div.current ~ div { 61 | transform: translateX(110%); 62 | } 63 | 64 | header { 65 | margin-top: 2.5rem; 66 | } 67 | } 68 | 69 | #panel-crypt { 70 | display: flex; 71 | flex-direction: column; 72 | justify-content: space-between; 73 | 74 | h1 { 75 | margin: 0.4rem 0; 76 | font-size: 1.4rem; 77 | } 78 | 79 | p.subtitle { 80 | margin-top: 0.6rem; 81 | } 82 | 83 | p.leadinfo { 84 | color: @dark; 85 | font-size: 0.8rem; 86 | margin: 0.4rem 1rem; 87 | } 88 | 89 | #fileInput { 90 | margin-top: 6vh; 91 | width: 100%; 92 | height: @fileInput-height; 93 | text-align: center; 94 | display: flex; 95 | flex-flow: column; 96 | cursor: pointer; 97 | align-items: center; 98 | justify-items: center; 99 | 100 | p { 101 | line-height: @fileInput-height; 102 | color: @white; 103 | width: 70%; 104 | white-space: nowrap; 105 | overflow: hidden; 106 | text-overflow: ellipsis; 107 | } 108 | } 109 | } 110 | 111 | #panel-crypted { 112 | #finfo { 113 | margin: 1rem; 114 | 115 | h3 > div { 116 | text-align: center; 117 | overflow: hidden; 118 | white-space: nowrap; 119 | text-overflow: ellipsis; 120 | } 121 | 122 | table { 123 | width: 100%; 124 | font-size: 0.8rem; 125 | 126 | tr { 127 | margin-top: 0.4rem; 128 | 129 | .bttline { 130 | vertical-align: inherit; 131 | } 132 | 133 | .file-path { 134 | position: relative; 135 | 136 | img { 137 | height: 16px; 138 | position: absolute; 139 | right: 0; 140 | background-image: linear-gradient(to right, rgba(255,255,255,0.5), rgba(255,255,255,1)); 141 | padding-left: 5px; 142 | } 143 | } 144 | 145 | td { 146 | vertical-align: top; 147 | 148 | input[type='text'] { 149 | display: table-cell; 150 | width: 50vw; 151 | float: right; 152 | white-space: nowrap; 153 | text-overflow: ellipsis; 154 | } 155 | 156 | a > img { 157 | height: 1rem; 158 | margin-left: 0.1rem; 159 | } 160 | } 161 | 162 | td:first-child { 163 | text-align: left; 164 | color: @dark * 0.8; 165 | } 166 | 167 | td:last-child { 168 | text-align: left; 169 | padding-left: 0.2rem; 170 | } 171 | } 172 | } 173 | } 174 | } 175 | 176 | a[data-action='app:open-settings'] { 177 | img.info { 178 | height: 1.2rem; 179 | position: absolute; 180 | right: 0; 181 | top: 0; 182 | padding: 0.5rem; 183 | } 184 | } 185 | 186 | a.back { 187 | float: left; 188 | } 189 | 190 | a.back > img { 191 | height: 1rem; 192 | width: auto; 193 | } 194 | 195 | footer { 196 | display: flex; 197 | clear: both; 198 | position: absolute; 199 | border-top: 1px solid @light; 200 | width: 100%; 201 | padding-bottom: 0; 202 | background-color: @white; 203 | bottom: 0; 204 | left: 0; 205 | } 206 | 207 | footer > a { 208 | float: right; 209 | padding: 0.2rem; 210 | } 211 | /* INVALID STYLING */ 212 | p#errLabel { 213 | display: none; 214 | display: inline-block; 215 | font-size: 0.8rem; 216 | padding: 0.2rem; 217 | color: @invalid-color; 218 | } 219 | -------------------------------------------------------------------------------- /app/static/setup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 33 | 50 | Crypter Logo 51 |

Welcome to Crypter

52 |

Encrypt unlimited bits. Remember only a bit.

53 |

An innovative, convenient and secure encryption app that simplifies password generation and management by requiring you to only remember one bit, the MasterPass.

54 |
55 | 56 |
57 | 58 |
59 |
60 |
61 | 64 |

Set a MasterPass for encryption

65 | 66 |

A MasterPassKey will be derived from the MasterPass. This will then be used to derive the keys used to encrypt your data.

67 |
68 |
69 |
70 | 71 | 72 |
73 | 74 |
75 |

76 | NOTE: If lost, the MasterPass will be unverifiable! So, please store it safely. 77 |

78 |
79 |
80 | 81 |
82 |
83 | 86 |

All done!

87 |

You have successfully setup Crypter. 88 |
89 | You just need to verify your MasterPass to start Crypting shortly.

90 |
91 | 92 | 99 |
100 |
101 |
102 | 103 | 104 | -------------------------------------------------------------------------------- /app/static/js/crypter.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /** 3 | * crypter.js 4 | * Contains scripts for crypter.html 5 | ******************************/ 6 | const dialog = remote.dialog 7 | const paths = remote.getGlobal('paths') 8 | const { basename } = require('path') 9 | const os = require('os') 10 | let errLabel, 11 | fileInput, 12 | fileInputD, 13 | cryptedContainer, 14 | fileInputText, 15 | ifileInputText, 16 | crypted_template 17 | 18 | $(window).on('load', function () { 19 | // Get DOM elements 20 | errLabel = $('#errLabel') 21 | fileInput = $('#fileInput') 22 | fileInputD = document.getElementById('fileInput') 23 | cryptedContainer = $('#crypted-container') 24 | fileInputText = fileInput.find('#fileInputText') 25 | ifileInputText = fileInputText.text() 26 | // compile the crypted template 27 | crypted_template = Handlebars.compile($('#crypted-template').html()) 28 | // attach event 29 | fileInputD.ondragover = function () { 30 | return false 31 | } 32 | fileInputD.ondragleave = fileInputD.ondragend = function () { 33 | return false 34 | } 35 | 36 | enableFileInput() 37 | }) 38 | 39 | /* Event listeners */ 40 | ipcRenderer.on('cryptedFile', function (event, file) { 41 | logger.verbose(`IPCRENDER cryptedFile emitted`) 42 | let fileHTML = crypted_template(file) 43 | cryptedContainer.html(fileHTML) 44 | enableUI() 45 | navigate('crypted') 46 | }) 47 | 48 | ipcRenderer.on('decryptedFile', function (event, file) { 49 | logger.verbose(`IPCRENDER decryptedFile emitted`) 50 | let fileHTML = crypted_template(file) 51 | cryptedContainer.html(fileHTML) 52 | enableUI() 53 | navigate('crypted') 54 | }) 55 | 56 | ipcRenderer.on('cryptErr', function (event, err) { 57 | logger.verbose(`IPCRENDER cryptErr emitted`) 58 | errLabel.text(`ERROR: ${err}`).show() 59 | enableUI() 60 | }) 61 | 62 | ipcRenderer.on('encryptingFile', function (event, file) { 63 | logger.verbose(`IPCRENDER encryptingFile emitted`) 64 | fileInputText.text(`Encrypting ${basename(file)}...`) 65 | disableUI() 66 | }) 67 | 68 | ipcRenderer.on('decryptingFile', function (event, file) { 69 | logger.verbose(`IPCRENDER decryptingFile emitted`) 70 | fileInputText.text(`Decrypting ${basename(file)}...`) 71 | disableUI() 72 | }) 73 | 74 | /* Helper functions */ 75 | function disableFileInput () { 76 | fileInput.off('click', handler) 77 | fileInput.ondrop = function () { 78 | return false 79 | } 80 | } 81 | 82 | function enableFileInput () { 83 | fileInput.on('click', handler) 84 | fileInputD.ondrop = function (e) { 85 | e.preventDefault() 86 | logger.info(`ONDROP fired!`) 87 | if (e.dataTransfer.files[0].path) { 88 | logger.info(`Got file: ${e.dataTransfer.files[0].path}`) 89 | ipcRenderer.send('cryptFile', e.dataTransfer.files[0].path) 90 | } 91 | return false 92 | } 93 | } 94 | 95 | function enableUI () { 96 | fileInputText.text(ifileInputText) 97 | enableFileInput() 98 | } 99 | 100 | function disableUI () { 101 | disableFileInput() 102 | errLabel.hide() 103 | } 104 | 105 | function showFile (path) { 106 | shell.showItemInFolder(path) 107 | } 108 | 109 | function showOpenDialog (properties) { 110 | // Create file input dialog 111 | dialog 112 | .showOpenDialog({ 113 | title: 'Choose a file to Encrypt', 114 | defaultPath: paths.documents, // open dialog at home directory 115 | properties: properties 116 | }) 117 | .then(fileData => { 118 | const filePath = fileData.filePaths 119 | console.log(filePath) 120 | // callback for selected file returns undefined if file not selected by user 121 | if (filePath && filePath.length) { 122 | // Prevent multiple input dialog 123 | fileInput.off('click', handler) 124 | // Select the first one 125 | ipcRenderer.send('cryptFile', filePath[0]) 126 | } else { 127 | fileInput.on('click', handler) 128 | } 129 | }) 130 | } 131 | 132 | function handler () { 133 | if (os.platform() === 'darwin') { 134 | // macOS allows selecting files and folders so just show dialog 135 | showOpenDialog(['openFile', 'openDirectory']) 136 | } else { 137 | // Windows/Linux only allow selecting either only files or folders so ask the user to choose 138 | dialog.showMessageBox( 139 | { 140 | type: 'question', 141 | message: 'What would you like to crypt?', 142 | buttons: ['File', 'Folder', 'Cancel'], 143 | defaultId: 1 144 | }, 145 | function (response) { 146 | switch (response) { 147 | case 0: 148 | // File 149 | showOpenDialog(['openFile']) 150 | break 151 | case 1: 152 | // Folder 153 | showOpenDialog(['openDirectory']) 154 | break 155 | 156 | default: 157 | } 158 | } 159 | ) 160 | } 161 | 162 | return false 163 | } 164 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Crypter", 3 | "productName": "Crypter", 4 | "version": "5.0.0", 5 | "description": "An innovative, convenient and secure cross-platform crypto app", 6 | "license": "MIT", 7 | "repository": "https://github.com/HR/Crypter", 8 | "homepage": "https://github.com/HR/Crypter", 9 | "bugs": "https://github.com/HR/Crypter/issues", 10 | "main": "./app/index.js", 11 | "author": { 12 | "name": "Habib Rehman", 13 | "email": "H@Rehman.email", 14 | "url": "https://git.io/HR" 15 | }, 16 | "build": { 17 | "appId": "com.github.hr.crypter", 18 | "asar": false, 19 | "files": [ 20 | "**/*", 21 | "!**/node_modules/.bin", 22 | "!**/._*", 23 | "!**/{.*,.git,*.yml,*.md,README,readme,test*,*.less,gulpfile.js}", 24 | "!script${/*}", 25 | "!test${/*}" 26 | ], 27 | "compression": "normal", 28 | "fileAssociations": { 29 | "ext": "crypto", 30 | "name": "CRYPTO", 31 | "role": "Editor", 32 | "description": "The Crypter encryption format. More info at git.io/Crypter.info#crypto-file" 33 | }, 34 | "mac": { 35 | "category": "public.app-category.utilities", 36 | "publish": "github", 37 | "identity": null 38 | }, 39 | "dmg": { 40 | "background": "build/background.tif", 41 | "icon": "vicon.icns", 42 | "iconSize": 116, 43 | "iconTextSize": 13, 44 | "contents": [ 45 | { 46 | "x": 240, 47 | "y": 135 48 | }, 49 | { 50 | "x": 240, 51 | "y": 400, 52 | "type": "link", 53 | "path": "/Applications" 54 | } 55 | ], 56 | "window": { 57 | "width": "480", 58 | "height": "540" 59 | } 60 | }, 61 | "linux": { 62 | "category": "Utility", 63 | "publish": "github" 64 | }, 65 | "win": { 66 | "icon": "icon.ico", 67 | "target": "nsis", 68 | "publish": "github" 69 | }, 70 | "nsis": { 71 | "oneClick": true, 72 | "perMachine": true 73 | }, 74 | "snap": { 75 | "publish": "github" 76 | } 77 | }, 78 | "scripts": { 79 | "pack": "electron-builder --dir", 80 | "build:mac": "electron-builder -m -c.electronVersion $(npm info electron version)", 81 | "build:lin": "electron-builder -l --x64 --ia32 -c.electronVersion $(npm info electron version)", 82 | "build:win": "electron-builder -w --x64 --ia32", 83 | "start": "electron .", 84 | "postinstall": "electron-builder install-app-deps", 85 | "nodeGypReBuild": "electron-rebuild .", 86 | "electronV": "electron -v | sed s/\\v//g", 87 | "test": "TEST_RUN=true mocha --require @babel/register test/test.js", 88 | "coverage": "TEST_RUN=true nyc --reporter=lcov mocha -- test/test.js", 89 | "coveralls": "coveralls < coverage/lcov.info", 90 | "codeclimate": "node codeclimate-test-reporter < coverage/lcov.info", 91 | "package": "electron-packager . $npm_package_productName --out=dest --ignore='(test|dest|coverage)' --prune --asar --all --version=$(npm run electronVersion)", 92 | "winpackage": "chmod +x script/win-build.sh && script/win-build.sh", 93 | "xtest": "npm run xtestbuild && npm run xltest", 94 | "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", 95 | "xltest": "unset TEST_RUN && rm -rf ~/Library/Application\\ Support/CrypterTest/ && mocha --require @babel/register ./test/ui/*.js", 96 | "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" 97 | }, 98 | "keywords": [ 99 | "encryption", 100 | "decryption", 101 | "crypto", 102 | "end-to-end", 103 | "client", 104 | "electron" 105 | ], 106 | "devDependencies": { 107 | "@babel/core": "^7.11.0", 108 | "@babel/preset-env": "^7.11.0", 109 | "@babel/register": "^7.10.5", 110 | "babel-cli": "6.26.0", 111 | "chai": "4.2.0", 112 | "chai-as-promised": "7.1.1", 113 | "codeclimate-test-reporter": "0.5.1", 114 | "coveralls": "3.1.0", 115 | "electron": "9.1.2", 116 | "electron-builder": "^22.8.0", 117 | "electron-packager": "15.0.0", 118 | "electron-rebuild": "1.11.0", 119 | "gulp": "4.0.2", 120 | "gulp-babel": "8.0.0", 121 | "gulp-babel-istanbul": "1.6.0", 122 | "gulp-env": "0.4.0", 123 | "gulp-inject-modules": "1.0.0", 124 | "gulp-json-editor": "2.5.4", 125 | "gulp-less": "4.0.1", 126 | "gulp-mocha": "7.0.2", 127 | "isparta": "4.1.1", 128 | "less-plugin-clean-css": "1.5.1", 129 | "mocha": "8.1.0", 130 | "mocha-lcov-reporter": "1.3.0", 131 | "nyc": "^15.1.0", 132 | "spectron": "11.1.0" 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /app/static/styles/mixins.css: -------------------------------------------------------------------------------- 1 | @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} -------------------------------------------------------------------------------- /app/core/Db.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /** 3 | * Db.js 4 | * Custom JSON DB API implementation 5 | ******************************/ 6 | 7 | const _ = require('lodash') 8 | const logger = require('electron-log') 9 | const fs = require('fs-extra') 10 | 11 | /** 12 | * Constructor 13 | * An alias for the levelup constructor 14 | * @param {location}:string the location at which the Db to create or is at 15 | */ 16 | 17 | // Db class constructor 18 | function Db (path, cb) { 19 | // If Db already exists at the path, it opens the Db 20 | // If no Db exists at the path, it creates a new Db 21 | 22 | // Set db path 23 | this.path = path 24 | // Save context to instance 25 | const self = this 26 | 27 | // Create db if does not exist 28 | fs.ensureFile(path, (err) => { 29 | if (err) { 30 | throw err 31 | return 32 | } 33 | // file has now been created or exists 34 | // either way, read the json now 35 | fs.readFile(path, (err, db) => { 36 | if (err) { 37 | throw err 38 | return 39 | } 40 | if (_.isEmpty(db)) { 41 | logger.verbose('db is empty so initialising it...') 42 | self._db = {} 43 | self.open = true 44 | } else { 45 | try { 46 | logger.verbose('db is not empty so json parsing it...') 47 | // Get original JSON object or create new if does not exist 48 | self._db = JSON.parse(db) 49 | self.open = true 50 | } catch (err) { 51 | throw err 52 | return 53 | } 54 | } 55 | // call callback with newly created object 56 | cb(self) 57 | }) 58 | }) 59 | } 60 | 61 | // Flush db to filesystem 62 | Db.prototype.flush = function () { 63 | logger.verbose('flushing _db to fs') 64 | const self = this 65 | return new Promise((resolve, reject) => { 66 | fs.outputJson(self.path, self._db, (err) => { 67 | if (err) reject(err) 68 | // successfully flushed db to disk 69 | resolve() 70 | }) 71 | }) 72 | } 73 | 74 | // Add items to db 75 | Db.prototype.put = function (key, value) { 76 | logger.verbose(`Putting ${key} in _db`) 77 | const self = this 78 | return new Promise((resolve, reject) => { 79 | // set value with key in the internal db 80 | self._db[key] = value 81 | // then flush db to fs 82 | self 83 | .flush() 84 | .then(() => { 85 | resolve() 86 | }) 87 | .catch((err) => { 88 | reject(err) 89 | }) 90 | }) 91 | } 92 | 93 | // Get items from db 94 | Db.prototype.get = function (key) { 95 | logger.verbose(`Getting ${key} from _db`) 96 | const self = this 97 | return new Promise((resolve, reject) => { 98 | // Return the object if it exists otherwise return false 99 | resolve(_.has(self._db, key) ? self._db[key] : false) 100 | }) 101 | } 102 | 103 | Db.prototype.close = function () { 104 | logger.verbose(`Closing _db`) 105 | const self = this 106 | return new Promise((resolve, reject) => { 107 | // Check if db is open 108 | if (self.open) { 109 | self 110 | .flush() 111 | .then(() => { 112 | self.open = false 113 | resolve() 114 | }) 115 | .catch((err) => { 116 | reject(err) 117 | }) 118 | } 119 | }) 120 | } 121 | 122 | // save a global object of a name (objName) 123 | Db.prototype.saveGlobalObj = function (objName) { 124 | logger.verbose(`Saving global obj ${objName} to _db`) 125 | const self = this // get reference to the class instance 126 | return new Promise((resolve, reject) => { 127 | // If object not empty then save it in db 128 | if (!_.isEmpty(global[objName])) { 129 | // stringify object and store as a string with objName as key 130 | try { 131 | // wrap serialization of object around try catch as it could throw error 132 | const serializedObj = JSON.stringify(global[objName]) 133 | self 134 | .put(objName, serializedObj) 135 | .then(() => { 136 | resolve() 137 | }) 138 | .catch((err) => { 139 | reject(err) // db save error 140 | }) 141 | } catch (err) { 142 | reject(err) 143 | } 144 | } else { 145 | // If object empty then do not save it 146 | resolve() 147 | } 148 | }) 149 | } 150 | 151 | // restores a global object of a name (objName) 152 | Db.prototype.restoreGlobalObj = function (objName) { 153 | logger.verbose(`Restoring global obj ${objName} from _db`) 154 | const self = this // get reference to th class instance 155 | return new Promise((resolve, reject) => { 156 | // get serialized object from db 157 | self 158 | .get(objName) 159 | .then((serializedObj) => { 160 | try { 161 | // deserialize object and set as global 162 | global[objName] = JSON.parse(serializedObj) // try parsing JSON 163 | resolve() 164 | } catch (err) { 165 | // if error occurs while parsing, reject promise 166 | reject(err) 167 | } 168 | }) 169 | .catch((err) => { 170 | // I/O or other error, pass it up the callback 171 | reject(err) 172 | }) 173 | }) 174 | } 175 | 176 | module.exports = Db 177 | -------------------------------------------------------------------------------- /app/static/styles/settings.less: -------------------------------------------------------------------------------- 1 | // out: ./settings.css, compress: true 2 | /* Settings styles 3 | ========================================================================== 4 | */ 5 | @import (less) "mixins.less"; 6 | /* Variable declarations*/ 7 | @list-height: 10rem; 8 | @border-width: 2px; 9 | @spacing04: 0.4rem; 10 | @ic-size: 1.4rem; 11 | @header-ic-size: 6rem; 12 | 13 | .panel-container { 14 | overflow: hidden; 15 | position: relative; 16 | height: 100vh; 17 | 18 | & > div:not(.menu) { 19 | position: absolute; 20 | text-align: center; 21 | transform: translateX(-110%); 22 | transition: transform 0.5s; 23 | 24 | & > button { 25 | margin-top: 1rem; 26 | } 27 | } 28 | 29 | & > div.current { 30 | width: 100%; 31 | min-height: 100vh; 32 | transform: none; 33 | position: relative; 34 | display: flex; 35 | flex-direction: column; 36 | 37 | section { 38 | height: 90vh; 39 | } 40 | } 41 | 42 | & > div.current ~ div { 43 | transform: translateX(110%); 44 | } 45 | } 46 | 47 | section#settings { 48 | min-height: 100%; 49 | position: relative; 50 | background-color: @white; 51 | } 52 | 53 | p#errLabel { 54 | text-align: center; 55 | } 56 | 57 | a:not(.navigationLink) { 58 | font-size: 0.8rem; 59 | color: @blacker; 60 | text-decoration: none; 61 | outline: none; 62 | } 63 | 64 | .left { 65 | -webkit-align-self: flex-start; 66 | align-self: flex-start; 67 | } 68 | 69 | .right { 70 | margin-left: auto; 71 | } 72 | 73 | .menu { 74 | display: flex; 75 | margin-left: 0; 76 | margin-right: 0; 77 | align-self: flex-end; 78 | transition: color 0.1s ease; 79 | 80 | &:after { 81 | content: ''; 82 | display: block; 83 | height: 0; 84 | clear: both; 85 | visibility: hidden; 86 | } 87 | 88 | .item:after { 89 | display: none; 90 | } 91 | 92 | .item { 93 | position: relative; 94 | cursor: pointer; 95 | line-height: 1.4rem; 96 | text-decoration: none; 97 | -webkit-tap-highlight-color: transparent; 98 | -webkit-box-flex: 0; 99 | flex: 0 0 auto; 100 | -webkit-user-select: none; 101 | user-select: none; 102 | padding: 0.7rem 0.8rem 0rem; 103 | margin-right: 0.4rem; 104 | text-transform: none; 105 | color: rgba(0, 0, 0, 0.87); 106 | transition: background 0.1s ease, box-shadow 0.1s ease, color 0.1s ease; 107 | } 108 | 109 | .active.item { 110 | border-bottom: @border-width solid rgba(0, 0, 0, 0.8); 111 | color: rgba(0, 0, 0, 0.95); 112 | font-weight: normal; 113 | box-shadow: none; 114 | } 115 | } 116 | 117 | a.item:active { 118 | border-bottom: @border-width solid rgba(0, 0, 0, 0.1); 119 | margin-bottom: -@border-width; 120 | } 121 | 122 | .active.item { 123 | background-color: transparent; 124 | box-shadow: none; 125 | border-color: @black; 126 | font-weight: 700; 127 | margin-bottom: -@border-width; 128 | color: rgba(0,0,0,.95); 129 | } 130 | 131 | .item.right { 132 | display: flex; 133 | margin-left: auto !important; 134 | } 135 | 136 | img.icon { 137 | width: @ic-size; 138 | height: @ic-size; 139 | } 140 | img.header { 141 | width: 6rem; 142 | height: auto; 143 | bottom: 0; 144 | } 145 | 146 | h3 { 147 | margin: 0.5rem 0; 148 | font-weight: normal; 149 | } 150 | 151 | h4 { 152 | margin-top: 0.5rem; 153 | margin-bottom: 0.5rem; 154 | font-weight: 300; 155 | text-align: left; 156 | border-bottom: 1px solid @light; 157 | padding-bottom: 5px; 158 | } 159 | 160 | header { 161 | img.icon { 162 | width: @header-ic-size; 163 | height: @header-ic-size; 164 | } 165 | } 166 | 167 | footer { 168 | display: flex; 169 | position: absolute; 170 | border-top: 1px solid @light; 171 | width: 100%; 172 | bottom: 0; 173 | left: 0; 174 | padding: 0.3rem 0; 175 | font-size: 0.8rem; 176 | align-items: center; 177 | justify-content: center; 178 | background: @white; 179 | 180 | & > img { 181 | height: 1rem !important; 182 | } 183 | } 184 | 185 | input[type=text] { 186 | width: 15rem; 187 | } 188 | 189 | section.category { 190 | text-align: left; 191 | padding: 1rem; 192 | } 193 | 194 | div.options { 195 | margin-left: 1rem; 196 | } 197 | 198 | div.option { 199 | display: flex; 200 | margin-top: 0.2rem; 201 | font-size: 0.8rem; 202 | } 203 | 204 | div.list { 205 | height: @list-height; 206 | width: 100%; 207 | overflow-y: auto; 208 | } 209 | 210 | div.item { 211 | display: flex; 212 | align-items: center; 213 | border-bottom: 1px solid @light; 214 | padding: @spacing04 0; 215 | text-align: left; 216 | } 217 | /* Panel styles 218 | * ================= 219 | */ 220 | section.general { 221 | width: 90%; 222 | margin: auto; 223 | button { 224 | margin: 0.4rem 0; 225 | } 226 | button:nth-child(1) { 227 | margin-right: 0.4rem; 228 | } 229 | } 230 | 231 | section.contribute { 232 | width: 70%; 233 | margin: auto; 234 | 235 | header { 236 | border-bottom: 1px solid @light; 237 | } 238 | 239 | div.list { 240 | width: 100%; 241 | margin-bottom: 3rem; 242 | height: 100%; 243 | 244 | .item { 245 | a { 246 | display: flex; 247 | align-items: center; 248 | text-align: left; 249 | } 250 | 251 | .name { 252 | font-weight: 300; 253 | } 254 | 255 | .icon { 256 | padding: 0.4rem; 257 | } 258 | } 259 | } 260 | } 261 | 262 | section.crypto { 263 | width: 90%; 264 | margin: 1rem auto auto; 265 | 266 | header > img { 267 | height: 4rem !important; 268 | width: auto; 269 | } 270 | 271 | button { 272 | width: 20%; 273 | } 274 | } -------------------------------------------------------------------------------- /app/static/styles/masterpassprompt.css: -------------------------------------------------------------------------------- 1 | @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} -------------------------------------------------------------------------------- /test/ui/test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const Application = require('spectron').Application 3 | const assert = require('assert') 4 | const chai = require('chai') 5 | const fs = require('fs-extra') 6 | const expect = chai.expect 7 | const chaiAsPromised = require('chai-as-promised') 8 | const path = require('path') 9 | const logger = require('electron-log') 10 | 11 | chai.should() 12 | chai.use(chaiAsPromised) 13 | 14 | console.log(`cwd: ${process.cwd()}`) 15 | console.log(`__dirname: ${__dirname}`) 16 | 17 | let wait = function (s) { 18 | return new Promise((resolve, reject) => { 19 | setTimeout(function () { 20 | resolve() 21 | }, s) 22 | }) 23 | } 24 | 25 | let saveScreenshot = function (client, filename) { 26 | return new Promise(function(resolve, reject) { 27 | // receive screenshot as Buffer 28 | var screenshot = client.saveScreenshot() 29 | fs.writeFileSync(filename, screenshot, 'base64') 30 | resolve() 31 | // fs.writeFile(filename, client.saveScreenshot() , 'base64', function (err) { 32 | // if (err) reject(err) 33 | // resolve() 34 | // }) 35 | }) 36 | } 37 | 38 | const masterpass = 'random#101' 39 | 40 | var responses = { 41 | invalid: 'MUST CONTAIN 1 ALPHABET, 1 NUMBER, 1 SYMBOL AND BE AT LEAST 8 CHARACTERS', 42 | incorrect: 'INCORRECT MASTERPASS', 43 | correct: 'CORRECT MASTERPASS', 44 | setSuccess: 'MASTERPASS SUCCESSFULLY SET', 45 | empty: 'PLEASE ENTER A MASTERPASS', 46 | } 47 | 48 | describe("Crypter Render Modules's tests", function () { 49 | this.timeout(10000) 50 | 51 | // before(function () { 52 | // global.paths = { 53 | // mdb: `${this.app.getPath('userData')}/mdb` 54 | // } 55 | // 56 | // console.log(require('util').inspect(global.paths, { depth: null })) 57 | // return this.app.start() 58 | // }) 59 | 60 | beforeEach(function () { 61 | this.app = new Application({ 62 | // path: '../dest/Crypter-darwin-x64/Crypter.app/Contents/MacOS/Electron', 63 | path: path.join(__dirname, '../../dest/CrypterTest-darwin-x64/CrypterTest.app/Contents/MacOS/CrypterTest') 64 | }) 65 | 66 | return this.app.start() 67 | }) 68 | 69 | afterEach(function () { 70 | if (this.app && this.app.isRunning()) { 71 | return this.app.stop() 72 | } 73 | }) 74 | 75 | it('should show setup window', function () { 76 | return this.app.client.getWindowCount().then(function (count) { 77 | assert.equal(count, 1) 78 | }) 79 | }) 80 | 81 | describe('Setup', function () { 82 | // before(() => { 83 | // // remove /Crypter app dir in userdata before test 84 | // fs.removeSync(``) 85 | // }) 86 | 87 | it('should give response for masterpass input', function () { 88 | const nomp = '' 89 | const invalidmp = 'random' 90 | let checkResponse = function (client, input, response) { 91 | client.setValue('#setMasterPassInput', input) 92 | .click('#setMasterPass') 93 | .then(() => { 94 | return wait(200) // wait 2 seconds 95 | }) 96 | // .then(() => { 97 | // return saveScreenshot(client, `./screens/${response}.png`) 98 | // }) 99 | .getText('#setMasterPassLabel') 100 | .then((text) => { 101 | expect(text).to.equal(response) 102 | return 103 | }) 104 | .catch((err) => { 105 | throw err 106 | }) 107 | } 108 | return this.app.client 109 | .then(() => { 110 | return wait(2000) // wait 2 seconds 111 | }) 112 | .click('#getstarted') 113 | .then(() => { 114 | return wait(1000) // wait 2 seconds 115 | }) 116 | .then(() => { 117 | return checkResponse(this.app.client, nomp, responses.empty) 118 | }) 119 | // .then(() => { 120 | // return saveScreenshot(this.app.client, '../../screens/masterpass_empty.png') 121 | // }) 122 | .then(() => { 123 | return checkResponse(this.app.client, invalidmp, responses.invalid) 124 | }) 125 | .then(() => { 126 | return checkResponse(this.app.client, masterpass, responses.setSuccess) 127 | }) 128 | .then(() => { 129 | return wait(2000) // wait 2 seconds 130 | }) 131 | .catch((err) => { 132 | throw err 133 | }) 134 | }) 135 | }) 136 | 137 | describe('Main', function () { 138 | before(() => { 139 | }) 140 | 141 | let checkResponse = function (client, input, response) { 142 | return new Promise(function(resolve, reject) { 143 | return client.setValue('#checkMasterPassInput', input) 144 | .click('#checkMasterPass') 145 | .then(() => { 146 | return wait(1000) 147 | }) 148 | .getText('#checkMasterPassLabel') 149 | .then((text) => { 150 | expect(text).to.equal(response) 151 | resolve() 152 | }) 153 | .catch((err) => { 154 | reject(err) 155 | }) 156 | }) 157 | } 158 | 159 | it('should give response for incorrect masterpass', function () { 160 | const mp = 'yolo#10111' 161 | return this.app.client 162 | .then(() => { 163 | return checkResponse(this.app.client, mp, responses.incorrect) 164 | }) 165 | }) 166 | 167 | it('should give response for invalid masterpass', function () { 168 | const mp = 'yolo' 169 | return this.app.client 170 | .then(() => { 171 | return checkResponse(this.app.client, mp, responses.invalid) 172 | }) 173 | }) 174 | 175 | it('should give response for empty masterpass', function () { 176 | const mp = '' 177 | return this.app.client 178 | .then(() => { 179 | return checkResponse(this.app.client, mp, responses.empty) 180 | }) 181 | }) 182 | 183 | it('should give response for correct masterpass', function () { 184 | return this.app.client 185 | .then(() => { 186 | return checkResponse(this.app.client, masterpass, responses.correct) 187 | }) 188 | .getWindowCount() 189 | .then((count) => { 190 | console.log(`window count: ${count}`) 191 | return 192 | }) 193 | }) 194 | 195 | // it('should open file dialog on clicking window', function () { 196 | // return this.app.client 197 | // .then(() => { 198 | // return checkResponse(this.app.client, masterpass, responses.correct) 199 | // }) 200 | // .then(() => { 201 | // return wait('5000') 202 | // }) 203 | // .click("#holder") 204 | // }) 205 | }) 206 | }) 207 | -------------------------------------------------------------------------------- /app/static/styles/crypter.css: -------------------------------------------------------------------------------- 1 | @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} -------------------------------------------------------------------------------- /app/static/styles/setup.less: -------------------------------------------------------------------------------- 1 | // out: ./setup.css, compress: true 2 | /* Setup styles 3 | ========================================================================== 4 | */ 5 | @import (less) "mixins.less"; 6 | /* Variable declarations*/ 7 | @border-width: 2px; 8 | @side-margin: 0.8rem; 9 | @section-width: 60%; 10 | @spacing04: 0.4rem; 11 | @spacing-ic-txt: @side-margin - 0.4rem; 12 | /* TODO: ADD MARGIN RELATIVELY/CONTEXTUALLY TO CORE ELEMENTS I.E. for a button, use panel-container > button*/ 13 | /* Animations */ 14 | @keyframes colorTrans { 15 | from { 16 | background-color: @white; 17 | } 18 | to { 19 | background-color: @light; 20 | } 21 | } 22 | @-webkit-keyframes OrangeAnimGrad { 23 | 0% { 24 | background-position: 0 51%; 25 | } 26 | 50% { 27 | background-position: 100% 50%; 28 | } 29 | 100% { 30 | background-position: 0 51%; 31 | } 32 | } 33 | @keyframes OrangeAnimGrad { 34 | 0% { 35 | background-position: 0 51%; 36 | } 37 | 50% { 38 | background-position: 100% 50%; 39 | } 40 | 100% { 41 | background-position: 0 51%; 42 | } 43 | } 44 | /* General styles */ 45 | #setup { 46 | height : 100%; 47 | width : 100%; 48 | text-align: center; 49 | overflow : hidden; 50 | } 51 | form { 52 | margin: 0; 53 | } 54 | header { 55 | margin: 0; 56 | p { 57 | color: @blacker; 58 | } 59 | .minimal { 60 | h1 { 61 | margin-top : 1rem; 62 | font-weight: 300; 63 | } 64 | } 65 | } 66 | h1 { 67 | font-size : 1.4rem; 68 | margin-bottom: 0.4rem; 69 | font-weight : 400; 70 | } 71 | p { 72 | margin: 0; 73 | color : @dark; 74 | } 75 | h1 + p { 76 | margin: 0 2rem; 77 | } 78 | .panel-container { 79 | position: relative; 80 | & > div { 81 | position : absolute; 82 | text-align: center; 83 | transform : translateX(-110%); 84 | transition: transform 0.5s; 85 | & > button { 86 | display: none; 87 | } 88 | } 89 | & > div.current { 90 | width : 100%; 91 | height: 100vh; 92 | overflow : hidden; 93 | transform: none; 94 | position : relative; 95 | & > button { 96 | margin-top: 1rem; 97 | display: inline-block; 98 | } 99 | } 100 | & > div.current ~ div { 101 | transform: translateX(110%); 102 | } 103 | header { 104 | padding-top: 2rem; 105 | } 106 | } 107 | 108 | // .panel-container > div { 109 | // position: absolute; 110 | // text-align: center; 111 | // transform: translateX(-110%); 112 | // transition: transform 0.5s; 113 | // } 114 | // .panel-container > div > footer { 115 | // transform: translateX(-110%); 116 | // } 117 | // 118 | // .panel-container > div.current > footer { 119 | // transform: translateX(100%); 120 | // } 121 | /* TODO DISABLE ANIMATION ON FIRST PANE */ 122 | // #panel-welcome ~ div { 123 | // transform: none; 124 | // } 125 | // #panel-welcome > div { 126 | // transform: none; 127 | // transition: none; 128 | // } 129 | .right { 130 | margin-left: auto !important; 131 | } 132 | h3 { 133 | margin : 0.5rem 0; 134 | font-weight: normal; 135 | } 136 | a.csp { 137 | padding: 0.6rem 0 !important; 138 | img { 139 | height: 1.6rem; 140 | widht : auto; 141 | } 142 | } 143 | footer { 144 | display : flex; 145 | clear : both; 146 | position : absolute; 147 | border-top : 1px solid @light; 148 | width : 100%; 149 | padding-bottom : 0; 150 | background-color: @white; 151 | bottom : 0; 152 | left : 0; 153 | } 154 | footer > a { 155 | float : right; 156 | padding: 0.2rem; 157 | } 158 | .himg { 159 | width : 9rem; 160 | height : auto; 161 | z-index: 1000; 162 | } 163 | div.list { 164 | width : 100%; 165 | overflow: auto; 166 | } 167 | div.item > a { 168 | display : flex; 169 | align-items : center; 170 | border-bottom: 1px solid @light; 171 | padding : @spacing04 0; 172 | text-align : left; 173 | } 174 | img.info { 175 | float : right; 176 | height: 0.9rem; 177 | } 178 | p.info { 179 | display : block; 180 | width : 100%; 181 | box-sizing : border-box; 182 | max-height : 0; 183 | overflow : hidden; 184 | padding : 0 0.5rem; 185 | transition : max-height 0.3s, padding 0.3s; 186 | background-color: #efefef; 187 | font-size : 0.8rem; 188 | } 189 | input { 190 | width: 98%; 191 | } 192 | input[type=password] { 193 | margin-top : 1rem; 194 | border : none; 195 | border-bottom: 1px solid @light; 196 | outline : none; 197 | &:focus { 198 | border-bottom: 1px solid @black; 199 | } 200 | } 201 | input[type=submit] { 202 | margin-top : 1rem; 203 | height : 2rem; 204 | font-weight : normal; 205 | background-color: @light; 206 | border : 1px solid @light; 207 | width : 100%; 208 | outline : none; 209 | &:focus { 210 | border: 1px solid @black; 211 | } 212 | } 213 | div.submit { 214 | text-align : right; 215 | background-color: @white; 216 | width : 100%; 217 | } 218 | .none { 219 | display: none; 220 | } 221 | 222 | /* Panel styles 223 | * ================= 224 | */ 225 | .banner { 226 | margin : 1rem auto 0; 227 | position : relative; 228 | align-items : center; 229 | justify-content: center; 230 | width : 100%; 231 | } 232 | .banner-left { 233 | margin-left: -3rem; 234 | float : left; 235 | } 236 | .banner-right { 237 | margin-right: -3rem; 238 | float : right; 239 | } 240 | /* Welcome Panel styles 241 | * ================= 242 | */ 243 | #panel-welcome { 244 | .marquee { 245 | overflow: hidden; 246 | position: relative; 247 | } 248 | .marquee-1 { 249 | visibility: hidden; 250 | } 251 | .marquee-2 { 252 | visibility: hidden; 253 | } 254 | .marquee.visible { 255 | visibility: visible; 256 | } 257 | & > header { 258 | .banner { 259 | margin : 2rem auto 0; 260 | position: relative; 261 | display : inline-block; 262 | width : 50%; 263 | z-index : -1; 264 | color : @light; 265 | } 266 | .banner + .himg { 267 | margin-top: -7.5rem !important; 268 | } 269 | .himg { 270 | z-index: 1000; 271 | } 272 | } 273 | } 274 | /* Done Panel styles 275 | * ================= 276 | */ 277 | #panel-done { 278 | footer { 279 | padding: 0.3rem 0; 280 | text-align : center; 281 | display : flex; 282 | align-items : center; 283 | font-size : 0.8rem; 284 | justify-content: center; 285 | img { 286 | height: 1rem !important; 287 | } 288 | } 289 | } 290 | 291 | /* MasterPass Panel styles 292 | * ================= 293 | */ 294 | #masterpassprompt { 295 | height : 100%; 296 | min-height: 20rem; 297 | width : @section-width; 298 | margin : auto; 299 | } 300 | #setMasterPass { 301 | margin : 0.5rem 0; 302 | font-weight: normal; 303 | width : 100%; 304 | } 305 | #masterpassprompt p.note { 306 | margin : none; 307 | font-size: 0.8rem; 308 | } 309 | .forgotMP { 310 | padding : 0.2rem 0.2rem 0 0; 311 | text-align: right; 312 | } 313 | 314 | /* INVALID STYLING */ 315 | .masterpass > label { 316 | display : inline-block; 317 | font-size: 0.8rem; 318 | margin : 0.2rem 0; 319 | color : @invalid-color; 320 | } 321 | .invalid { 322 | border-color: @invalid-color !important; 323 | } 324 | -------------------------------------------------------------------------------- /app/static/styles/settings.css: -------------------------------------------------------------------------------- 1 | @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%} -------------------------------------------------------------------------------- /app/static/styles/setup.css: -------------------------------------------------------------------------------- 1 | @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} -------------------------------------------------------------------------------- /app/static/styles/mixins.less: -------------------------------------------------------------------------------- 1 | // out: ./mixins.css, compress: true 2 | @import (css) "../../node_modules/normalize.css/normalize.css"; 3 | 4 | /* theme colors */ 5 | @white: #FFFFFF; 6 | @light: #DDDDDD; 7 | @dark: #9D9D9D; 8 | @darker: #555555; 9 | @black: #333333; 10 | @blacker: #222222; 11 | @invalid-color: #F05A5C; 12 | @orange: #EEA849; 13 | @orange_d: #F46B45; 14 | @ic-size: 1.4rem; 15 | @max-height: 100vh; 16 | 17 | /* Animations */ 18 | @keyframes colorTrans { 19 | from { 20 | border-bottom: 1px solid @light; 21 | } 22 | to { 23 | border-bottom: 1px solid @black; 24 | } 25 | } 26 | 27 | /* General (shared) styles */ 28 | body { 29 | font-family : "Roboto", "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, "Lucida Grande", sans-serif; 30 | font-weight : 300; 31 | margin : 0; 32 | padding : 0; 33 | background-color: @white; 34 | width : 100%; 35 | height : 100vh; 36 | overflow : hidden; 37 | } 38 | p { 39 | margin: 0; 40 | } 41 | div.none { 42 | display: none; 43 | } 44 | .left { 45 | -webkit-align-self: flex-start; 46 | align-self : flex-start !important; 47 | } 48 | .right { 49 | margin-left: auto !important; 50 | } 51 | button, .button { 52 | background-color: @white; 53 | border : 1px solid @light; 54 | width : 100%; 55 | padding : 0.3rem; 56 | font-weight : 400; 57 | outline : none; 58 | &:active, 59 | &:hover { 60 | background-color: @light; 61 | } 62 | } 63 | button.fancy { 64 | margin-top : 0.2rem; 65 | border : none; 66 | color : @white; 67 | width : 30% !important; 68 | background : linear-gradient(to right, @orange, @orange_d); 69 | background-size: 200% 200%; 70 | animation : OrangeAnimGrad 3s ease infinite; 71 | &:active, 72 | &:hover { 73 | opacity: 0.8; 74 | } 75 | } 76 | .fancy { 77 | border : none; 78 | color : @white; 79 | background : linear-gradient(to right, @orange, @orange_d); 80 | background-size: 200% 200%; 81 | animation : OrangeAnimGrad 3s ease infinite; 82 | &:active, 83 | &:hover { 84 | opacity: 0.8; 85 | } 86 | } 87 | input[type=text] { 88 | border: none; 89 | border-bottom: 1px solid @light; 90 | outline: none; 91 | vertical-align: top; 92 | 93 | &:focus { 94 | animation-name: colorTrans; 95 | animation-duration: 2s; 96 | border-bottom: 1px solid @black; 97 | } 98 | } 99 | img.header { 100 | width: 7rem; 101 | height: auto; 102 | bottom: 0; 103 | } 104 | img.icon { 105 | width : @ic-size; 106 | height: @ic-size; 107 | } 108 | img.info { 109 | padding-left: 0.2rem; 110 | height : 0.8rem; 111 | } 112 | p.intrfo { 113 | color: @dark; 114 | font-size: 0.8rem; 115 | margin: 0.4rem; 116 | } 117 | p.info { 118 | display : block; 119 | width : 100%; 120 | box-sizing : border-box; 121 | max-height : 0; 122 | padding : 0 0.5rem; 123 | transition : max-height 0.5s, padding 0.3s; 124 | background-color: @light; 125 | font-size : 0.8rem; 126 | } 127 | img.info:hover + p.info { 128 | max-height : 10rem; 129 | padding-top : 0.5rem; 130 | padding-bottom: 0.5rem; 131 | } 132 | a { 133 | cursor: pointer; 134 | cursor: hand; 135 | font-size : 0.8rem; 136 | color : @blacker; 137 | text-decoration: none; 138 | outline : none; 139 | } 140 | a.back { 141 | float: left; 142 | } 143 | a.back > img { 144 | height: 1rem; 145 | padding: 0.2rem; 146 | width : auto; 147 | } 148 | footer { 149 | display: flex; 150 | position: absolute; 151 | border-top: 1px solid @light; 152 | width: 100%; 153 | bottom: 0; 154 | left: 0; 155 | font-size: 0.8rem; 156 | align-items: center; 157 | justify-content: left; 158 | background: @white; 159 | } 160 | /* font declarations */ 161 | @font-face { 162 | font-family: 'Roboto'; 163 | src : url('../fonts/Roboto-Italic.eot'); 164 | src : url('../fonts/Roboto-Italic.eot?#iefix') format('embedded-opentype'), url('../fonts/Roboto-Italic.woff') format('woff'), url('../fonts/Roboto-Italic.ttf') format('truetype'); 165 | font-weight: normal; 166 | font-style : italic; 167 | } 168 | @font-face { 169 | font-family: 'Roboto'; 170 | src : url('../fonts/Roboto-BlackItalic.eot'); 171 | src : url('../fonts/Roboto-BlackItalic.eot?#iefix') format('embedded-opentype'), url('../fonts/Roboto-BlackItalic.woff') format('woff'), url('../fonts/Roboto-BlackItalic.ttf') format('truetype'); 172 | font-weight: 900; 173 | font-style : italic; 174 | } 175 | @font-face { 176 | font-family: 'Roboto'; 177 | src : url('../fonts/Roboto-Bold.eot'); 178 | src : url('../fonts/Roboto-Bold.eot?#iefix') format('embedded-opentype'), url('../fonts/Roboto-Bold.woff') format('woff'), url('../fonts/Roboto-Bold.ttf') format('truetype'); 179 | font-weight: bold; 180 | font-style : normal; 181 | } 182 | @font-face { 183 | font-family: 'Roboto'; 184 | src : url('../fonts/Roboto-Thin.eot'); 185 | src : url('../fonts/Roboto-Thin.eot?#iefix') format('embedded-opentype'), url('../fonts/Roboto-Thin.woff') format('woff'), url('../fonts/Roboto-Thin.ttf') format('truetype'); 186 | font-weight: 100; 187 | font-style : normal; 188 | } 189 | @font-face { 190 | font-family: 'Roboto'; 191 | src : url('../fonts/Roboto-Medium.eot'); 192 | src : url('../fonts/Roboto-Medium.eot?#iefix') format('embedded-opentype'), url('../fonts/Roboto-Medium.woff') format('woff'), url('../fonts/Roboto-Medium.ttf') format('truetype'); 193 | font-weight: 500; 194 | font-style : normal; 195 | } 196 | @font-face { 197 | font-family: 'Roboto'; 198 | src : url('../fonts/Roboto-Light.eot'); 199 | src : url('../fonts/Roboto-Light.eot?#iefix') format('embedded-opentype'), url('../fonts/Roboto-Light.woff') format('woff'), url('../fonts/Roboto-Light.ttf') format('truetype'); 200 | font-weight: 300; 201 | font-style : normal; 202 | } 203 | @font-face { 204 | font-family: 'Roboto'; 205 | src : url('../fonts/Roboto-Regular.eot'); 206 | src : url('../fonts/Roboto-Regular.eot?#iefix') format('embedded-opentype'), url('../fonts/Roboto-Regular.woff') format('woff'), url('../fonts/Roboto-Regular.ttf') format('truetype'); 207 | font-weight: normal; 208 | font-style : normal; 209 | } 210 | @font-face { 211 | font-family: 'Roboto'; 212 | src : url('../fonts/Roboto-ThinItalic.eot'); 213 | src : url('../fonts/Roboto-ThinItalic.eot?#iefix') format('embedded-opentype'), url('../fonts/Roboto-ThinItalic.woff') format('woff'), url('../fonts/Roboto-ThinItalic.ttf') format('truetype'); 214 | font-weight: 100; 215 | font-style : italic; 216 | } 217 | @font-face { 218 | font-family: 'Roboto'; 219 | src : url('../fonts/Roboto-BoldItalic.eot'); 220 | src : url('../fonts/Roboto-BoldItalic.eot?#iefix') format('embedded-opentype'), url('../fonts/Roboto-BoldItalic.woff') format('woff'), url('../fonts/Roboto-BoldItalic.ttf') format('truetype'); 221 | font-weight: bold; 222 | font-style : italic; 223 | } 224 | @font-face { 225 | font-family: 'Roboto'; 226 | src : url('../fonts/Roboto-Black.eot'); 227 | src : url('../fonts/Roboto-Black.eot?#iefix') format('embedded-opentype'), url('../fonts/Roboto-Black.woff') format('woff'), url('../fonts/Roboto-Black.ttf') format('truetype'); 228 | font-weight: 900; 229 | font-style : normal; 230 | } 231 | @font-face { 232 | font-family: 'Roboto'; 233 | src : url('../fonts/Roboto-MediumItalic.eot'); 234 | src : url('../fonts/Roboto-MediumItalic.eot?#iefix') format('embedded-opentype'), url('../fonts/Roboto-MediumItalic.woff') format('woff'), url('../fonts/Roboto-MediumItalic.ttf') format('truetype'); 235 | font-weight: 500; 236 | font-style : italic; 237 | } 238 | @font-face { 239 | font-family: 'Roboto'; 240 | src : url('../fonts/Roboto-LightItalic.eot'); 241 | src : url('../fonts/Roboto-LightItalic.eot?#iefix') format('embedded-opentype'), url('../fonts/Roboto-LightItalic.woff') format('woff'), url('../fonts/Roboto-LightItalic.ttf') format('truetype'); 242 | font-weight: 300; 243 | font-style : italic; 244 | } 245 | -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /** 3 | * index.js 4 | * Entry point for app execution 5 | ******************************/ 6 | 7 | const { app, dialog, BrowserWindow } = require('electron'), 8 | { openNewGitHubIssue, debugInfo } = require('electron-util'), 9 | debug = require('electron-debug'), 10 | unhandled = require('electron-unhandled') 11 | 12 | unhandled({ 13 | reportButton: error => { 14 | openNewGitHubIssue({ 15 | user: 'HR', 16 | repo: 'Crypter', 17 | body: `\`\`\`\n${error.stack}\n\`\`\`\n\n---\n\n${debugInfo()}` 18 | }) 19 | } 20 | }) 21 | debug() 22 | app.setAppUserModelId('com.github.hr.crypter') 23 | 24 | // MasterPass credentials global 25 | global.creds = {} 26 | // User settings global 27 | global.settings = {} 28 | // Paths global (only resolved at runtime) 29 | global.paths = { 30 | mdb: `${app.getPath('userData')}/mdb`, 31 | userData: app.getPath('userData'), 32 | home: app.getPath('home'), 33 | documents: app.getPath('documents') 34 | } 35 | 36 | let fileToCrypt 37 | let settingsWindowNotOpen = true 38 | 39 | const logger = require('electron-log') 40 | const { existsSync } = require('fs-extra') 41 | const { checkUpdate } = require('./utils/update') 42 | // Core 43 | const Db = require('./core/Db') 44 | const MasterPass = require('./core/MasterPass') 45 | const MasterPassKey = require('./core/MasterPassKey') 46 | // Windows 47 | const crypter = require('./src/crypter') 48 | const masterPassPrompt = require('./src/masterPassPrompt') 49 | const setup = require('./src/setup') 50 | const settings = require('./src/settings') 51 | const { ERRORS } = require('./config') 52 | 53 | // Debug info 54 | logger.info(`Crypter v${app.getVersion()}`) 55 | logger.info(`Process args: ${process.argv}`) 56 | logger.info(`AppPath: ${app.getAppPath()}`) 57 | logger.info(`UseData Path: ${app.getPath('userData')}`) 58 | // Change exec path 59 | process.chdir(app.getAppPath()) 60 | logger.info(`Changed cwd to: ${process.cwd()}`) 61 | logger.info(`Electron v${process.versions.electron}`) 62 | logger.info(`Electron node v${process.versions.node}`) 63 | // Prevent second instance 64 | const gotTheLock = app.requestSingleInstanceLock() 65 | if (!gotTheLock) { 66 | app.quit() 67 | } 68 | 69 | /** 70 | * Promisification of initialisation 71 | **/ 72 | 73 | const init = function () { 74 | return new Promise(function (resolve, reject) { 75 | // initialise mdb 76 | global.mdb = new Db(global.paths.mdb, function (mdb) { 77 | // Get the credentials serialized object from mdb 78 | resolve(mdb.get('creds')) 79 | }) 80 | }) 81 | } 82 | 83 | const initMain = function () { 84 | logger.verbose(`initialising Main...`) 85 | return global.mdb 86 | .restoreGlobalObj('creds') 87 | .then(() => MasterPass.init()) 88 | .then(mpk => mpk && (global.MasterPassKey = new MasterPassKey(mpk.key))) 89 | } 90 | 91 | /** 92 | * Event handlers 93 | **/ 94 | 95 | // Main event handler 96 | app.on('ready', function () { 97 | // Check for updates, silently 98 | checkUpdate().catch(err => { 99 | logger.warn(err) 100 | }) 101 | // Check synchronously whether paths exist 102 | init() 103 | .then(mainRun => { 104 | logger.info(`Init done.`) 105 | // If the credentials not find in mdb, run setup 106 | // otherwise run main 107 | if (mainRun) { 108 | // Run main 109 | logger.info(`Main run. Creating CrypterWindow...`) 110 | 111 | // Initialise (open mdb and get creds) 112 | initMain() 113 | .then(mpLoaded => { 114 | logger.verbose( 115 | 'INIT: MasterPass', 116 | mpLoaded ? 'loaded' : 'not saved' 117 | ) 118 | // Obtain MasterPass, derive MasterPassKey and set globally 119 | return mpLoaded || createWindow(masterPassPrompt, false) 120 | }) 121 | .then(() => { 122 | // Create the Crypter window and open it 123 | return createWindow(crypter, fileToCrypt) 124 | }) 125 | .then(() => { 126 | // Quit app after crypterWindow is closed 127 | app.quit() 128 | }) 129 | .catch(function (error) { 130 | if (error) { 131 | // Catch any fatal errors and exit 132 | logger.error(`PROMISE ERR: ${error.stack}`) 133 | dialog.showErrorBox(ERRORS.PROMISE, error.message) 134 | } 135 | app.quit() 136 | }) 137 | } else { 138 | // Run Setup 139 | logger.info('Setup run. Creating Setup wizard...') 140 | 141 | createWindow(setup) 142 | .then(() => { 143 | logger.info('MAIN Setup successfully completed. quitting...') 144 | // setup successfully completed 145 | app.quit() 146 | }) 147 | .catch(function (error) { 148 | logger.error(`PROMISE ERR: ${error.stack}`) 149 | // Display error to user 150 | dialog.showErrorBox(ERRORS.PROMISE, error.message) 151 | app.quit() 152 | }) 153 | } 154 | }) 155 | .catch(function (error) { 156 | logger.error(`PROMISE ERR: ${error.stack}`) 157 | // Display error to user 158 | dialog.showErrorBox(ERRORS.PROMISE, error.message) 159 | app.quit() 160 | }) 161 | }) 162 | 163 | /** 164 | * Electron events 165 | **/ 166 | app.on('will-finish-launching', () => { 167 | // Check if launched with a file (opened with app in macOS) 168 | app.on('open-file', (event, file) => { 169 | if (app.isReady() === false) { 170 | // Opening when not launched yet 171 | logger.info('Launching with open-file ' + file) 172 | fileToCrypt = file 173 | } 174 | event.preventDefault() 175 | }) 176 | 177 | // Check if launched with a file (opened with app in Windows) 178 | if ( 179 | process.argv[1] && 180 | process.argv[1].length > 1 && 181 | existsSync(process.argv[1]) 182 | ) { 183 | fileToCrypt = process.argv[1] 184 | } 185 | }) 186 | 187 | app.on('window-all-closed', () => { 188 | logger.verbose('APP: window-all-closed event emitted') 189 | // On macOS it is common for applications and their menu bar 190 | // to stay active until the user quits explicitly with Cmd + Q 191 | }) 192 | 193 | app.on('quit', () => { 194 | logger.info('APP: quit event emitted') 195 | global.mdb.close().catch(err => { 196 | console.error(err) 197 | throw err 198 | }) 199 | }) 200 | 201 | app.on('will-quit', event => { 202 | // will exit program once exit procedures have been run 203 | logger.info(`APP.ON('will-quit'): will-quit event emitted`) 204 | global.mdb.close().catch(err => { 205 | console.error(err) 206 | throw err 207 | }) 208 | }) 209 | 210 | /** 211 | * Custom events 212 | **/ 213 | 214 | app.on('app:quit', () => { 215 | logger.verbose('APP: app:quit event emitted') 216 | app.quit() 217 | }) 218 | 219 | app.on('app:open-settings', () => { 220 | logger.verbose('APP: app:open-settings event emitted') 221 | createWindow(settings) 222 | }) 223 | 224 | app.on('app:check-update', () => { 225 | logger.verbose('APP: app:check-updates event emitted') 226 | // Check for updates 227 | checkUpdate() 228 | .then(updateAvailable => { 229 | if (!updateAvailable) { 230 | dialog.showMessageBox({ 231 | type: 'info', 232 | message: 'No update available.', 233 | detail: `You have the latest version Crypter ${app.getVersion()} :)` 234 | }) 235 | } 236 | }) 237 | .catch(err => { 238 | logger.warn(err) 239 | dialog.showErrorBox( 240 | 'Failed to check for update', 241 | `An error occured while checking for update:\n ${err.message}` 242 | ) 243 | }) 244 | }) 245 | 246 | app.on('app:relaunch', () => { 247 | logger.verbose('APP: app:relaunch event emitted') 248 | // Relaunch Crypter 249 | app.relaunch() 250 | // Exit successfully 251 | app.quit(0) 252 | // app.exit(0) 253 | }) 254 | 255 | app.on('app:reset-masterpass', () => { 256 | logger.verbose('APP: app:reset-masterpass event emitted') 257 | createWindow(masterPassPrompt) 258 | }) 259 | 260 | /** 261 | * Promisification of windows 262 | **/ 263 | 264 | function createWindow (window, ...args) { 265 | const winInst = BrowserWindow.getAllWindows().find( 266 | win => win.getTitle() === window.title 267 | ) 268 | // Focus on existing instance 269 | if (winInst) return winInst.focus() 270 | return new Promise((resolve, reject) => 271 | window.window(global, ...args, err => { 272 | if (err) reject(err) 273 | resolve() 274 | }) 275 | ) 276 | } 277 | -------------------------------------------------------------------------------- /app/static/fonts/LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /app/core/crypto.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /** 3 | * crypto.js 4 | * Provides the crypto functionality required 5 | ******************************/ 6 | 7 | const fs = require('fs-extra') 8 | const path = require('path') 9 | const scrypto = require('crypto') 10 | const logger = require('electron-log') 11 | const Readable = require('stream').Readable 12 | const tar = require('tar-fs') 13 | const { CRYPTO, REGEX, ERRORS } = require('../config') 14 | 15 | // Helper functions 16 | 17 | let readFile = path => { 18 | return new Promise((resolve, reject) => { 19 | fs.readFile(path, 'utf-8', (err, data) => { 20 | if (err) reject(err) 21 | resolve(data) 22 | }) 23 | }) 24 | } 25 | 26 | // Exports 27 | 28 | exports.crypt = (origpath, masterpass) => { 29 | return new Promise((resolve, reject) => { 30 | logger.verbose(`Encrypting ${origpath}...`) 31 | // Resolve the destination path for encrypted file 32 | exports 33 | .encrypt(origpath, masterpass) 34 | .then(creds => { 35 | resolve({ 36 | op: CRYPTO.ENCRYPT_OP, // Crypter operation 37 | name: path.basename(origpath), // filename 38 | path: origpath, // path of the (unencrypted) file 39 | cryptPath: creds.cryptpath, // path of the encrypted file 40 | salt: creds.salt.toString('hex'), // salt used to derivekey in hex 41 | key: creds.key.toString('hex'), // dervived key in hex 42 | iv: creds.iv.toString('hex'), // iv in hex 43 | authTag: creds.tag.toString('hex') // authTag in hex 44 | }) 45 | }) 46 | .catch(err => { 47 | reject(err) 48 | }) 49 | }) 50 | } 51 | 52 | exports.encrypt = (origpath, mpkey) => { 53 | // Encrypts any arbitrary data passed with the pass 54 | return new Promise((resolve, reject) => { 55 | // derive the encryption key 56 | exports 57 | .deriveKey(mpkey, null, CRYPTO.DEFAULTS.ITERATIONS) 58 | .then(dcreds => { 59 | let tag 60 | let isDirectory = fs.lstatSync(origpath).isDirectory() 61 | let tempd = `${path.dirname(origpath)}/${CRYPTO.ENCRYPTION_TMP_DIR}` 62 | let dataDestPath = `${tempd}/data` 63 | let credsDestPath = `${tempd}/creds` 64 | logger.verbose( 65 | `tempd: ${tempd}, dataDestPath: ${dataDestPath}, credsDestPath: ${credsDestPath}, isDirectory: ${isDirectory}` 66 | ) 67 | // create tempd temporary directory 68 | fs.mkdirs(tempd, err => { 69 | if (err) reject(err) 70 | logger.verbose(`Created ${tempd} successfully`) 71 | // readstream to read the (unencrypted) file 72 | const origin = isDirectory 73 | ? tar.pack(origpath) 74 | : fs.createReadStream(origpath) 75 | // create data and creds file 76 | const dataDest = fs.createWriteStream(dataDestPath) 77 | const credsDest = fs.createWriteStream(credsDestPath) 78 | // generate a cryptographically secure random iv 79 | const iv = scrypto.randomBytes(CRYPTO.DEFAULTS.IVLENGTH) 80 | // create the AES-256-GCM cipher with iv and derive encryption key 81 | const cipher = scrypto.createCipheriv( 82 | CRYPTO.DEFAULTS.ALGORITHM, 83 | dcreds.key, 84 | iv 85 | ) 86 | 87 | // Read file, apply tranformation (encryption) to stream and 88 | // then write stream to filesystem 89 | origin 90 | .on('error', err => reject(err)) 91 | .pipe(cipher) 92 | .on('error', err => reject(err)) 93 | .pipe(dataDest) 94 | .on('error', err => reject(err)) 95 | .on('finish', () => { 96 | // get the generated Message Authentication Code 97 | tag = cipher.getAuthTag() 98 | // Write credentials used to encrypt in creds file 99 | const creds = { 100 | type: 'CRYPTO', 101 | iv: iv.toString('hex'), 102 | authTag: tag.toString('hex'), 103 | salt: dcreds.salt.toString('hex'), 104 | isDir: isDirectory 105 | } 106 | credsDest.end(JSON.stringify(creds)) 107 | }) 108 | 109 | // writestream finish handler 110 | credsDest.on('finish', () => { 111 | let tarDestPath = origpath + CRYPTO.EXT 112 | const tarDest = fs.createWriteStream(tarDestPath) 113 | const tarPack = tar.pack(tempd) 114 | // Pack directory and zip into a .crypto file 115 | tarPack 116 | .on('error', err => reject(err)) 117 | .pipe(tarDest) 118 | .on('error', err => reject(err)) 119 | .on('finish', () => { 120 | // Remove temporary dir tempd 121 | fs.remove(tempd, err => { 122 | if (err) reject(err) 123 | // return all the credentials and parameters used for encryption 124 | logger.verbose('Successfully deleted tempd!') 125 | resolve({ 126 | salt: dcreds.salt, 127 | key: dcreds.key, 128 | cryptpath: tarDestPath, 129 | tag: tag, 130 | iv: iv 131 | }) 132 | }) 133 | }) 134 | }) 135 | }) 136 | }) 137 | .catch(err => reject(err)) 138 | }) 139 | } 140 | 141 | exports.decrypt = (origpath, mpkey) => { 142 | // Decrypts a crypto format file passed with the pass 143 | return new Promise((resolve, reject) => { 144 | logger.verbose(`Decrypting ${origpath}...`) 145 | // Extract a directory 146 | let tempd = `${path.dirname(origpath)}/${CRYPTO.DECRYPTION_TMP_DIR}` 147 | let dataOrigPath = `${tempd}/${CRYPTO.FILE_DATA}` 148 | let credsOrigPath = `${tempd}/${CRYPTO.FILE_CREDS}` 149 | let dataDestPath = origpath.replace(CRYPTO.EXT, '') 150 | dataDestPath = dataDestPath.replace( 151 | path.basename(dataDestPath), 152 | path.basename(dataDestPath) 153 | ) 154 | let tarOrig = fs.createReadStream(origpath) 155 | let tarExtr = tar.extract(tempd) 156 | // Extract tar to CRYPTO.DECRYPTION_TMP_DIR directory 157 | tarOrig 158 | .on('error', err => reject(err)) 159 | .pipe(tarExtr) 160 | .on('error', err => reject(err)) 161 | .on('finish', () => { 162 | // Now read creds and use to decrypt data 163 | logger.verbose('Finished extracting') 164 | 165 | readFile(credsOrigPath) 166 | .then(credsData => { 167 | let creds 168 | try { 169 | // Try creds v2 170 | creds = JSON.parse(credsData) 171 | creds = [creds.iv, creds.authTag, creds.salt, creds.isDir] 172 | } catch (error) { 173 | // Try creds v1 174 | let credsLine = credsData.trim().match(REGEX.ENCRYPTION_CREDS) 175 | if (!credsLine) { 176 | return reject(new Error(ERRORS.DECRYPT)) 177 | } 178 | creds = credsLine[0].split('#').slice(1) 179 | } 180 | 181 | const iv = Buffer.from(creds[0], 'hex') 182 | const authTag = Buffer.from(creds[1], 'hex') 183 | const salt = Buffer.from(creds[2], 'hex') 184 | const isDir = creds[3] 185 | 186 | logger.verbose( 187 | `Extracted data, iv: ${iv}, authTag: ${authTag}, salt: ${salt}` 188 | ) 189 | // Read encrypted data stream 190 | const dataOrig = fs.createReadStream(dataOrigPath) 191 | // derive the original encryption key for the file 192 | exports 193 | .deriveKey(mpkey, salt, CRYPTO.DEFAULTS.ITERATIONS) 194 | .then(dcreds => { 195 | try { 196 | let decipher = scrypto.createDecipheriv( 197 | CRYPTO.DEFAULTS.ALGORITHM, 198 | dcreds.key, 199 | iv 200 | ) 201 | decipher.setAuthTag(authTag) 202 | let dataDest = isDir 203 | ? tar.extract(dataDestPath) 204 | : fs.createWriteStream(dataDestPath) 205 | 206 | dataOrig 207 | .on('error', err => reject(err)) 208 | .pipe(decipher) 209 | .on('error', err => reject(err)) 210 | .pipe(dataDest) 211 | .on('error', err => reject(err)) 212 | .on('finish', () => { 213 | logger.verbose(`Encrypted to ${dataDestPath}`) 214 | // Now delete tempd (temporary directory) 215 | fs.remove(tempd, err => { 216 | if (err) reject(err) 217 | logger.verbose(`Removed temp dir ${tempd}`) 218 | resolve({ 219 | op: CRYPTO.DECRYPT_OP, 220 | name: path.basename(origpath), 221 | path: origpath, 222 | cryptPath: dataDestPath, 223 | salt: salt.toString('hex'), 224 | key: dcreds.key.toString('hex'), 225 | iv: iv.toString('hex'), 226 | authTag: authTag.toString('hex') 227 | }) 228 | }) 229 | }) 230 | } catch (err) { 231 | reject(err) 232 | } 233 | }) 234 | }) 235 | .catch(err => { 236 | reject(err) 237 | }) 238 | }) 239 | }) 240 | } 241 | 242 | exports.deriveKey = (pass, psalt) => { 243 | return new Promise((resolve, reject) => { 244 | // reject with error if pass not provided 245 | if (!pass) reject(new Error('Pass to derive key from not provided')) 246 | 247 | // If psalt is provided and is a Buffer then assign it 248 | // If psalt is provided and is not a Buffer then coerce it and assign it 249 | // If psalt is not provided then generate a cryptographically secure salt 250 | // and assign it 251 | const salt = psalt 252 | ? Buffer.isBuffer(psalt) 253 | ? psalt 254 | : Buffer.from(psalt) 255 | : scrypto.randomBytes(CRYPTO.DEFAULTS.KEYLENGTH) 256 | 257 | // derive the key using the salt, password and default crypto setup 258 | scrypto.pbkdf2( 259 | pass, 260 | salt, 261 | CRYPTO.DEFAULTS.MPK_ITERATIONS, 262 | CRYPTO.DEFAULTS.KEYLENGTH, 263 | CRYPTO.DEFAULTS.DIGEST, 264 | (err, key) => { 265 | if (err) reject(err) 266 | // return the key and the salt 267 | resolve({ key, salt }) 268 | } 269 | ) 270 | }) 271 | } 272 | 273 | // create a sha256 hash of the MasterPassKey 274 | exports.genPassHash = (masterpass, salt) => { 275 | return new Promise((resolve, reject) => { 276 | // convert the masterpass (of type Buffer) to a hex encoded string 277 | // if it is not already one 278 | const pass = Buffer.isBuffer(masterpass) 279 | ? masterpass.toString('hex') 280 | : masterpass 281 | 282 | // if salt provided then the MasterPass is being checked 283 | // if salt not provided then the MasterPass is being set 284 | if (salt) { 285 | // create hash from the contanation of the pass and salt 286 | // assign the hex digest of the created hash 287 | const hash = scrypto 288 | .createHash(CRYPTO.DEFAULTS.HASH_ALG) 289 | .update(`${pass}${salt}`) 290 | .digest('hex') 291 | resolve({ hash, key: masterpass }) 292 | } else { 293 | // generate a cryptographically secure salt and use it as the salt 294 | const salt = scrypto 295 | .randomBytes(CRYPTO.DEFAULTS.KEYLENGTH) 296 | .toString('hex') 297 | // create hash from the contanation of the pass and salt 298 | // assign the hex digest of the created hash 299 | const hash = scrypto 300 | .createHash(CRYPTO.DEFAULTS.HASH_ALG) 301 | .update(`${pass}${salt}`) 302 | .digest('hex') 303 | resolve({ hash, salt, key: masterpass }) 304 | } 305 | }) 306 | } 307 | 308 | // Converts a buffer array to a hex string 309 | exports.buf2hex = arr => { 310 | const buf = Buffer.from(arr) 311 | return buf.toString('hex') 312 | } 313 | 314 | // Compares vars in a constant time (protects against timing attacks) 315 | exports.timingSafeEqual = (a, b) => { 316 | // convert args to buffers if not already 317 | a = Buffer.isBuffer(a) ? a : Buffer.from(a) 318 | b = Buffer.isBuffer(b) ? b : Buffer.from(b) 319 | var result = 0 320 | var l = a.length 321 | while (l--) { 322 | // bitwise comparison 323 | result |= a[l] ^ b[l] 324 | } 325 | return result === 0 326 | } 327 | --------------------------------------------------------------------------------