Commit bdec7549 authored by Frauke's avatar Frauke
Browse files

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
# Private message Spike
Explore sending end-to-end encrypted messages
## About
### Goals
The goal is for this second node is to send us a private message, encrypted with our public key, that we will decrypt and read using our private key (as created in Spikes 1 & 2).
## 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/Private-messages; 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-4-end-to-end-encrypted-private-messages)
## Notes
## Changelog
__0.1.0__
- Initial release
## 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')
var glob = require('glob')
var es = require('event-stream')
gulp.task('js:bundle', function (done) {
glob('./src/**/**.js', function (err, files) {
if (err) done(err)
var tasks = files.map(function (entry) {
return browserify({ entries: [entry] })
.bundle()
.pipe(source(entry))
.pipe(rename({
dirname: ''
}))
.pipe(gulp.dest('./public/js'))
})
es.merge(tasks).on('end', done)
})
})
gulp.task('watch', () =>
gulp.watch('./src/**/*.js', ['js:bundle'])
)
This diff is collapsed.
{
"name": "private-message-spike",
"version": "1.0.0",
"description": "Explore sending end-to-end encrypted messages",
"main": "./server/server.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/Private-messages.git"
},
"author": "Wim Vantomme, Frauke Vanderzijpen",
"license": "AGPLv3",
"devDependencies": {
"browserify": "^15.1.0",
"event-stream": "^3.3.4",
"glob": "^7.1.2",
"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",
"jsonwebtoken": "^8.1.0",
"opencrypto": "github:indie-mirror/OpenCrypto",
"passport": "^0.4.0",
"passport-custom": "^1.0.5",
"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>
<h1>Welcome to my.gent</h1>
<p>This is my personal space</p>
<form action="logout" id="logout">
<input type="submit" value="Log out">
</form>
<script src="js/vendor/OpenCrypto.js"></script>
<script src="js/logout.js"></script>
</body>
</html>
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
// 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
}
},{}]},{},[1]);
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
// 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
}
},{}],2:[function(require,module,exports){
const indexedDB = require('./indexedDB')
const form = document.getElementById('logout')
form.addEventListener('submit', (e) => {
e.preventDefault()
indexedDB.callOnStore('indienet', 'keyStore', (store) => {
store.clear()
document.cookie = 'access_token=; Max-Age=-99999999;'
window.location = '/sign-in'
})
})
},{"./indexedDB":1}]},{},[2]);
This diff is collapsed.
This diff is collapsed.
<!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>
<h1>Welcome to Igent</h1>
<p>Please register a new domain</p>
<form action="register" method="post" id="register">
<label for="domain">Domain</label>
<input type="text" name="domain" id="domain">
<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>
\ No newline at end of file
<!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>
<h1>Welcome to Igent</h1>
<p>Please sign in on your website</p>
<form action="sign-in" method="post" id="sign-in">
<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/sign-in.js"></script>
</body>
</html>
const express = require('express')
const app = express()
const path = require('path')
const bodyParser = require('body-parser')
const crypto = require('crypto')
const jwt = require('jsonwebtoken')
const passport = require('passport')
const fileUtils = require('./utils/fileUtils')
const passportUtils = require('./utils/passportStrategy.js')(app)
var nonce
app.use(express.static(path.join(__dirname, '../public')))
app.use(bodyParser.json())
app.use(bodyParser.raw())
app.use(bodyParser.urlencoded({extended: false}))
app.use(passport.initialize())
// private path which needs authentication
app.get('/hello', passport.authenticate('jwt-strategy', { failureRedirect: '/sign-in', session: false }), (req, res) => {
res.status(200).send('Hello')
})
app.get('/sign-in', (req, res) => {
res.status(200).sendFile(path.join(__dirname, '../public/sign-in.html'))
})
app.post('/sign-in', (req, res) => {
fileUtils.readFile('./server/files/signpublickey.json').then((data) => {
data = JSON.parse(data)
const verify = crypto.createVerify('SHA512')
verify.update(nonce)
if (verify.verify(data.publicKey, req.body)) {
// creaye secret to create JWT
// TODO: save on server
crypto.randomBytes(256, (err, buf) => {
if (err) {
res.status(500).send('Ooops, something went wrong')
console.log(err)
}
app.set('tokenSecret', buf)
const token = jwt.sign({text: 'test'}, buf)
res.status(200).send(token)
})
} else {
res.status(401).send('Login denied!')
}
}).catch((err) => {
res.status(500).send('Ooops, something went wrong')
console.log(err)
})
})
app.get('/register', (req, res) => {
res.status(200).sendFile(path.join(__dirname, '../public/register.html'))
})
app.post('/register', (req, res) => {
const encryptPublicKey = JSON.stringify({publicKey: req.body.encryptPublicKey})
const encryptPrivateKeySalt = JSON.stringify({salt: req.body.salt, privateKey: req.body.encryptPrivateKey})
const signPublicKey = JSON.stringify({publicKey: req.body.signPublicKey})
const signPrivateKeySalt = JSON.stringify({salt: req.body.salt, privateKey: req.body.signPrivateKey})
// Save keys & salt to files
const pathName = './server/files/'
const writeEncryptPublicKey = fileUtils.writeFile('encryptpublickey.json', encryptPublicKey, pathName)
const writeEncryptPrivateKeySalt = fileUtils.writeFile('encryptprivatekey.json', encryptPrivateKeySalt, pathName)
const writeSignPublicKey = fileUtils.writeFile('signpublickey.json', signPublicKey, pathName)
const writeSignPrivateKeySalt = fileUtils.writeFile('signprivatekey.json', signPrivateKeySalt, pathName)
Promise.all([writeEncryptPublicKey, writeEncryptPrivateKeySalt, writeSignPublicKey, writeSignPrivateKeySalt]).then(() => {
res.status(200).end()
}).catch((er) => {
console.log(er)
res.status(500).send('Ooops, something went wrong')
})
})
app.get('/encryptpublickey', (req, res) => {
fileUtils.readFile('./server/files/encryptpublickey.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('/encryptprivatekey', (req, res) => {
fileUtils.readFile('./server/files/encryptprivatekey.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('/signpublickey', (req, res) => {
fileUtils.readFile('./server/files/signpublickey.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('/signprivatekey', (req, res) => {
fileUtils.readFile('./server/files/signprivatekey.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('/nonce', (req, res) => {
crypto.randomBytes(256, (err, buf) => {
if (err) {
res.status(500).send('Ooops, something went wrong')
console.log(err)
}
nonce = buf
res.status(200).send(nonce)
})
})
app.listen(8080)
const fs = require('fs')
const path = require('path')
// Writes a string to the filesystem.
function writeFile (fileName, value, pathName) {
return new Promise((resolve, reject) => {
makeDir(pathName).then(() => {
fs.writeFile(path.join(pathName, fileName), value, (err) => {
if (err) {
reject(err)
}
resolve()
})
})
})
}
// Creates a dir on the filesystem if it does not exist.
function makeDir (pathName) {
return new Promise((resolve, reject) => {
if (!fs.existsSync(pathName)) {
fs.mkdir(pathName, (err) => {
if (err) {
reject(err)
}
resolve()
})
} else {
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
}
const passport = require('passport')
const CustomStrategy = require('passport-custom')
const jwt = require('jsonwebtoken')
module.exports = function (app) {
passport.use('jwt-strategy', new CustomStrategy(
function (req, done) {
const cookie = decodeURIComponent(req.headers.cookie)
console.log(cookie)
if (cookie) {
const jwtToken = getCookie(cookie, 'access_token')
if (app.get('tokenSecret')) {
jwt.verify(jwtToken, app.get('tokenSecret'), function (err, decoded) {
if (err) {
done(err, false, {message: 'Access denied'})
} else {
done(null, decoded)
}
})
} else {
done(null, false, {message: 'Access denied'})
}
} else {
done(null, false, {message: 'Access denied'})
}
}
))
}
function getCookie (decodedCookie, cname) {
const name = cname + '='
const ca = decodedCookie.split(';')
for (let i = 0; i < ca.length; i++) {
let c = ca[i]
while (c.charAt(0) === ' ') {
c = c.substring(1)
}
if (c.indexOf(name) === 0) {
return c.