Improve SEA: .auth, .certify, check.pub (#1130)

* Improve SEA.certify

- Improve certify() logic
- "Blacklist" renamed to "block". BLACK LIVES MATTER!
- Fix user.is.alias, now it doesn't contain full pair any more.

* SEA.certify wire logic tests

The "block" (former "blacklist") feature is not working yet (due to a bug in gun)

* skip certify block feature unit test
This commit is contained in:
mimiza 2021-09-06 12:40:36 +07:00 committed by GitHub
parent 89c5286a52
commit 63b0043076
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 200 additions and 203 deletions

37
sea.js
View File

@ -674,23 +674,21 @@
// This is to certify that a group of "certificants" can "put" anything at a group of matched "paths" to the certificate authority's graph
SEA.certify = SEA.certify || (async (certificants, policy = {}, authority, cb, opt = {}) => { try {
/*
The Certify Protocol was made out of love by a Vietnamese code enthusiast. Vietnamese people around the world deserve respect!
IMPORTANT: A Certificate is like a Signature. No one knows who (authority) created/signed a cert until you put it into their graph.
"certificants": '*' or a String (Bob.pub) || an Object that contains "pub" as a key || an array of [object || string]. These people will have the rights.
"policy": A string ('inbox'), or a RAD/LEX object {'*': 'inbox'}, or an Array of RAD/LEX objects or strings. RAD/LEX object can contain key "?" with indexOf("*") > -1 to force key equals certificant pub. This rule is used to check against soul+'/'+key using Gun.text.match or String.match.
"authority": Key pair or priv of the certificate authority.
"cb": A callback function after all things are done.
"opt": If opt.expiry (a timestamp) is set, SEA won't sync data after opt.expiry. If opt.blacklist is set, SEA will look for blacklist before syncing.
"opt": If opt.expiry (a timestamp) is set, SEA won't sync data after opt.expiry. If opt.block is set, SEA will look for block before syncing.
*/
console.log('SEA.certify() is an early experimental community supported method that may change API behavior without warning in any future version.')
certificants = (() => {
var data = []
if (certificants) {
if ((typeof certificants === 'string' || Array.isArray(certificants)) && certificants.indexOf('*') !== -1) return '*'
if (typeof certificants === 'string') {
return certificants
}
if ((typeof certificants === 'string' || Array.isArray(certificants)) && certificants.indexOf('*') > -1) return '*'
if (typeof certificants === 'string') return certificants
if (Array.isArray(certificants)) {
if (certificants.length === 1 && certificants[0]) return typeof certificants[0] === 'object' && certificants[0].pub ? certificants[0].pub : typeof certificants[0] === 'string' ? certificants[0] : null
certificants.map(certificant => {
@ -702,7 +700,7 @@
if (typeof certificants === 'object' && certificants.pub) return certificants.pub
return data.length > 0 ? data : null
}
return null
return
})()
if (!certificants) return console.log("No certificant found.")
@ -710,8 +708,11 @@
const expiry = opt.expiry && (typeof opt.expiry === 'number' || typeof opt.expiry === 'string') ? parseFloat(opt.expiry) : null
const readPolicy = (policy || {}).read ? policy.read : null
const writePolicy = (policy || {}).write ? policy.write : typeof policy === 'string' || Array.isArray(policy) || policy["+"] || policy["#"] || policy["."] || policy["="] || policy["*"] || policy[">"] || policy["<"] ? policy : null
const readBlacklist = ((opt || {}).blacklist || {}).read && (typeof opt.blacklist.read === 'string' || opt.blacklist.read['#']) ? opt.blacklist.read : null
const writeBlacklist = typeof (opt || {}).blacklist === 'string' || (((opt || {}).blacklist || {}).write || {})['#'] ? opt.blacklist : ((opt || {}).blacklist || {}).write && (typeof opt.blacklist.write === 'string' || opt.blacklist.write['#']) ? opt.blacklist.write : null
// The "blacklist" feature is now renamed to "block". Why ? BECAUSE BLACK LIVES MATTER!
// We can now use 3 keys: block, blacklist, ban
const block = (opt || {}).block || (opt || {}).blacklist || (opt || {}).ban || {}
const readBlock = block.read && (typeof block.read === 'string' || (block.read || {})['#']) ? block.read : null
const writeBlock = typeof block === 'string' ? block : block.write && (typeof block.write === 'string' || block.write['#']) ? block.write : null
if (!readPolicy && !writePolicy) return console.log("No policy found.")
@ -721,8 +722,8 @@
...(expiry ? {e: expiry} : {}), // inject expiry if possible
...(readPolicy ? {r: readPolicy } : {}), // "r" stands for read, which means read permission.
...(writePolicy ? {w: writePolicy} : {}), // "w" stands for write, which means write permission.
...(readBlacklist ? {rb: readBlacklist} : {}), // inject READ blacklist if possible
...(writeBlacklist ? {wb: writeBlacklist} : {}), // inject WRITE blacklist if possible
...(readBlock ? {rb: readBlock} : {}), // inject READ block if possible
...(writeBlock ? {wb: writeBlock} : {}), // inject WRITE block if possible
})
const certificate = await SEA.sign(data, authority, null, {raw:1})
@ -934,7 +935,7 @@
if(!act.h.ok || !act.i.ok){ return }
cat.ing = false;
cb({ok: 0, pub: act.pair.pub}); // callback that the user has been created. (Note: ok = 0 because we didn't wait for disk to ack)
if(noop === cb){ pair? gun.auth(pair) : gun.auth(alias, pass) } // if no callback is passed, auto-login after signing up.
if(noop === cb){ pair ? gun.auth(pair) : gun.auth(alias, pass) } // if no callback is passed, auto-login after signing up.
}
root.get('~@'+alias).once(act.a);
return gun;
@ -1025,7 +1026,7 @@
at = user._ = root.get('~'+pair.pub)._;
at.opt = upt;
// add our credentials in-memory only to our root user instance
user.is = {pub: pair.pub, epub: pair.epub, alias: alias || pair};
user.is = {pub: pair.pub, epub: pair.epub, alias: alias || pair.pub};
at.sea = act.pair;
cat.ing = false;
try{if(pass && u == (obj_ify(cat.root.graph['~'+pair.pub].auth)||'')[':']){ opt.shuffle = opt.change = pass; } }catch(e){} // migrate UTF8 & Shuffle!
@ -1368,12 +1369,12 @@
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)
// path is allowed, but is there any WRITE block? Check it out
if (data.wb && (typeof data.wb === 'string' || ((data.wb || {})['#']))) { // "data.wb" = path to the WRITE block
var root = eve.as.root.$.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.")
if (value && (value === 1 || value === true)) return no(`Certificant ${certificant} blocked.`)
return cb(data)
})
}
@ -1507,4 +1508,4 @@
// TODO: Potential bug? If pub/priv key starts with `-`? IDK how possible.
})(USE, './index');
}());
}());

