-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
SEA.certify
THIS IS AN EARLY EXPERIMENTAL COMMUNITY SUPPORTED METHOD THAT MAY CHANGE API BEHAVIOR WITHOUT WARNING IN ANY FUTURE VERSION.
SEA is the Security, Encryption, and Authorization system used with GUN.
Do you want to allow others to write to parts of your own organization's graph without sharing your keypair with them? Then this feature is for you! With SEA.certify, you can create a cryptographically signed Certificate that gives other people write permission. A Certificate describes WHO has the right to write to your graph, WHERE they can write, and (pending security review) until WHEN. The Certificate should not be encrypted because it must be plain text so that it is interpretable by any and every peer and machine in the network, so every peer enforces the same security rules, whether it is a browser, phone, IoT device, or relay.
All you have to do is pass the certificate into any save operation in GUN:
gun.put(data, function(ack){}, {opt: {cert: certificate}})
The certificate will be stored in the graph along with the data for everybody to verify it at any time.
So, now how do you create a certificate?
SEA.certify(who, policy, authority, cb, opt)Who the certificate is for. These are the people you allow to write to your own graph. This could be:
-
'pub'- the public key string of the other user -
{pub}- an object that has apubproperty on it. -
[Bob.pub, Carl.pub]a list of public keys, -
[Bob, Carl]or a list of objects that have apubproperty on them. -
"*"wildcard symbol for everyone/anyone.
policy - The rules of the Certificate. Policy may be set in a couple of ways:
-
'inbox'a string, -
{'*': 'inbox'}a LEX object, -
[LEX objects || strings]or an Array of any of them.
These rules are used to check against Path and Key using Gun.text.match or String.match. These rules are used to check against path.
Path is the string that stands after the graph owner's Pub when someone tries to put data to your graph. For example, Bob is trying to run a gun.put like this:
gun.user(yourPub).get('private').get('deep').get('deeper').put('secret')
In this case, the Path is private/deep and Key is deeper and Value is secret.
-
policies.read [LEX String || Array of LEXs || LEX Object]: Rules for read permissions, TO BE DEVELOPED.
-
policies.write [LEX String || Array of LEXs || LEX Object]: Rules for write permissions. You can set write rules directly in policies if there is no policies.read. In case you have policies.read, you need to set policies.write.
-
OVERWRITE-PROOF – personal paths: If any LEX object is matched, and that LEX object has Key
"+"and its Value contains"*", like this{"*": "something", "+": "*"}, then either Path string or Key string must contain Certificant's Pub string. Certificant's Pub is the pub key of the one who puts. This feature helps fight against data overwrite, but it is flexible because multiple users can still update the same Path, and everyone can still have their own space if required.-
KEEP IN MIND: The above OVERWRITE-PROOF feature is not part of RAD/LEX feature, although it is injected to a LEX object. RAD/LEX only have 4 operators:
=, *, >, < -
To learn more about RAD/LEX, please check out these docs: https://gun.eco/docs/RAD or https://gun.eco/docs/LEX
-
{"*": "notifications", "+": "*"} // Path must start with "notifications", then Path or Key must contain Certificant's Pub (it's just (path||key).indexOf(pub)!=-1)
{"#": {"*": "inbox"}} // Path must start with "inbox". "get('inbox').get('Alice').get('secret').put('abc', null, cert)" and "get('inbox').get('Bob').get('sensitive').put('something', null, cert)" ARE ALL OK.
{"#": {"*": "project"}, ".": {"*": "Bob"}, {"+": "*"}} // Path must start with "project" and Key must start with "Bob", then Path or Key must contain Certificant's Pub.
"inbox/Bob" // Path must equal "inbox/Bob", it is a LEX exact match {"=":"inbox/Bob"}
["inbox", {"*":"projects", "+": "*"}, {"*":"employees"}] // an Array of rules. If any matches, continue.authority - Certificate Authority or Certificate Issuer. This is your priv, or your key pair.
cb - A callback function that runs after a Certificate is created.
opt - the options of the Certificate. Opt is an object that describe WHEN the Certificate expires, and a BLACKLIST in case you want to revoke access that you gave to someone.
-
opt.expiry [Integer || Float]: A timestamp (ie.
Date.now()+10000orGun.state()+10000) to set the Certificate to expire in the future.- If opt.expiry IS NOT SET, the Certificate is valid PERMANENTLY, and this is dangerous!
-
opt.blacklist [String || Object]: A blacklist, in case you want to revoke ie. Bob after giving him the Certificate.
-
opt.blacklist.read [String || Object]: path or a Gun ref object
{'#': '~ManagerPub/blacklist'}to the READ blacklist. -
opt.blacklist OR opt.blacklist.write [String || Object]: path or a Gun ref object
{'#': '~ManagerPub/blacklist'}to the WRITE blacklist. -
If it is a string and starts WITH '~', ie. '~AlicePub/blacklist', SEA will look for it at
-
gun.get('~AlicePub/blacklist').get(Bob.pub).once(value => value === true || value === 1)
* If it starts WITHOUT '~', _ie. 'blacklist'_, SEA will look for it at
gun.get('~YourPub').get('blacklist').get(Bob.pub).once(value => value === true || value === 1)
- If SEA finds the value and it equals true or 1, then Bob is blacklisted and SEA won't sync his writes.
var Alice = await SEA.pair()
var AliceHusband = await SEA.pair()
var Bob = await SEA.pair()
var Dave = await SEA.pair()
// Alice wants to allow Bob and Dave to use write to her "inbox" and "stories" UNTIL TOMORROW
// On Alice's side:
var certificate = await SEA.certify([Bob.pub, Dave.pub], [{"*": "inbox", "+": "*"}, {"*": "stories"}], Alice, null, {expiry: Gun.state()+(60*60*24*1000), blacklist: 'blacklist'})
// Now on Bob/Dave's side, they can write to Alice's graph using gun.put:
gun.get('~'+Alice.pub).get('inbox').get('deeper'+Bob.pub).put('hello world', null, {opt: {cert: certificate}}) // {opt: {cert: certificate}} is how you use Certificate in gun.put
// Now Alice wants to revoke access of Bob. She has TWO OPTIONS. OPTION 1 is to manage the blacklist by herself.
user.get('blacklist').get(Bob.pub).put(true) // OPTION 1: She directly manages her blacklist, in her graph.
// OPTION 2: Alice could point the blacklist to her husband's graph:
user.get('blacklist').put({'#': '~'+AliceHusband.pub+'/blacklist'})
// Now on AliceHusband's side, HE can add Bob to his blacklist:
user.get('blacklist').get(Bob.pub).put(true)With certify you can use a dedicated user space as a place for storing structured app data.
const room = await SEA.pair() // generate a new crypto pair
const certificate = await SEA.certify( '*', { '*':'path', '+': '*' }, roomPair, null, { blacklist: 'ban' } ) // issue a certificate for all to write personal items to the 'path' directory
gun.user().auth(roomPair, () => { // authenticate with the pair, and run the callback
gun.user().get('certs').get('path').put(certificate) // put the certificate right to the room for ease of later use
})const user = await SEA.pair() // generate a new user pair
const certificate = await gun.get('~'+room.pub).get('certs').get('path').then() // load the room certificate
gun.get('~'+room.pub).get('path').get(userPub).put('some user data', null, {opt: {cert: certificate }} // use the certificate to write to the personal root at the room
gun.get('~'+roomPub).get('path').get(userPub).put(null, null, {opt: {cert: certificate }} // user can null his data later, if needed