From 69613e25b18d97cc5b577167c5937cedb363b76a Mon Sep 17 00:00:00 2001 From: Mark Nadal Date: Fri, 6 Aug 2021 17:21:37 -0700 Subject: [PATCH 1/7] probs better this way, safer --- gun.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/gun.js b/gun.js index 8a20ebd8..567b5cfc 100644 --- a/gun.js +++ b/gun.js @@ -210,6 +210,7 @@ USE('./onto'); // depends upon onto! module.exports = function ask(cb, as){ if(!this.on){ return } + var lack = (this.opt||{}).lack || 9000; if(!('function' == typeof cb)){ if(!cb){ return } var id = cb['#'] || cb, tmp = (this.tag||'')[id]; @@ -217,16 +218,16 @@ if(as){ tmp = this.on(id, as); clearTimeout(tmp.err); + tmp.err = setTimeout(function(){ tmp.off() }, lack); } return true; } var id = (as && as['#']) || Math.random().toString(36).slice(2); if(!cb){ return id } var to = this.on(id, cb, as); - to.err = to.err || setTimeout(function(){ + to.err = to.err || setTimeout(function(){ to.off(); to.next({err: "Error: No ACK yet.", lack: true}); - to.off(); - }, (this.opt||{}).lack || 9000); + }, lack); return id; } })(USE, './ask'); @@ -344,7 +345,7 @@ ++ni; kl = null; pop(o); }()); } Gun.on.put = put; - console.log("BEWARE: BETA VERSION OF NEW GUN! NOT ALL FEATURES FINISHED!"); // clock below, reconnect sync. // msg put, put, say ack, hear loop... + console.log("BEWARE: BETA VERSION OF NEW GUN! NOT ALL FEATURES FINISHED!"); // clock below, reconnect sync, SEA certify wire merge, // msg put, put, say ack, hear loop... function ham(val, key, soul, state, msg){ var ctx = msg._||'', root = ctx.root, graph = root.graph, lot, tmp; var vertex = graph[soul] || empty, was = state_is(vertex, key, 1), known = vertex[key]; @@ -359,7 +360,7 @@ } if(state < was){ /*old;*/ if(!ctx.miss){ return } } // but some chains have a cache miss that need to re-fire. // TODO: Improve in future. // for AXE this would reduce rebroadcast, but GUN does it on message forwarding. if(!ctx.faith){ // TODO: BUG? Can this be used for cache miss as well? // Yes this was a bug, need to check cache miss for RAD tests, but should we care about the faith check now? Probably not. - if(state === was && (val === known || L(val) <= L(known))){ /*console.log("same");*/ /*same;*/ if(true || !ctx.miss){ return } } // same + if(state === was && (val === known || L(val) <= L(known))){ /*console.log("same");*/ /*same;*/ if(!ctx.miss){ return } } // same } ctx.stun++; // TODO: 'forget' feature in SEA tied to this, bad approach, but hacked in for now. Any changes here must update there. var aid = msg['#']+ctx.all++, id = {toString: function(){ return aid }, _: ctx}; // this *trick* makes it compatible between old & new versions. @@ -774,7 +775,7 @@ }; Gun.on.unlink = unlink; function ack(msg, ev){ - //if(!msg['%'] && (this||'').off){ this.off() } // do NOT memory leak, turn off listeners! + //if(!msg['%'] && (this||'').off){ this.off() } // do NOT memory leak, turn off listeners! Now handled by .ask itself // manhattan: var as = this.as, at = as.$._, root = at.root, get = as.get||'', tmp = (msg.put||'')[get['#']]||''; if(!msg.put || ('string' == typeof get['.'] && u === tmp[get['.']])){ From b20c156ad48f8e4532c0843f79db1ef60c2bde69 Mon Sep 17 00:00:00 2001 From: Martti Malmi Date: Sun, 8 Aug 2021 15:58:32 +0300 Subject: [PATCH 2/7] Update sea.js Same as https://github.com/amark/gun/pull/1062 --- sea.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sea.js b/sea.js index d3b0e7b5..b55b38f8 100644 --- a/sea.js +++ b/sea.js @@ -278,7 +278,7 @@ SEA.work = SEA.work || (async (data, pair, cb, opt) => { try { // used to be named `proof` var salt = (pair||{}).epub || pair; // epub not recommended, salt should be random! - var opt = opt || {}; + opt = opt || {}; if(salt instanceof Function){ cb = salt; salt = u; @@ -517,7 +517,7 @@ const importGen = async (key, salt, opt) => { //const combo = shim.Buffer.concat([shim.Buffer.from(key, 'utf8'), salt || shim.random(8)]).toString('utf8') // old - var opt = opt || {}; + opt = opt || {}; const combo = key + (salt || shim.random(8)).toString('utf8'); // new const hash = shim.Buffer.from(await sha256hash(combo), 'binary') @@ -1427,4 +1427,4 @@ // TODO: Potential bug? If pub/priv key starts with `-`? IDK how possible. })(USE, './index'); -}()); \ No newline at end of file +}()); From 3efa02f1a3367000c748576da0ec1384d65798dc Mon Sep 17 00:00:00 2001 From: Martti Malmi Date: Sun, 8 Aug 2021 16:42:27 +0300 Subject: [PATCH 3/7] Update gun.js var tmp --- gun.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gun.js b/gun.js index 8a20ebd8..df302817 100644 --- a/gun.js +++ b/gun.js @@ -448,7 +448,7 @@ // PERF: Consider commenting this out to force disk-only reads for perf testing? // TODO: .keys( is slow node && (function go(){ S = +new Date; - var i = 0, k, put = {}; + var i = 0, k, put = {}, tmp; while(i < 9 && (k = keys[i++])){ state_ify(put, k, state_is(node, k), node[k], soul); } @@ -1008,7 +1008,7 @@ function stun(as, id){ if(!id){ return } id = (id._||'').id||id; - tmp = as.root.stun || (as.root.stun = as.ta); + var tmp = as.root.stun || (as.root.stun = as.ta); var it = {run: as.run, stun: as.stun}; (tmp[id]? (tmp[id].last.next = it) : (tmp[id] = it)).last = it; } @@ -1702,4 +1702,4 @@ }); })(USE, './localStorage'); -}()); \ No newline at end of file +}()); From 7222112e59a61a10940f6c12585b420e25df99a8 Mon Sep 17 00:00:00 2001 From: Mark Nadal Date: Sun, 8 Aug 2021 10:25:01 -0700 Subject: [PATCH 4/7] Update upload.js --- lib/upload.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/upload.js b/lib/upload.js index 4fddb83b..1b3b1d0c 100644 --- a/lib/upload.js +++ b/lib/upload.js @@ -18,7 +18,7 @@ if(files[++i]){ upload.drop(files,i) } return false; } - reader = new FileReader(); + var reader = new FileReader(); reader.onload = function(e){ cb({file: files[i], event: e, id: i}, upload); if(files[++i]){ upload.drop(files,i) } From d3dd54de0a2fb118842f9f7e547e6704f1cc8854 Mon Sep 17 00:00:00 2001 From: Mark Nadal Date: Sun, 8 Aug 2021 10:43:53 -0700 Subject: [PATCH 5/7] merge, update stun --- gun.js | 65 ++++++++++++++++++++++++++++++++++------------------------ 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/gun.js b/gun.js index 091895fc..6a9ae6ad 100644 --- a/gun.js +++ b/gun.js @@ -62,7 +62,7 @@ return l; } ;(function(){ // max ~1ms or before stack overflow - var u, sT = setTimeout, l = 0, c = 0, sI = (typeof setImmediate !== ''+u && setImmediate) || sT; + var u, sT = setTimeout, l = 0, c = 0, sI = (typeof setImmediate !== ''+u && setImmediate) || sT; // queueMicrotask faster but blocks UI sT.poll = sT.poll || function(f){ if((1 >= (+new Date - l)) && c++ < 3333){ f(); return } sI(function(){ l = +new Date; f() },c=0) @@ -345,7 +345,7 @@ ++ni; kl = null; pop(o); }()); } Gun.on.put = put; - console.log("BEWARE: BETA VERSION OF NEW GUN! NOT ALL FEATURES FINISHED!"); // clock below, reconnect sync, SEA certify wire merge, // msg put, put, say ack, hear loop... + console.log("BEWARE: BETA VERSION OF NEW GUN! NOT ALL FEATURES FINISHED!"); // clock below, reconnect sync, SEA certify wire merge, User.auth taking multiple times, // msg put, put, say ack, hear loop... function ham(val, key, soul, state, msg){ var ctx = msg._||'', root = ctx.root, graph = root.graph, lot, tmp; var vertex = graph[soul] || empty, was = state_is(vertex, key, 1), known = vertex[key]; @@ -407,7 +407,7 @@ ;(function(){ Gun.on.get = function(msg, gun){ - var root = gun._, get = msg.get, soul = get['#'], node = root.graph[soul], has = get['.'], tmp; + var root = gun._, get = msg.get, soul = get['#'], node = root.graph[soul], has = get['.']; var next = root.next || (root.next = {}), at = next[soul]; // queue concurrent GETs? // TODO: consider tagging original message into dup for DAM. @@ -823,7 +823,7 @@ var wait = {}; // can we assign this to the at instead, like in once? function any(msg, eve, f){ if(any.stun){ return } - var at = msg.$._, data = at.put, aid, tmp; + var at = msg.$._, data = at.put, aid, test, tmp; if((tmp = root.pass) && !tmp[id]){ return } if(!at.has && !at.soul){ data = (u !== (msg.put||'')['='])? msg.put['='] : msg.put } // handles non-core messages. if('string' == typeof (tmp = Gun.valid(data))){ data = root.$.get(tmp)._.put } // TODO: Can we delete this line of code, because the line below (which was inspired by @rogowski) handles it anyways? @@ -832,14 +832,20 @@ if(u === opt.stun){ //if(tmp = root.stun){ tmp = tmp[at.id] || at.$.back(function(back){ return tmp[back.id] || u }); if(tmp && !tmp.end && any.id > (tmp._||'').id){ // this is more thorough, but below seems to work too? //if((tmp = root.stun) && (tmp = tmp[at.id] || tmp[at.back.id]) && !tmp.end && any.id > (tmp._||'').id){ // if we are in the middle of a write, don't read until it is done, unless our callback was earlier than the write. - if((tmp = root.stun) && (tmp = tmp[aid = cat.id] || tmp[aid = at.id] || (msg.$$ && tmp[aid = msg.$$._.id]) /*|| tmp[aid = at.back.id]*/) && any.id > tmp.run){ - if(tmp.stun && !tmp.stun.end){ - tmp.stun[id] = function(){any(msg,eve,1)}; // add ourself to the stun callback list that is called at end of the write. - return; + if((tmp = root.stun) && tmp.on){ + tmp.on(''+(aid = cat.id), test = {}); + !test.run && tmp.on(''+(aid = at.id), test); + !test.run && msg.$$ && tmp.on(''+(aid = msg.$$._.id), test); + if(test.run && any.id > test.run){ // what if I'm less than the last item but more than an earlier item? Don't I need to check first item but add to last item? + if(!test.stun || test.stun.end){ + test.stun = tmp.on('stun'); + test.stun = test.stun && test.stun.last; + } + if(test.stun && !test.stun.end){ + (test.stun.add || (test.stun.add = {}))[id] = function(){any(msg,eve,1)} // add ourself to the stun callback list that is called at end of the write. + return; + } } - root.stun[aid] = tmp.next; - any(msg,eve,f); - return; } if((tmp = root.hatch) && !tmp.end && u === opt.hatch && !f){ // quick hack! // What's going on here? Because data is streamed, we get things one by one, but a lot of developers would rather get a callback after each batch instead, so this does that by creating a wait list per chain id that is then called at the end of the batch by the hatch code in the root put listener. if(wait[at.$._.id]){ return } wait[at.$._.id] = 1; @@ -879,9 +885,6 @@ if(cb){ cb.call(gun, gun._.err) } return gun; } - if(tmp = this._.stun){ // TODO: Refactor? - gun._.stun = gun._.stun || tmp; - } if(cb && 'function' == typeof cb){ gun.get(cb, as); } @@ -945,7 +948,6 @@ as = as || {}; as.root = at.root; as.run || (as.run = root.once); - (as.stun || (as.stun = function(){ return as.run })).back = (root.stun || (root.stun = {}))._; (as.ta = root.stun)._ = as.stun; stun(as, at.id); // set a flag for reads to check if this chain is writing. as.ack = as.ack || cb; as.via = as.via || gun; @@ -1009,9 +1011,23 @@ function stun(as, id){ if(!id){ return } id = (id._||'').id||id; - var tmp = as.root.stun || (as.root.stun = as.ta); - var it = {run: as.run, stun: as.stun}; - (tmp[id]? (tmp[id].last.next = it) : (tmp[id] = it)).last = it; + var run = as.root.stun || (as.root.stun = {on: Gun.on}), test = {}, tmp; + as.stun || (as.stun = run.on('stun', function(){ })); + if(tmp = run.on(''+id)){ tmp.the.last.next(test) } + if(test.run >= as.run){ return } + run.on(''+id, function(test){ + if(as.stun.end){ + this.off(); + this.to.next(test); + return; + } + test.run = test.run || as.run; + if(this.to.to){ + this.the.last.next(test); + return; + } + test.stun = as.stun; + }); } function ran(as){ @@ -1026,14 +1042,9 @@ }, as.opt), acks = 0, stun = as.stun, tmp; (tmp = function(){ // this is not official yet, but quick solution to hack in for now. if(!stun){ return } stun.end = noop; // like with the earlier id, cheaper to make this flag a function so below callbacks do not have to do an extra type check. - if(root.stun){ delete root.stun['s'+as.run] } - if((tmp = root.stun) && stun === tmp._){ - if(!(tmp = stun.back) || tmp.end){ - delete root.stun; // ABC, ACB, BAC, BCA, CBA, CAB; - } - (root.stun||{})._ = tmp; - } - setTimeout.each(Object.keys(stun), function(cb){ if(cb = stun[cb]){cb()} }); // resume the stunned reads // Any perf reasons to CPU schedule this .keys( ? + if(stun.the.to === stun && stun === stun.the.last){ delete root.stun } + stun.off(); + setTimeout.each(Object.keys(stun = stun.add||''), function(cb){ if(cb = stun[cb]){cb()} }); // resume the stunned reads // Any perf reasons to CPU schedule this .keys( ? }).hatch = tmp; // this is not official yet ^ //console.log(1, "PUT", as.run, as.graph); (as.via._).on('out', {put: as.out = as.graph, opt: as.opt, '#': ask, _: tmp}); @@ -1703,4 +1714,4 @@ }); })(USE, './localStorage'); -}()); +}()); \ No newline at end of file From 333dd745f74400426e107dec606b1c3ae9c617ea Mon Sep 17 00:00:00 2001 From: mimiza Date: Tue, 10 Aug 2021 14:40:46 +0700 Subject: [PATCH 6/7] SEA.certify wire logic + unit tests (#1110) * SEA.certify wire logic + unit tests * picking white hair --- sea.js | 124 ++++++++++++++++++++++++++------- test/sea/sea.js | 179 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 277 insertions(+), 26 deletions(-) diff --git a/sea.js b/sea.js index b55b38f8..4a366931 100644 --- a/sea.js +++ b/sea.js @@ -1260,7 +1260,7 @@ })(USE, './share'); ;USE(function(module){ - var SEA = USE('./sea'), noop = function(){}, u; + var SEA = USE('./sea'), S = USE('./settings'), noop = function() {}, u; var Gun = (''+u != typeof window)? (window.Gun||{on:noop}) : USE((''+u === typeof MODULE?'.':'')+'./gun', 1); // After we have a GUN extension to make user registration/login easy, we then need to handle everything else. @@ -1291,7 +1291,6 @@ function check(msg){ // REVISE / IMPROVE, NO NEED TO PASS MSG/EVE EACH SUB? var eve = this, at = eve.as, put = msg.put, soul = put['#'], key = put['.'], val = put[':'], state = put['>'], id = msg['#'], tmp; if(!soul || !key){ return } - //console.log('check', put, msg); if((msg._||'').faith && (at.opt||'').faith && 'function' == typeof msg._){ SEA.opt.pack(put, function(raw){ SEA.verify(raw, false, function(data){ // this is synchronous if false @@ -1343,33 +1342,106 @@ if(key === link_is(val)){ return eve.to.next(msg) } // and the ID must be EXACTLY equal to its property no("Alias not same!"); // that way nobody can tamper with the list of public keys. }; - check.pub = function(eve, msg, val, key, soul, at, no, user, pub){ var tmp; // Example: {_:#~asdf, hello:'world'~fdsa}} - if('pub' === key && '~'+pub === soul){ - if(val === pub){ return eve.to.next(msg) } // the account MUST match `pub` property that equals the ID of the public key. - return no("Account not same!"); - } - if((tmp = user.is) && pub === tmp.pub){// && (tmp = msg._.$) && (tmp = tmp._) && tmp !== tmp.root){ - SEA.opt.pack(msg.put, function(raw){ - SEA.sign(raw, (user._).sea, function(data){ - if(u === data){ return no(SEA.err || 'Signature fail.') } - if(tmp = link_is(val)){ (at.sea.own[tmp] = at.sea.own[tmp] || {})[pub] = 1 } - JSON.stringifyAsync({':': tmp = SEA.opt.unpack(data.m), '~': data.s}, function(err,s){ - if(err){ return no(err || "Stringify error.") } - msg.put['='] = tmp; - msg.put[':'] = s; - eve.to.next(msg); + check.pub = async function(eve, msg, val, key, soul, at, no, user, pub){ var tmp // Example: {_:#~asdf, hello:'world'~fdsa}} + const raw = await S.parse(val) || {} + const verify = (certificate, certificant, cb) => { + if (certificate.m && certificate.s && certificant && pub) + // now verify certificate + return SEA.verify(certificate, pub, data => { // check if "pub" (of the graph owner) really issued this cert + if (u !== data && u !== data.e && msg.put['>'] && msg.put['>'] > parseFloat(data.e)) return no("Certificate expired.") // certificate expired + // "data.c" = a list of certificants/certified users + // "data.w" = lex WRITE permission, in the future, there will be "data.r" which means lex READ permission + if (u !== data && data.c && data.w && (data.c === certificant || data.c.indexOf('*' || certificant) > -1)) { + // ok, now "certificant" is in the "certificants" list, but is "path" allowed? Check path + let path = soul.indexOf('/') > -1 ? soul.replace(soul.substring(0, soul.indexOf('/') + 1), '') : '' + String.match = String.match || Gun.text.match + const w = Array.isArray(data.w) ? data.w : typeof data.w === 'object' || typeof data.w === 'string' ? [data.w] : [] + for (const lex of w) { + if ((String.match(path, lex['#']) && String.match(key, lex['.'])) || (!lex['.'] && String.match(path, lex['#'])) || (!lex['#'] && String.match(key, lex['.'])) || String.match((path ? path + '/' + key : key), lex['#'] || lex)) { + // is Certificant forced to present in Path + if (lex['+'] && lex['+'].indexOf('*') > -1 && path && path.indexOf(certificant) == -1 && key.indexOf(certificant) == -1) return no(`Path "${path}" or key "${key}" must contain string "${certificant}".`) + // path is allowed, but is there any WRITE blacklist? Check it out + if (data.wb && (typeof data.wb === 'string' || ((data.wb || {})['#']))) { // "data.wb" = path to the WRITE blacklist + var root = at.$.back(-1) + if (typeof data.wb === 'string' && '~' !== data.wb.slice(0, 1)) root = root.get('~' + pub) + return root.get(data.wb).get(certificant).once(value => { + if (value && (value === 1 || value === true)) return no("Certificant blacklisted.") + return cb(data) + }) + } + return cb(data) + } + } + return no("Certificate verification fail.") + } }) - }, {raw: 1})}); + return + } + + if ('pub' === key && '~' + pub === soul) { + if (val === pub) return eve.to.next(msg) // the account MUST match `pub` property that equals the ID of the public key. + return no("Account not same!") + } + + if ((tmp = user.is) && tmp.pub && !raw['*'] && !raw['+'] && (pub === tmp.pub || (pub !== tmp.pub && ((msg._.msg || {}).opt || {}).cert))){ + SEA.opt.pack(msg.put, packed => { + SEA.sign(packed, (user._).sea, async function(data) { + if (u === data) return no(SEA.err || 'Signature fail.') + msg.put[':'] = {':': tmp = SEA.opt.unpack(data.m), '~': data.s} + msg.put['='] = tmp + + // if writing to own graph, just allow it + if (pub === user.is.pub) { + if (tmp = link_is(val)) (at.sea.own[tmp] = at.sea.own[tmp] || {})[pub] = 1 + JSON.stringifyAsync(msg.put[':'], function(err,s){ + if(err){ return no(err || "Stringify error.") } + msg.put[':'] = s; + return eve.to.next(msg); + }) + return + } + + // if writing to other's graph, check if cert exists then try to inject cert into put, also inject self pub so that everyone can verify the put + if (pub !== user.is.pub && ((msg._.msg || {}).opt || {}).cert) { + const cert = await S.parse(msg._.msg.opt.cert) + // even if cert exists, we must verify it + if (cert && cert.m && cert.s) + verify(cert, user.is.pub, _ => { + msg.put[':']['+'] = cert // '+' is a certificate + msg.put[':']['*'] = user.is.pub // '*' is pub of the user who puts + JSON.stringifyAsync(msg.put[':'], function(err,s){ + if(err){ return no(err || "Stringify error.") } + msg.put[':'] = s; + return eve.to.next(msg); + }) + return + }) + } + }, {raw: 1}) + }) return; } - SEA.opt.pack(msg.put, function(raw){ - SEA.verify(raw, pub, function(data){ var tmp; - data = SEA.opt.unpack(data); - if(u === data){ return no("Unverified data.") } // make sure the signature matches the account it claims to be on. // reject any updates that are signed with a mismatched account. - if((tmp = link_is(data)) && pub === SEA.opt.pub(tmp)){ (at.sea.own[tmp] = at.sea.own[tmp] || {})[pub] = 1 } - msg.put['='] = data; - eve.to.next(msg); - })}); + + SEA.opt.pack(msg.put, packed => { + SEA.verify(packed, raw['*'] || pub, function(data){ var tmp; + data = SEA.opt.unpack(data); + if (u === data) return no("Unverified data.") // make sure the signature matches the account it claims to be on. // reject any updates that are signed with a mismatched account. + if ((tmp = link_is(data)) && pub === SEA.opt.pub(tmp)) (at.sea.own[tmp] = at.sea.own[tmp] || {})[pub] = 1 + + // check if cert ('+') and putter's pub ('*') exist + if (raw['+'] && raw['+']['m'] && raw['+']['s'] && raw['*']) + // now verify certificate + verify(raw['+'], raw['*'], _ => { + msg.put['='] = data; + return eve.to.next(msg); + }) + else { + msg.put['='] = data; + return eve.to.next(msg); + } + }); + }) + return }; check.any = function(eve, msg, val, key, soul, at, no, user){ var tmp, pub; if(at.opt.secure){ return no("Soul missing public key at '" + key + "'.") } diff --git a/test/sea/sea.js b/test/sea/sea.js index 9b4e60fd..85078ba5 100755 --- a/test/sea/sea.js +++ b/test/sea/sea.js @@ -541,6 +541,185 @@ describe('SEA', function(){ gun.user().auth(alice); }); }); + + describe('CERTIFY', function () { + var gun = Gun() + var user = gun.user() + + it('Certify: Simple', function(done){(async function(){ + var alice = await SEA.pair() + var bob = await SEA.pair() + var dave = await SEA.pair() + var cert = await SEA.certify(bob, {"*": "private"}, alice) + + user.leave() + user.auth(bob, () => { + var data = Gun.state().toString(36) + gun.get("~" + alice.pub) + .get("private") + .get("asdf") + .get("qwerty") + .put(data, () => { + // Bob reads + gun.get("~" + alice.pub) + .get("private") + .get("asdf") + .get("qwerty").once(_data=>{ + expect(_data).to.be(data) + user.leave() + // everyone reads + gun.get("~" + alice.pub) + .get("private") + .get("asdf") + .get("qwerty").once(_data=>{ + expect(_data).to.be(data) + user.auth(dave, () => { + // Dave reads + gun.get("~" + alice.pub) + .get("private") + .get("asdf") + .get("qwerty").once(_data=>{ + expect(_data).to.be(data) + user.leave() + done() + }) + }) + }) + }) + }, { opt: { cert } }) + }) + }())}) + + it('Certify: Attack', function(done){(async function(){ + var alice = await SEA.pair() + var bob = await SEA.pair() + var cert = await SEA.certify(bob, {"*": "private"}, alice) + + user.leave() + user.auth(bob, () => { + var data = Gun.state().toString(36) + gun.get("~" + alice.pub) + .get("wrongway") + .get("asdf") + .get("qwerty") + .put(data, ack => { + expect(ack.err).to.be.ok() + user.leave() + done() + }, { opt: { cert } }) + }) + }())}) + + it('Certify: Public inbox', function(done){(async function(){ + var alice = await SEA.pair() + var bob = await SEA.pair() + var cert = await SEA.certify('*', [{"*": "test", "+": "*"}, {"*": "inbox", "+": "*"}], alice) + + user.leave() + user.auth(bob, () => { + var data = Gun.state().toString(36) + gun.get("~" + alice.pub) + .get("inbox") + .get(user.is.pub) + .put(data, ack => { + expect(ack.err).to.not.be.ok() + user.leave() + done() + }, { opt: { cert } }) + }) + }())}) + + it('Certify: Expiry', function(done){(async function(){ + var alice = await SEA.pair() + var bob = await SEA.pair() + var cert = await SEA.certify(bob, {"*": "private"}, alice, null, { + expiry: Gun.state() - 100, // expired 100 miliseconds ago + }) + + user.leave() + user.auth(bob, () => { + var data = Gun.state().toString(36) + gun.get("~" + alice.pub) + .get("private") + .get("asdf") + .get("qwerty") + .put(data, ack => { + expect(ack.err).to.be.ok() + user.leave() + done() + }, { opt: { cert } }) + }) + }())}) + + it('Certify: Path or Key must contain Certificant Pub', function(done){(async function(){ + var alice = await SEA.pair() + var bob = await SEA.pair() + var cert = await SEA.certify(bob, {"*": "private", "+": "*"}, alice) + + user.leave() + user.auth(bob, () => { + var data = Gun.state().toString(36) + gun.get("~" + alice.pub) + .get("private") + .get('wrongway') + .put(data, ack => { + expect(ack.err).to.be.ok() + gun.get("~" + alice.pub) + .get("private") + .get(bob.pub) + .get('today') + .put(data, ack => { + expect(ack.err).to.not.be.ok() + gun.get("~" + alice.pub) + .get("private") + .get(bob.pub) + .get('today') + .once(_data => { + expect(_data).to.be(data) + done() + }) + }, { opt: { cert } }) + }, { opt: { cert } }) + }) + }())}) + + it('Certify: Advanced - Blacklist', function(done){(async function(){ + var alice = await SEA.pair() + var dave = await SEA.pair() + var bob = await SEA.pair() + var cert = await SEA.certify(bob, {"*": "private"}, alice, null, { + expiry: Gun.state() + 5000, // expires in 5 seconds + blacklist: 'blacklist' // path to blacklist in Alice's graph + }) + + // Alice points her blacklist to Dave's graph + user.leave() + user.auth(alice, async () => { + await user.get('blacklist').put({'#': '~'+dave.pub+'/blacklist'}) + await user.leave() + + // Dave logins, he adds Bob to his blacklist, which is connected to the certificate that Alice issued for Bob + user.auth(dave, async () => { + await user.get('blacklist').get(bob.pub).put(true) + await user.leave() + + // Bob logins and tries to hack Alice + user.auth(bob, async () => { + var data = Gun.state().toString(36) + gun.get("~" + alice.pub) + .get("private") + .get("asdf") + .get("qwerty") + .put(data, ack => { + expect(ack.err).to.be.ok() + user.leave() + done() + }, { opt: { cert } }) + }) + }) + }) + }())}) + }); }); }) From f2c8c9a1e821321e86667706c129d97e7bf555c7 Mon Sep 17 00:00:00 2001 From: Mark Nadal Date: Tue, 10 Aug 2021 01:06:21 -0700 Subject: [PATCH 7/7] ack err --- gun.js | 2 ++ sea.js | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/gun.js b/gun.js index 6a9ae6ad..d84bc7e0 100644 --- a/gun.js +++ b/gun.js @@ -396,7 +396,9 @@ tmp.acks = (tmp.acks||0) + 1; if(0 == tmp.stun && tmp.acks == tmp.all){ // TODO: if ack is synchronous this may not work? root && root.on('in', {'@': tmp['#'], err: msg.err, ok: 'shard'}); + return; } + if(msg.err){ msg['@'] = tmp['#'] } } var ERR = "Error: Invalid graph!"; diff --git a/sea.js b/sea.js index 4a366931..200f2d45 100644 --- a/sea.js +++ b/sea.js @@ -495,11 +495,13 @@ sig = new Uint8Array(buf) check = await (shim.ossl || shim.subtle).verify({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, sig, new Uint8Array(hash)) if(!check){ throw "Signature did not match." } - }catch(e){ + }catch(e){ try{ buf = shim.Buffer.from(json.s, 'utf8') // AUTO BACKWARD OLD UTF8 DATA! sig = new Uint8Array(buf) check = await (shim.ossl || shim.subtle).verify({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, sig, new Uint8Array(hash)) + }catch(e){ if(!check){ throw "Signature did not match." } + } } var r = check? await S.parse(json.m) : u; O.fall_soul = tmp['#']; O.fall_key = tmp['.']; O.fall_val = data; O.fall_state = tmp['>'];