Revocation: safe links & multiple tabs

This commit is contained in:
yflory 2023-04-12 11:35:52 +02:00
parent 9156096629
commit de448f0fab
6 changed files with 186 additions and 63 deletions

View File

@ -763,7 +763,7 @@ define([
};
common.getPadAttribute = function (attr, cb, href, channel) {
href = Hash.getRelativeHref(href || currentPad.href);
if (!href) {
if (!href && !channel) {
return void cb('E404');
}
postMessage("GET_PAD_ATTRIBUTE", {

View File

@ -1188,10 +1188,7 @@ console.error(el, data, app, isRo);
var obj = { t: APP.team };
if (!data.roHref && parsed.revocable) { // Revocable
if (isRo) { obj.mode = 'view'; } // force view VS use best rights
return void common.openURL(Hash.getNewPadURL(href, obj));
}
if (isRo && parsed.revocable) { obj.mode = 'view'; }
var priv = metadataMgr.getPrivateData();
@ -1201,7 +1198,8 @@ console.error(el, data, app, isRo);
}
// Get hidden hash
var secret = Hash.getSecrets(parsed.type, parsed.hash, data.password);
var secret = parsed.revocable ? { key: true, channel: data.channel }
: Hash.getSecrets(parsed.type, parsed.hash, data.password);
var opts = {};
if (isRo) { opts.view = true; }
var hash = Hash.getHiddenHashFromKeys(parsed.type, secret, opts);

View File

@ -1244,7 +1244,7 @@ define([
}
}
if (h.mode === 'view') { return; }
if (h.mode === 'view' || accessType) { return; }
// If we only have rohref, it means we have a stronger href
if (!pad.href) {
@ -1388,6 +1388,13 @@ define([
return chans.some(function (pad) {
if (!pad || !pad.data) { return; }
var data = pad.data;
// Revocable pads: the revocation module will try to get the best access later
if (data.r) {
res = data;
return true;
}
// We've found a match: return the value and stop the loops
if ((edit && data.href) || (!edit && data.roHref) || isFile) {
res = data;

View File

@ -85,7 +85,21 @@ id: {
});
};
var isValidRotateMessage = function (ctx, log, newSeeds, channel, _cb) {
var askPassword = function (ctx, clients, cb) {
var uid = Util.uid();
ctx.passwords.expect(uid, cb)
ctx.emit('ASK_PASSWORD', {
uid: uid
}, clients);
};
var onPassword = function (ctx, data) {
var uid = data.uid;
var pw = data.pw;
ctx.passwords.handle(uid, pw);
};
var isValidRotateMessage = function (ctx, clients, log, newSeeds, channel, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
var rotateUid = newSeeds.uid;
@ -132,8 +146,20 @@ id: {
} else {
valid = isValidPw(pw);
}
if (valid) { return void cb(true, pw); }
return void cb(false);
if (valid) {
if (!knownPasswords.includes(pw)) {
return ctx.Store.setPadAttribute(null, {
channel: channel,
attr: 'password',
value: pw
}, function () {
console.error('stored');
cb(true, pw);
});
}
return void cb(true, pw);
}
//return void cb(false);
/*
// XXX check last password from drive
// XXX then start asking passwords to the user
@ -143,23 +169,24 @@ id: {
// TODO since we need to support old pads, we may have to move all the password logic to the worker, even for old pads, to avoid duplicate code
askPassword(ctx, function (pw) {
*/
askPassword(ctx, clients, function (pw) {
console.error(pw);
check(pw);
});
*/
};
check(false);
};
var getChannelFromBox = function (messages) {
if (!Array.isArray(messages) || !messages.length) { return void cb('EEMPTY'); }
if (!Array.isArray(messages) || !messages.length) { return; }
var init = messages[0];
if (init.type !== 'INIT') { return; }
var content = init.content;
return content && content.doc && content.doc.channel;
};
var getMailboxParser = function (ctx, onNewKeys) {
var getMailboxParser = function (ctx, clients, onNewKeys) {
var data = {
doc: {}
};
@ -169,7 +196,7 @@ id: {
// Only the INIT message can add a private key AND must be line 0
INIT: function (msg, log, channel, i, waitFor) {
if (i || !msg.content || !msg.content.doc || !msg.content.edPrivate) { return; }
isValidRotateMessage(ctx, log, msg.content.doc, channel,
isValidRotateMessage(ctx, clients, log, msg.content.doc, channel,
waitFor(function (valid, password) {
if (!valid) { return; }
data = JSON.parse(JSON.stringify(msg.content));
@ -180,7 +207,7 @@ 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.content, channel, waitFor(function (valid, password) {
isValidRotateMessage(ctx, clients, log, msg.content, channel, waitFor(function (valid, password) {
if (!valid) { return; }
data.doc = msg.content
data.password = password;
@ -264,7 +291,7 @@ id: {
var cb = Util.once(Util.mkAsync(_cb));
var seed = boxData.seed;
console.error(seed);
var secret = Hash.getRevocable('pad', seed);
@ -308,15 +335,18 @@ id: {
cb({ error: info.type });
};
box.parse = getMailboxParser(ctx, function (keys) {
box.parse = getMailboxParser(ctx, box.clients, function (keys) {
if (keys === false) {
// XXX revoked
// XXX close mailbox, etc.
box.revoked = true;
cb({error: 'EFORBIDDEN'});
}
if (first) { cb(keys); }
first = false;
if (first) {
cb(keys);
first = false;
return;
}
onNewKeys(keys);
});
@ -329,6 +359,7 @@ id: {
if (box.ready) { return; }
box.ready = true;
console.error(box.messages);
var padChan = getChannelFromBox(box.messages);
if (!padChan) { return void cb({error: 'ENOCHAN'}); }
@ -346,7 +377,11 @@ id: {
box.messages.push(parsed);
if (box.ready) { box.parse.addMessages([parsed]); }
};
CPNetflux.start(config);
config.onConnectionChange = function (info) {
console.warn(secret.channel, info.state, info.myID, ctx.store.network.myID);
};
box.cpNf = CPNetflux.start(config);
};
@ -427,8 +462,10 @@ id: {
accesses.map(function (obj) {
n = n(function (waitFor) {
loadMailbox(ctx, clientId, obj, function (newKeys) {
// XXX called when we received new keys in real time
}, waitFor());
data.onNewKeysEvt.fire(newKeys);
}, waitFor(function () {
}));
}).nThen;
});
@ -454,18 +491,50 @@ id: {
loadMailbox(ctx, clientId, {
seed: seed
}, function (newKeys) {
// XXX called when we received new keys in real time
data.onNewKeysEvt.fire(newKeys);
}, function (obj) {
if (obj && obj.error) { return void cb(obj); }
// XXX we now know the channel ID and we can load the other mailboxes on this channel
console.error(obj);
var chan = obj.channel;
loadMailboxesFromChannel(ctx, {channel: chan}, clientId, function (bestKeys) {
loadMailboxesFromChannel(ctx, {
channel: chan,
onNewKeysEvt: data.onNewKeysEvt
}, clientId, function (bestKeys) {
cb(bestKeys);
});
});
};
var loadPad = function (ctx, data, clientId, cb) {
var seed = data.seed;
var chan = data.chan;
var onNewKeysEvt = Util.mkEvent();
onNewKeysEvt.reg(function (newKeys) {
// XXX REVOCATION all mailboxes can fire at once
console.error('NEW KEYS', newKeys);
});
if (chan) {
loadMailboxesFromChannel(ctx, {
channel: chan,
onNewKeysEvt: onNewKeysEvt
}, clientId, function (bestKeys) {
cb(bestKeys);
});
return;
}
loadPadFromBox(ctx, {
seed: seed,
onNewKeysEvt: onNewKeysEvt
}, clientId, function (bestKeys) {
cb(bestKeys);
});
};
var listPadAccess = function (ctx, data, clientId, cb) {
@ -770,17 +839,17 @@ console.error(keyHashStr);
});
};
var leaveBox = function (ctx, box, boxChannel) {
if (box.cpNf) { box.cpNf.stop(); }
delete ctx.mailboxes[boxChannel];
};
var leaveChannel = function (ctx, padChan) {
// Leave channel and prevent reconnect when we leave a pad
Object.keys(ctx.mailboxes).some(function (boxChannel) {
var box = ctx.mailboxes[boxChannel];
if (box.padChan !== padChan) { return; }
if (box.wc) { box.wc.leave(); }
if (box.onReconnect) {
var network = ctx.store.network;
network.off('reconnect', box.onReconnect);
}
delete ctx.mailboxes[boxChannel];
leaveBox(ctx, box, boxChannel);
return true;
});
};
@ -796,14 +865,7 @@ console.error(keyHashStr);
for (var k in ctx.mailboxes) {
box = ctx.mailboxes[k];
box.clients = box.clients.filter(filter);
if (box.clients.length === 0) {
if (box.wc) { box.wc.leave(); }
if (box.onReconnect) {
var network = ctx.store.network;
network.off('reconnect', box.onReconnect);
}
delete ctx.mailboxes[k];
}
if (box.clients.length === 0) { leaveBox(ctx, box, k); }
}
};
@ -819,6 +881,7 @@ console.error(keyHashStr);
store: cfg.store,
Store: cfg.Store,
emit: emit,
passwords: Util.response(function (l, i) { console.error('REV_PW_' + l, i); }),
mailboxes: {},
};
@ -841,11 +904,14 @@ console.error(keyHashStr);
return void addAccess(ctx, data, clientId, cb);
}
if (cmd === 'LOAD_PAD') {
return void loadPadFromBox(ctx, data, clientId, cb);
return void loadPad(ctx, data, clientId, cb);
}
if (cmd === 'CREATE_PAD') {
return void createPad(ctx, data, clientId, cb);
}
if (cmd === 'PASSWORD') {
return void onPassword(ctx, data, clientId, cb);
}
};
return revocation;

View File

@ -1089,7 +1089,7 @@ define([
}
Env.user.proxy[UserObject.SHARED_FOLDERS][sfId][data.attr] = data.value;
}
var datas = findHref(Env, data.href);
var datas = data.channel ? findChannel(Env, data.channel) : findHref(Env, data.href);
var nt = nThen;
datas.forEach(function (d) {
nt = nt(function (waitFor) {

View File

@ -479,28 +479,46 @@ define([
value: ''
};
// Hidden hash: can't find the channel in our drives: abort
var noPadData = function (err) {
sframeChan.event("EV_PAD_NODATA", err);
};
var getRevocable = function (w) {
var correctPassword = waitFor();
var newHref;
var expire;
nThen(function (w) {
// XXX REVOCATION
if (parsed.hashData.version !== 5) { return; }
var seed = parsed.hashData.key;
Cryptpad.universal.execCommand({
type: 'revocation',
data: {
cmd: 'LOAD_PAD',
var revCommand = function (cmd, data, cb) {
Cryptpad.universal.execCommand({
type: 'revocation',
data: {
seed: seed
cmd: cmd,
data: data
}
}
}, cb)
};
var lastUid;
sframeChan.on('Q_PAD_PASSWORD_VALUE', function (data, cb) {
password = data;
revCommand('PASSWORD', {
uid: lastUid,
pw: password
});
});
var onEvent = function (obj) {
if (obj.type !== 'revocation') { return; }
var q = obj.data;
if (q.ev !== 'ASK_PASSWORD') { return; }
lastUid = q.data && q.data.uid;
var oldPw = q.data && q.data.pw;
if (oldPw) { passwordCfg.value = oldPw; }
sframeChan.event("EV_PAD_PASSWORD", passwordCfg);
};
Cryptpad.universal.onEvent.reg(onEvent);
var seed = parsed.revocable && parsed.hashData.key;
var chan = parsed.hashData && parsed.hashData.channel;
revCommand('LOAD_PAD', {
seed: seed,
chan: chan
}, w(function (obj) {
// XXX fix with password workflow
w.abort();
if (obj && obj.error) {
if (obj.error === 'EFORBIDDEN') {
sframeChan.event("EV_RESTRICTED_ERROR");
@ -509,6 +527,9 @@ define([
return;
}
Cryptpad.universal.onEvent.unreg(onEvent);
correctPassword();
// XXX get access type (sf/team/user/link)
currentPad.type = 'link';
@ -530,8 +551,22 @@ define([
keys: secret.revocation
}
};
done();
}));
};
// Hidden hash: can't find the channel in our drives: abort
var noPadData = function (err) {
sframeChan.event("EV_PAD_NODATA", err);
};
var newHref;
var expire;
var revocable;
nThen(function (w) {
return;
// XXX REVOCATION
if (parsed.hashData.version !== 5) { return; }
}).nThen(function (w) {
// If we're using an unsafe link, get pad attribute
if (parsed.hashData.key || !parsed.hashData.channel) {
@ -548,6 +583,7 @@ define([
edit: edit,
file: parsed.hashData.type === 'file'
}, w(function (err, res) {
console.error(res);
// Error while getting data? abort
if (err || !res || res.error) {
w.abort();
@ -560,6 +596,9 @@ define([
}
// Data found but weaker? warn
expire = res.expire;
if (res.r) { revocable = true; return; } // Revocable
if (edit && !res.href) {
newHref = res.roHref;
return;
@ -576,13 +615,25 @@ define([
currentPad.href = parsed.getUrl(opts);
currentPad.hash = parsed.hashData && parsed.hashData.getHash(opts);
}
var chan = parsed.hashData.version === 3 && parsed.hashData.channel;
var url = revocable ? null : parsed.getUrl();
Cryptpad.getPadAttribute('channel', w(function (err, data) {
console.error(err, data);
stored = (!err && typeof (data) === "string");
}));
}), null, chan);
Cryptpad.getPadAttribute('password', w(function (err, val) {
console.error(err, val);
password = val;
}), parsed.getUrl());
}), url, chan);
}).nThen(function (w) {
console.error(expire, password);
// Revocable pad, safe (revocable) or unsafe (parsed.revocable) link
if (parsed.revocable || revocable) {
console.error(parsed.revocable, revocable);
return void getRevocable(w);
}
// If we've already tested this password and this is a redirect, force
if (typeof(newPadPassword) !== "undefined" && newPadPasswordForce) {
password = newPadPassword;
@ -1244,7 +1295,8 @@ define([
var useUnsafe = Utils.Util.find(settings, ['security', 'unsafeLinks']);
if (useUnsafe !== true && window.history && window.history.replaceState) {
if (!/^#/.test(hash)) { hash = '#' + hash; }
window.history.replaceState({}, window.document.title, hash);
//window.history.replaceState({}, window.document.title, hash);
// XXX REVOCATION USE SAFE HERE
}
}
cb({error: err});