diff --git a/NetfluxWebsocketSrv.js b/NetfluxWebsocketSrv.js index a5e75c6cf..499a3cde8 100644 --- a/NetfluxWebsocketSrv.js +++ b/NetfluxWebsocketSrv.js @@ -1,5 +1,6 @@ ;(function () { 'use strict'; const Crypto = require('crypto'); +const Nacl = require('tweetnacl'); const LogStore = require('./storage/LogStore'); @@ -27,6 +28,15 @@ const sendMsg = function (ctx, user, msg) { const sendChannelMessage = function (ctx, channel, msgStruct) { msgStruct.unshift(0); + if (msgStruct[2] === 'MSG' && channel.validateKey) { + let msg = Nacl.util.decodeBase64(msgStruct[4]); + let validated = Nacl.sign.open(msg, channel.validateKey); + if (!validated) { + console.log("Unsigned message rejected"); + return; + } + msgStruct[4] = Nacl.util.encodeUTF8(validated); + } channel.forEach(function (user) { if(msgStruct[2] !== 'MSG' || user.id !== msgStruct[1]) { // We don't want to send back a message to its sender, in order to save bandwidth sendMsg(ctx, user, msgStruct); @@ -149,6 +159,17 @@ const handleMessage = function (ctx, user, msg) { clearTimeout(ctx.timeouts[chanName]); } + // validation key? + if (json[2]) { + if (chan.validateKey && Nacl.util.encodeBase64(chan.validateKey) !== json[2]) { + sendMsg(ctx, user, [seq, 'ERROR', 'INVALID_KEY', obj]); + return; + } + if (!chan.validateKey) { + chan.validateKey = Nacl.util.decodeBase64(json[2]); + } + } + chan.id = chanName; if (USE_HISTORY_KEEPER) { sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'JOIN', chanName]); diff --git a/package.json b/package.json index c61266ee7..ec9dbf734 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "dependencies": { "express": "~4.10.1", "ws": "^1.0.1", - "nthen": "~0.1.0" + "nthen": "~0.1.0", + "tweetnacl": "~0.12.2" }, "devDependencies": { "jshint": "~2.9.1", diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 1e1a67069..6b8415fd6 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -128,7 +128,7 @@ define([ var base64ToHex = common.base64ToHex = function (b64String) { var hexArray = []; - atob(b64String.replace(/-/g, '/') + "==").split("").forEach(function(e){ + atob(b64String.replace(/-/g, '/')).split("").forEach(function(e){ var h = e.charCodeAt(0).toString(16); if (h.length === 1) { h = "0"+h; } hexArray.push(h); @@ -136,18 +136,28 @@ define([ return hexArray.join(""); }; - var getHashFromKeys = common.getHashFromKeys = function (chanKey, cryptKey) { - return '/1/' + hexToBase64(chanKey) + '/' + cryptKey.replace(/\//g, '-'); + + var getEditHashFromKeys = common.getEditHashFromKeys = function (chanKey, keys) { + if (typeof keys === 'string') { + return chanKey + Crypto.b64RemoveSlashes(keys); + } + return '/1/edit/' + hexToBase64(chanKey) + '/' + Crypto.b64RemoveSlashes(keys.editKeyStr); }; + var getViewHashFromKeys = common.getViewHashFromKeys = function (chanKey, keys) { + return '/1/view/' + hexToBase64(chanKey) + '/' + Crypto.b64RemoveSlashes(keys.viewKeyStr); + }; + var getHashFromKeys = common.getHashFromKeys = getEditHashFromKeys; var getSecrets = common.getSecrets = function () { var secret = {}; if (!/#/.test(window.location.href)) { - secret.key = Crypto.genKey(); + secret.keys = Crypto.createEditCryptor(); + secret.key = Crypto.createEditCryptor().editKeyStr; } else { var hash = window.location.hash.slice(1); if (hash.length === 0) { - secret.key = Crypto.genKey(); + secret.keys = Crypto.createEditCryptor(); + secret.key = Crypto.createEditCryptor().editKeyStr; return secret; } common.redirect(hash); @@ -166,15 +176,35 @@ define([ throw new Error("Unable to parse the key"); } var version = hashArray[1]; - if (version === "1") { + /*if (version === "1") { secret.channel = base64ToHex(hashArray[2]); secret.key = hashArray[3].replace(/-/g, '/'); //TODO replace / by - if (secret.channel.length !== 32 || secret.key.length !== 24) { common.alert("The channel key and/or the encryption key is invalid"); - console.log("Channel key length : " + secret.channel.length + " != 32"); - console.log("Encryption key length : " + secret.key.length + " != 24"); throw new Error("The channel key and/or the encryption key is invalid"); } + }*/ + if (version === "1") { + var mode = hashArray[2]; + if (mode === 'edit') { + secret.channel = base64ToHex(hashArray[3]); + var keys = Crypto.createEditCryptor(hashArray[4].replace(/-/g, '/')); + secret.keys = keys; + secret.key = keys.editKeyStr; + if (secret.channel.length !== 32 || secret.key.length !== 24) { + common.alert("The channel key and/or the encryption key is invalid"); + throw new Error("The channel key and/or the encryption key is invalid"); + } + } + else if (mode === 'view') { + secret.channel = base64ToHex(hashArray[3]); + var keys = Crypto.createViewCryptor(hashArray[4].replace(/-/g, '/')); + secret.keys = keys; + if (secret.channel.length !== 32) { + common.alert("The channel key is invalid"); + throw new Error("The channel key is invalid"); + } + } } } } diff --git a/www/pad/main.js b/www/pad/main.js index fac663e51..922cf1f02 100644 --- a/www/pad/main.js +++ b/www/pad/main.js @@ -115,7 +115,6 @@ define([ } else { module.spinner.show(); } - inner.setAttribute('contenteditable', bool); }; @@ -317,14 +316,15 @@ define([ // the channel we will communicate over channel: secret.channel, - // our encryption key - cryptKey: secret.key, + // our public key. send -1 if view mode + validateKey: secret.keys.validateKey || undefined, + readOnly: secret.keys.editKeyStr ? undefined : 1, // method which allows us to get the id of the user setMyID: setMyID, // Pass in encrypt and decrypt methods - crypto: Crypto.createEncryptor(secret.key), + crypto: Crypto.createEncryptor(secret.keys), // really basic operational transform transformFunction : JsonOT.validate, @@ -545,7 +545,10 @@ define([ $rightside.append($forgetPad); // set the hash - window.location.hash = Cryptpad.getHashFromKeys(info.channel, secret.key); + if (secret.keys.editKeyStr) { + window.location.hash = Cryptpad.getEditHashFromKeys(info.channel, secret.keys); + } + console.log("View Hash : " + Cryptpad.getViewHashFromKeys(info.channel, secret.keys)); Cryptpad.getPadTitle(function (err, title) { if (err) {