Ability to send a read-only URL

This commit is contained in:
Yann Flory 2016-09-16 18:45:40 +02:00
parent 721cb8fed1
commit 368e253c9f
4 changed files with 69 additions and 14 deletions

View file

@ -1,5 +1,6 @@
;(function () { 'use strict'; ;(function () { 'use strict';
const Crypto = require('crypto'); const Crypto = require('crypto');
const Nacl = require('tweetnacl');
const LogStore = require('./storage/LogStore'); const LogStore = require('./storage/LogStore');
@ -27,6 +28,15 @@ const sendMsg = function (ctx, user, msg) {
const sendChannelMessage = function (ctx, channel, msgStruct) { const sendChannelMessage = function (ctx, channel, msgStruct) {
msgStruct.unshift(0); 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) { 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 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); sendMsg(ctx, user, msgStruct);
@ -149,6 +159,17 @@ const handleMessage = function (ctx, user, msg) {
clearTimeout(ctx.timeouts[chanName]); 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; chan.id = chanName;
if (USE_HISTORY_KEEPER) { if (USE_HISTORY_KEEPER) {
sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'JOIN', chanName]); sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'JOIN', chanName]);

View file

@ -5,7 +5,8 @@
"dependencies": { "dependencies": {
"express": "~4.10.1", "express": "~4.10.1",
"ws": "^1.0.1", "ws": "^1.0.1",
"nthen": "~0.1.0" "nthen": "~0.1.0",
"tweetnacl": "~0.12.2"
}, },
"devDependencies": { "devDependencies": {
"jshint": "~2.9.1", "jshint": "~2.9.1",

View file

@ -128,7 +128,7 @@ define([
var base64ToHex = common.base64ToHex = function (b64String) { var base64ToHex = common.base64ToHex = function (b64String) {
var hexArray = []; 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); var h = e.charCodeAt(0).toString(16);
if (h.length === 1) { h = "0"+h; } if (h.length === 1) { h = "0"+h; }
hexArray.push(h); hexArray.push(h);
@ -136,18 +136,28 @@ define([
return hexArray.join(""); 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 getSecrets = common.getSecrets = function () {
var secret = {}; var secret = {};
if (!/#/.test(window.location.href)) { if (!/#/.test(window.location.href)) {
secret.key = Crypto.genKey(); secret.keys = Crypto.createEditCryptor();
secret.key = Crypto.createEditCryptor().editKeyStr;
} else { } else {
var hash = window.location.hash.slice(1); var hash = window.location.hash.slice(1);
if (hash.length === 0) { if (hash.length === 0) {
secret.key = Crypto.genKey(); secret.keys = Crypto.createEditCryptor();
secret.key = Crypto.createEditCryptor().editKeyStr;
return secret; return secret;
} }
common.redirect(hash); common.redirect(hash);
@ -166,15 +176,35 @@ define([
throw new Error("Unable to parse the key"); throw new Error("Unable to parse the key");
} }
var version = hashArray[1]; var version = hashArray[1];
if (version === "1") { /*if (version === "1") {
secret.channel = base64ToHex(hashArray[2]); secret.channel = base64ToHex(hashArray[2]);
secret.key = hashArray[3].replace(/-/g, '/'); //TODO replace / by - secret.key = hashArray[3].replace(/-/g, '/'); //TODO replace / by -
if (secret.channel.length !== 32 || secret.key.length !== 24) { if (secret.channel.length !== 32 || secret.key.length !== 24) {
common.alert("The channel key and/or the encryption key is invalid"); 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"); 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");
}
}
} }
} }
} }

View file

@ -115,7 +115,6 @@ define([
} else { } else {
module.spinner.show(); module.spinner.show();
} }
inner.setAttribute('contenteditable', bool); inner.setAttribute('contenteditable', bool);
}; };
@ -317,14 +316,15 @@ define([
// the channel we will communicate over // the channel we will communicate over
channel: secret.channel, channel: secret.channel,
// our encryption key // our public key. send -1 if view mode
cryptKey: secret.key, validateKey: secret.keys.validateKey || undefined,
readOnly: secret.keys.editKeyStr ? undefined : 1,
// method which allows us to get the id of the user // method which allows us to get the id of the user
setMyID: setMyID, setMyID: setMyID,
// Pass in encrypt and decrypt methods // Pass in encrypt and decrypt methods
crypto: Crypto.createEncryptor(secret.key), crypto: Crypto.createEncryptor(secret.keys),
// really basic operational transform // really basic operational transform
transformFunction : JsonOT.validate, transformFunction : JsonOT.validate,
@ -545,7 +545,10 @@ define([
$rightside.append($forgetPad); $rightside.append($forgetPad);
// set the hash // 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) { Cryptpad.getPadTitle(function (err, title) {
if (err) { if (err) {