From dc567fa7f3c78ca7e271716125bf0510cfac099f Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 7 Mar 2017 17:30:32 +0100 Subject: [PATCH 1/7] signing keys are stored in hex so encode and decode them --- www/common/encode.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 www/common/encode.js diff --git a/www/common/encode.js b/www/common/encode.js new file mode 100644 index 000000000..44b2071ba --- /dev/null +++ b/www/common/encode.js @@ -0,0 +1,19 @@ +define([], function () { + var exports = {}; + + var hexToUint8Array = exports.hexToUint8Array = function (s) { + // if not hex or odd number of characters + if (!/[a-fA-F0-9]+/.test(s) || s.length % 2) { throw new Error("string is not hex"); } + return s.split(/([0-9a-fA-F]{2})/) + .filter(function (x) { return x; }) + .map(function (x) { return Number('0x' + x); }); + }; + + var uint8ArrayToHex = exports.uint8ArrayToHex = function (a) { + return a.reduce(function(memo, i) { + return memo + ((i < 16) ? '0' : '') + i.toString(16); + }, ''); + }; + + return exports; +}); From e2418a6be21945b20a4b109dde85d0ac594c189a Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 7 Mar 2017 17:33:31 +0100 Subject: [PATCH 2/7] sketch of how pinning rpc will look --- www/common/rpc.js | 48 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 www/common/rpc.js diff --git a/www/common/rpc.js b/www/common/rpc.js new file mode 100644 index 000000000..3d15843cc --- /dev/null +++ b/www/common/rpc.js @@ -0,0 +1,48 @@ +define([ + '/common/encode.js', + + '/bower_components/tweetnacl/nacl-fast.min.js', +], function (Encode) { + + var getHistoryKeeperName = function (network) { + var wc = network.webChannels[0]; + if (!wc) { + throw new Error("ERROR: no joined webchannels so we can't get the history keeper name"); + } + if (!wc.history_keeper) { throw new Error("ERROR: no history keeper"); } + return wc.history_keeper; + }; + + var sendMsg = function (ctx, cb) { + var hkn = getHistoryKeeperName(ctx.network); + }; + + var onMsg = function (ctx, msg) { + console.log(msg); + }; + + var cookie = function (ctx, cb) { + // TODO + }; + + var signMsg = function (msg, secKey) { + // TODO + }; + + var create = function (network, edPrivateKey) { + if (!/[0-9a-f]{64}/.test(edPrivateKey)) { + throw new Error("private signing key is not valid"); + } + var ctx = { + privateKey: Encode.hexToUint8Array(edPrivateKey), + seq: new Date().getTime(), + network: network + }; + network.on('message', function (msg) { onMsg(ctx, msg); }); + return { + cookie: function (cb) { cookie(ctx, cb); }, + }; + }; + + return { create: create }; +}); From 612a00b484435cb80225348c8ddae1278e24d661 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 10 Mar 2017 18:03:15 +0100 Subject: [PATCH 3/7] implement serverside RPC infrastructure --- NetfluxWebsocketSrv.js | 20 ++++++++++++++++++-- config.js.dist | 7 +++++++ rpc.js | 37 +++++++++++++++++++++++++++++++++++++ server.js | 37 ++++++++++++++++++++++++++++--------- 4 files changed, 90 insertions(+), 11 deletions(-) create mode 100644 rpc.js diff --git a/NetfluxWebsocketSrv.js b/NetfluxWebsocketSrv.js index b15cdd33b..bd6b6da87 100644 --- a/NetfluxWebsocketSrv.js +++ b/NetfluxWebsocketSrv.js @@ -209,6 +209,21 @@ const handleMessage = function (ctx, user, msg) { let parsedMsg = {state: 1, channel: parsed[1]}; sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify(parsedMsg)]); }); + } else if (ctx.rpc) { + /* RPC Calls... */ + var rpc_call = parsed.slice(1); + + // slice off the sequence number and pass in the rest of the message + ctx.rpc(rpc_call, function (err, output) { + if (err) { + console.error('[' + err + ']', output); + sendMsg(ctx, user, [seq, 'ACK']); + sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify([parsed[0], 'ERROR', err])]); + return + } + sendMsg(ctx, user, [seq, 'ACK']); + sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify([parsed[0]].concat(output))]); + }); } return; } @@ -251,7 +266,7 @@ const handleMessage = function (ctx, user, msg) { } }; -let run = module.exports.run = function (storage, socketServer, config) { +let run = module.exports.run = function (storage, socketServer, config, rpc) { /* Channel removal timeout defaults to 60000ms (one minute) */ config.channelRemovalTimeout = typeof(config.channelRemovalTimeout) === 'number'? @@ -263,7 +278,8 @@ let run = module.exports.run = function (storage, socketServer, config) { channels: {}, timeouts: {}, store: storage, - config: config + config: config, + rpc: rpc, }; setInterval(function () { Object.keys(ctx.users).forEach(function (userId) { diff --git a/config.js.dist b/config.js.dist index da80bb374..3818ae002 100644 --- a/config.js.dist +++ b/config.js.dist @@ -130,6 +130,13 @@ module.exports = { */ openFileLimit: 2048, + /* Cryptpad's socket server can be extended to respond to RPC calls + * you can configure it to respond to custom RPC calls if you like. + * provide the path to your RPC module here, or `false` if you would + * like to disable the RPC interface completely + */ + rpc: './rpc.js', + /* it is recommended that you serve cryptpad over https * the filepaths below are used to configure your certificates */ diff --git a/rpc.js b/rpc.js new file mode 100644 index 000000000..66973039b --- /dev/null +++ b/rpc.js @@ -0,0 +1,37 @@ +/* Use Nacl for checking signatures of messages + +*/ +var Nacl = require("tweetnacl"); + +var RPC = module.exports; + +var pin = function (ctx, cb) { }; +var unpin = function (ctx, cb) { }; +var getHash = function (ctx, cb) { }; +var getTotalSize = function (ctx, cb) { }; +var getFileSize = function (ctx, cb) { }; + +RPC.create = function (config, cb) { + // load pin-store... + + console.log('loading rpc module...'); + rpc = function (msg, respond) { + switch (msg[0]) { + case 'ECHO': + respond(void 0, msg); + break; + case 'PIN': + case 'UNPIN': + case 'GET_HASH': + case 'GET_TOTAL_SIZE': + case 'GET_FILE_SIZE': + + default: + respond('UNSUPPORTED_RPC_CALL', msg); + break; + } + }; + + cb(void 0, rpc); +}; + diff --git a/server.js b/server.js index 34f8cfedf..ea26778f9 100644 --- a/server.js +++ b/server.js @@ -117,13 +117,32 @@ httpServer.listen(config.httpPort,config.httpAddress,function(){ var wsConfig = { server: httpServer }; -if(!config.useExternalWebsocket) { - if (websocketPort !== config.httpPort) { - console.log("setting up a new websocket server"); - wsConfig = { port: websocketPort}; +var createSocketServer = function (err, rpc) { + if(!config.useExternalWebsocket) { + if (websocketPort !== config.httpPort) { + console.log("setting up a new websocket server"); + wsConfig = { port: websocketPort}; + } + var wsSrv = new WebSocketServer(wsConfig); + Storage.create(config, function (store) { + NetfluxSrv.run(store, wsSrv, config, rpc); + }); } - var wsSrv = new WebSocketServer(wsConfig); - Storage.create(config, function (store) { - NetfluxSrv.run(store, wsSrv, config); - }); -} +}; + +var loadRPC = function (cb) { + config.rpc = typeof(config.rpc) === 'undefined'? './rpc.js' : config.rpc; + + if (typeof(config.rpc) === 'string') { + // load pin store... + var Rpc = require(config.rpc); + Rpc.create(config, function (e, rpc) { + if (e) { throw e; } + cb(void 0, rpc); + }); + } else { + cb(); + } +}; + +loadRPC(createSocketServer); From b3cc8da315c0017ff78ef36136390e020b30bb26 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 10 Mar 2017 18:03:52 +0100 Subject: [PATCH 4/7] implement basic clientside RPC infrastructure and provide a basic example for testing it --- www/common/rpc.js | 88 ++++++++++++++++++++++++++++++------- www/examples/rpc/index.html | 13 ++++++ www/examples/rpc/inner.html | 8 ++++ www/examples/rpc/main.js | 35 +++++++++++++++ 4 files changed, 129 insertions(+), 15 deletions(-) create mode 100644 www/examples/rpc/index.html create mode 100644 www/examples/rpc/inner.html create mode 100644 www/examples/rpc/main.js diff --git a/www/common/rpc.js b/www/common/rpc.js index 3d15843cc..8cb7c6f8a 100644 --- a/www/common/rpc.js +++ b/www/common/rpc.js @@ -3,26 +3,73 @@ define([ '/bower_components/tweetnacl/nacl-fast.min.js', ], function (Encode) { + var MAX_LAG_BEFORE_TIMEOUT = 30000; - var getHistoryKeeperName = function (network) { - var wc = network.webChannels[0]; - if (!wc) { - throw new Error("ERROR: no joined webchannels so we can't get the history keeper name"); - } - if (!wc.history_keeper) { throw new Error("ERROR: no history keeper"); } - return wc.history_keeper; + var uid = function () { + return Number(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)) + .toString(32).replace(/\./g, ''); }; - var sendMsg = function (ctx, cb) { - var hkn = getHistoryKeeperName(ctx.network); + /* +types of messages: + pin -> hash + unpin -> hash + getHash -> hash + getTotalSize -> bytes + getFileSize -> bytes + + */ + +/* RPC communicates only with the history keeper + messages have the format: + [TYPE, txid, msg] +*/ + var sendMsg = function (ctx, type, msg, cb) { + var network = ctx.network; + var hkn = network.historyKeeper; + var txid = uid(); + + ctx.pending[txid] = cb; + + return network.sendto(hkn, JSON.stringify([txid, type, msg])); }; + var parse = function (msg) { + try { + return JSON.parse(msg); + } catch (e) { + return null; + } + }; + +/* Returning messages have the format: + [txid, {}] +*/ var onMsg = function (ctx, msg) { - console.log(msg); + var parsed = parse(msg); + + if (!parsed) { + // TODO handle error + console.log(msg); + return; + } + + var txid = parsed[0]; + var pending = ctx.pending[txid]; + var response = parsed.slice(1); + + if (typeof(pending) === 'function') { + if (response[0] === 'ERROR') { + return void pending(response[1]); + } + pending(void 0, response); + } else { + console.log("No callback provided"); + } }; var cookie = function (ctx, cb) { - // TODO + // TODO txid }; var signMsg = function (msg, secKey) { @@ -31,16 +78,27 @@ define([ var create = function (network, edPrivateKey) { if (!/[0-9a-f]{64}/.test(edPrivateKey)) { - throw new Error("private signing key is not valid"); + //throw new Error("private signing key is not valid"); } var ctx = { - privateKey: Encode.hexToUint8Array(edPrivateKey), + //privateKey: Encode.hexToUint8Array(edPrivateKey), seq: new Date().getTime(), - network: network + network: network, + timeouts: {}, // timeouts + pending: {}, // callbacks + }; + + var pin = function (channel, cb) { }; + + var send = function (type, msg, cb) { + return sendMsg(ctx, type, msg, cb); }; - network.on('message', function (msg) { onMsg(ctx, msg); }); + network.on('message', function (msg, sender) { + onMsg(ctx, msg); + }); return { cookie: function (cb) { cookie(ctx, cb); }, + send: send, }; }; diff --git a/www/examples/rpc/index.html b/www/examples/rpc/index.html new file mode 100644 index 000000000..e17a68143 --- /dev/null +++ b/www/examples/rpc/index.html @@ -0,0 +1,13 @@ + + + + CryptPad + + + + +
+ +
+ + diff --git a/www/examples/rpc/inner.html b/www/examples/rpc/inner.html new file mode 100644 index 000000000..9680685b7 --- /dev/null +++ b/www/examples/rpc/inner.html @@ -0,0 +1,8 @@ + + + + + + + +

PEWPEW diff --git a/www/examples/rpc/main.js b/www/examples/rpc/main.js new file mode 100644 index 000000000..528290d66 --- /dev/null +++ b/www/examples/rpc/main.js @@ -0,0 +1,35 @@ +require.config({ paths: { 'json.sortify': '/bower_components/json.sortify/dist/JSON.sortify' } }); +define([ + '/common/cryptpad-common.js', + '/common/rpc.js', + '/bower_components/jquery/dist/jquery.min.js', +], function (Cryptpad, RPC) { + var $ = window.jQuery; + var APP = window.APP = { + Cryptpad: Cryptpad, + }; + + $(function () { + Cryptpad.ready(function (err, env) { + var network = Cryptpad.getNetwork(); + var rpc = RPC.create(network); // TODO signing key + + var payload = { + a: Math.floor(Math.random() * 1000), + b: 7, + }; + + // console.log(payload); + rpc.send('ECHO', payload, function (e, msg) { + if (e) { return void console.error(e); } + console.log(msg); + }); + + // test a non-existent RPC call + rpc.send('PEWPEW', ['pew'], function (e, msg) { + if (e) { return void console.error(e); } + console.log(msg); + }); + }); + }); +}); From d9996cc8743bb303fd9db6e9230183bd381a67b2 Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 13 Mar 2017 10:56:08 +0100 Subject: [PATCH 5/7] current work for pinning --- www/common/pinpad.js | 57 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 www/common/pinpad.js diff --git a/www/common/pinpad.js b/www/common/pinpad.js new file mode 100644 index 000000000..b3285b8f8 --- /dev/null +++ b/www/common/pinpad.js @@ -0,0 +1,57 @@ +define([ + '/common/cryptpad-common.js', + '/common/rpc.js', + + '/bower_components/tweetnacl/nacl-fast.min.js' +], function (Cryptpad, Rpc) { + + var Nacl = window.nacl; + + var deduplicate = function (array) { + var a = array.slice(); + for(var i=0; i Date: Mon, 13 Mar 2017 18:16:17 +0100 Subject: [PATCH 6/7] add a todo --- NetfluxWebsocketSrv.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetfluxWebsocketSrv.js b/NetfluxWebsocketSrv.js index bd6b6da87..0108659fb 100644 --- a/NetfluxWebsocketSrv.js +++ b/NetfluxWebsocketSrv.js @@ -216,7 +216,7 @@ const handleMessage = function (ctx, user, msg) { // slice off the sequence number and pass in the rest of the message ctx.rpc(rpc_call, function (err, output) { if (err) { - console.error('[' + err + ']', output); + console.error('[' + err + ']', output); // TODO make this disableable sendMsg(ctx, user, [seq, 'ACK']); sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify([parsed[0], 'ERROR', err])]); return From df6298eeb68047a56f5ae968c9bc5ba948cc9c5c Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 13 Mar 2017 18:18:17 +0100 Subject: [PATCH 7/7] apply styles to confirm buttons if provided --- www/common/cryptpad-common.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 8f67a542b..5e9d0bd6f 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -1521,6 +1521,11 @@ define([ cb(false); stopListening(keyHandler); }); + + window.setTimeout(function () { + if (opt.okClass) { findOKButton().addClass(opt.okClass); } + if (opt.cancelClass) { findCancelButton().addClass(opt.cancelClass); } + }, 0); }; common.log = function (msg) {