From aa9d4e950bb095b0d8a7d919df5030641254fb00 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 6 Apr 2023 18:53:41 +0200 Subject: [PATCH] Revocation: add/update/revoke access (clientside) --- lib/historyKeeper.js | 2 +- www/common/inner/share.js | 110 +++++++++--- www/common/outer/async-store.js | 1 + www/common/outer/mailbox.js | 4 + www/common/outer/revocation.js | 272 ++++++++++++++++++++++++----- www/common/revocable.js | 75 ++++++++ www/common/sframe-app-framework.js | 3 + www/common/sframe-common-outer.js | 152 +++------------- 8 files changed, 430 insertions(+), 189 deletions(-) diff --git a/lib/historyKeeper.js b/lib/historyKeeper.js index fda225ac3..edf3fd401 100644 --- a/lib/historyKeeper.js +++ b/lib/historyKeeper.js @@ -72,7 +72,7 @@ module.exports.create = function (Env, cb) { if (metadata.access) { if (!signature) { return void ('ERESTRICTED'); } let c = Revocable.checkRead(channelName, userId, signature, metadata); - return void cb(!c ? 'EFORBIDDEN' : undefined); + return void cb(!c ? 'ERESTRICTED' : undefined); } if (!metadata.restricted) { diff --git a/www/common/inner/share.js b/www/common/inner/share.js index d336c05f4..994bde709 100644 --- a/www/common/inner/share.js +++ b/www/common/inner/share.js @@ -352,6 +352,7 @@ define([ var revocation = common.makeUniversal('revocation'); var updateAccess = function () {}; + var addAccess = function () {}; var TYPES = { user: { icon: '.fa.fa-user', order: 1 }, @@ -390,9 +391,16 @@ define([ buttonCls: 'btn btn-secondary' }; var select = UIElements.createDropdown(dropdownConfig); - select.setValue(value); + select.setValue(current); return select; }; + var getRights = function (dd, $d) { + var r = dd.getValue() || 'r'; + var d = Util.isChecked($d); + var rights = r === 'm' ? 'rwm' : (r === 'w' ? 'rw' : 'r'); + if (d) { rights += 'd'; } + return rights; + }; var renderAccess = function (edPublic, accessData, editable, maxR, renderedAs) { var type = accessData.notes.type var icon = TYPES[type].icon; @@ -407,32 +415,29 @@ define([ h('span', 'REVOKE') // XXX ]); if (!editable) { $(revoke).attr('disabled', 'disabled'); } - - var getRights = function () {}; + else { + $(revoke).click(function () { + UI.confirm(Messages.areYouSure, function (yes) { // XXX REVOCATION message + if (!yes) { return; } + updateAccess(edPublic, false, renderedAs.key); + }); + }); + } var $d = $(canDestroy).find('input'); if (editable && maxR === 'm') { $d.on('change', function () { - updateAccess(edPublic, getRights(), renderedAs.key); + updateAccess(edPublic, getRights(dd, $d), renderedAs.key); }); } var dd = makeDD(rights, editable, maxR); if (dd.onChange) { dd.onChange.reg(function () { - console.warn('ici'); - console.error(getRights()); - updateAccess(edPublic, getRights(), renderedAs.key); + updateAccess(edPublic, getRights(dd, $d), renderedAs.key); }); } - getRights = function () { - var r = dd.getValue() || 'r'; - var d = Util.isChecked($d); - var rights = r === 'm' ? 'rwm' : (r === 'w' ? 'rw' : 'r'); - if (d) { rights += 'd'; } - return rights; - }; return h('div.cp-share-access', { order: TYPES[type].order @@ -446,18 +451,72 @@ define([ ]); }; + var addAccessButton = function (maxR, renderedAs) { + // new form + var input = h('input', {placeholder:'Note'}); // XXX + var dd = makeDD('r', true, maxR); + var canDestroy = UI.createCheckbox('cp-share-can-destroy', h('i.fa.fa-trash'), false, {}); + var $d = $(canDestroy).find('input'); + var saveBtn = h('button.btn.btn-primary', [ + h('i.fa.fa-floppy-o'), + h('span', 'SAVE') // XXX + ]); + var cancelBtn = h('button.btn.btn-cancel', [ + h('i.fa.fa-times') + ]); + + var temp = h('div.cp-share-access', {order:100, style: 'display:none;'}, [ + h('i.fa.fa-plus'), + input, + dd[0], + canDestroy, + saveBtn, + cancelBtn + ]); + var $temp = $(temp); + + // show form btn + var button = h('button.btn.btn-primary', [ + h('i.fa.fa-plus'), + h('span', 'ADD') // XXX + ]); + var $b = $(button); + + $(saveBtn).click(function () { + var access = getRights(dd, $d); + var note = { + type: 'link', + note: $(input).val() + }; + if (false) { note.edPublic = "edpublic"; } // XXX user access + addAccess(access, note, renderedAs.key); + }); + $(cancelBtn).click(function () { + $b.show(); + $temp.hide(); + }); + $b.click(function () { + $b.hide(); + $temp.css('display', 'flex'); + + }); + + $content.append(temp); + $(content).append(h('div', button)); + }; var renderAll = function (obj, renderedAs) { $content.empty(); var list = obj.list; + var myAccess = list[renderedAs.key]; + var maxRights = myAccess.rights.includes('m') ? 'm' : + (myAccess.rights.includes('w') ? 'w' : 'r'); Object.keys(list || {}).forEach(function (ed) { var editable = renderedAs.moderator || renderedAs.key === list[ed].from; - var myAccess = list[renderedAs.key]; - var maxRights = myAccess.rights.includes('m') ? 'm' : - (myAccess.rights.includes('w') ? 'w' : 'r'); var a = renderAccess(ed, list[ed], editable, maxRights, renderedAs); $content.append(a); }); + addAccessButton(maxRights, renderedAs); }; var renderAs = function (obj) { $viewAs.empty(); @@ -491,6 +550,18 @@ define([ renderAll(obj, myKeys[0]); }; + addAccess = function (rights, note, updateAs) { + revocation.execCommand('ADD_ACCESS', { + type: priv.app, + channel: channel, + rights: rights, + note: note, + from: updateAs + }, function (obj) { + console.warn(obj); + // XXX refresh view + }); + }; updateAccess = function (user, rights, updateAs) { var access = !rights ? false : { rights: rights @@ -503,7 +574,7 @@ define([ }, from: updateAs }, function () { - + // XXX refresh view }); }; @@ -520,9 +591,6 @@ define([ renderAs(obj); }); - - var b = h('button', 'button'); - cb(void 0, { content: content, buttons: [{ diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 9dec3ac64..05e22f422 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -3138,6 +3138,7 @@ define([ // initialize the chat (messenger) and the cursor modules. loadUniversal(Cursor, 'cursor', function () {}); loadUniversal(Messenger, 'messenger', function () {}); + loadUniversal(Revocation, 'revocation', waitFor, clientId); store.messenger = store.modules['messenger']; // And now we're ready diff --git a/www/common/outer/mailbox.js b/www/common/outer/mailbox.js index 0da75fd96..e1c48a932 100644 --- a/www/common/outer/mailbox.js +++ b/www/common/outer/mailbox.js @@ -659,6 +659,10 @@ proxy.mailboxes = { sendTo(ctx, type, msg, user, cb); }; + mailbox.sendAs = function (type, msg, to, as, cb) { + Mailbox.sendAs(ctx, as, type, msg, to, cb); + }; + mailbox.removeClient = function (clientId) { removeClient(ctx, clientId); }; diff --git a/www/common/outer/revocation.js b/www/common/outer/revocation.js index b67eff383..02b375b42 100644 --- a/www/common/outer/revocation.js +++ b/www/common/outer/revocation.js @@ -53,10 +53,32 @@ id: { return pw; }; - var getPadMetadata = function (ctx, channel, cb) { + var getPadMetadata = function (ctx, channel, cb, keys) { ctx.Store.getPadMetadata(null, { channel: channel }, function (md) { + if (!keys || !md || md.error) { return void cb(md); } + + var secret = Hash.getRevocableSecret({ + channel: '', + viewerSeedStr: keys.doc.viewer, + editorSeedStr: keys.doc.editor, + }, keys.password); + var crypto = Crypto.createEncryptor(secret.keys); + + Object.keys(md.access || {}).forEach(function (ed) { + var a = md.access[ed]; + if (a.mailbox) { + try { + a.mailbox = crypto.decrypt(a.mailbox, true, true); + } catch (e) { console.error(e); } + } + if (a.notes) { // XXX use moderator keys! + try { + a.notes = JSON.parse(crypto.decrypt(a.notes, true, true)); + } catch (e) { console.error(e); } + } + }); cb(md); }); }; @@ -136,7 +158,9 @@ id: { return content && content.doc && content.doc.channel; }; var getMailboxParser = function (ctx, onNewKeys) { - var data = {}; + var data = { + doc: {} + }; var channel; var MAILBOX_COMMANDS = { @@ -154,11 +178,23 @@ id: { if (!i || !data.doc) { return; } // Trust that all the keys you need are there // Make sure it was sent by the same moderator than in the moderators log - isValidRotateMessage(ctx, log, msg, channel, waitFor(function (valid, password) { + isValidRotateMessage(ctx, log, msg.content, channel, waitFor(function (valid, password) { if (!valid) { return; } data.doc = msg.content data.password = password; })); + }, + UPDATE: function (msg, log, channel, i, waitFor) { + if (!data || !data.doc) { return; } + var doc = data.doc; + var content = msg.content; + if (content.uid !== doc.uid) { return; } // doesn't match our viewer key version + if (content.editor && !doc.editor) { + doc.editor = content.editor; + } + if (content.moderator && !doc.moderator) { + doc.moderator = content.moderator; + } } }; @@ -190,9 +226,15 @@ id: { }).nThen; }); + nn(waitFor(function () { data.channel = channel; - onNewKeys(Util.clone(data)); + var clone = Util.clone(data); + var myAccess = md.access && md.access[data.edPublic]; + //if (!myAccess) { return void onNewKeys(false); } + if (!Revocable.isModerator(myAccess)) { delete data.doc.moderator; } + if (!Revocable.isEditor(myAccess)) { delete data.doc.editor; } + onNewKeys(clone); // XXX once messages handled, emit to client // XXX password change or keys rotation // XXX make sure only one notication per pad (in case of multiple mailboxes) @@ -226,7 +268,7 @@ id: { var box = ctx.mailboxes[secret.channel]; if (box) { - // XXX return existing box + if (box.revoked) { return void cb({error: 'EFORBIDDEN'}); } if (!box.clients.includes(clientId)) { box.clients.push(clientId); } return void cb(box.parse.getContent()); } @@ -234,10 +276,10 @@ id: { box = ctx.mailboxes[secret.channel] = { messages: [], clients: [clientId], + secret: secret, ready: false }; - if (boxData.link || !boxData.store) { box.origin = 'URL ' + seed; // XXX } else if (boxData.fId) { @@ -265,6 +307,12 @@ id: { }; box.parse = getMailboxParser(ctx, function (keys) { + if (keys === false) { + // XXX revoked + // XXX close mailbox, etc. + box.revoked = true; + cb({error: 'EFORBIDDEN'}); + } if (first) { cb(keys); } first = false; onNewKeys(keys); @@ -441,34 +489,14 @@ id: { }; }); - var secret = Hash.getRevocableSecret({ - channel: '', - viewerSeedStr: best.doc.viewer, - editorSeedStr: best.doc.editor, - }, best.password); - var crypto = Crypto.createEncryptor(secret.keys); - getPadMetadata(ctx, channel, waitFor(function (md) { if (md && md.error) { return; } var access = md.access; - Object.keys(access).forEach(function (ed) { - var a = access[ed]; - if (a.mailbox) { - try { - a.mailbox = crypto.decrypt(a.mailbox, true, true); - } catch (e) { console.error(e); } - } - if (a.notes) { - try { - a.notes = JSON.parse(crypto.decrypt(a.notes, true, true)); - } catch (e) { console.error(e); } - } - }); cb({ list: access, myKeys: myKeys, }); - })); + }), best); }); }; @@ -505,6 +533,15 @@ updateAccess({ return _box; }; + + var sendMailboxMsg = function (ctx, type, content, to, from, cb) { + if (!ctx.store.mailbox) { return void cb({error: 'NOT_READY'}); } + ctx.store.mailbox.sendAs(type, content, to, from, cb); + }; + var sendInitMsg = function (ctx, data, cb) { + sendMailboxMsg(ctx, data.type, data.msg, data.user, data.keys, cb) + }; + /* ctx.Store.anonRpcMsg(clientId, { msg: 'SET_REVOCATION_METADATA', @@ -523,14 +560,36 @@ updateAccess({ } }, cb); */ + + var sendUpdateMsg = function (ctx, target, newAccess, box, md, _cb) { + var cb = Util.once(Util.mkAsync(_cb)); + + var oldAccess = md.access[target]; + var keys = box.parse.getContent(); + var toSend = Revocable.getUpgradeMessage(keys.doc, newAccess, oldAccess); + if (!toSend) { return void cb(false); } + + toSend.uid = keys.doc.uid; + var sendTo = { + channel: oldAccess.mailbox, // decrypted from getPadMetadata + curvePublic: oldAccess.curvePublic + }; + var sendFrom = { + curvePrivate: box.secret.curvePrivate, + curvePublic: box.secret.curvePublic + }; + sendMailboxMsg(ctx, 'UPDATE', toSend, sendTo, sendFrom, cb); + }; + var updateAccess = function (ctx, obj, clientId, cb) { var key = obj.from; var box = findBoxFromKey(ctx, key); if (!box) { return void cb({error: 'EINVAL'}); } - var edPrivate = box.parse.getContent().edPrivate; + var keys = box.parse.getContent(); getPadMetadata(ctx, obj.channel, function (md) { if (md && md.error) { return; } + var edPrivate = keys.edPrivate; var log = Revocable.getSanitizedLog(md); var last = log[log.length-1]; @@ -555,15 +614,6 @@ updateAccess({ var netfluxId = getNetfluxId(ctx); var sig = Revocable.signLog([JSON.stringify(data), netfluxId], edPrivate); -console.warn({ - msg: 'SET_REVOCATION_METADATA', - data: { - data: data, - signature: sig, - key: key - } - }); - ctx.Store.anonRpcMsg(clientId, { msg: 'SET_REVOCATION_METADATA', data: { @@ -571,14 +621,150 @@ console.warn({ signature: sig, key: key } - }, function (obj) { - console.error(obj); - cb(obj); + }, function (res) { + if (obj.add) { return void cb(res); } + + sendUpdateMsg(ctx, value.user, newRights, box, md, function (sent) { + if (sent === false) { return; } + console.log('UPDATE message sent'); + }); + cb(res); }); + }, keys); + }; + + var addAccess = function (ctx, obj, clientId, cb) { + var key = obj.from; + var box = findBoxFromKey(ctx, key); + if (!box) { return void cb({error: 'EINVAL'}); } + var keys = box.parse.getContent(); + + var type = obj.type; + var rights = obj.rights; + var note = obj.note; + + var doc = Hash.getRevocableSecret({ + channel: keys.channel, + viewerSeedStr: keys.doc.viewer, + editorSeedStr: keys.doc.editor, + moderatorSeedStr: keys.doc.moderator + }, keys.password); + var crypto = Crypto.createEncryptor(doc.keys); + var cryptoSym = crypto.encrypt; + var cryptoAsym = crypto.encrypt; // XXX + + var mailboxData = Revocable.createMailbox(type, box.secret, keys.doc, rights); + var access = Revocable.createAccess(type, mailboxData, note, cryptoSym, cryptoAsym); +console.error('NEW HASH', Hash.getRevocableHashFromKeys(type, mailboxData.mailbox)); // XXX + + sendInitMsg(ctx, mailboxData.initMsg, function () { + updateAccess(ctx, { + add: true, + channel: keys.channel, + value: { + user: mailboxData.edPublic, + access: access + }, + from: obj.from, + }, clientId, cb); }); }; + var createPad = function (ctx, obj, clientId, cb) { + var type = obj.type; + var password = obj.password; + var edPublic = obj.edPublic; + + // random curve keyPair for the first mailbox + var randomKeys = Hash.getRevocable(type); + + var doc = Hash.getRevocableSecret(undefined, password); // generate document keys + var docKeys = { + uid: Util.uid(), + viewer: doc.keys.viewerSeed, + editor: doc.keys.editorSeed, + moderator: doc.keys.moderatorSeed, + moderatorCurve: doc.keys.moderatorCurvePublic, + channel: doc.channel + }; + + var moderator = Revocable.createMailbox(type, randomKeys, docKeys, 'rwmd'); + var editor = Revocable.createMailbox(type, moderator.mailbox, docKeys, 'rw'); + +console.error('MODERATOR HASH', Hash.getRevocableHashFromKeys(type, moderator.mailbox)); // XXX + + var crypto = Crypto.createEncryptor(doc.keys); + + var cryptoSym = crypto.encrypt; + var cryptoAsym = crypto.encrypt; // XXX + + var access = {}; + var modNote = { + type: edPublic ? 'user' : 'link', + note: 'Document creator' // XXX + }; + if (edPublic) { modNote.edPublic = edPublic; } + access[moderator.edPublic] = Revocable.createAccess(type, moderator, modNote, cryptoSym, cryptoAsym); + var editNote = { + type: 'link', + note: 'Initial editor access' // XXX + }; + access[editor.edPublic] = Revocable.createAccess(type, editor, editNote, cryptoSym, cryptoAsym); + + // Get a hash of the cryptKey for initial moderators log + var keyHashStr = Revocable.hashBytes(doc.keys.cryptKey); + + var first = Revocable.firstLog(moderator.edPublic); + var prevHash = Revocable.hashMsg(first); + var rotateMsg = Revocable.rotateLog(keyHashStr, doc.keys.validateKey, docKeys.uid, prevHash); + var signature = Revocable.signLog(rotateMsg, moderator.mailbox.edPrivate); + + var data = { + // displayed hash + newHash: Hash.getRevocableHashFromKeys(type, editor.mailbox, password), + docKeys: docKeys, + crypto: crypto, + rtConfig: { + creation: { + creatorEdPrivate: doc.keys.creator, + }, + authentication: { + edPublic: moderator.mailbox.edPublic, + edPrivate: moderator.mailbox.edPrivate + }, + metadata: { + validateKey: (doc.keys && doc.keys.validateKey) || undefined, + access: access, + revocableData: { + creatorKey: moderator.mailbox.curvePublic, + creatorVKey: moderator.mailbox.edPublic, + rotate: { + uid: docKeys.uid, + hash: keyHashStr, + validateKey: doc.keys.validateKey, + signature: signature, + } + } + } + + + } + }; + +console.error(doc); +console.error(moderator); +console.error(editor); +console.error(access); +console.error(keyHashStr); + + nThen(function (waitFor) { + sendInitMsg(ctx, moderator.initMsg, waitFor()); + sendInitMsg(ctx, editor.initMsg, waitFor()); + }).nThen(function (waitFor) { + cb(data); + }); + }; var leaveChannel = function (ctx, padChan) { // Leave channel and prevent reconnect when we leave a pad @@ -647,9 +833,15 @@ console.warn({ if (cmd === 'UPDATE_ACCESS') { return void updateAccess(ctx, data, clientId, cb); } + if (cmd === 'ADD_ACCESS') { + return void addAccess(ctx, data, clientId, cb); + } if (cmd === 'LOAD_PAD') { return void loadPadFromBox(ctx, data, clientId, cb); } + if (cmd === 'CREATE_PAD') { + return void createPad(ctx, data, clientId, cb); + } }; return revocation; diff --git a/www/common/revocable.js b/www/common/revocable.js index 2808e6a45..bcba4f750 100644 --- a/www/common/revocable.js +++ b/www/common/revocable.js @@ -4,9 +4,23 @@ var factory = function (Hash, Nacl) { // Access metadata Revocable.isModerator = function (access) { + if (typeof(access) === "string") { + return access.includes('m'); + } if (!access || !access.rights) { return false; } return access.rights.includes('m'); }; + Revocable.isEditor = function (access) { + if (typeof(access) === "string") { + return access.includes('w'); + } + if (!access || !access.rights) { return false; } + return access.rights.includes('w'); + }; + Revocable.isValidRights = function (rights) { + if (typeof(rights) !== "string") { return false; } + return /^rw?m?d?$/.test(rights); + }; Revocable.isValidAccessUpdate = function (oldValue, newValue) { if (oldValue && !newValue) { return true; } // Deletion if (oldValue) { // Update: I can only change "rights" @@ -141,6 +155,67 @@ var factory = function (Hash, Nacl) { return result; }; + // Mailbox + var RIGHTS = { + r: 'viewer', + w: 'editor', + m: 'moderator' + }; + // Send missing keys to the user + Revocable.getUpgradeMessage = function (docKeys, newAccess, oldAccess) { + if (!newAccess) { return; } // Revoke + if (!oldAccess) { return; } // Create + var oldR = oldAccess.rights || 'r'; + var newR = newAccess.rights || 'r'; + var toSend = {}; + newR.split('').forEach(function (key) { + if (oldR.includes(key)) { return; } // they already have this seed + if (!RIGHTS[key]) { return; } // no destroy seed + var type = RIGHTS[key]; + if (!docKeys[type]) { return; } // make sure I know this key + toSend[type] = docKeys[type]; + }); + if (!Object.keys(toSend).length) { return; } + return toSend; + }; + + + Revocable.createMailbox = function (type, fromKeys, docKeys, rights) { + if (!Revocable.isValidRights(rights)) { return; } + var mailbox = Hash.getRevocable(type); + var clone = JSON.parse(JSON.stringify(docKeys)); + if (!Revocable.isModerator(rights)) { delete clone.moderator; } + if (!Revocable.isEditor(rights)) { delete clone.editor; } + var initMsg = { + type: "INIT", + msg: { + doc: clone, + edPublic: mailbox.keys.edPublic, + edPrivate: mailbox.keys.edPrivate, + }, + user: mailbox, // send to + keys: fromKeys // send from + }; + return { + rights: rights, + initMsg: initMsg, + mailbox: mailbox, + edPublic: mailbox.keys.edPublic + }; + }; + Revocable.createAccess = function (type, user, notes, encryptSym, encryptAsym, contact) { + if (!Revocable.isValidRights(user.rights)) { return; } + notes.hash = Hash.getRevocableHashFromKeys(type, user.mailbox); + var access = { + rights: user.rights, + mailbox: encryptSym(user.mailbox.channel), + curvePublic: user.mailbox.curvePublic, + notes: encryptAsym(JSON.stringify(notes)) + }; + if (contact) { access.contact = encryptSym(contact); } + return access; + }; + // Authentication diff --git a/www/common/sframe-app-framework.js b/www/common/sframe-app-framework.js index 04821ce15..650be2b79 100644 --- a/www/common/sframe-app-framework.js +++ b/www/common/sframe-app-framework.js @@ -81,6 +81,7 @@ define([ var toolbar; var state = STATE.DISCONNECTED; var firstConnection = true; + var restricted = false; var toolbarContainer = options.toolbarContainer || (function () { throw new Error("toolbarContainer must be specified"); }()); @@ -200,6 +201,7 @@ define([ break; } case STATE.ERROR: { + if (text === 'ERESTRICTED') { restricted = true; } evStart.reg(function () { if (text === 'ERESTRICTED') { toolbar.failed(true); @@ -481,6 +483,7 @@ define([ sframeChan.event("EV_CORRUPTED_CACHE"); }; var onCacheReady = function () { + if (state === STATE.ERROR && restricted) { return; } stateChange(STATE.INITIALIZING); toolbar.offline(true); var newContentStr = cpNfInner.chainpad.getUserDoc(); diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 9aecf7660..b6fc35e58 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -502,6 +502,9 @@ define([ // XXX fix with password workflow w.abort(); if (obj && obj.error) { + if (obj.error === 'EFORBIDDEN') { + sframeChan.event("EV_RESTRICTED_ERROR"); + } console.error(obj.error); return; } @@ -2113,67 +2116,30 @@ define([ password = data.password; var newHash = Utils.Hash.createRandomHash(parsed.type, password); - // XXX MAILBOX - var randomKeys = Utils.Hash.getRevocable(parsed.type); // random curve keyPair for the first mailbox - var moderator = Utils.Hash.getRevocable(parsed.type); - var editor = Utils.Hash.getRevocable(parsed.type); - newHash = Utils.Hash.getRevocableHashFromKeys(parsed.type, editor, password); // displayed hash - var doc = Utils.Hash.getRevocableSecret(undefined, password); // generate document keys - var serverCurvePublic; - var rotateUid = Utils.Util.uid(); - nThen(function (waitFor) { - var creatorKeys = { - curvePrivate: doc.keys.creator, - curvePublic: doc.keys.channel - }; - sendMailboxMsg({ - type: "INIT", - msg: { - doc: { - uid: rotateUid, - viewer: doc.keys.viewerSeed, - editor: doc.keys.editorSeed, - moderator: doc.keys.moderatorSeed, - moderatorCurve: doc.keys.moderatorCurvePublic, - channel: doc.channel - }, - edPublic: moderator.keys.edPublic, - edPrivate: moderator.keys.edPrivate, - }, - user: moderator, // send to - keys: randomKeys // send from - }, waitFor()); - sendMailboxMsg({ - type: "INIT", - msg: { - doc: { - uid: rotateUid, - viewer: doc.keys.viewerSeed, - editor: doc.keys.editorSeed, - moderatorCurve: doc.keys.moderatorCurvePublic, - channel: doc.channel - }, - edPublic: editor.keys.edPublic, - edPrivate: editor.keys.edPrivate, - }, - user: editor, // send to - keys: moderator // send from - }, waitFor()); - require([ - '/api/broadcast?'+ (+new Date()), - ], waitFor(function (Broadcast) { - serverCurvePublic = Broadcast.curvePublic; - })); - }).nThen(function (waitFor) { - - secret = Utils.secret = doc; - var crypto = Utils.crypto = Utils.Crypto.createEncryptor(Utils.secret.keys); + Cryptpad.universal.execCommand({ + type: 'revocation', + data: { + cmd: 'CREATE_PAD', + data: { + type: parsed.type, + password: password, + edPublic: edPublic + } + } + }, function (data) { + var docKeys = data.docKeys; + secret = Utils.secret = Utils.Hash.getRevocableSecret({ + channel: docKeys.channel, + viewerSeedStr: docKeys.viewer, + editorSeedStr: docKeys.editor, + }, password); + var crypto = Utils.crypto = Crypto.createEncryptor(secret.keys); // Update the hash in the address bar - currentPad.hash = newHash; - currentPad.href = '/' + parsed.type + '/#' + newHash; - Cryptpad.setTabHash(newHash); + currentPad.hash = data.newHash; + currentPad.href = '/' + parsed.type + '/#' + data.newHash; + Cryptpad.setTabHash(data.newHash); parsed = Utils.Hash.parsePadUrl(currentPad.href); defaultTitle = Utils.UserObject.getDefaultName(parsed); @@ -2181,72 +2147,9 @@ define([ readOnly = false; updateMeta(); - // Create initial accesses - var access = {}; - var modData = curvePublic ? { - value: curvePublic, - type: 'user', - note: 'Document creator' // XXX - } : { - value: Utils.Hash.getRevocableHashFromKeys(parsed.type, moderator), - type: 'link', - note: 'Initial moderator access' // XXX - }; - console.error('MODERATOR HASH', Utils.Hash.getRevocableHashFromKeys(parsed.type, moderator)); - access[moderator.edPublic] = { - rights: 'rwmd', - mailbox: crypto.encrypt(moderator.channel), - curvePublic: moderator.curvePublic, - //contact: crypto.encrypt(), // XXX my contact mailbox - notes: crypto.encrypt(JSON.stringify(modData)), - }; - access[editor.edPublic] = { - rights: 'rw', - mailbox: crypto.encrypt(editor.channel), - curvePublic: editor.curvePublic, - notes: crypto.encrypt(JSON.stringify({ - value: Utils.Hash.getRevocableHashFromKeys(parsed.type, editor), - type: 'link', - note: 'Initial editor access' // XXX - })) - }; - if (data.team) { Cryptpad.initialTeam = data.team.id; } - - // Get a hash of the cryptKeyfirst initial moderators log - var keyHashStr = Revocable.hashBytes(doc.keys.cryptKey); - - var first = Revocable.firstLog(moderator.keys.edPublic); - var prevHash = Revocable.hashMsg(first); - - var rotateMsg = Revocable.rotateLog(keyHashStr, doc.keys.validateKey, rotateUid, prevHash); - var signature = Revocable.signLog(rotateMsg, moderator.keys.edPrivate); - - var rtConfig = { - creation: { - creatorEdPrivate: doc.keys.creator, - }, - authentication: { - edPublic: moderator.edPublic, - edPrivate: moderator.edPrivate - }, - metadata: { - validateKey: (secret.keys && secret.keys.validateKey) || undefined, - access: access, - revocableData: { - creatorKey: moderator.curvePublic, - creatorVKey: moderator.keys.edPublic, - rotate: { - uid: rotateUid, - hash: keyHashStr, - validateKey: doc.keys.validateKey, - signature: signature, - } - } - } - }; - + var rtConfig = data.rtConfig; if (data.expire) { rtConfig.metadata.expire = data.expire; } /* @@ -2261,11 +2164,6 @@ define([ })); } */ -console.error(doc); -console.error(moderator); -console.error(editor); -console.error(access); -console.error(keyHashStr); startRealtime(rtConfig); // XXX