diff --git a/lib/client/index.js b/lib/client/index.js index fb5753058..8faf8f4a2 100644 --- a/lib/client/index.js +++ b/lib/client/index.js @@ -3,19 +3,9 @@ var WebSocket = require("ws"); // jshint ignore:line var nThen = require("nthen"); var Util = require("../../www/common/common-util"); -var Rpc = require("../../www/common/rpc"); var Nacl = require("tweetnacl"); -var makeKeys = function () { - var keys = Nacl.sign.keyPair.fromSeed(Nacl.randomBytes(Nacl.sign.seedLength)); - return { - secret: Nacl.util.encodeBase64(keys.secretKey), - public: Nacl.util.encodeBase64(keys.publicKey), - }; -}; - - var Client = module.exports; var createNetwork = Client.createNetwork = function (url, cb) { @@ -30,8 +20,7 @@ var createNetwork = Client.createNetwork = function (url, cb) { .on('error', function (err) { CB(err); }) - .on('close', function (err) { - console.log("CLOSE_ERROR", err); + .on('close', function (/* err */) { delete info.websocket; }); return info.websocket; @@ -102,21 +91,6 @@ Client.create = function (config, cb) { w.abort(); CB(err); }); - }).nThen(function (w) { - // connect to the anonRpc - Rpc.createAnonymous(config.network, w(function (err, rpc) { - if (err) { - return void CB('ANON_RPC_CONNECT_ERR'); - } - client.anonRpc = rpc; - })); - var keys = makeKeys(); - Rpc.create(config.network, keys.secret, keys.public, w(function (err, rpc) { - if (err) { - return void CB('RPC_CONNECT_ERR'); - } - client.rpc = rpc; - })); }).nThen(function () { CB(void 0, client); }); diff --git a/package-lock.json b/package-lock.json index 4c32aff68..0f19280a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -98,10 +98,24 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" }, + "chainpad-crypto": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/chainpad-crypto/-/chainpad-crypto-0.2.2.tgz", + "integrity": "sha512-7MJ7qPz/C4sJPsDhPMjdSRmliOCPoRO0XM1vUomcgXA6HINlW+if9AAt/H4q154nYhZ/b57njgC6cWgd/RDidg==", + "requires": { + "tweetnacl": "git://github.com/dchest/tweetnacl-js.git#v0.12.2" + }, + "dependencies": { + "tweetnacl": { + "version": "git://github.com/dchest/tweetnacl-js.git#8a21381d696acdc4e99c9f706f1ad23285795f79", + "from": "git://github.com/dchest/tweetnacl-js.git#v0.12.2" + } + } + }, "chainpad-server": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/chainpad-server/-/chainpad-server-3.0.3.tgz", - "integrity": "sha512-NRfV7FFBEYy4ZVX7h0P5znu55X8v5K4iGWeMGihkfWZLKu70GmCPUTwpBCP79dUvnCToKEa4/e8aoSPcvZC8pA==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/chainpad-server/-/chainpad-server-3.0.5.tgz", + "integrity": "sha512-USKOMSHsNjnme81Qy3nQ+ji9eCkBPokYH4T82LVHAI0aayTSCXcTPUDLVGDBCRqe8NsXU4io1WPXn1KiZwB8fA==", "requires": { "nthen": "^0.1.8", "pull-stream": "^3.6.9", diff --git a/package.json b/package.json index 562035f45..f14fda1aa 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "url": "git://github.com/xwiki-labs/cryptpad.git" }, "dependencies": { - "chainpad-server": "~3.0.2", + "chainpad-crypto": "^0.2.2", + "chainpad-server": "^3.0.5", "express": "~4.16.0", "fs-extra": "^7.0.0", "get-folder-size": "^2.0.1", diff --git a/scripts/tests/test-rpc.js b/scripts/tests/test-rpc.js index 2c355af3b..54393efbb 100644 --- a/scripts/tests/test-rpc.js +++ b/scripts/tests/test-rpc.js @@ -1,7 +1,38 @@ +/* globals process */ + var Client = require("../../lib/client/"); var Mailbox = require("../../www/bower_components/chainpad-crypto").Mailbox; var Nacl = require("tweetnacl"); var nThen = require("nthen"); +var Rpc = require("../../www/common/rpc"); +var Hash = require("../../www/common/common-hash"); +var CpNetflux = require("../../www/bower_components/chainpad-netflux"); + +var createMailbox = function (config, cb) { + var webchannel; + + CpNetflux.start({ + network: config.network, + channel: config.channel, + crypto: config.crypto, + owners: [ config.edPublic ], + + noChainPad: true, + onConnect: function (wc /*, sendMessage */) { + webchannel = wc; + }, + onMessage: function (/* msg, user, vKey, isCp, hash, author */) { + + }, + onReady: function () { + cb(void 0, webchannel); + }, + }); +}; + +process.on('unhandledRejection', function (err) { + console.error(err); +}); var makeCurveKeys = function () { var pair = Nacl.box.keyPair(); @@ -11,14 +42,166 @@ var makeCurveKeys = function () { }; }; -Client.create(function (err, client) { - if (err) { return void console.error(err); } +var makeEdKeys = function () { + var keys = Nacl.sign.keyPair.fromSeed(Nacl.randomBytes(Nacl.sign.seedLength)); + return { + edPrivate: Nacl.util.encodeBase64(keys.secretKey), + edPublic: Nacl.util.encodeBase64(keys.publicKey), + }; +}; + +var EMPTY_ARRAY_HASH = 'slspTLTetp6gCkw88xE5BIAbYBXllWvQGahXCx/h1gQOlE7zze4W0KRlA8puZZol8hz5zt3BPzUqPJgTjBXWrw=='; + +var createUser = function (config, cb) { + // config should contain keys for a team rpc (ed) + // teamEdKeys + + var user; + nThen(function (w) { + Client.create(w(function (err, client) { + if (err) { + w.abort(); + return void cb(err); + } + user = client; + })); + }).nThen(function (w) { + // make all the parameters you'll need + + var network = user.network = user.config.network; + user.edKeys = makeEdKeys(); + + user.curveKeys = makeCurveKeys(); + user.mailbox = Mailbox.createEncryptor(user.curveKeys); + user.mailboxChannel = Hash.createChannelId(); + + // create an anon rpc for alice + Rpc.createAnonymous(network, w(function (err, rpc) { + if (err) { + w.abort(); + user.shutdown(); + return void console.error('ANON_RPC_CONNECT_ERR'); + } + user.anonRpc = rpc; + })); + + Rpc.create(network, user.edKeys.edPrivate, user.edKeys.edPublic, w(function (err, rpc) { + if (err) { + w.abort(); + user.shutdown(); + console.error(err); + return console.log('RPC_CONNECT_ERR'); + } + user.rpc = rpc; + })); + + Rpc.create(network, config.teamEdKeys.edPrivate, config.teamEdKeys.edPublic, w(function (err, rpc) { + if (err) { + w.abort(); + user.shutdown(); + return console.log('RPC_CONNECT_ERR'); + } + user.team_rpc = rpc; + })); + }).nThen(function (w) { + // some basic sanity checks... + user.rpc.send('GET_HASH', user.edKeys.edPublic, w(function (err, hash) { + if (err) { + w.abort(); + return void cb(err); + } + + if (!hash || hash[0] !== EMPTY_ARRAY_HASH) { + console.error("EXPECTED EMPTY ARRAY HASH"); + process.exit(1); + } + })); + }).nThen(function (w) { + // create and subscribe to your mailbox + createMailbox({ + network: user.network, + channel: user.mailboxChannel, + crypto: user.mailbox, + edPublic: user.edKeys.edPublic, + }, w(function (err, wc) { + if (err) { + w.abort(); + console.error("Mailbox creation error"); + process.exit(1); + } + wc.leave(); + })); + }).nThen(function (w) { + // confirm that you own your mailbox + user.anonRpc.send("GET_METADATA", user.mailboxChannel, w(function (err, data) { + if (err) { + w.abort(); + return void cb(err); + } + try { + if (data[0].owners[0] !== user.edKeys.edPublic) { + throw new Error("INCORRECT MAILBOX OWNERSHIP METADATA"); + } + } catch (err2) { + w.abort(); + return void cb(err2); + } + })); + }).nThen(function () { + + }).nThen(function () { + + cb(void 0, user); + }); +}; + +var alice, bob; + +nThen(function (w) { + var sharedConfig = { + teamEdKeys: makeEdKeys(), + }; + + createUser(sharedConfig, w(function (err, _alice) { + if (err) { + w.abort(); + return void console.log(err); + } + alice = _alice; + })); + createUser(sharedConfig, w(function (err, _bob) { + if (err) { + w.abort(); + return void console.log(err); + } + bob = _bob; + })); +}).nThen(function (w) { + // Alice sends a message to Bob's mailbox + + + var message = alice.mailbox.encrypt(JSON.stringify({ + type: "CHEESE", + author: alice.curveKeys.curvePublic, + content: { + text: "CAMEMBERT", + } + }), bob.curveKeys.curvePublic); + + alice.anonRpc.send('WRITE_PRIVATE_MESSAGE', [bob.mailboxChannel, message], w(function (err, response) { + if (err) { + return void console.error(err); + } + + // XXX validate that the write was actually successful by checking its size + + response = response; + // shutdown doesn't work, so we need to do this instead + })); +}).nThen(function () { + + nThen(function () { - nThen(function () { // BASIC KEY MANAGEMENT - // generate keys with login - // signing keys - // curve keys - // drive }).nThen(function () { // make a drive // pin it @@ -46,29 +229,9 @@ Client.create(function (err, client) { }).nThen(function () { }); - - var channel = "d34ebe83931382fcad9fe2e2d0e2cb5f"; // channel - var recipient = "e8jvf36S3chzkkcaMrLSW7PPrz7VDp85lIFNI26dTmw="; // curvePublic - - // curve keys - var keys = makeCurveKeys(); - var cryptor = Mailbox.createEncryptor(keys); - - var message = cryptor.encrypt(JSON.stringify({ - type: "CHEESE", - author: keys.curvePublic, - content: { - text: "CAMEMBERT", - } - }), recipient); - - client.anonRpc.send('WRITE_PRIVATE_MESSAGE', [channel, message], function (err, response) { - if (err) { - return void console.error(err); - } - - response = response; - // shutdown doesn't work, so we need to do this instead - client.shutdown(); - }); +}).nThen(function () { + alice.shutdown(); + bob.shutdown(); }); + + diff --git a/www/common/common-hash.js b/www/common/common-hash.js index 199814577..5c1c1a81d 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -1,4 +1,4 @@ -(function () { +(function (window) { var factory = function (Util, Crypto, Nacl) { var Hash = window.CryptPad_Hash = {}; @@ -547,4 +547,4 @@ Version 1 } else { // unsupported initialization } -}()); +}(typeof(window) !== 'undefined'? window : {})); diff --git a/www/common/rpc.js b/www/common/rpc.js index e3c6a21a4..75ea7a0de 100644 --- a/www/common/rpc.js +++ b/www/common/rpc.js @@ -19,26 +19,21 @@ var factory = function (Util, Nacl) { // and finally sends it off to the historyKeeper, which delegates its // processing to the RPC submodule var sendMsg = function (ctx, data, cb) { - // enforce async behaviour - setTimeout(function () { - if (typeof(cb) !== 'function') { - return console.error('expected callback'); - } + if (typeof(cb) !== 'function') { throw new Error('expected callback'); } - var network = ctx.network; - var hkn = network.historyKeeper; - if (typeof(hkn) !== 'string') { return void cb("NO_HISTORY_KEEPER"); } + var network = ctx.network; + var hkn = network.historyKeeper; + if (typeof(hkn) !== 'string') { return void cb("NO_HISTORY_KEEPER"); } - var txid = uid(); + var txid = uid(); - var pending = ctx.pending[txid] = function (err, response) { - cb(err, response); - }; - pending.data = data; - pending.called = 0; + var pending = ctx.pending[txid] = function (err, response) { + cb(err, response); + }; + pending.data = data; + pending.called = 0; - return network.sendto(hkn, JSON.stringify([txid, data])); - }); + return network.sendto(hkn, JSON.stringify([txid, data])); }; var matchesAnon = function (ctx, txid) { @@ -190,9 +185,11 @@ var factory = function (Util, Nacl) { connected: true, }; - var send = ctx.send = function (type, msg, cb) { + var send = ctx.send = function (type, msg, _cb) { + var cb = Util.mkAsync(_cb); + if (!ctx.connected && type !== 'COOKIE') { - return void Util.mkAsync(cb)("DISCONNECTED"); + return void cb("DISCONNECTED"); } // construct a signed message... @@ -233,12 +230,9 @@ var factory = function (Util, Nacl) { } }; - send.unauthenticated = function (type, msg, cb) { - if (!ctx.connected) { - return void setTimeout(function () { - cb('DISCONNECTED'); - }); - } + send.unauthenticated = function (type, msg, _cb) { + var cb = Util.mkAsync(_cb); + if (!ctx.connected) { return void cb('DISCONNECTED'); } // construct an unsigned message var data = [null, keys.publicKeyString, null, type, msg]; @@ -284,8 +278,10 @@ var factory = function (Util, Nacl) { return initAuthenticatedRpc(networkContext, keys); }; - var create = function (network, edPrivateKey, edPublicKey, cb) { - if (typeof(cb) !== 'function') { throw new Error("expected callback"); } + var create = function (network, edPrivateKey, edPublicKey, _cb) { + if (typeof(_cb) !== 'function') { throw new Error("expected callback"); } + + var cb = Util.mkAsync(_cb); var signKey; @@ -335,12 +331,9 @@ var factory = function (Util, Nacl) { // any particular network will only ever need one anonymous rpc networkContext.anon = ctx; - ctx.send = function (type, msg, cb) { - if (!ctx.connected) { - return void setTimeout(function () { - cb('DISCONNECTED'); - }); - } + ctx.send = function (type, msg, _cb) { + var cb = Util.mkAsync(_cb); + if (!ctx.connected) { return void cb('DISCONNECTED'); } // construct an unsigned message... var data = [type, msg]; @@ -382,7 +375,10 @@ var factory = function (Util, Nacl) { return networkContext.anon || initAnonRpc(networkContext); }; - var createAnonymous = function (network, cb) { + var createAnonymous = function (network, _cb) { + // enforce asynchrony + var cb = Util.mkAsync(_cb); + if (typeof(cb) !== 'function') { throw new Error("expected callback"); } if (!network) { return void cb('NO_NETWORK'); }