From e59bb2a1cf006448a896d33abf9eec20a736fd95 Mon Sep 17 00:00:00 2001 From: Dan Seripap Date: Fri, 19 Feb 2016 11:39:04 -0500 Subject: [PATCH 1/8] Creating interface for darkwire, started work on filetransfer --- readme.md | 10 +- src/app.js | 8 +- src/js/darkwire.js | 200 ++++++++++++++++++++++++++++++++++++ src/js/fileHandler.js | 56 ++++++++++ src/js/main.js | 213 ++++----------------------------------- src/js/window.js | 13 +++ src/public/style.css | 9 +- src/room.js | 1 + src/views/index.mustache | 2 + 9 files changed, 314 insertions(+), 198 deletions(-) create mode 100644 src/js/darkwire.js create mode 100644 src/js/fileHandler.js diff --git a/readme.md b/readme.md index 061f78f..66fa446 100644 --- a/readme.md +++ b/readme.md @@ -42,7 +42,15 @@ Group chats work the same way because in step 5 we encrypt keys with everyone's Darkwire does not provide any guarantee that the person you're communicating with is who you think they are. Authentication functionality may be incorporated in future versions. -### Sockets & Server +## File Transfer + +Files are not transferred over the wire-only the file name and extension. Darkwire encodes documents into base64 using [btoa](https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/btoa) and is encrypted the same way chat messages are. + +1. When a file is "uploaded", the document is encoded on the client and the server recieves the encrypted base64 string. +2. The server sends the encrypted base64 string to clients in the same chat room. +3. Clients recieving the encrypted base64 string then decrypts the string, then decodes the base64 string using [atob](https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/atob). + +## Sockets & Server Darkwire uses [socket.io](http://socket.io) to transmit encrypted information using secure [WebSockets](https://en.wikipedia.org/wiki/WebSocket) (WSS). diff --git a/src/app.js b/src/app.js index 41bc917..898de97 100644 --- a/src/app.js +++ b/src/app.js @@ -10,7 +10,9 @@ import fs from 'fs'; import Room from './room'; -const $PORT = 3000; +const $CONFIG = { + port: 3000 +} const app = express(); const server = http.createServer(app); @@ -54,6 +56,6 @@ app.get('/:roomId', (req, res) => { return res.redirect('/'); }); -server.listen($PORT, () => { - console.log(`darkwire is online on port ${$PORT}.`); +server.listen($CONFIG.port, () => { + console.log(`darkwire is online on port ${$CONFIG.port}.`); }); diff --git a/src/js/darkwire.js b/src/js/darkwire.js new file mode 100644 index 0000000..e0c188c --- /dev/null +++ b/src/js/darkwire.js @@ -0,0 +1,200 @@ +import _ from 'underscore'; +import AudioHandler from './audio'; +import CryptoUtil from './crypto'; + +export default class Darkwire { + constructor() { + this._audio = new AudioHandler(); + this._cryptoUtil = new CryptoUtil(); + this._myUserId = false; + this._connected = false; + this._users = []; + } + + get users() { + return this._users; + } + + get audio() { + return this._audio; + } + + addUser(data) { + let importKeysPromises = []; + // Import all user keys if not already there + _.each(data.users, (user) => { + if (!_.findWhere(this._users, {id: user.id})) { + let promise = new Promise((resolve, reject) => { + let currentUser = user; + Promise.all([ + this._cryptoUtil.importPrimaryKey(currentUser.publicKey, "spki") + ]) + .then((keys) => { + this._users.push({ + id: currentUser.id, + username: currentUser.username, + publicKey: keys[0] + }); + resolve(); + }); + }); + + importKeysPromises.push(promise); + } + }); + + if (!this._myUserId) { + // Set my id if not already set + let me = _.findWhere(data.users, {username: username}); + this._myUserId = me.id; + } + + return importKeysPromises; + } + + sendMessage(message, messageType) { + // Don't send unless other users exist + console.log(this._users); + if (this._users.length <= 1) return; + + // if there is a non-empty message and a socket connection + if (message && this._connected) { + $inputMessage.val(''); + $('#send-message-btn').removeClass('active'); + addChatMessage({ + username: username, + message: message + }); + let vector = this._cryptoUtil.crypto.getRandomValues(new Uint8Array(16)); + + let secretKey; + let secretKeys; + let messageData; + let signature; + let signingKey; + let encryptedMessageData; + + // Generate new secret key and vector for each message + this._cryptoUtil.createSecretKey() + .then(function(key) { + secretKey = key; + return this._cryptoUtil.createSigningKey(); + }) + .then(function(key) { + signingKey = key; + // Generate secretKey and encrypt with each user's public key + let promises = []; + _.each(this._users, function(user) { + // If not me + if (user.username !== window.username) { + let promise = new Promise(function(resolve, reject) { + let thisUser = user; + + let secretKeyStr; + + // Export secret key + this._cryptoUtil.exportKey(secretKey, "raw") + .then(function(data) { + return this._cryptoUtil.encryptSecretKey(data, thisUser.publicKey); + }) + .then(function(encryptedSecretKey) { + let encData = new Uint8Array(encryptedSecretKey); + secretKeyStr = this._cryptoUtil.convertArrayBufferViewToString(encData); + // Export HMAC signing key + return this._cryptoUtil.exportKey(signingKey, "raw"); + }) + .then(function(data) { + // Encrypt signing key with user's public key + return this._cryptoUtil.encryptSigningKey(data, thisUser.publicKey); + }) + .then(function(encryptedSigningKey) { + let encData = new Uint8Array(encryptedSigningKey); + var str = this._cryptoUtil.convertArrayBufferViewToString(encData); + resolve({ + id: thisUser.id, + secretKey: secretKeyStr, + encryptedSigningKey: str + }); + }); + }); + promises.push(promise); + } + }); + return Promise.all(promises); + }) + .then(function(data) { + secretKeys = data; + messageData = this._cryptoUtil.convertStringToArrayBufferView(message); + return this._cryptoUtil.signKey(messageData, signingKey); + }) + .then(function(data) { + signature = data; + return this._cryptoUtil.encryptMessage(messageData, secretKey, vector); + }) + .then(function(data) { + encryptedMessageData = data; + let msg = this._cryptoUtil.convertArrayBufferViewToString(new Uint8Array(encryptedMessageData)); + let vct = this._cryptoUtil.convertArrayBufferViewToString(new Uint8Array(vector)); + let sig = this._cryptoUtil.convertArrayBufferViewToString(new Uint8Array(signature)); + socket.emit('new message', { + message: msg, + vector: vct, + messageType: type, + secretKeys: secretKeys, + signature: sig + }); + }); + } + } + + decodeMessage(data) { + return new Promise( (resolve, reject) => { + let message = data.message; + let messageData = this._cryptoUtil.convertStringToArrayBufferView(message); + let username = data.username; + let senderId = data.id + let vector = data.vector; + let vectorData = this._cryptoUtil.convertStringToArrayBufferView(vector); + let secretKeys = data.secretKeys; + let decryptedMessageData; + let decryptedMessage; + + let mySecretKey = _.find(secretKeys, (key) => { + return key.id === this._myUserId; + }); + let signature = data.signature; + let signatureData = this._cryptoUtil.convertStringToArrayBufferView(signature); + let secretKeyArrayBuffer = this._cryptoUtil.convertStringToArrayBufferView(mySecretKey.secretKey); + let signingKeyArrayBuffer = this._cryptoUtil.convertStringToArrayBufferView(mySecretKey.encryptedSigningKey); + + this._cryptoUtil.decryptSecretKey(secretKeyArrayBuffer, keys.private) + .then((data) => { + return this._cryptoUtil.importSecretKey(new Uint8Array(data), "raw"); + }) + .then((data) => { + let secretKey = data; + return this._cryptoUtil.decryptMessage(messageData, secretKey, vectorData); + }) + .then((data) => { + decryptedMessageData = data; + decryptedMessage = this._cryptoUtil.convertArrayBufferViewToString(new Uint8Array(data)) + return this._cryptoUtil.decryptSigningKey(signingKeyArrayBuffer, keys.private) + }) + .then((data) => { + return this._cryptoUtil.importSigningKey(new Uint8Array(data), "raw"); + }) + .then((data) => { + let signingKey = data; + return this._cryptoUtil.verifyKey(signatureData, decryptedMessageData, signingKey); + }) + .then((bool) => { + if (bool) { + resolve({ + username: username, + message: decryptedMessage + }); + } + }); + }); + } +} diff --git a/src/js/fileHandler.js b/src/js/fileHandler.js new file mode 100644 index 0000000..adb1744 --- /dev/null +++ b/src/js/fileHandler.js @@ -0,0 +1,56 @@ +export default class FileHandler { + constructor() { + if (window.File && window.FileReader && window.FileList && window.Blob && window.btoa) { + this._isSupported = true; + this.listen(); + } else { + this._isSupported = false; + } + } + + get isSupported() { + return this._isSupported; + } + + encodeFile(event) { + const file = event.target.files && event.target.files[0]; + + if (file) { + + let encodedFile = { + fileName: file.name, + fileSize: file.fileSize, + base64: null + }; + + // Support for only 1MB + if (file.size > 1000000) { + console.log(file); + alert("Max filesize is 1MB."); + return false; + } + + const reader = new FileReader(); + + reader.onload = (readerEvent) => { + const base64 = window.btoa(readerEvent.target.result); + encodedFile.base64 = base64; + } + + reader.readAsBinaryString(file); + + return base64; + } + + return false; + } + + listen() { + // browser API + document.getElementById('fileInput').addEventListener('change', this.encodeFile, false); + + // darkwire + + return this; + } +} diff --git a/src/js/main.js b/src/js/main.js index 100221e..042279c 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -1,15 +1,14 @@ -import AudioHandler from './audio'; -import CryptoUtil from './crypto'; +import Darkwire from './darkwire'; import WindowHandler from './window'; +import CryptoUtil from './crypto'; let fs = window.RequestFileSystem || window.webkitRequestFileSystem; $(function() { - const audio = new AudioHandler(); + const darkwire = new Darkwire(); const cryptoUtil = new CryptoUtil(); const windowHandler = new WindowHandler(); - let newMessages = 0; let FADE_TIME = 150; // ms let TYPING_TIMER_LENGTH = 400; // ms @@ -28,12 +27,7 @@ $(function() { let $chatPage = $('.chat.page'); // The chatroom page - let users = []; - - // Prompt for setting a username let username; - let myUserId; - let connected = false; let typing = false; let lastTypingTime; @@ -195,7 +189,7 @@ $(function() { // Updates the typing event function updateTyping () { - if (connected) { + if (darkwire.connected) { if (!typing) { typing = true; socket.emit('typing'); @@ -237,7 +231,8 @@ $(function() { $window.keydown(function (event) { // When the client hits ENTER on their keyboard and chat message input is focused if (event.which === 13 && $('.inputMessage').is(':focus')) { - sendMessage(); + let message = cleanInput($inputMessage.val()); + darkwire.sendMessage(message, 'chat'); socket.emit('stop typing'); typing = false; } @@ -278,203 +273,34 @@ $(function() { // Whenever the server emits 'login', log the login message socket.on('user joined', function (data) { - connected = true; + darkwire.connected = true; addParticipantsMessage(data); - - let importKeysPromises = []; - - // Import all user keys if not already there - _.each(data.users, function(user) { - if (!_.findWhere(users, {id: user.id})) { - let promise = new Promise(function(resolve, reject) { - let currentUser = user; - Promise.all([ - cryptoUtil.importPrimaryKey(currentUser.publicKey, "spki") - ]) - .then(function(keys) { - users.push({ - id: currentUser.id, - username: currentUser.username, - publicKey: keys[0] - }); - resolve(); - }); - }); - importKeysPromises.push(promise); - } - }); - - if (!myUserId) { - // Set my id if not already set - let me = _.findWhere(data.users, {username: username}); - myUserId = me.id; - } - - Promise.all(importKeysPromises) - .then(function() { + let importKeysPromises = darkwire.addUser(data); + Promise.all(importKeysPromises).then(() => { // All users' keys have been imported if (data.numUsers === 1) { $('#first-modal').modal('show'); } log(data.username + ' joined'); - renderParticipantsList(); - }); + + }); }); - // Sends a chat message - function sendMessage () { - // Don't send unless other users exist - if (users.length <= 1) return; - - let message = $inputMessage.val(); - // Prevent markup from being injected into the message - message = cleanInput(message); - // if there is a non-empty message and a socket connection - if (message && connected) { - $inputMessage.val(''); - $('#send-message-btn').removeClass('active'); - addChatMessage({ - username: username, - message: message - }); - let vector = cryptoUtil.crypto.getRandomValues(new Uint8Array(16)); - - let secretKey; - let secretKeys; - let messageData; - let signature; - let signingKey; - let encryptedMessageData; - - // Generate new secret key and vector for each message - cryptoUtil.createSecretKey() - .then(function(key) { - secretKey = key; - return cryptoUtil.createSigningKey(); - }) - .then(function(key) { - signingKey = key; - // Generate secretKey and encrypt with each user's public key - let promises = []; - _.each(users, function(user) { - // If not me - if (user.username !== window.username) { - let promise = new Promise(function(resolve, reject) { - let thisUser = user; - - let secretKeyStr; - - // Export secret key - cryptoUtil.exportKey(secretKey, "raw") - .then(function(data) { - return cryptoUtil.encryptSecretKey(data, thisUser.publicKey); - }) - .then(function(encryptedSecretKey) { - let encData = new Uint8Array(encryptedSecretKey); - secretKeyStr = cryptoUtil.convertArrayBufferViewToString(encData); - // Export HMAC signing key - return cryptoUtil.exportKey(signingKey, "raw"); - }) - .then(function(data) { - // Encrypt signing key with user's public key - return cryptoUtil.encryptSigningKey(data, thisUser.publicKey); - }) - .then(function(encryptedSigningKey) { - let encData = new Uint8Array(encryptedSigningKey); - var str = cryptoUtil.convertArrayBufferViewToString(encData); - resolve({ - id: thisUser.id, - secretKey: secretKeyStr, - encryptedSigningKey: str - }); - }); - }); - promises.push(promise); - } - }); - return Promise.all(promises); - }) - .then(function(data) { - secretKeys = data; - messageData = cryptoUtil.convertStringToArrayBufferView(message); - return cryptoUtil.signKey(messageData, signingKey); - }) - .then(function(data) { - signature = data; - return cryptoUtil.encryptMessage(messageData, secretKey, vector); - }) - .then(function(data) { - encryptedMessageData = data; - let msg = cryptoUtil.convertArrayBufferViewToString(new Uint8Array(encryptedMessageData)); - let vct = cryptoUtil.convertArrayBufferViewToString(new Uint8Array(vector)); - let sig = cryptoUtil.convertArrayBufferViewToString(new Uint8Array(signature)); - socket.emit('new message', { - message: msg, - vector: vct, - secretKeys: secretKeys, - signature: sig - }); - }); - } - } - // Whenever the server emits 'new message', update the chat body socket.on('new message', function (data) { // Don't show messages if no key if (!windowHandler.isActive) { windowHandler.notifyFavicon(); - audio.play(); + darkwire.audio.play(); } - let message = data.message; - let messageData = cryptoUtil.convertStringToArrayBufferView(message); - let username = data.username; - let senderId = data.id - let vector = data.vector; - let vectorData = cryptoUtil.convertStringToArrayBufferView(vector); - let secretKeys = data.secretKeys; - let decryptedMessageData; - let decryptedMessage; - - let mySecretKey = _.find(secretKeys, function(key) { - return key.id === myUserId; - }); - let signature = data.signature; - let signatureData = cryptoUtil.convertStringToArrayBufferView(signature); - let secretKeyArrayBuffer = cryptoUtil.convertStringToArrayBufferView(mySecretKey.secretKey); - let signingKeyArrayBuffer = cryptoUtil.convertStringToArrayBufferView(mySecretKey.encryptedSigningKey); - - cryptoUtil.decryptSecretKey(secretKeyArrayBuffer, keys.private) - .then(function(data) { - return cryptoUtil.importSecretKey(new Uint8Array(data), "raw"); + let decoded = darkwire.decode(data); + decoded.then( (data) => { + console.log(data); }) - .then(function(data) { - let secretKey = data; - return cryptoUtil.decryptMessage(messageData, secretKey, vectorData); - }) - .then(function(data) { - decryptedMessageData = data; - decryptedMessage = cryptoUtil.convertArrayBufferViewToString(new Uint8Array(data)) - return cryptoUtil.decryptSigningKey(signingKeyArrayBuffer, keys.private) - }) - .then(function(data) { - return cryptoUtil.importSigningKey(new Uint8Array(data), "raw"); - }) - .then(function(data) { - let signingKey = data; - return cryptoUtil.verifyKey(signatureData, decryptedMessageData, signingKey); - }) - .then(function(bool) { - if (bool) { - addChatMessage({ - username: username, - message: decryptedMessage - }); - } - }); }); // Whenever the server emits 'user left', log it in the chat body @@ -518,7 +344,7 @@ $(function() { function renderParticipantsList() { $('#participants-modal ul.users').empty(); - _.each(users, function(user) { + _.each(darkwire.users, function(user) { let li; if (user.username === window.username) { // User is me @@ -532,7 +358,10 @@ $(function() { } $('#send-message-btn').click(function() { - sendMessage(); + let message = $inputMessage.val(); + // Prevent markup from being injected into the message + message = cleanInput(message); + darkwire.sendMessage(message, 'chat'); socket.emit('stop typing'); typing = false; }); @@ -544,7 +373,7 @@ $(function() { let audioSwitch = $('input.bs-switch').bootstrapSwitch(); audioSwitch.on('switchChange.bootstrapSwitch', function(event, state) { - audio.soundEnabled = state; + darkwire.audio.soundEnabled = state; }); }); diff --git a/src/js/window.js b/src/js/window.js index 88e217f..8aff85b 100644 --- a/src/js/window.js +++ b/src/js/window.js @@ -1,6 +1,9 @@ +import FileHandler from './fileHandler'; + export default class WindowHandler { constructor() { this._isActive = false; + this.fileHandler = new FileHandler(); this.newMessages = 0; this.favicon = new Favico({ @@ -8,6 +11,7 @@ export default class WindowHandler { type : 'rectangle' }); + this.enableFileTransfer(); this.bindEvents(); } @@ -25,6 +29,15 @@ export default class WindowHandler { this.favicon.badge(this.newMessages); } + enableFileTransfer() { + if (this.fileHandler.isSupported) { + $('#send-file').click((e) => { + e.preventDefault(); + $('#fileInput').trigger('click'); + }); + } + } + bindEvents() { window.onfocus = () => { this._isActive = true; diff --git a/src/public/style.css b/src/public/style.css index eb9beff..c8ea770 100644 --- a/src/public/style.css +++ b/src/public/style.css @@ -222,11 +222,12 @@ input { cursor: pointer; } +/* html.no-touchevents .chat #input-icons { display: none; -} +}*/ -.chat #input-icons #send-message-btn { +.chat #input-icons #send-message-btn, .chat #input-icons #send-file { font-size: 25px; opacity: 0.3; color: white; @@ -291,3 +292,7 @@ html.no-touchevents .chat #input-icons { background: #2a9fd6 !important; border-color: #2a9fd6 !important; } + +#fileSendForm { + display: none; +} diff --git a/src/room.js b/src/room.js index 221634a..1d17bcc 100644 --- a/src/room.js +++ b/src/room.js @@ -22,6 +22,7 @@ class Room { username: socket.username, id: socket.user.id, message: data.message, + messageType: data.messageType, vector: data.vector, secretKeys: data.secretKeys, signature: data.signature diff --git a/src/views/index.mustache b/src/views/index.mustache index 1d8c8ae..4d9f076 100644 --- a/src/views/index.mustache +++ b/src/views/index.mustache @@ -55,6 +55,8 @@
+ +
From ebeab321e16b8d785361eaa14468fb5177f2dbec Mon Sep 17 00:00:00 2001 From: Dan Seripap Date: Fri, 19 Feb 2016 11:57:01 -0500 Subject: [PATCH 2/8] Creating some stability --- src/js/darkwire.js | 189 ++++++++++++++++++++++++--------------------- src/js/main.js | 19 +++-- 2 files changed, 113 insertions(+), 95 deletions(-) diff --git a/src/js/darkwire.js b/src/js/darkwire.js index e0c188c..4f9b40f 100644 --- a/src/js/darkwire.js +++ b/src/js/darkwire.js @@ -52,99 +52,108 @@ export default class Darkwire { return importKeysPromises; } - sendMessage(message, messageType) { + removeUser(data) { + this._users = _.without(this._users, _.findWhere(this._users, {id: data.id})); + return this._users; + } + + encodeMessage(message, messageType) { // Don't send unless other users exist - console.log(this._users); - if (this._users.length <= 1) return; + return new Promise( (resolve, reject) => { + if (this._users.length <= 1) { + reject(); + return; + }; - // if there is a non-empty message and a socket connection - if (message && this._connected) { - $inputMessage.val(''); - $('#send-message-btn').removeClass('active'); - addChatMessage({ - username: username, - message: message - }); - let vector = this._cryptoUtil.crypto.getRandomValues(new Uint8Array(16)); - - let secretKey; - let secretKeys; - let messageData; - let signature; - let signingKey; - let encryptedMessageData; - - // Generate new secret key and vector for each message - this._cryptoUtil.createSecretKey() - .then(function(key) { - secretKey = key; - return this._cryptoUtil.createSigningKey(); - }) - .then(function(key) { - signingKey = key; - // Generate secretKey and encrypt with each user's public key - let promises = []; - _.each(this._users, function(user) { - // If not me - if (user.username !== window.username) { - let promise = new Promise(function(resolve, reject) { - let thisUser = user; - - let secretKeyStr; - - // Export secret key - this._cryptoUtil.exportKey(secretKey, "raw") - .then(function(data) { - return this._cryptoUtil.encryptSecretKey(data, thisUser.publicKey); - }) - .then(function(encryptedSecretKey) { - let encData = new Uint8Array(encryptedSecretKey); - secretKeyStr = this._cryptoUtil.convertArrayBufferViewToString(encData); - // Export HMAC signing key - return this._cryptoUtil.exportKey(signingKey, "raw"); - }) - .then(function(data) { - // Encrypt signing key with user's public key - return this._cryptoUtil.encryptSigningKey(data, thisUser.publicKey); - }) - .then(function(encryptedSigningKey) { - let encData = new Uint8Array(encryptedSigningKey); - var str = this._cryptoUtil.convertArrayBufferViewToString(encData); - resolve({ - id: thisUser.id, - secretKey: secretKeyStr, - encryptedSigningKey: str - }); - }); - }); - promises.push(promise); - } - }); - return Promise.all(promises); - }) - .then(function(data) { - secretKeys = data; - messageData = this._cryptoUtil.convertStringToArrayBufferView(message); - return this._cryptoUtil.signKey(messageData, signingKey); - }) - .then(function(data) { - signature = data; - return this._cryptoUtil.encryptMessage(messageData, secretKey, vector); - }) - .then(function(data) { - encryptedMessageData = data; - let msg = this._cryptoUtil.convertArrayBufferViewToString(new Uint8Array(encryptedMessageData)); - let vct = this._cryptoUtil.convertArrayBufferViewToString(new Uint8Array(vector)); - let sig = this._cryptoUtil.convertArrayBufferViewToString(new Uint8Array(signature)); - socket.emit('new message', { - message: msg, - vector: vct, - messageType: type, - secretKeys: secretKeys, - signature: sig - }); + // if there is a non-empty message and a socket connection + if (message && this._connected) { + $('#send-message-btn').removeClass('active'); + addChatMessage({ + username: username, + message: message }); - } + let vector = this._cryptoUtil.crypto.getRandomValues(new Uint8Array(16)); + + let secretKey; + let secretKeys; + let messageData; + let signature; + let signingKey; + let encryptedMessageData; + + // Generate new secret key and vector for each message + this._cryptoUtil.createSecretKey() + .then(function(key) { + secretKey = key; + return this._cryptoUtil.createSigningKey(); + }) + .then(function(key) { + signingKey = key; + // Generate secretKey and encrypt with each user's public key + let promises = []; + _.each(this._users, (user) => { + // If not me + if (user.username !== window.username) { + let promise = new Promise((res, rej) => { + let thisUser = user; + + let secretKeyStr; + + // Export secret key + this._cryptoUtil.exportKey(secretKey, "raw") + .then(function(data) { + return this._cryptoUtil.encryptSecretKey(data, thisUser.publicKey); + }) + .then(function(encryptedSecretKey) { + let encData = new Uint8Array(encryptedSecretKey); + secretKeyStr = this._cryptoUtil.convertArrayBufferViewToString(encData); + // Export HMAC signing key + return this._cryptoUtil.exportKey(signingKey, "raw"); + }) + .then(function(data) { + // Encrypt signing key with user's public key + return this._cryptoUtil.encryptSigningKey(data, thisUser.publicKey); + }) + .then(function(encryptedSigningKey) { + let encData = new Uint8Array(encryptedSigningKey); + var str = this._cryptoUtil.convertArrayBufferViewToString(encData); + res({ + id: thisUser.id, + secretKey: secretKeyStr, + encryptedSigningKey: str + }); + }); + }); + promises.push(promise); + } + }); + return Promise.all(promises); + }) + .then(function(data) { + secretKeys = data; + messageData = this._cryptoUtil.convertStringToArrayBufferView(message); + return this._cryptoUtil.signKey(messageData, signingKey); + }) + .then(function(data) { + signature = data; + return this._cryptoUtil.encryptMessage(messageData, secretKey, vector); + }) + .then(function(data) { + encryptedMessageData = data; + let msg = this._cryptoUtil.convertArrayBufferViewToString(new Uint8Array(encryptedMessageData)); + let vct = this._cryptoUtil.convertArrayBufferViewToString(new Uint8Array(vector)); + let sig = this._cryptoUtil.convertArrayBufferViewToString(new Uint8Array(signature)); + resolve({ + message: msg, + vector: vct, + messageType: type, + secretKeys: secretKeys, + signature: sig + }); + }); + } + + }); } decodeMessage(data) { diff --git a/src/js/main.js b/src/js/main.js index 042279c..70f7ec1 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -232,7 +232,12 @@ $(function() { // When the client hits ENTER on their keyboard and chat message input is focused if (event.which === 13 && $('.inputMessage').is(':focus')) { let message = cleanInput($inputMessage.val()); - darkwire.sendMessage(message, 'chat'); + darkwire.encodeMessage(message, 'chat').then( (message) => { + $inputMessage.val(''); + socket.emit('new message', message); + }).catch( (err) => { + console.log(err); + }); socket.emit('stop typing'); typing = false; } @@ -309,7 +314,7 @@ $(function() { addParticipantsMessage(data); removeChatTyping(data); - users = _.without(users, _.findWhere(users, {id: data.id})); + darkwire.removeUser(data); renderParticipantsList(); }); @@ -358,10 +363,14 @@ $(function() { } $('#send-message-btn').click(function() { - let message = $inputMessage.val(); + let message = cleanInput($inputMessage.val()); // Prevent markup from being injected into the message - message = cleanInput(message); - darkwire.sendMessage(message, 'chat'); + darkwire.encodeMessage(message, 'chat').then( (message) => { + $inputMessage.val(''); + socket.emit('new message', message); + }).catch( (err) => { + console.log(err); + }); socket.emit('stop typing'); typing = false; }); From c6670b56d6cefa995c4bb43411b8385392614232 Mon Sep 17 00:00:00 2001 From: Dan Seripap Date: Fri, 19 Feb 2016 12:39:47 -0500 Subject: [PATCH 3/8] Working implementation of darkwire interface --- .gitignore | 1 + gulpfile.js | 11 +++++++- src/js/darkwire.js | 60 ++++++++++++++++++++++++++----------------- src/js/fileHandler.js | 2 +- src/js/main.js | 54 +++++++++++++++++++------------------- src/public/style.css | 2 +- 6 files changed, 77 insertions(+), 53 deletions(-) diff --git a/.gitignore b/.gitignore index f918f96..1c3273d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules +npm-debug.log src/public/main.js src/.secret diff --git a/gulpfile.js b/gulpfile.js index 1c27cdb..9bca124 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -16,6 +16,15 @@ gulp.task('bundle', function() { .pipe(gulp.dest('src/public')) }); +gulp.task('dev', function() { + return browserify('src/js/main.js', { debug: true }).transform(babel.configure({ + presets: ["es2015"] + })).bundle() + .pipe(source('main.js')) + .pipe(buffer()) + .pipe(gulp.dest('src/public')) +}); + gulp.task('start', function() { nodemon({ script: 'index.js', @@ -24,6 +33,6 @@ gulp.task('start', function() { env: { 'NODE_ENV': 'development' }, - tasks: ['bundle'] + tasks: ['dev'] }) }); diff --git a/src/js/darkwire.js b/src/js/darkwire.js index 4f9b40f..7188aae 100644 --- a/src/js/darkwire.js +++ b/src/js/darkwire.js @@ -9,6 +9,25 @@ export default class Darkwire { this._myUserId = false; this._connected = false; this._users = []; + this._keys = {}; + } + + get keys() { + return this._keys; + } + + set keys(keys) { + this._keys = keys; + return this._keys; + } + + get connected() { + return this._connected; + } + + set connected(state) { + this._connected = state; + return this._connected; } get users() { @@ -60,18 +79,14 @@ export default class Darkwire { encodeMessage(message, messageType) { // Don't send unless other users exist return new Promise( (resolve, reject) => { - if (this._users.length <= 1) { - reject(); - return; - }; - + // if (this._users.length <= 1) { + // console.log('rejected:' + this._users); + // reject(); + // return; + // }; + // if there is a non-empty message and a socket connection if (message && this._connected) { - $('#send-message-btn').removeClass('active'); - addChatMessage({ - username: username, - message: message - }); let vector = this._cryptoUtil.crypto.getRandomValues(new Uint8Array(16)); let secretKey; @@ -80,14 +95,13 @@ export default class Darkwire { let signature; let signingKey; let encryptedMessageData; - // Generate new secret key and vector for each message this._cryptoUtil.createSecretKey() - .then(function(key) { + .then((key) => { secretKey = key; return this._cryptoUtil.createSigningKey(); }) - .then(function(key) { + .then((key) => { signingKey = key; // Generate secretKey and encrypt with each user's public key let promises = []; @@ -101,20 +115,20 @@ export default class Darkwire { // Export secret key this._cryptoUtil.exportKey(secretKey, "raw") - .then(function(data) { + .then((data) => { return this._cryptoUtil.encryptSecretKey(data, thisUser.publicKey); }) - .then(function(encryptedSecretKey) { + .then((encryptedSecretKey) => { let encData = new Uint8Array(encryptedSecretKey); secretKeyStr = this._cryptoUtil.convertArrayBufferViewToString(encData); // Export HMAC signing key return this._cryptoUtil.exportKey(signingKey, "raw"); }) - .then(function(data) { + .then((data) => { // Encrypt signing key with user's public key return this._cryptoUtil.encryptSigningKey(data, thisUser.publicKey); }) - .then(function(encryptedSigningKey) { + .then((encryptedSigningKey) => { let encData = new Uint8Array(encryptedSigningKey); var str = this._cryptoUtil.convertArrayBufferViewToString(encData); res({ @@ -129,16 +143,16 @@ export default class Darkwire { }); return Promise.all(promises); }) - .then(function(data) { + .then((data) => { secretKeys = data; messageData = this._cryptoUtil.convertStringToArrayBufferView(message); return this._cryptoUtil.signKey(messageData, signingKey); }) - .then(function(data) { + .then((data) => { signature = data; return this._cryptoUtil.encryptMessage(messageData, secretKey, vector); }) - .then(function(data) { + .then((data) => { encryptedMessageData = data; let msg = this._cryptoUtil.convertArrayBufferViewToString(new Uint8Array(encryptedMessageData)); let vct = this._cryptoUtil.convertArrayBufferViewToString(new Uint8Array(vector)); @@ -146,7 +160,7 @@ export default class Darkwire { resolve({ message: msg, vector: vct, - messageType: type, + messageType: messageType, secretKeys: secretKeys, signature: sig }); @@ -176,7 +190,7 @@ export default class Darkwire { let secretKeyArrayBuffer = this._cryptoUtil.convertStringToArrayBufferView(mySecretKey.secretKey); let signingKeyArrayBuffer = this._cryptoUtil.convertStringToArrayBufferView(mySecretKey.encryptedSigningKey); - this._cryptoUtil.decryptSecretKey(secretKeyArrayBuffer, keys.private) + this._cryptoUtil.decryptSecretKey(secretKeyArrayBuffer, this._keys.private) .then((data) => { return this._cryptoUtil.importSecretKey(new Uint8Array(data), "raw"); }) @@ -187,7 +201,7 @@ export default class Darkwire { .then((data) => { decryptedMessageData = data; decryptedMessage = this._cryptoUtil.convertArrayBufferViewToString(new Uint8Array(data)) - return this._cryptoUtil.decryptSigningKey(signingKeyArrayBuffer, keys.private) + return this._cryptoUtil.decryptSigningKey(signingKeyArrayBuffer, this._keys.private) }) .then((data) => { return this._cryptoUtil.importSigningKey(new Uint8Array(data), "raw"); diff --git a/src/js/fileHandler.js b/src/js/fileHandler.js index adb1744..722005c 100644 --- a/src/js/fileHandler.js +++ b/src/js/fileHandler.js @@ -39,7 +39,7 @@ export default class FileHandler { reader.readAsBinaryString(file); - return base64; + return encodedFile; } return false; diff --git a/src/js/main.js b/src/js/main.js index 70f7ec1..b3c83e7 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -33,8 +33,6 @@ $(function() { let roomId = window.location.pathname.length ? window.location.pathname : null; - let keys = {}; - if (!roomId) return; $('input.share-text').val(document.location.protocol + "//" + document.location.host + roomId); @@ -77,7 +75,7 @@ $(function() { cryptoUtil.createPrimaryKeys() ]) .then(function(data) { - keys = { + darkwire.keys = { public: data[0].publicKey, private: data[0].privateKey }; @@ -231,13 +229,7 @@ $(function() { $window.keydown(function (event) { // When the client hits ENTER on their keyboard and chat message input is focused if (event.which === 13 && $('.inputMessage').is(':focus')) { - let message = cleanInput($inputMessage.val()); - darkwire.encodeMessage(message, 'chat').then( (message) => { - $inputMessage.val(''); - socket.emit('new message', message); - }).catch( (err) => { - console.log(err); - }); + handleMessageSending(); socket.emit('stop typing'); typing = false; } @@ -296,16 +288,14 @@ $(function() { // Whenever the server emits 'new message', update the chat body socket.on('new message', function (data) { - // Don't show messages if no key - if (!windowHandler.isActive) { - windowHandler.notifyFavicon(); - darkwire.audio.play(); - } + darkwire.decodeMessage(data).then( (data) => { + if (!windowHandler.isActive) { + windowHandler.notifyFavicon(); + darkwire.audio.play(); + } + addChatMessage(data); + }); - let decoded = darkwire.decode(data); - decoded.then( (data) => { - console.log(data); - }) }); // Whenever the server emits 'user left', log it in the chat body @@ -363,14 +353,7 @@ $(function() { } $('#send-message-btn').click(function() { - let message = cleanInput($inputMessage.val()); - // Prevent markup from being injected into the message - darkwire.encodeMessage(message, 'chat').then( (message) => { - $inputMessage.val(''); - socket.emit('new message', message); - }).catch( (err) => { - console.log(err); - }); + handleMessageSending(); socket.emit('stop typing'); typing = false; }); @@ -385,4 +368,21 @@ $(function() { darkwire.audio.soundEnabled = state; }); + function handleMessageSending() { + let message = $inputMessage; + let cleanedMessage = cleanInput(message.val()); + // Prevent markup from being injected into the message + darkwire.encodeMessage(cleanedMessage, 'chat').then( (socketData) => { + message.val(''); + $('#send-message-btn').removeClass('active'); + addChatMessage({ + username: username, + message: cleanedMessage + }); + socket.emit('new message', socketData); + }).catch( (err) => { + console.log(err); + }); + } + }); diff --git a/src/public/style.css b/src/public/style.css index c8ea770..873608f 100644 --- a/src/public/style.css +++ b/src/public/style.css @@ -293,6 +293,6 @@ html.no-touchevents .chat #input-icons { border-color: #2a9fd6 !important; } -#fileSendForm { +#fileInput { display: none; } From 0762bbd15c7901baa8fd5e41716be7da20da265f Mon Sep 17 00:00:00 2001 From: Dan Seripap Date: Fri, 19 Feb 2016 12:52:07 -0500 Subject: [PATCH 4/8] Passing socket to window handler --- src/js/main.js | 1 + src/js/window.js | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/src/js/main.js b/src/js/main.js index b3c83e7..422fc4b 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -44,6 +44,7 @@ $(function() { }); let socket = io(roomId); + windowHandler.socket = socket; FastClick.attach(document.body); diff --git a/src/js/window.js b/src/js/window.js index 8aff85b..c165de0 100644 --- a/src/js/window.js +++ b/src/js/window.js @@ -2,6 +2,7 @@ import FileHandler from './fileHandler'; export default class WindowHandler { constructor() { + this._socket = null; this._isActive = false; this.fileHandler = new FileHandler(); @@ -24,6 +25,10 @@ export default class WindowHandler { return this; } + set socket(socket) { + this._socket = socket; + } + notifyFavicon() { this.newMessages++; this.favicon.badge(this.newMessages); From e75f48ef8880a34e5fcd29985e3a1e8d6a004e79 Mon Sep 17 00:00:00 2001 From: Dan Seripap Date: Fri, 19 Feb 2016 13:12:30 -0500 Subject: [PATCH 5/8] Sending and reciving base64 strings --- src/js/darkwire.js | 16 +++++++++------- src/js/fileHandler.js | 22 ++++++++++++---------- src/js/main.js | 3 +-- src/js/window.js | 9 ++------- 4 files changed, 24 insertions(+), 26 deletions(-) diff --git a/src/js/darkwire.js b/src/js/darkwire.js index 7188aae..2da5b18 100644 --- a/src/js/darkwire.js +++ b/src/js/darkwire.js @@ -89,12 +89,13 @@ export default class Darkwire { if (message && this._connected) { let vector = this._cryptoUtil.crypto.getRandomValues(new Uint8Array(16)); - let secretKey; - let secretKeys; - let messageData; - let signature; - let signingKey; - let encryptedMessageData; + let secretKey = null; + let secretKeys = null; + let messageData = null; + let signature = null; + let signingKey = null; + let encryptedMessageData = null; + // Generate new secret key and vector for each message this._cryptoUtil.createSecretKey() .then((key) => { @@ -154,9 +155,10 @@ export default class Darkwire { }) .then((data) => { encryptedMessageData = data; - let msg = this._cryptoUtil.convertArrayBufferViewToString(new Uint8Array(encryptedMessageData)); let vct = this._cryptoUtil.convertArrayBufferViewToString(new Uint8Array(vector)); let sig = this._cryptoUtil.convertArrayBufferViewToString(new Uint8Array(signature)); + let msg = this._cryptoUtil.convertArrayBufferViewToString(new Uint8Array(encryptedMessageData)); + resolve({ message: msg, vector: vct, diff --git a/src/js/fileHandler.js b/src/js/fileHandler.js index 722005c..4936edc 100644 --- a/src/js/fileHandler.js +++ b/src/js/fileHandler.js @@ -1,7 +1,9 @@ export default class FileHandler { - constructor() { + constructor(darkwire, socket) { if (window.File && window.FileReader && window.FileList && window.Blob && window.btoa) { this._isSupported = true; + this.darkwire = darkwire; + this.socket = socket; this.listen(); } else { this._isSupported = false; @@ -17,11 +19,11 @@ export default class FileHandler { if (file) { - let encodedFile = { - fileName: file.name, - fileSize: file.fileSize, - base64: null - }; + // let encodedFile = { + // fileName: file.name, + // fileSize: file.fileSize, + // base64: null + // }; // Support for only 1MB if (file.size > 1000000) { @@ -34,12 +36,12 @@ export default class FileHandler { reader.onload = (readerEvent) => { const base64 = window.btoa(readerEvent.target.result); - encodedFile.base64 = base64; + this.darkwire.encodeMessage(base64, 'file').then( (socketData) => { + this.socket.emit('new message', socketData); + }); } reader.readAsBinaryString(file); - - return encodedFile; } return false; @@ -47,7 +49,7 @@ export default class FileHandler { listen() { // browser API - document.getElementById('fileInput').addEventListener('change', this.encodeFile, false); + document.getElementById('fileInput').addEventListener('change', jQuery.proxy(this.encodeFile, this), false); // darkwire diff --git a/src/js/main.js b/src/js/main.js index 422fc4b..a1028aa 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -7,7 +7,6 @@ let fs = window.RequestFileSystem || window.webkitRequestFileSystem; $(function() { const darkwire = new Darkwire(); const cryptoUtil = new CryptoUtil(); - const windowHandler = new WindowHandler(); let FADE_TIME = 150; // ms let TYPING_TIMER_LENGTH = 400; // ms @@ -44,7 +43,7 @@ $(function() { }); let socket = io(roomId); - windowHandler.socket = socket; + const windowHandler = new WindowHandler(darkwire, socket); FastClick.attach(document.body); diff --git a/src/js/window.js b/src/js/window.js index c165de0..738f6bc 100644 --- a/src/js/window.js +++ b/src/js/window.js @@ -1,10 +1,9 @@ import FileHandler from './fileHandler'; export default class WindowHandler { - constructor() { - this._socket = null; + constructor(darkwire, socket) { this._isActive = false; - this.fileHandler = new FileHandler(); + this.fileHandler = new FileHandler(darkwire, socket); this.newMessages = 0; this.favicon = new Favico({ @@ -25,10 +24,6 @@ export default class WindowHandler { return this; } - set socket(socket) { - this._socket = socket; - } - notifyFavicon() { this.newMessages++; this.favicon.badge(this.newMessages); From c1b902a5bfdfbc1ee14a7446db560d6294962137 Mon Sep 17 00:00:00 2001 From: Dan Seripap Date: Fri, 19 Feb 2016 13:35:44 -0500 Subject: [PATCH 6/8] Sending file test, currently working for images only --- src/js/darkwire.js | 3 ++- src/js/fileHandler.js | 6 +++++- src/js/main.js | 24 +++++++++++++++++++++--- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/js/darkwire.js b/src/js/darkwire.js index 2da5b18..b35ac59 100644 --- a/src/js/darkwire.js +++ b/src/js/darkwire.js @@ -216,7 +216,8 @@ export default class Darkwire { if (bool) { resolve({ username: username, - message: decryptedMessage + message: decryptedMessage, + messageType: data.messageType }); } }); diff --git a/src/js/fileHandler.js b/src/js/fileHandler.js index 4936edc..e00c9bb 100644 --- a/src/js/fileHandler.js +++ b/src/js/fileHandler.js @@ -1,6 +1,6 @@ export default class FileHandler { constructor(darkwire, socket) { - if (window.File && window.FileReader && window.FileList && window.Blob && window.btoa) { + if (window.File && window.FileReader && window.FileList && window.Blob && window.btoa && window.atob) { this._isSupported = true; this.darkwire = darkwire; this.socket = socket; @@ -47,6 +47,10 @@ export default class FileHandler { return false; } + decodeFile(base64) { + return window.atob(base64); + } + listen() { // browser API document.getElementById('fileInput').addEventListener('change', jQuery.proxy(this.encodeFile, this), false); diff --git a/src/js/main.js b/src/js/main.js index a1028aa..6a63fae 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -120,10 +120,19 @@ $(function() { let $usernameDiv = $('') .text(data.username) .css('color', getUsernameColor(data.username)); - let $messageBodyDiv = $('') - .html(data.message); + let $messageBodyDiv = $(''); + + if (options.file) { + let image = new Image(); + image.src = `data:image/png;base64,${data.message}`; + $messageBodyDiv.html(image); + } else { + $messageBodyDiv.html(data.message); + } let typingClass = data.typing ? 'typing' : ''; + + let $messageDiv = $('
  • ') .data('username', data.username) .addClass(typingClass) @@ -293,7 +302,16 @@ $(function() { windowHandler.notifyFavicon(); darkwire.audio.play(); } - addChatMessage(data); + if (data.messageType === 'file') { + // let file = windowHandler.fileHandler.decodeFile(data.message); + // let chatMessage = { + // username: data.username, + // message: file + // } + addChatMessage(data, {file: true}) + } else { + addChatMessage(data); + } }); }); From 6273e21b22947e3710a330fa184cefc28b1ce1c4 Mon Sep 17 00:00:00 2001 From: Dan Seripap Date: Fri, 19 Feb 2016 13:41:35 -0500 Subject: [PATCH 7/8] Bind vs proxy --- src/js/fileHandler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/fileHandler.js b/src/js/fileHandler.js index e00c9bb..4b73225 100644 --- a/src/js/fileHandler.js +++ b/src/js/fileHandler.js @@ -53,7 +53,7 @@ export default class FileHandler { listen() { // browser API - document.getElementById('fileInput').addEventListener('change', jQuery.proxy(this.encodeFile, this), false); + document.getElementById('fileInput').addEventListener('change', this.encodeFile.bind(this), false); // darkwire From 59ee6ca56fd5d1e37f82a63121009113de15ee4d Mon Sep 17 00:00:00 2001 From: Dan Seripap Date: Sat, 20 Feb 2016 09:09:05 -0500 Subject: [PATCH 8/8] Actual configurable port --- src/app.js | 2 +- src/js/main.js | 10 ++++++---- src/room.js | 1 + 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/app.js b/src/app.js index 898de97..134fbc5 100644 --- a/src/app.js +++ b/src/app.js @@ -11,7 +11,7 @@ import fs from 'fs'; import Room from './room'; const $CONFIG = { - port: 3000 + port: process.env.port || 3000 } const app = express(); diff --git a/src/js/main.js b/src/js/main.js index 6a63fae..76e577e 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -106,7 +106,7 @@ $(function() { } // Adds the visual chat message to the message list - function addChatMessage (data, options) { + function addChatMessage (data, options, dataType) { if (!data.message.trim().length) return; // Don't fade the message in if there is an 'X was typing' @@ -121,8 +121,10 @@ $(function() { .text(data.username) .css('color', getUsernameColor(data.username)); let $messageBodyDiv = $(''); - - if (options.file) { + // TODO: Ask client if accept/reject attachment + // If reject, destroy object in memory + // If accept, render image or content dispose + if (dataType.file) { let image = new Image(); image.src = `data:image/png;base64,${data.message}`; $messageBodyDiv.html(image); @@ -308,7 +310,7 @@ $(function() { // username: data.username, // message: file // } - addChatMessage(data, {file: true}) + addChatMessage(data, false, {file: true}) } else { addChatMessage(data); } diff --git a/src/room.js b/src/room.js index 1d17bcc..5e357d8 100644 --- a/src/room.js +++ b/src/room.js @@ -23,6 +23,7 @@ class Room { id: socket.user.id, message: data.message, messageType: data.messageType, + data: data.data, vector: data.vector, secretKeys: data.secretKeys, signature: data.signature