View File

@ -551,191 +551,6 @@ describe('SEA', function(){
});
});
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)
user.leave();
done()
})
}, { opt: { cert } })
}, { opt: { cert } })
})
}())})
it.skip('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
})
console.log(111111);
// Alice points her blacklist to Dave's graph
user.leave()
user.auth(alice, async () => {
console.log("meeeeoooooow");
var ref = gun.get('~'+dave.pub+'/blacklist');
await user.get('blacklist').put(ref);
user.leave()
console.log(2222222);
// 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)
user.leave()
console.log(333333);
// Bob logins and tries to hack Alice
user.auth(bob, async () => {
console.log(4444444);
var data = Gun.state().toString(36)
gun.get("~" + alice.pub)
.get("private")
.get("asdf")
.get("qwerty")
.put(data, ack => {
console.log(555555);
expect(ack.err).to.be.ok()
user.leave()
done()
}, { opt: { cert } })
})
})
})
}())})
});
describe('node', function(){
var u;
if(''+u === typeof process){ return }
@ -743,6 +558,187 @@ describe('SEA', function(){
});
});
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)
user.leave();
done()
})
}, { opt: { cert } })
}, { opt: { cert } })
})
}())})
it.skip('Certify: Advanced - Block', 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
block: 'block' // path to block in Alice's graph
})
// Alice points her block to Dave's graph
await user.auth(alice)
if (user.is) {
await user.get('block').put({'#': '~'+dave.pub+'/block'});
await user.leave()
}
// Dave logins, he adds Bob to his block, which is connected to the certificate that Alice issued for Bob
await user.auth(dave)
if (user.is) {
await user.get('block').get(bob.pub).put(true)
await user.leave()
}
// Bob logins and tries to hack Alice
await user.auth(bob)
if (user.is) {
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 } })
}
}())})
});
})
}());