diff --git a/src/js/main.js b/src/js/main.js index 5f387cc..cd887f8 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -20,7 +20,6 @@ $(function() { ]; let $window = $(window); - let $usernameInput = $('.usernameInput'); // Input for username let $messages = $('.messages'); // Messages area let $inputMessage = $('.inputMessage'); // Input message input box let $key = $('.key'); @@ -33,14 +32,15 @@ $(function() { // Prompt for setting a username let username; + let myUserId; let connected = false; let typing = false; let lastTypingTime; - let $currentInput = $usernameInput.focus(); - let encryptionKey; let roomId = window.location.pathname.length ? window.location.pathname : null; + let keys = {}; + if (!roomId) return; if (!window.crypto || (!window.crypto.subtle && !window.crypto.webkitSubtle)) { @@ -53,11 +53,18 @@ $(function() { return; } + $('textarea.share-text').val("Let's chat on darkwire.io at https://darkwire.io" + roomId); + + $('textarea.share-text').click(function() { + $(this).focus(); + $(this).select(); + this.setSelectionRange(0, 9999); + }); + var crypto = window.crypto; var cryptoSubtle = window.crypto.subtle || window.crypto.webkitSubtle; let socket = io(roomId); - $('#roomIdKey').text(roomId.replace('/', '')); FastClick.attach(document.body); @@ -69,7 +76,7 @@ $(function() { } // Sets the client's username - function setUsername () { + function initChat () { username = window.username; // warn not incognitor if (!fs) { @@ -83,19 +90,41 @@ $(function() { // If the username is valid if (username) { $chatPage.show(); - $currentInput = $inputMessage.focus(); + $inputMessage.focus(); - // Tell the server your username - socket.emit('add user', username); + Promise.all([ + createPrimaryKeys(), + createSigningKeys() + ]) + .then(function(data) { + keys.primary = { + public: data[0].publicKey, + private: data[0].privateKey + }; + keys.signing = { + public: data[1].publicKey, + private: data[1].privateKey + }; + return Promise.all([ + exportKey(data[0].publicKey), + exportKey(data[1].publicKey), + ]); + }) + .then(function(exportedKeys) { + // Tell the server your username and send public keys + socket.emit('add user', { + username: username, + publicPrimaryKey: exportedKeys[0], + publicSigningKey: exportedKeys[1] + }); + }); } } // Sends a chat message function sendMessage () { - // Don't allow sending if key is empty - if (!encryptionKey.trim().length) return; - - var vector = crypto.getRandomValues(new Uint8Array(16)); + // Don't send unless other users exist + if (users.length <= 1) return; let message = $inputMessage.val(); // Prevent markup from being injected into the message @@ -108,16 +137,63 @@ $(function() { username: username, message: message }); - // tell server to execute 'new message' and send along one parameter - createKey(encryptionKey) + let vector = crypto.getRandomValues(new Uint8Array(16)); + + let secretKey; + let secretKeys; + let messageData; + let signature; + + // Generate new secret key and vector for each message + createSecretKey() .then(function(key) { - return encryptData(message, key, vector); + secretKey = key; + // Generate secretKey and encrypt with each user's public key + let promises = []; + _.each(users, function(user) { + // It not me + if (user.username !== window.username) { + let promise = new Promise(function(resolve, reject) { + let thisUser = user; + + let exportedSecretKey; + exportKey(key, "raw") + .then(function(data) { + exportedSecretKey = data; + return encryptSecretKey(data, thisUser.publicPrimaryKey); + }) + .then(function(encryptedSecretKey) { + var encData = new Uint8Array(encryptedSecretKey); + var str = convertArrayBufferViewToString(encData); + resolve({ + id: thisUser.id, + secretKey: str + }); + }); + }); + promises.push(promise); + } + }); + return Promise.all(promises); }) .then(function(data) { - var encryptedData = new Uint8Array(data); + secretKeys = data; + messageData = convertStringToArrayBufferView(message); + return signKey(messageData, keys.signing.private) + }) + .then(function(data) { + signature = data; + return encryptMessage(messageData, secretKey, vector) + }) + .then(function(encryptedData) { + let msg = convertArrayBufferViewToString(new Uint8Array(encryptedData)); + let vct = convertArrayBufferViewToString(new Uint8Array(vector)); + let sig = convertArrayBufferViewToString(new Uint8Array(signature)); socket.emit('new message', { - message: convertArrayBufferViewtoString(encryptedData), - vector: convertArrayBufferViewtoString(vector) + message: msg, + vector: vct, + secretKeys: secretKeys, + signature: sig }); }); } @@ -264,15 +340,6 @@ $(function() { typing = false; } - // If enter is pressed on key input then close key modal - if (event.which === 13 && $('#join-modal input').is(':focus')) { - checkJoinKey(); - } - - // If enter is pressed on edit key input - if (event.which === 13 && $('#settings-modal .edit-key input.key').is(':focus')) { - saveKey(); - } }); $inputMessage.on('input propertychange paste change', function() { @@ -285,11 +352,6 @@ $(function() { } }); - $genKey.click(function () { - let key = generatePassword(); - updateKeyVal(key); - }); - // Select message input when closing modal $('.modal').on('hidden.bs.modal', function (e) { $inputMessage.focus(); @@ -312,30 +374,57 @@ $(function() { return text; } - // Socket events - // Whenever the server emits 'login', log the login message - socket.on('login', function (data) { + socket.on('user joined', function (data) { connected = true; addParticipantsMessage(data); - users = data.users; + 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([ + importPrimaryKey(currentUser.publicPrimaryKey), + importSigningKey(currentUser.publicSigningKey) + ]) + .then(function(keys) { + users.push({ + id: currentUser.id, + username: currentUser.username, + publicPrimaryKey: keys[0], + publicSigningKey: keys[1] + }); + resolve(); + }); + }); + importKeysPromises.push(promise); + } + }); - let key = generatePassword(); + if (!myUserId) { + // Set my id if not already set + let me = _.findWhere(data.users, {username: username}); + myUserId = me.id; + } - if (data.numUsers > 1) { - $('#join-modal').modal('show'); - $('#join-modal').on('shown.bs.modal', function (e) { - $('#join-modal input').focus(); + Promise.all(importKeysPromises) + .then(function() { + // All users' keys have been imported + if (data.numUsers === 1) { + $('#first-modal').modal('show'); + } + + $('.modal').on('shown.bs.modal', function (e) { + autosize.update($('textarea.share-text')); }); - key = ''; - } - updateKeyVal(key); + log(data.username + ' joined'); - $('.modal').on('shown.bs.modal', function (e) { - autosize.update($('textarea.share-text')); - }); + renderParticipantsList(); + }); }); @@ -349,44 +438,64 @@ $(function() { beep.play(); } } - - var username = data.username; - createKey(encryptionKey) - .then(function(key) { - var msg = convertStringToArrayBufferView(data.message); - var vector = convertStringToArrayBufferView(data.vector); - return decryptData(msg, key, vector) + let message = data.message; + let messageData = convertStringToArrayBufferView(message); + let username = data.username; + let senderId = data.id + let vector = data.vector; + let vectorData = 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 = convertStringToArrayBufferView(signature); + let secretKeyArrayBuffer = convertStringToArrayBufferView(mySecretKey.secretKey); + + decryptSecretKey(secretKeyArrayBuffer, keys.primary.private) + .then(function(data) { + return new Uint8Array(data); }) .then(function(data) { - var decryptedData = new Uint8Array(data); - var msg = convertArrayBufferViewtoString(decryptedData); - addChatMessage({ - username: username, - message: msg - }); + return importSecretKey(data, "raw"); }) - .catch(function() { - + .then(function(data) { + let secretKey = data; + return decryptMessage(messageData, secretKey, vectorData); + }) + .then(function(data) { + decryptedMessageData = data; + decryptedMessage = convertArrayBufferViewToString(new Uint8Array(data)) + }) + .then(function() { + // Find who sent msg (senderId), get their public key and verifyKey() with it and signature + let sender = _.find(users, function(user) { + return user.id === senderId; + }); + let senderPublicVerifyKey = sender.publicSigningKey; + return verifyKey(signatureData, decryptedMessageData, senderPublicVerifyKey) + }) + .then(function(bool) { + if (bool) { + addChatMessage({ + username: username, + message: decryptedMessage + }); + } }); }); - // Whenever the server emits 'user joined', log it in the chat body - socket.on('user joined', function (data) { - log(data.username + ' joined'); - addParticipantsMessage(data); - - users = data.users; - renderParticipantsList(); - }); - // Whenever the server emits 'user left', log it in the chat body socket.on('user left', function (data) { log(data.username + ' left'); addParticipantsMessage(data); removeChatTyping(data); - users = data.users; + users = _.without(users, _.findWhere(users, {id: data.id})); renderParticipantsList(); }); @@ -401,11 +510,7 @@ $(function() { removeChatTyping(data); }); - socket.on('first', function() { - $('#first-modal').modal('show'); - }); - - setUsername(); + initChat(); window.onfocus = function () { isActive = true; @@ -426,33 +531,8 @@ $(function() { $('#about-modal').modal('show'); }); - $('.room-url').text('https://darkwire.io' + roomId); - $('.room-id').text(roomId.replace('/', '')); - $('[data-toggle="tooltip"]').tooltip(); - function joinKeyInputChanged(val) { - if (!val.trim().length) { - $('#join-modal .modal-footer button').attr('disabled', 'disabled'); - } else { - $('#join-modal .modal-footer button').removeAttr('disabled'); - } - } - - $('#join-modal .key').on('input propertychange paste change', function() { - let val = $(this).val().trim(); - joinKeyInputChanged(val); - }); - - $('#settings-modal input.key').on('input propertychange paste change', function() { - let val = $(this).val().trim(); - if (val !== encryptionKey && val.length) { - $('#settings-modal #save-key-edit').removeAttr('disabled'); - } else { - $('#settings-modal #save-key-edit').attr('disabled', 'disabled'); - } - }); - $('.navbar .participants').click(function() { renderParticipantsList(); $('#participants-modal').modal('show'); @@ -460,101 +540,29 @@ $(function() { function renderParticipantsList() { $('#participants-modal ul.users').empty(); - _.each(users, function(username) { + _.each(users, function(user) { let li; - if (username === window.username) { + if (user.username === window.username) { // User is me - li = $("
WARNING: This product is in beta and its source code has not been peer-reviewed or undergone a security audit. This is a demo only and not intended for security-critical use.
+WARNING: This product is in beta and its source code has not been peer-reviewed or undergone a security audit. This is a demo only and not intended for security-critical use. View source code.
-darkwire.io is the simplest way to chat anonymously, and with encryption, online. Chat history is never stored on a server or database, and plain text messages are never transferred over the wire.
- -Your encryption passphrase ensures that only people you trust can decipher your messages. If you change your passphrase, make sure to notify all other participants.
+darkwire.io is the simplest way to chat with encryption online. Chat history is never stored on a server or database, and plain text messages are never transferred over the wire.
Questions/comments? Email us at hello[at]darkwire.io
WARNING: This product is in beta and its source code has not been peer-reviewed or undergone a security audit. This is a demo only and not intended for security-critical use. View source code.
-We've placed you in a new chat room.
@@ -144,25 +128,6 @@