Creating interface for darkwire, started work on filetransfer

This commit is contained in:
Dan Seripap 2016-02-19 11:39:04 -05:00
parent 4338f1aa8a
commit e59bb2a1cf
9 changed files with 314 additions and 198 deletions

View File

@ -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).

View File

@ -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}.`);
});

200
src/js/darkwire.js Normal file
View File

@ -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
});
}
});
});
}
}

56
src/js/fileHandler.js Normal file
View File

@ -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;
}
}

View File

@ -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;
});
});

View File

@ -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;

View File

@ -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;
}

View File

@ -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

View File

@ -55,6 +55,8 @@
</div>
<input class="inputMessage" placeholder="Type here..."/>
<div id="input-icons">
<span class="glyphicon glyphicon-file" id="send-file"></span>
<input type="file" id="fileInput">
<span class="glyphicon glyphicon-send" id="send-message-btn" aria-hidden="true"></span>
</div>
</li>