Commit 9f2c0c0d authored by wim-vantomme's avatar wim-vantomme

Initial commit

parents
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directory
# Commenting this out is preferred by some people, see
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
node_modules
# Users Environment Variables
.lock-wscript
# IDEs and editors (shamelessly copied from @angular/cli's .gitignore)
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
### Linux ###
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
### OSX ###
*.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### Windows ###
# Windows thumbnail cache files
Thumbs.db
ehthumbs.db
ehthumbs_vista.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
# Others
lib/
data/
public/js/script.js
server/files
# OpenCrypto Spike
Explore OpenCrypto implementation for the [Indienet Engine](https://indienet.info/engine) project.
## About
### Goals
* Explore the design; does it work? Do we encounter any pain points or obvious flaws?
* Explore the WebCrypto API-based OpenCrypto library. Is it fit for purpose?
## Getting Started
Getting up and running is as easy as 1, 2, 3.
1. Make sure you have [NodeJS](https://nodejs.org/) **latest LTS version** and [npm](https://www.npmjs.com/) installed.
2. Install your dependencies
```
cd path/to/spike/opencrypto; npm install
```
3. Start your app
```
npm start
```
## Help
For more information on this spike visit the [Indienet documentation](https://indienet.info/spikes/security/#spike-1-opencrypto-in-browser-key-generation-persistence-retrieval)
## Notes
## Changelog
__0.1.0__
- Initial release
- Created a public key and an extractable private key
- Persisted the extractable unencryptedPrivateKey in IndexedDB
- Created an ephemeral symmetric key from a master password
- Encrypted the unencrypted PrivateKey
- Imported the private key as unextractable.
- Saved unextractable private key to IndexedDB.
- Transfer publicKey and encryptedPrivateKey to the server
- Made encryptedPublicKey accessible from a route on the server
- Made encryptedPrivateKey accessible from a route on the server
## License
Copyright (c) 2018 Aral Balkan, Ind.ie (Article 12)
Licensed under the [AGPLv3](LICENSE).
var browserify = require('browserify')
var gulp = require('gulp')
var source = require('vinyl-source-stream')
var rename = require('gulp-rename')
gulp.task('js:bundle', () => {
browserify({
entries: 'src/js/script.js',
debug: true
})
.bundle()
.pipe(source('src/js/script.js'))
.pipe(rename({
dirname: ''
}))
.pipe(gulp.dest('./public/js'))
})
gulp.task('watch', () =>
gulp.watch('./src/**/*.js', ['js:bundle'])
)
This diff is collapsed.
{
"name": "opencrypto-spike",
"version": "1.0.0",
"description": "Explore OpenCrypto implementation for the [Indienet Engine](https://indienet.info/engine) project.",
"main": "./server/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"watch": "nodemon",
"start": "node ./server/server.js"
},
"repository": {
"type": "git",
"url": "git@source.ind.ie:indienet/spikes/security/OpenCrypto.git"
},
"author": "Wim Vantomme, Frauke Vanderzijpen",
"license": "AGPLv3",
"devDependencies": {
"browserify": "^15.1.0",
"gulp": "^3.9.1",
"gulp-rename": "^1.2.2",
"mocha": "^4.1.0",
"standard": "^10.0.3",
"vinyl-source-stream": "^2.0.0"
},
"dependencies": {
"axios": "^0.17.1",
"body-parser": "^1.18.2",
"express": "^4.16.2",
"opencrypto": "github:safebash/OpenCrypto",
"winston": "^2.4.0"
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<form action="register" method="post" id="register">
<label for="password">Password</label>
<input type="password" name="password" id="password">
<input type="submit" value="Create site">
</form>
<script src="js/vendor/OpenCrypto.js"></script>
<script src="js/script.js"></script>
</body>
</html>
This diff is collapsed.
const fs = require('fs')
const express = require('express')
const app = express()
const path = require('path')
const bodyParser = require('body-parser')
const fileUtils = require('./utils/fileUtils')
app.use(express.static(path.join(__dirname, '../public')))
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({extended: false}))
app.post('/register', (req, res) => {
const salt = JSON.stringify({salt: req.body.salt})
const publicKey = JSON.stringify({publickKey: req.body.publicKey})
const privateKey = JSON.stringify({privateKey: req.body.privateKey})
const writeSalt = fileUtils.writeFile('./server/files/salt.json', salt)
const writePublicKey = fileUtils.writeFile('./server/files/publickey.json', publicKey)
const writePrivateKey = fileUtils.writeFile('./server/files/privatekey.json', privateKey)
Promise.all([writeSalt, writePublicKey, writePrivateKey]).then(() => {
res.status(200).end()
}).catch((er) => {
res.status(500).send('Ooops, something went wrong')
})
})
app.get('/publickey', (req, res) => {
fileUtils.readFile('./server/files/publickey.json').then((data) => {
data = JSON.parse(data)
res.status(200).send(data)
}).catch((err) => {
res.status(500).send('Ooops, something went wrong')
console.log(err)
})
})
app.get('/privatekey', (req, res) => {
fileUtils.readFile('./server/files/privatekey.json').then((data) => {
data = JSON.parse(data)
res.status(200).send(data)
}).catch((err) => {
res.status(500).send('Ooops, something went wrong')
console.log(err)
})
})
app.listen(8080)
const fs = require('fs')
// Writes a string to the filesystem.
function writeFile (fileName, value) {
return new Promise((resolve, reject) => {
fs.writeFile(fileName, value, (err) => {
if (err) {
reject(err)
}
resolve()
})
})
}
// Reads a file from the filesystem.
function readFile (fileName) {
return new Promise((resolve, reject) => {
fs.readFile(fileName, 'utf8', (err, data) => {
if (err) {
reject(err)
}
resolve(data)
})
})
}
module.exports = {
writeFile,
readFile
}
// Basic helper functions to directly write stuff to indexedDB through the browserAPI.
// Might be that we need to implement a shim for older browsers (shimIndexedDB)
function callOnStore (dbname, storeName, fn_) {
const indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB
// Open or create the database.
const request = indexedDB.open(dbname, 1)
// Create the schema
request.onupgradeneeded = () => {
let db = request.result
let store = db.createObjectStore(storeName, {keyPath: 'id'})
}
request.onsuccess = () => {
let db = request.result
let transaction = db.transaction(storeName, 'readwrite')
let store = transaction.objectStore(storeName)
fn_(store)
transaction.oncomplete = () => db.close()
}
}
module.exports = {
callOnStore
}
const crypt = new OpenCrypto()
const axios = require('axios')
// Custom modules
const indexedDB = require('./indexedDB')
const form = document.getElementById('register')
function loadedKeyPair () {
indexedDB.callOnStore('testkeystore', 'keyStore', (store) => {
const getData = store.get(1)
getData.onsuccess = (event) => {
const keys = getData.result.keys
}
})
}
loadedKeyPair()
form.addEventListener('submit', (e) => {
e.preventDefault()
var saltValue = ''
const password = e.target.password.value
const salt = crypt.getRandomSalt()
salt.then((salt) => {
const keys = crypt.getKeyPair(undefined, undefined, undefined, true)
const pass = crypt.keyFromPassphrase(password, salt, 300000)
saltValue = salt
return Promise.all([keys, pass])
}).then((values) => {
const keyPair = values[0]
const hash = values[1]
const encryptedPrivateKey = crypt.encryptPrivateKey(keyPair.privateKey, hash)
const publicKeyValue = crypt.cryptoPublicToPem(keyPair.publicKey)
// Export key as an arraybuffer to import later as unextractable private key.
const exportedPrivateKey = crypto.subtle.exportKey('pkcs8', keyPair.privateKey)
return Promise.all([encryptedPrivateKey, publicKeyValue, exportedPrivateKey])
}).then((values) => {
// Import arraybuffer as an unextractable private key.
const nonExtractablePrivateKey = crypto.subtle.importKey('pkcs8', values[2], {name: 'RSA-OAEP', hash: {name: 'SHA-512'}, modulusLength: 2048, publicExponent: new Uint8Array([0x01, 0x00, 0x01])}, false, ['decrypt', 'unwrapKey'])
const postKeys = axios.post('register',
{
salt: saltValue,
publicKey: values[1],
privateKey: values[0]
})
return Promise.all([nonExtractablePrivateKey, postKeys])
}).then((values) => {
// Save unextractable private key to indexedDB.
indexedDB.callOnStore('testkeystore', 'keyStore', (store) => {
store.put({
id: 1,
keys: values[0]
})
})
}).catch((err) => {
console.log(err)
})
})
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment