diff --git a/rpc.js b/rpc.js index 66c8af873..3c4e3507b 100644 --- a/rpc.js +++ b/rpc.js @@ -1,6 +1,4 @@ -/* Use Nacl for checking signatures of messages - -*/ +/* Use Nacl for checking signatures of messages */ var Nacl = require("tweetnacl"); var RPC = module.exports; @@ -15,43 +13,124 @@ var isValidChannel = function (chan) { return /^[a-fA-F0-9]/.test(chan); }; -var checkSignature = function (signedMsg, publicKey) { - if (!(signedMsg && publicKey)) { return null; } +var makeCookie = function (seq) { + return [ + Math.floor(new Date() / (1000*60*60*24)), + process.pid, // jshint ignore:line + seq + ].join('|'); +}; + +var parseCookie = function (cookie) { + if (!(cookie && cookie.split)) { return null; } + + var parts = cookie.split('|'); + if (parts.length !== 3) { return null; } + + var c = {}; + c.time = new Date(parts[0]); + c.pid = parts[1]; + c.seq = parts[2]; + return c; +}; + +var isValidCookie = function (ctx, cookie) { + var now = +new Date(); + if (now - cookie.time > 300000) { // 5 minutes + return false; + } + + // different process. try harder + if (process.pid !== cookie.pid) { // jshint ignore:line + return false; + } + + //if (cookie.seq !== + + return true; +}; + +var checkSignature = function (signedMsg, signature, publicKey) { + if (!(signedMsg && publicKey)) { return false; } var signedBuffer; var pubBuffer; + var signatureBuffer; + try { - signedBuffer = Nacl.util.decodeBase64(signedMsg); - pubBuffer = Nacl.util.decodeBase64(publicKey); + signedBuffer = Nacl.util.decodeUTF8(signedMsg); } catch (e) { + console.log('invalid signedBuffer'); + console.log(signedMsg); return null; } - var opened = Nacl.sign.open(signedBuffer, pubBuffer); + try { + pubBuffer = Nacl.util.decodeBase64(publicKey); + } catch (e) { + return false; + } + + try { + signatureBuffer = Nacl.util.decodeBase64(signature); + } catch (e) { + return false; + } + + if (pubBuffer.length !== 32) { + console.log('public key length: ' + pubBuffer.length); + console.log(publicKey); + return false; + } - if (opened) { - var decoded = Nacl.util.encodeUTF8(opened); - try { - return JSON.parse(decoded); - } catch (e) { } // fall through to return + if (signatureBuffer.length !== 64) { + return false; } - return null; + + return Nacl.sign.detached.verify(signedBuffer, signatureBuffer, pubBuffer); }; RPC.create = function (config, cb) { // load pin-store... console.log('loading rpc module...'); - var rpc = function (ctx, args, respond) { - if (args.length < 2) { + + var Cookies = {}; + + + + var rpc = function (ctx, data, respond) { + if (!data.length) { return void respond("INSUFFICIENT_ARGS"); + } else if (data.length !== 1) { + console.log(data.length); } - var signed = args[0]; - var publicKey = args[1]; + var msg = data[0].slice(0); + var signature = msg.shift(); + var publicKey = msg.shift(); + var cookie = parseCookie(msg.shift()); + + if (!cookie) { + // no cookie is fine if the RPC is to get a cookie + if (msg[0] !== 'COOKIE') { + return void respond('NO_COOKIE'); + } + } else if (!isValidCookie(cookie)) { // is it a valid cookie? + return void respond('INVALID_COOKIE'); + } + + var serialized = JSON.stringify(msg); + + if (!(serialized && publicKey)) { + return void respond('INVALID_MESSAGE_OR_PUBLIC_KEY'); + } + + if (checkSignature(serialized, signature, publicKey) !== true) { + return void respond("INVALID_SIGNATURE_OR_PUBLIC_KEY"); + } - var msg = checkSignature(signed, publicKey); - if (!msg) { + if (!msg.length) { return void respond("INVALID_SIGNATURE_OR_PUBLIC_KEY"); } @@ -60,6 +139,8 @@ RPC.create = function (config, cb) { } switch (msg[0]) { + case 'COOKIE': + return void respond(void 0, makeCookie()); case 'ECHO': return void respond(void 0, msg); case 'RESET': diff --git a/www/common/rpc.js b/www/common/rpc.js index 25248b147..5c176963a 100644 --- a/www/common/rpc.js +++ b/www/common/rpc.js @@ -1,8 +1,6 @@ define([ - '/common/encode.js', - '/bower_components/tweetnacl/nacl-fast.min.js', -], function (Encode) { +], function () { var MAX_LAG_BEFORE_TIMEOUT = 30000; var Nacl = window.nacl; @@ -11,34 +9,27 @@ define([ .toString(32).replace(/\./g, ''); }; - var signMsg = function (type, msg, signKey) { - var toSign = JSON.stringify([type, msg]); - var buffer = Nacl.util.decodeUTF8(toSign); - return Nacl.util.encodeBase64(Nacl.sign(buffer, signKey)); + var signMsg = function (data, signKey) { + var buffer = Nacl.util.decodeUTF8(JSON.stringify(data)); + return Nacl.util.encodeBase64(Nacl.sign.detached(buffer, signKey)); }; - /* +/* 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, signed, id, cb) { + + var sendMsg = function (ctx, data, cb) { var network = ctx.network; var hkn = network.historyKeeper; var txid = uid(); ctx.pending[txid] = cb; - - return network.sendto(hkn, JSON.stringify([txid, signed, id])); + return network.sendto(hkn, JSON.stringify([txid, data])); }; var parse = function (msg) { @@ -49,20 +40,20 @@ types of messages: } }; -/* Returning messages have the format: - [txid, {}] -*/ var onMsg = function (ctx, msg) { var parsed = parse(msg); if (!parsed) { - // TODO handle error - console.log(msg); - return; + return void console.error(new Error('could not parse message: %s', msg)); } var txid = parsed[0]; var pending = ctx.pending[txid]; + + if (!(parsed && parsed.slice)) { + return void console.error('MALFORMED_RPC_RESPONSE'); + } + var response = parsed.slice(1); if (typeof(pending) === 'function') { @@ -76,39 +67,57 @@ types of messages: }; var create = function (network, edPrivateKey, edPublicKey) { - var signKey = Nacl.util.decodeBase64(edPrivateKey); + var signKey; try { + signKey = Nacl.util.decodeBase64(edPrivateKey); if (signKey.length !== 64) { throw new Error('private key did not match expected length of 64'); } - } catch (err) { - throw new Error("private signing key is not valid"); - } + } catch (err) { throw err; } - // TODO validate public key as well + var pubBuffer; + try { + pubBuffer = Nacl.util.decodeBase64(edPublicKey); + if (pubBuffer.length !== 32) { + throw new Error('expected public key to be 32 uint'); + } + } catch (err) { throw err; } var ctx = { - //privateKey: Encode.hexToUint8Array(edPrivateKey), seq: new Date().getTime(), network: network, timeouts: {}, // timeouts pending: {}, // callbacks + cookie: null, }; - var pin = function (channel, cb) { }; - var send = function (type, msg, cb) { // construct a signed message... - var signed = signMsg(type, msg, signKey); + var data = [type, msg]; + var sig = signMsg(data, signKey); - return sendMsg(ctx, type, signed, edPublicKey, cb); + data.unshift(ctx.cookie); // + data.unshift(edPublicKey); + data.unshift(sig); + + // [sig, edPublicKey, cookie, type, msg] + return sendMsg(ctx, data, cb); + }; + + var getCookie = function (cb) { + send('COOKIE', "", function (e, msg) { + console.log('cookie message', e, msg); + cb(e, msg); + }); }; + network.on('message', function (msg, sender) { onMsg(ctx, msg); }); return { send: send, + ready: getCookie, }; }; diff --git a/www/examples/rpc/main.js b/www/examples/rpc/main.js index ee01c7897..5802885c8 100644 --- a/www/examples/rpc/main.js +++ b/www/examples/rpc/main.js @@ -24,65 +24,72 @@ define([ b: 7, }; - // console.log(payload); - rpc.send('ECHO', payload, function (e, msg) { - if (e) { return void console.error(e); } - console.log(msg); - }); + rpc.ready(function () { - // test a non-existent RPC call - rpc.send('PEWPEW', ['pew'], function (e, msg) { - if (e) { - if (e === 'UNSUPPORTED_RPC_CALL') { return; } - return void console.error(e); - } - console.log(msg); - }); + // console.log(payload); + rpc.send('ECHO', payload, function (e, msg) { + if (e) { return void console.error(e); } + console.log(msg); + }); - var list = Cryptpad.getUserChannelList(); - if (list.length) { - rpc.send('GET_FILE_SIZE', list[0], function (e, msg) { + // test a non-existent RPC call + rpc.send('PEWPEW', ['pew'], function (e, msg) { if (e) { + if (e === 'UNSUPPORTED_RPC_CALL') { return; } return void console.error(e); } console.log(msg); }); - } - rpc.send('GET_FILE_SIZE', 'pewpew', function (e, msg) { - if (e) { - if (e === 'INVALID_CHAN') { return; } - return void console.error(e); - } - console.log(msg); - }); - - rpc.send('GET_FILE_SIZE', '26f014b2ab959418605ea37a6785f317', function (e, msg) { - if (e) { - if (e === 'ENOENT') { return; } - return void console.error(e); + var list = Cryptpad.getUserChannelList(); + if (list.length) { + rpc.send('GET_FILE_SIZE', list[0], function (e, msg) { + if (e) { + return void console.error(e); + } + console.log(msg); + }); } - console.error("EXPECTED ENOENT"); - console.log(msg); - }); - - (function () { - var bytes = 0; - list.forEach(function (chan) { - rpc.send('GET_FILE_SIZE', chan, function (e, msg) { - if (e) { return void console.error(e); } - if (msg && msg[0] && typeof(msg[0]) === 'number') { - bytes += msg[0]; - console.log(bytes); - } else { - console.log(msg); + rpc.send('GET_FILE_SIZE', 'pewpew', function (e, msg) { + if (e) { + if (e === 'INVALID_CHAN') { return; } + return void console.error(e); + } + console.log(msg); + }); - } - }); + rpc.send('GET_FILE_SIZE', '26f014b2ab959418605ea37a6785f317', function (e, msg) { + if (e) { + if (e === 'ENOENT') { return; } + return void console.error(e); + } + console.error("EXPECTED ENOENT"); + console.log(msg); }); - }()); + if (false) { + (function () { + var bytes = 0; + list.forEach(function (chan) { + rpc.send('GET_FILE_SIZE', chan, function (e, msg) { + if (e) { + if (e === 'ENOENT') { + return void console.log(e, chan); + } + return void console.error(e); + } + if (msg && msg[0] && typeof(msg[0]) === 'number') { + bytes += msg[0]; + console.log(bytes); + } else { + console.log(msg); + } + }); + }); + }()); + } + }); }); }); });