index.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. var SEA = require('./sea')
  2. var Gun = SEA.Gun;
  3. // After we have a GUN extension to make user registration/login easy, we then need to handle everything else.
  4. // We do this with a GUN adapter, we first listen to when a gun instance is created (and when its options change)
  5. Gun.on('opt', function(at){
  6. if(!at.sea){ // only add SEA once per instance, on the "at" context.
  7. at.sea = {own: {}};
  8. //at.on('in', security, at); // now listen to all input data, acting as a firewall.
  9. //at.on('out', signature, at); // and output listeners, to encrypt outgoing data.
  10. at.on('put', check, at);
  11. }
  12. this.to.next(at); // make sure to call the "next" middleware adapter.
  13. });
  14. // Alright, this next adapter gets run at the per node level in the graph database.
  15. // This will let us verify that every property on a node has a value signed by a public key we trust.
  16. // If the signature does not match, the data is just `undefined` so it doesn't get passed on.
  17. // If it does match, then we transform the in-memory "view" of the data into its plain value (without the signature).
  18. // Now NOTE! Some data is "system" data, not user data. Example: List of public keys, aliases, etc.
  19. // This data is self-enforced (the value can only match its ID), but that is handled in the `security` function.
  20. // From the self-enforced data, we can see all the edges in the graph that belong to a public key.
  21. // Example: ~ASDF is the ID of a node with ASDF as its public key, signed alias and salt, and
  22. // its encrypted private key, but it might also have other signed values on it like `profile = <ID>` edge.
  23. // Using that directed edge's ID, we can then track (in memory) which IDs belong to which keys.
  24. // Here is a problem: Multiple public keys can "claim" any node's ID, so this is dangerous!
  25. // This means we should ONLY trust our "friends" (our key ring) public keys, not any ones.
  26. // I have not yet added that to SEA yet in this alpha release. That is coming soon, but beware in the meanwhile!
  27. function each(msg){ // TODO: Warning: Need to switch to `gun.on('node')`! Do not use `Gun.on('node'` in your apps!
  28. // NOTE: THE SECURITY FUNCTION HAS ALREADY VERIFIED THE DATA!!!
  29. // WE DO NOT NEED TO RE-VERIFY AGAIN, JUST TRANSFORM IT TO PLAINTEXT.
  30. var to = this.to, vertex = (msg.$._).put, c = 0, d;
  31. Gun.node.is(msg.put, function(val, key, node){
  32. // only process if SEA formatted?
  33. var tmp = Gun.obj.ify(val) || noop;
  34. if(u !== tmp[':']){
  35. node[key] = SEA.opt.unpack(tmp);
  36. return;
  37. }
  38. if(!SEA.opt.check(val)){ return }
  39. c++; // for each property on the node
  40. SEA.verify(val, false, function(data){ c--; // false just extracts the plain data.
  41. node[key] = SEA.opt.unpack(data, key, node);; // transform to plain value.
  42. if(d && !c && (c = -1)){ to.next(msg) }
  43. });
  44. });
  45. if((d = true) && !c){ to.next(msg) }
  46. }
  47. // signature handles data output, it is a proxy to the security function.
  48. function signature(msg){
  49. if((msg._||noop).user){
  50. return this.to.next(msg);
  51. }
  52. var ctx = this.as;
  53. (msg._||(msg._=function(){})).user = ctx.user;
  54. security.call(this, msg);
  55. }
  56. var u;
  57. function check(msg){ // REVISE / IMPROVE, NO NEED TO PASS MSG/EVE EACH SUB?
  58. var eve = this, at = eve.as, put = msg.put, soul = put['#'], key = put['.'], val = put[':'], state = put['>'], id = msg['#'], tmp;
  59. if(!soul || !key){ return }
  60. if((msg._||'').faith && (at.opt||'').faith && 'function' == typeof msg._){
  61. SEA.verify(SEA.opt.pack(put), false, function(data){ // this is synchronous if false
  62. put['='] = SEA.opt.unpack(data);
  63. eve.to.next(msg);
  64. });
  65. return
  66. }
  67. var no = function(why){ at.on('in', {'@': id, err: why}) };
  68. //var no = function(why){ msg.ack(why) };
  69. (msg._||'').DBG && ((msg._||'').DBG.c = +new Date);
  70. if(0 <= soul.indexOf('<?')){ // special case for "do not sync data X old"
  71. // 'a~pub.key/b<?9'
  72. tmp = parseFloat(soul.split('<?')[1]||'');
  73. if(tmp && (state < (Gun.state() - (tmp * 1000)))){ // sec to ms
  74. (tmp = msg._) && (tmp = tmp.lot) && (tmp.more--); // THIS IS BAD CODE! It assumes GUN internals do something that will probably change in future, but hacking in now.
  75. return; // omit!
  76. }
  77. }
  78. if('~@' === soul){ // special case for shared system data, the list of aliases.
  79. check.alias(eve, msg, val, key, soul, at, no); return;
  80. }
  81. if('~@' === soul.slice(0,2)){ // special case for shared system data, the list of public keys for an alias.
  82. check.pubs(eve, msg, val, key, soul, at, no); return;
  83. }
  84. //if('~' === soul.slice(0,1) && 2 === (tmp = soul.slice(1)).split('.').length){ // special case, account data for a public key.
  85. if(tmp = SEA.opt.pub(soul)){ // special case, account data for a public key.
  86. check.pub(eve, msg, val, key, soul, at, no, at.user||'', tmp); return;
  87. }
  88. if(0 <= soul.indexOf('#')){ // special case for content addressing immutable hashed data.
  89. check.hash(eve, msg, val, key, soul, at, no); return;
  90. }
  91. check.any(eve, msg, val, key, soul, at, no, at.user||''); return;
  92. eve.to.next(msg); // not handled
  93. }
  94. check.hash = function(eve, msg, val, key, soul, at, no){
  95. SEA.work(val, null, function(data){
  96. if(data && data === key.split('#').slice(-1)[0]){ return eve.to.next(msg) }
  97. no("Data hash not same as hash!");
  98. }, {name: 'SHA-256'});
  99. }
  100. check.alias = function(eve, msg, val, key, soul, at, no){ // Example: {_:#~@, ~@alice: {#~@alice}}
  101. if(!val){ return no("Data must exist!") } // data MUST exist
  102. if('~@'+key === link_is(val)){ return eve.to.next(msg) } // in fact, it must be EXACTLY equal to itself
  103. no("Alias not same!"); // if it isn't, reject.
  104. };
  105. check.pubs = function(eve, msg, val, key, soul, at, no){ // Example: {_:#~@alice, ~asdf: {#~asdf}}
  106. if(!val){ return no("Alias must exist!") } // data MUST exist
  107. if(key === link_is(val)){ return eve.to.next(msg) } // and the ID must be EXACTLY equal to its property
  108. no("Alias not same!"); // that way nobody can tamper with the list of public keys.
  109. };
  110. check.pub = function(eve, msg, val, key, soul, at, no, user, pub){ var tmp; // Example: {_:#~asdf, hello:'world'~fdsa}}
  111. if('pub' === key && '~'+pub === soul){
  112. if(val === pub){ return eve.to.next(msg) } // the account MUST match `pub` property that equals the ID of the public key.
  113. return no("Account not same!");
  114. }
  115. if((tmp = user.is) && pub === tmp.pub){
  116. SEA.sign(SEA.opt.pack(msg.put), (user._).sea, function(data){
  117. if(u === data){ return no(SEA.err || 'Signature fail.') }
  118. if(tmp = link_is(val)){ (at.sea.own[tmp] = at.sea.own[tmp] || {})[pub] = 1 }
  119. msg.put[':'] = JSON.stringify({':': tmp = SEA.opt.unpack(data.m), '~': data.s});
  120. msg.put['='] = tmp;
  121. eve.to.next(msg);
  122. }, {raw: 1});
  123. return;
  124. }
  125. SEA.verify(SEA.opt.pack(msg.put), pub, function(data){ var tmp;
  126. data = SEA.opt.unpack(data);
  127. 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.
  128. if((tmp = link_is(data)) && pub === SEA.opt.pub(tmp)){ (at.sea.own[tmp] = at.sea.own[tmp] || {})[pub] = 1 }
  129. msg.put['='] = data;
  130. eve.to.next(msg);
  131. });
  132. };
  133. check.any = function(eve, msg, val, key, soul, at, no, user){ var tmp, pub;
  134. if(at.opt.secure){ return no("Soul missing public key at '" + key + "'.") }
  135. // TODO: Ask community if should auto-sign non user-graph data.
  136. at.on('secure', function(msg){ this.off();
  137. if(!at.opt.secure){ return eve.to.next(msg) }
  138. no("Data cannot be changed.");
  139. }).on.on('secure', msg);
  140. return;
  141. }
  142. var link_is = Gun.val.link.is, state_ify = Gun.state.ify;
  143. // okay! The security function handles all the heavy lifting.
  144. // It needs to deal read and write of input and output of system data, account/public key data, and regular data.
  145. // This is broken down into some pretty clear edge cases, let's go over them:
  146. function security(msg){
  147. var at = this.as, sea = at.sea, to = this.to;
  148. if(at.opt.faith && (msg._||noop).faith){ // you probably shouldn't have faith in this!
  149. this.to.next(msg); // why do we allow skipping security? I'm very scared about it actually.
  150. return; // but so that way storage adapters that already verified something can get performance boost. This was a community requested feature. If anybody finds an exploit with it, please report immediately. It should only be exploitable if you have XSS control anyways, which if you do, you can bypass security regardless of this.
  151. }
  152. if(msg.get){
  153. // if there is a request to read data from us, then...
  154. var soul = msg.get['#'];
  155. if(soul){ // for now, only allow direct IDs to be read.
  156. if(typeof soul !== 'string'){ return to.next(msg) } // do not handle lexical cursors.
  157. if('alias' === soul){ // Allow reading the list of usernames/aliases in the system?
  158. return to.next(msg); // yes.
  159. } else
  160. if('~@' === soul.slice(0,2)){ // Allow reading the list of public keys associated with an alias?
  161. return to.next(msg); // yes.
  162. } else { // Allow reading everything?
  163. return to.next(msg); // yes // TODO: No! Make this a callback/event that people can filter on.
  164. }
  165. }
  166. }
  167. if(msg.put){
  168. /*
  169. NOTICE: THIS IS OLD AND GETTING DEPRECATED.
  170. ANY SECURITY CHANGES SHOULD HAPPEN ABOVE FIRST
  171. THEN PORTED TO HERE.
  172. */
  173. // potentially parallel async operations!!!
  174. var check = {}, each = {}, u;
  175. each.node = function(node, soul){
  176. if(Gun.obj.empty(node, '_')){ return check['node'+soul] = 0 } // ignore empty updates, don't reject them.
  177. Gun.obj.map(node, each.way, {soul: soul, node: node});
  178. };
  179. each.way = function(val, key){
  180. var soul = this.soul, node = this.node, tmp;
  181. if('_' === key){ return } // ignore meta data
  182. if('~@' === soul){ // special case for shared system data, the list of aliases.
  183. each.alias(val, key, node, soul); return;
  184. }
  185. if('~@' === soul.slice(0,2)){ // special case for shared system data, the list of public keys for an alias.
  186. each.pubs(val, key, node, soul); return;
  187. }
  188. if('~' === soul.slice(0,1) && 2 === (tmp = soul.slice(1)).split('.').length){ // special case, account data for a public key.
  189. each.pub(val, key, node, soul, tmp, (msg._||noop).user); return;
  190. }
  191. each.any(val, key, node, soul, (msg._||noop).user); return;
  192. return each.end({err: "No other data allowed!"});
  193. };
  194. each.alias = function(val, key, node, soul){ // Example: {_:#~@, ~@alice: {#~@alice}}
  195. if(!val){ return each.end({err: "Data must exist!"}) } // data MUST exist
  196. if('~@'+key === Gun.val.link.is(val)){ return check['alias'+key] = 0 } // in fact, it must be EXACTLY equal to itself
  197. each.end({err: "Mismatching alias."}); // if it isn't, reject.
  198. };
  199. each.pubs = function(val, key, node, soul){ // Example: {_:#~@alice, ~asdf: {#~asdf}}
  200. if(!val){ return each.end({err: "Alias must exist!"}) } // data MUST exist
  201. if(key === Gun.val.link.is(val)){ return check['pubs'+soul+key] = 0 } // and the ID must be EXACTLY equal to its property
  202. each.end({err: "Alias must match!"}); // that way nobody can tamper with the list of public keys.
  203. };
  204. each.pub = function(val, key, node, soul, pub, user){ var tmp; // Example: {_:#~asdf, hello:'world'~fdsa}}
  205. if('pub' === key){
  206. if(val === pub){ return (check['pub'+soul+key] = 0) } // the account MUST match `pub` property that equals the ID of the public key.
  207. return each.end({err: "Account must match!"});
  208. }
  209. check['user'+soul+key] = 1;
  210. if(Gun.is(msg.$) && user && user.is && pub === user.is.pub){
  211. SEA.sign(SEA.opt.prep(tmp = SEA.opt.parse(val), key, node, soul), (user._).sea, function(data){ var rel;
  212. if(u === data){ return each.end({err: SEA.err || 'Pub signature fail.'}) }
  213. if(rel = Gun.val.link.is(val)){
  214. (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true;
  215. }
  216. node[key] = JSON.stringify({':': SEA.opt.unpack(data.m), '~': data.s});
  217. check['user'+soul+key] = 0;
  218. each.end({ok: 1});
  219. }, {check: SEA.opt.pack(tmp, key, node, soul), raw: 1});
  220. return;
  221. }
  222. SEA.verify(SEA.opt.pack(val,key,node,soul), pub, function(data){ var rel, tmp;
  223. data = SEA.opt.unpack(data, key, node);
  224. if(u === data){ // make sure the signature matches the account it claims to be on.
  225. return each.end({err: "Unverified data."}); // reject any updates that are signed with a mismatched account.
  226. }
  227. if((rel = Gun.val.link.is(data)) && pub === SEA.opt.pub(rel)){
  228. (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true;
  229. }
  230. check['user'+soul+key] = 0;
  231. each.end({ok: 1});
  232. });
  233. };
  234. each.any = function(val, key, node, soul, user){ var tmp, pub;
  235. if(!(pub = SEA.opt.pub(soul))){
  236. if(at.opt.secure){
  237. each.end({err: "Soul is missing public key at '" + key + "'."});
  238. return;
  239. }
  240. // TODO: Ask community if should auto-sign non user-graph data.
  241. check['any'+soul+key] = 1;
  242. at.on('secure', function(msg){ this.off();
  243. check['any'+soul+key] = 0;
  244. if(at.opt.secure){ msg = null }
  245. each.end(msg || {err: "Data cannot be modified."});
  246. }).on.on('secure', msg);
  247. //each.end({err: "Data cannot be modified."});
  248. return;
  249. }
  250. if(Gun.is(msg.$) && user && user.is && pub === user.is.pub){
  251. /*var other = Gun.obj.map(at.sea.own[soul], function(v, p){
  252. if((user.is||{}).pub !== p){ return p }
  253. });
  254. if(other){
  255. each.any(val, key, node, soul);
  256. return;
  257. }*/
  258. check['any'+soul+key] = 1;
  259. SEA.sign(SEA.opt.prep(tmp = SEA.opt.parse(val), key, node, soul), (user._).sea, function(data){
  260. if(u === data){ return each.end({err: 'My signature fail.'}) }
  261. node[key] = JSON.stringify({':': SEA.opt.unpack(data.m), '~': data.s});
  262. check['any'+soul+key] = 0;
  263. each.end({ok: 1});
  264. }, {check: SEA.opt.pack(tmp, key, node, soul), raw: 1});
  265. return;
  266. }
  267. check['any'+soul+key] = 1;
  268. SEA.verify(SEA.opt.pack(val,key,node,soul), pub, function(data){ var rel;
  269. data = SEA.opt.unpack(data, key, node);
  270. if(u === data){ return each.end({err: "Mismatched owner on '" + key + "'."}) } // thanks @rogowski !
  271. if((rel = Gun.val.link.is(data)) && pub === SEA.opt.pub(rel)){
  272. (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true;
  273. }
  274. check['any'+soul+key] = 0;
  275. each.end({ok: 1});
  276. });
  277. }
  278. each.end = function(ctx){ // TODO: Can't you just switch this to each.end = cb?
  279. if(each.err){ return }
  280. if((each.err = ctx.err) || ctx.no){
  281. console.log('NO!', each.err, msg.put); // 451 mistmached data FOR MARTTI
  282. return;
  283. }
  284. if(!each.end.ed){ return }
  285. if(Gun.obj.map(check, function(no){
  286. if(no){ return true }
  287. })){ return }
  288. (msg._||{}).user = at.user || security; // already been through firewall, does not need to again on out.
  289. to.next(msg);
  290. };
  291. Gun.obj.map(msg.put, each.node);
  292. each.end({end: each.end.ed = true});
  293. return; // need to manually call next after async.
  294. }
  295. to.next(msg); // pass forward any data we do not know how to handle or process (this allows custom security protocols).
  296. }
  297. var pubcut = /[^\w_-]/; // anything not alphanumeric or _ -
  298. SEA.opt.pub = function(s){
  299. if(!s){ return }
  300. s = s.split('~');
  301. if(!s || !(s = s[1])){ return }
  302. s = s.split(pubcut).slice(0,2);
  303. if(!s || 2 != s.length){ return }
  304. if('@' === (s[0]||'')[0]){ return }
  305. s = s.slice(0,2).join('.');
  306. return s;
  307. }
  308. SEA.opt.prep = function(d,k, n,s){ // prep for signing
  309. return {'#':s,'.':k,':':SEA.opt.parse(d),'>':Gun.state.is(n, k)};
  310. }
  311. SEA.opt.pack = function(d,k, n,s){ // pack for verifying
  312. if(SEA.opt.check(d)){ return d }
  313. var meta = (Gun.obj.ify((d && d[':'])||d)||''), sig = meta['~'];
  314. return sig? {m: {'#':s||d['#'],'.':k||d['.'],':':meta[':'],'>':d['>']||Gun.state.is(n, k)}, s: sig} : d;
  315. }
  316. var O = SEA.opt;
  317. SEA.opt.unpack = function(d, k, n){ var tmp;
  318. if(u === d){ return }
  319. if(d && (u !== (tmp = d[':']))){ return tmp }
  320. k = k || O.fall_key; if(!n && O.fall_val){ n = {}; n[k] = O.fall_val }
  321. if(!k || !n){ return }
  322. if(d === n[k]){ return d }
  323. if(!SEA.opt.check(n[k])){ return d }
  324. var soul = Gun.node.soul(n) || O.fall_soul, s = Gun.state.is(n, k) || O.fall_state;
  325. if(d && 4 === d.length && soul === d[0] && k === d[1] && fl(s) === fl(d[3])){
  326. return d[2];
  327. }
  328. if(s < SEA.opt.shuffle_attack){
  329. return d;
  330. }
  331. }
  332. SEA.opt.shuffle_attack = 1546329600000; // Jan 1, 2019
  333. var noop = function(){}, u;
  334. var fl = Math.floor; // TODO: Still need to fix inconsistent state issue.
  335. var rel_is = Gun.val.rel.is;
  336. var obj_ify = Gun.obj.ify;
  337. // TODO: Potential bug? If pub/priv key starts with `-`? IDK how possible.