From 39e4d3d56c41c0aaa3a4bf477463c96b28fcf8c3 Mon Sep 17 00:00:00 2001 From: Alan Friedman Date: Wed, 20 Jan 2016 16:48:47 -0500 Subject: [PATCH] Use crypto web API and prevent usage in unsupported browsers --- src/js/main.js | 107 +++++++++++++++++++++++++++++++++------ src/room.js | 3 +- src/views/index.mustache | 14 +++++ 3 files changed, 108 insertions(+), 16 deletions(-) diff --git a/src/js/main.js b/src/js/main.js index 2cb2482..d10c436 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -45,6 +45,18 @@ $(function() { if (!roomId) return; + if ((!window.crypto && !window.msCrypto) || !window.crypto.subtle) { + $('#no-crypto').modal({ + backdrop: 'static', + show: false, + keyboard: false + }) + $('#no-crypto').modal('show'); + return; + } + + var crypto = window.crypto; + let socket = io(roomId); $('#roomIdKey').text(roomId.replace('/', '')); @@ -82,7 +94,9 @@ $(function() { // Sends a chat message function sendMessage () { // Don't allow sending if key is empty - if (!encryptionKey.trim().length) return; + if (!$('.key').text().trim().length) return; + + var vector = crypto.getRandomValues(new Uint8Array(16)); let message = $inputMessage.val(); // Prevent markup from being injected into the message @@ -96,18 +110,20 @@ $(function() { message: message }); // tell server to execute 'new message' and send along one parameter - socket.emit('new message', encrypt(message)); + createKey(encryptionKey) + .then(function(key) { + return encryptData(message, key, vector); + }) + .then(function(data) { + var encryptedData = new Uint8Array(data); + socket.emit('new message', { + message: convertArrayBufferViewtoString(encryptedData), + vector: convertArrayBufferViewtoString(vector) + }); + }); } } - function encrypt(text) { - return CryptoJS.AES.encrypt(text, $key.val()).toString(); - } - - function decrypt(text) { - return CryptoJS.AES.decrypt(text, $key.val()).toString(CryptoJS.enc.Utf8) || text; - } - // Log a message function log (message, options) { let html = options && options.html === true || false; @@ -327,7 +343,6 @@ $(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 (!isActive) { newMessages++; favicon.badge(newMessages); @@ -335,8 +350,26 @@ $(function() { beep.play(); } } - data.message = decrypt(data.message); - addChatMessage(data); + + var username = data.username; + + createKey(encryptionKey) + .then(function(key) { + var msg = convertStringToArrayBufferView(data.message); + var vector = convertStringToArrayBufferView(data.vector); + return decryptData(msg, key, vector) + }) + .then(function(data) { + var decryptedData = new Uint8Array(data); + var msg = convertArrayBufferViewtoString(decryptedData); + addChatMessage({ + username: username, + message: msg + }); + }) + .catch(function() { + + }); }); // Whenever the server emits 'user joined', log it in the chat body @@ -468,7 +501,8 @@ $(function() { function updateKeyVal(val) { $('.key').val(val); - $('.key').text(val); + $('.key').text(val); + encryptionKey = val; $('textarea.share-text').val("Let's chat on darkwire.io at https://darkwire.io" + roomId + " using the passphrase " + encryptionKey); autosize.update($('textarea.share-text')); @@ -551,6 +585,49 @@ $(function() { $('input.bs-switch').on('switchChange.bootstrapSwitch', function(event, state) { soundEnabled = state; - }); + }); + + function convertStringToArrayBufferView(str) { + var bytes = new Uint8Array(str.length); + for (var i = 0; i < str.length; i++) { + bytes[i] = str.charCodeAt(i); + } + + return bytes; + } + + function convertArrayBufferViewtoString(buffer) { + var str = ""; + for (var i = 0; i < buffer.byteLength; i++) { + str += String.fromCharCode(buffer[i]); + } + + return str; + } + + function createKey(password) { + return crypto.subtle.digest({ + name: "SHA-256" + }, convertStringToArrayBufferView(password)) + .then(function(result) { + return window.crypto.subtle.importKey("raw", result, { + name: "AES-CBC" + }, false, ["encrypt", "decrypt"]); + }); + } + + function encryptData(data, key, vector) { + return crypto.subtle.encrypt({ + name: "AES-CBC", + iv: vector + }, key, convertStringToArrayBufferView(data)); + } + + function decryptData(data, key, vector) { + return crypto.subtle.decrypt({ + name: "AES-CBC", + iv: vector + }, key, data); + } }); diff --git a/src/room.js b/src/room.js index ae844a7..e6d7289 100644 --- a/src/room.js +++ b/src/room.js @@ -19,7 +19,8 @@ class Room { // we tell the client to execute 'new message' socket.broadcast.emit('new message', { username: socket.username, - message: data + message: data.message, + vector: data.vector }); }); diff --git a/src/views/index.mustache b/src/views/index.mustache index 90ec3fc..329e2e1 100644 --- a/src/views/index.mustache +++ b/src/views/index.mustache @@ -180,6 +180,20 @@ + +