sea.js 50 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288
  1. ;(function(){
  2. /* UNBUILD */
  3. var root;
  4. if(typeof window !== "undefined"){ root = window }
  5. if(typeof global !== "undefined"){ root = global }
  6. root = root || {};
  7. var console = root.console || {log: function(){}};
  8. function USE(arg, req){
  9. return req? require(arg) : arg.slice? USE[R(arg)] : function(mod, path){
  10. arg(mod = {exports: {}});
  11. USE[R(path)] = mod.exports;
  12. }
  13. function R(p){
  14. return p.split('/').slice(-1).toString().replace('.js','');
  15. }
  16. }
  17. if(typeof module !== "undefined"){ var common = module }
  18. /* UNBUILD */
  19. ;USE(function(module){
  20. // Security, Encryption, and Authorization: SEA.js
  21. // MANDATORY READING: https://gun.eco/explainers/data/security.html
  22. // IT IS IMPLEMENTED IN A POLYFILL/SHIM APPROACH.
  23. // THIS IS AN EARLY ALPHA!
  24. if(typeof window !== "undefined"){ module.window = window }
  25. var tmp = module.window || module;
  26. var SEA = tmp.SEA || {};
  27. if(SEA.window = module.window){ SEA.window.SEA = SEA }
  28. try{ if(typeof common !== "undefined"){ common.exports = SEA } }catch(e){}
  29. module.exports = SEA;
  30. })(USE, './root');
  31. ;USE(function(module){
  32. var SEA = USE('./root');
  33. try{ if(SEA.window){
  34. if(location.protocol.indexOf('s') < 0
  35. && location.host.indexOf('localhost') < 0
  36. && location.protocol.indexOf('file:') < 0){
  37. location.protocol = 'https:'; // WebCrypto does NOT work without HTTPS!
  38. }
  39. } }catch(e){}
  40. })(USE, './https');
  41. ;USE(function(module){
  42. // This is Array extended to have .toString(['utf8'|'hex'|'base64'])
  43. function SeaArray() {}
  44. Object.assign(SeaArray, { from: Array.from })
  45. SeaArray.prototype = Object.create(Array.prototype)
  46. SeaArray.prototype.toString = function(enc, start, end) { enc = enc || 'utf8'; start = start || 0;
  47. const length = this.length
  48. if (enc === 'hex') {
  49. const buf = new Uint8Array(this)
  50. return [ ...Array(((end && (end + 1)) || length) - start).keys()]
  51. .map((i) => buf[ i + start ].toString(16).padStart(2, '0')).join('')
  52. }
  53. if (enc === 'utf8') {
  54. return Array.from(
  55. { length: (end || length) - start },
  56. (_, i) => String.fromCharCode(this[ i + start])
  57. ).join('')
  58. }
  59. if (enc === 'base64') {
  60. return btoa(this)
  61. }
  62. }
  63. module.exports = SeaArray;
  64. })(USE, './array');
  65. ;USE(function(module){
  66. // This is Buffer implementation used in SEA. Functionality is mostly
  67. // compatible with NodeJS 'safe-buffer' and is used for encoding conversions
  68. // between binary and 'hex' | 'utf8' | 'base64'
  69. // See documentation and validation for safe implementation in:
  70. // https://github.com/feross/safe-buffer#update
  71. var SeaArray = USE('./array');
  72. function SafeBuffer(...props) {
  73. console.warn('new SafeBuffer() is depreciated, please use SafeBuffer.from()')
  74. return SafeBuffer.from(...props)
  75. }
  76. SafeBuffer.prototype = Object.create(Array.prototype)
  77. Object.assign(SafeBuffer, {
  78. // (data, enc) where typeof data === 'string' then enc === 'utf8'|'hex'|'base64'
  79. from() {
  80. if (!Object.keys(arguments).length) {
  81. throw new TypeError('First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.')
  82. }
  83. const input = arguments[0]
  84. let buf
  85. if (typeof input === 'string') {
  86. const enc = arguments[1] || 'utf8'
  87. if (enc === 'hex') {
  88. const bytes = input.match(/([\da-fA-F]{2})/g)
  89. .map((byte) => parseInt(byte, 16))
  90. if (!bytes || !bytes.length) {
  91. throw new TypeError('Invalid first argument for type \'hex\'.')
  92. }
  93. buf = SeaArray.from(bytes)
  94. } else if (enc === 'utf8') {
  95. const length = input.length
  96. const words = new Uint16Array(length)
  97. Array.from({ length: length }, (_, i) => words[i] = input.charCodeAt(i))
  98. buf = SeaArray.from(words)
  99. } else if (enc === 'base64') {
  100. const dec = atob(input)
  101. const length = dec.length
  102. const bytes = new Uint8Array(length)
  103. Array.from({ length: length }, (_, i) => bytes[i] = dec.charCodeAt(i))
  104. buf = SeaArray.from(bytes)
  105. } else if (enc === 'binary') {
  106. buf = SeaArray.from(input)
  107. } else {
  108. console.info('SafeBuffer.from unknown encoding: '+enc)
  109. }
  110. return buf
  111. }
  112. const byteLength = input.byteLength // what is going on here? FOR MARTTI
  113. const length = input.byteLength ? input.byteLength : input.length
  114. if (length) {
  115. let buf
  116. if (input instanceof ArrayBuffer) {
  117. buf = new Uint8Array(input)
  118. }
  119. return SeaArray.from(buf || input)
  120. }
  121. },
  122. // This is 'safe-buffer.alloc' sans encoding support
  123. alloc(length, fill = 0 /*, enc*/ ) {
  124. return SeaArray.from(new Uint8Array(Array.from({ length: length }, () => fill)))
  125. },
  126. // This is normal UNSAFE 'buffer.alloc' or 'new Buffer(length)' - don't use!
  127. allocUnsafe(length) {
  128. return SeaArray.from(new Uint8Array(Array.from({ length : length })))
  129. },
  130. // This puts together array of array like members
  131. concat(arr) { // octet array
  132. if (!Array.isArray(arr)) {
  133. throw new TypeError('First argument must be Array containing ArrayBuffer or Uint8Array instances.')
  134. }
  135. return SeaArray.from(arr.reduce((ret, item) => ret.concat(Array.from(item)), []))
  136. }
  137. })
  138. SafeBuffer.prototype.from = SafeBuffer.from
  139. SafeBuffer.prototype.toString = SeaArray.prototype.toString
  140. module.exports = SafeBuffer;
  141. })(USE, './buffer');
  142. ;USE(function(module){
  143. const SEA = USE('./root')
  144. const Buffer = USE('./buffer')
  145. const api = {Buffer: Buffer}
  146. var o = {};
  147. if(SEA.window){
  148. api.crypto = window.crypto || window.msCrypto;
  149. api.subtle = (api.crypto||o).subtle || (api.crypto||o).webkitSubtle;
  150. api.TextEncoder = window.TextEncoder;
  151. api.TextDecoder = window.TextDecoder;
  152. api.random = (len) => Buffer.from(api.crypto.getRandomValues(new Uint8Array(Buffer.alloc(len))))
  153. }
  154. if(!api.crypto){try{
  155. var crypto = USE('crypto', 1);
  156. const { subtle } = USE('@trust/webcrypto', 1) // All but ECDH
  157. const { TextEncoder, TextDecoder } = USE('text-encoding', 1)
  158. Object.assign(api, {
  159. crypto,
  160. subtle,
  161. TextEncoder,
  162. TextDecoder,
  163. random: (len) => Buffer.from(crypto.randomBytes(len))
  164. });
  165. //try{
  166. const WebCrypto = USE('node-webcrypto-ossl', 1)
  167. api.ossl = new WebCrypto({directory: 'ossl'}).subtle // ECDH
  168. //}catch(e){
  169. //console.log("node-webcrypto-ossl is optionally needed for ECDH, please install if needed.");
  170. //}
  171. }catch(e){
  172. console.log("@trust/webcrypto and text-encoding are not included by default, you must add it to your package.json!");
  173. console.log("node-webcrypto-ossl is temporarily needed for ECDSA signature verification, and optionally needed for ECDH, please install if needed (currently necessary so add them to your package.json for now).");
  174. TRUST_WEBCRYPTO_OR_TEXT_ENCODING_NOT_INSTALLED;
  175. }}
  176. module.exports = api
  177. })(USE, './shim');
  178. ;USE(function(module){
  179. const SEA = USE('./root');
  180. const Buffer = USE('./buffer')
  181. const settings = {}
  182. // Encryption parameters
  183. const pbkdf2 = { hash: 'SHA-256', iter: 100000, ks: 64 }
  184. const ecdsaSignProps = { name: 'ECDSA', hash: { name: 'SHA-256' } }
  185. const ecdsaKeyProps = { name: 'ECDSA', namedCurve: 'P-256' }
  186. const ecdhKeyProps = { name: 'ECDH', namedCurve: 'P-256' }
  187. const _initial_authsettings = {
  188. validity: 12 * 60 * 60, // internally in seconds : 12 hours
  189. hook: (props) => props // { iat, exp, alias, remember }
  190. // or return new Promise((resolve, reject) => resolve(props)
  191. }
  192. // These are used to persist user's authentication "session"
  193. const authsettings = Object.assign({}, _initial_authsettings)
  194. // This creates Web Cryptography API compliant JWK for sign/verify purposes
  195. const keysToEcdsaJwk = (pub, d) => { // d === priv
  196. //const [ x, y ] = Buffer.from(pub, 'base64').toString('utf8').split(':') // old
  197. const [ x, y ] = pub.split('.') // new
  198. var jwk = { kty: "EC", crv: "P-256", x: x, y: y, ext: true }
  199. jwk.key_ops = d ? ['sign'] : ['verify'];
  200. if(d){ jwk.d = d }
  201. return jwk;
  202. }
  203. Object.assign(settings, {
  204. pbkdf2: pbkdf2,
  205. ecdsa: {
  206. pair: ecdsaKeyProps,
  207. sign: ecdsaSignProps
  208. },
  209. ecdh: ecdhKeyProps,
  210. jwk: keysToEcdsaJwk,
  211. recall: authsettings
  212. })
  213. SEA.opt = settings;
  214. module.exports = settings
  215. })(USE, './settings');
  216. ;USE(function(module){
  217. module.exports = (props) => {
  218. try {
  219. if(props.slice && 'SEA{' === props.slice(0,4)){
  220. props = props.slice(3);
  221. }
  222. return props.slice ? JSON.parse(props) : props
  223. } catch (e) {} //eslint-disable-line no-empty
  224. return props
  225. }
  226. })(USE, './parse');
  227. ;USE(function(module){
  228. const shim = USE('./shim');
  229. const Buffer = USE('./buffer')
  230. const parse = USE('./parse')
  231. const { pbkdf2 } = USE('./settings')
  232. // This internal func returns SHA-256 hashed data for signing
  233. const sha256hash = async (mm) => {
  234. const m = parse(mm)
  235. const hash = await shim.subtle.digest({name: pbkdf2.hash}, new shim.TextEncoder().encode(m))
  236. return Buffer.from(hash)
  237. }
  238. module.exports = sha256hash
  239. })(USE, './sha256');
  240. ;USE(function(module){
  241. // This internal func returns SHA-1 hashed data for KeyID generation
  242. const __shim = USE('./shim')
  243. const subtle = __shim.subtle
  244. const ossl = __shim.ossl ? __shim.ossl : subtle
  245. const sha1hash = (b) => ossl.digest({name: 'SHA-1'}, new ArrayBuffer(b))
  246. module.exports = sha1hash
  247. })(USE, './sha1');
  248. ;USE(function(module){
  249. var SEA = USE('./root');
  250. var shim = USE('./shim');
  251. var S = USE('./settings');
  252. var sha = USE('./sha256');
  253. var u;
  254. SEA.work = SEA.work || (async (data, pair, cb, opt) => { try { // used to be named `proof`
  255. var salt = (pair||{}).epub || pair; // epub not recommended, salt should be random!
  256. var opt = opt || {};
  257. if(salt instanceof Function){
  258. cb = salt;
  259. salt = u;
  260. }
  261. salt = salt || shim.random(9);
  262. if('SHA-256' === opt.name){
  263. var rsha = shim.Buffer.from(await sha(data), 'binary').toString(opt.encode || 'base64')
  264. if(cb){ try{ cb(rsha) }catch(e){console.log(e)} }
  265. return rsha;
  266. }
  267. const key = await (shim.ossl || shim.subtle).importKey(
  268. 'raw', new shim.TextEncoder().encode(data), { name: opt.name || 'PBKDF2' }, false, ['deriveBits']
  269. )
  270. const result = await (shim.ossl || shim.subtle).deriveBits({
  271. name: opt.name || 'PBKDF2',
  272. iterations: opt.iterations || S.pbkdf2.iter,
  273. salt: new shim.TextEncoder().encode(opt.salt || salt),
  274. hash: opt.hash || S.pbkdf2.hash,
  275. }, key, opt.length || (S.pbkdf2.ks * 8))
  276. data = shim.random(data.length) // Erase data in case of passphrase
  277. const r = shim.Buffer.from(result, 'binary').toString(opt.encode || 'base64')
  278. if(cb){ try{ cb(r) }catch(e){console.log(e)} }
  279. return r;
  280. } catch(e) {
  281. SEA.err = e;
  282. if(SEA.throw){ throw e }
  283. if(cb){ cb() }
  284. return;
  285. }});
  286. module.exports = SEA.work;
  287. })(USE, './work');
  288. ;USE(function(module){
  289. var SEA = USE('./root');
  290. var shim = USE('./shim');
  291. var S = USE('./settings');
  292. var Buff = (typeof Buffer !== 'undefined')? Buffer : shim.Buffer;
  293. SEA.name = SEA.name || (async (cb, opt) => { try {
  294. if(cb){ try{ cb() }catch(e){console.log(e)} }
  295. return;
  296. } catch(e) {
  297. console.log(e);
  298. SEA.err = e;
  299. if(SEA.throw){ throw e }
  300. if(cb){ cb() }
  301. return;
  302. }});
  303. //SEA.pair = async (data, proof, cb) => { try {
  304. SEA.pair = SEA.pair || (async (cb, opt) => { try {
  305. const ecdhSubtle = shim.ossl || shim.subtle
  306. // First: ECDSA keys for signing/verifying...
  307. var sa = await shim.subtle.generateKey(S.ecdsa.pair, true, [ 'sign', 'verify' ])
  308. .then(async (keys) => {
  309. // privateKey scope doesn't leak out from here!
  310. //const { d: priv } = await shim.subtle.exportKey('jwk', keys.privateKey)
  311. const key = {};
  312. key.priv = (await shim.subtle.exportKey('jwk', keys.privateKey)).d;
  313. const pub = await shim.subtle.exportKey('jwk', keys.publicKey)
  314. //const pub = Buff.from([ x, y ].join(':')).toString('base64') // old
  315. key.pub = pub.x+'.'+pub.y // new
  316. // x and y are already base64
  317. // pub is UTF8 but filename/URL safe (https://www.ietf.org/rfc/rfc3986.txt)
  318. // but split on a non-base64 letter.
  319. return key;
  320. })
  321. // To include PGPv4 kind of keyId:
  322. // const pubId = await SEA.keyid(keys.pub)
  323. // Next: ECDH keys for encryption/decryption...
  324. try{
  325. var dh = await ecdhSubtle.generateKey(S.ecdh, true, ['deriveKey'])
  326. .then(async (keys) => {
  327. // privateKey scope doesn't leak out from here!
  328. const key = {};
  329. key.epriv = (await ecdhSubtle.exportKey('jwk', keys.privateKey)).d;
  330. const pub = await ecdhSubtle.exportKey('jwk', keys.publicKey)
  331. //const epub = Buff.from([ ex, ey ].join(':')).toString('base64') // old
  332. key.epub = pub.x+'.'+pub.y // new
  333. // ex and ey are already base64
  334. // epub is UTF8 but filename/URL safe (https://www.ietf.org/rfc/rfc3986.txt)
  335. // but split on a non-base64 letter.
  336. return key;
  337. })
  338. }catch(e){
  339. if(SEA.window){ throw e }
  340. if(e == 'Error: ECDH is not a supported algorithm'){ console.log('Ignoring ECDH...') }
  341. else { throw e }
  342. } dh = dh || {};
  343. const r = { pub: sa.pub, priv: sa.priv, /* pubId, */ epub: dh.epub, epriv: dh.epriv }
  344. if(cb){ try{ cb(r) }catch(e){console.log(e)} }
  345. return r;
  346. } catch(e) {
  347. console.log(e);
  348. SEA.err = e;
  349. if(SEA.throw){ throw e }
  350. if(cb){ cb() }
  351. return;
  352. }});
  353. module.exports = SEA.pair;
  354. })(USE, './pair');
  355. ;USE(function(module){
  356. var SEA = USE('./root');
  357. var shim = USE('./shim');
  358. var S = USE('./settings');
  359. var sha256hash = USE('./sha256');
  360. var u;
  361. SEA.sign = SEA.sign || (async (data, pair, cb, opt) => { try {
  362. opt = opt || {};
  363. if(!(pair||opt).priv){
  364. pair = await SEA.I(null, {what: data, how: 'sign', why: opt.why});
  365. }
  366. const pub = pair.pub
  367. const priv = pair.priv
  368. const jwk = S.jwk(pub, priv)
  369. const hash = await sha256hash(JSON.stringify(data))
  370. const sig = await (shim.ossl || shim.subtle).importKey('jwk', jwk, S.ecdsa.pair, false, ['sign'])
  371. .then((key) => (shim.ossl || shim.subtle).sign(S.ecdsa.sign, key, new Uint8Array(hash))) // privateKey scope doesn't leak out from here!
  372. const r = 'SEA'+JSON.stringify({m: data, s: shim.Buffer.from(sig, 'binary').toString(opt.encode || 'base64')});
  373. if(cb){ try{ cb(r) }catch(e){console.log(e)} }
  374. return r;
  375. } catch(e) {
  376. console.log(e);
  377. SEA.err = e;
  378. if(SEA.throw){ throw e }
  379. if(cb){ cb() }
  380. return;
  381. }});
  382. module.exports = SEA.sign;
  383. })(USE, './sign');
  384. ;USE(function(module){
  385. var SEA = USE('./root');
  386. var shim = USE('./shim');
  387. var S = USE('./settings');
  388. var sha256hash = USE('./sha256');
  389. var parse = USE('./parse');
  390. var u;
  391. SEA.verify = SEA.verify || (async (data, pair, cb, opt) => { try {
  392. const json = parse(data)
  393. if(false === pair){ // don't verify!
  394. const raw = (json !== data)?
  395. (json.s && json.m)? parse(json.m) : data
  396. : json;
  397. if(cb){ try{ cb(raw) }catch(e){console.log(e)} }
  398. return raw;
  399. }
  400. opt = opt || {};
  401. // SEA.I // verify is free! Requires no user permission.
  402. if(json === data){ throw "No signature on data." }
  403. const pub = pair.pub || pair
  404. const jwk = S.jwk(pub)
  405. const key = await (shim.ossl || shim.subtle).importKey('jwk', jwk, S.ecdsa.pair, false, ['verify'])
  406. const hash = await sha256hash(json.m)
  407. var buf; var sig; var check; try{
  408. buf = shim.Buffer.from(json.s, opt.encode || 'base64') // NEW DEFAULT!
  409. sig = new Uint8Array(buf)
  410. check = await (shim.ossl || shim.subtle).verify(S.ecdsa.sign, key, sig, new Uint8Array(hash))
  411. if(!check){ throw "Signature did not match." }
  412. }catch(e){
  413. buf = shim.Buffer.from(json.s, 'utf8') // AUTO BACKWARD OLD UTF8 DATA!
  414. sig = new Uint8Array(buf)
  415. check = await (shim.ossl || shim.subtle).verify(S.ecdsa.sign, key, sig, new Uint8Array(hash))
  416. if(!check){ throw "Signature did not match." }
  417. }
  418. const r = check? parse(json.m) : u;
  419. if(cb){ try{ cb(r) }catch(e){console.log(e)} }
  420. return r;
  421. } catch(e) {
  422. console.log(e); // mismatched owner FOR MARTTI
  423. SEA.err = e;
  424. if(SEA.throw){ throw e }
  425. if(cb){ cb() }
  426. return;
  427. }});
  428. module.exports = SEA.verify;
  429. })(USE, './verify');
  430. ;USE(function(module){
  431. var shim = USE('./shim');
  432. var sha256hash = USE('./sha256');
  433. const importGen = async (key, salt, opt) => {
  434. //const combo = shim.Buffer.concat([shim.Buffer.from(key, 'utf8'), salt || shim.random(8)]).toString('utf8') // old
  435. var opt = opt || {};
  436. const combo = key + (salt || shim.random(8)).toString('utf8'); // new
  437. const hash = shim.Buffer.from(await sha256hash(combo), 'binary')
  438. return await shim.subtle.importKey('raw', new Uint8Array(hash), opt.name || 'AES-GCM', false, ['encrypt', 'decrypt'])
  439. }
  440. module.exports = importGen;
  441. })(USE, './aeskey');
  442. ;USE(function(module){
  443. var SEA = USE('./root');
  444. var shim = USE('./shim');
  445. var S = USE('./settings');
  446. var aeskey = USE('./aeskey');
  447. SEA.encrypt = SEA.encrypt || (async (data, pair, cb, opt) => { try {
  448. opt = opt || {};
  449. var key = (pair||opt).epriv || pair;
  450. if(!key){
  451. pair = await SEA.I(null, {what: data, how: 'encrypt', why: opt.why});
  452. key = pair.epriv || pair;
  453. }
  454. const msg = JSON.stringify(data)
  455. const rand = {s: shim.random(8), iv: shim.random(16)};
  456. const ct = await aeskey(key, rand.s, opt)
  457. .then((aes) => (/*shim.ossl ||*/ shim.subtle).encrypt({ // Keeping the AES key scope as private as possible...
  458. name: opt.name || 'AES-GCM', iv: new Uint8Array(rand.iv)
  459. }, aes, new shim.TextEncoder().encode(msg)))
  460. const r = 'SEA'+JSON.stringify({
  461. ct: shim.Buffer.from(ct, 'binary').toString(opt.encode || 'base64'),
  462. iv: rand.iv.toString(opt.encode || 'base64'),
  463. s: rand.s.toString(opt.encode || 'base64')
  464. });
  465. if(cb){ try{ cb(r) }catch(e){console.log(e)} }
  466. return r;
  467. } catch(e) {
  468. SEA.err = e;
  469. if(SEA.throw){ throw e }
  470. if(cb){ cb() }
  471. return;
  472. }});
  473. module.exports = SEA.encrypt;
  474. })(USE, './encrypt');
  475. ;USE(function(module){
  476. var SEA = USE('./root');
  477. var shim = USE('./shim');
  478. var S = USE('./settings');
  479. var aeskey = USE('./aeskey');
  480. var parse = USE('./parse');
  481. SEA.decrypt = SEA.decrypt || (async (data, pair, cb, opt) => { try {
  482. opt = opt || {};
  483. var key = (pair||opt).epriv || pair;
  484. if(!key){
  485. pair = await SEA.I(null, {what: data, how: 'decrypt', why: opt.why});
  486. key = pair.epriv || pair;
  487. }
  488. const json = parse(data)
  489. var buf; try{buf = shim.Buffer.from(json.s, opt.encode || 'base64') // NEW DEFAULT!
  490. }catch(e){buf = shim.Buffer.from(json.s, 'utf8')} // AUTO BACKWARD OLD UTF8 DATA!
  491. var bufiv; try{bufiv = shim.Buffer.from(json.iv, opt.encode || 'base64') // NEW DEFAULT!
  492. }catch(e){bufiv = shim.Buffer.from(json.iv, 'utf8')} // AUTO BACKWARD OLD UTF8 DATA!
  493. var bufct; try{bufct = shim.Buffer.from(json.ct, opt.encode || 'base64') // NEW DEFAULT!
  494. }catch(e){bufct = shim.Buffer.from(json.ct, 'utf8')} // AUTO BACKWARD OLD UTF8 DATA!
  495. const ct = await aeskey(key, buf, opt)
  496. .then((aes) => (/*shim.ossl ||*/ shim.subtle).decrypt({ // Keeping aesKey scope as private as possible...
  497. name: opt.name || 'AES-GCM', iv: new Uint8Array(bufiv)
  498. }, aes, new Uint8Array(bufct)))
  499. const r = parse(new shim.TextDecoder('utf8').decode(ct))
  500. if(cb){ try{ cb(r) }catch(e){console.log(e)} }
  501. return r;
  502. } catch(e) {
  503. SEA.err = e;
  504. if(SEA.throw){ throw e }
  505. if(cb){ cb() }
  506. return;
  507. }});
  508. module.exports = SEA.decrypt;
  509. })(USE, './decrypt');
  510. ;USE(function(module){
  511. var SEA = USE('./root');
  512. var shim = USE('./shim');
  513. var S = USE('./settings');
  514. // Derive shared secret from other's pub and my epub/epriv
  515. SEA.secret = SEA.secret || (async (key, pair, cb, opt) => { try {
  516. opt = opt || {};
  517. if(!pair || !pair.epriv || !pair.epub){
  518. pair = await SEA.I(null, {what: key, how: 'secret', why: opt.why});
  519. }
  520. const pub = key.epub || key
  521. const epub = pair.epub
  522. const epriv = pair.epriv
  523. const ecdhSubtle = shim.ossl || shim.subtle
  524. const pubKeyData = keysToEcdhJwk(pub)
  525. const props = Object.assign(
  526. S.ecdh,
  527. { public: await ecdhSubtle.importKey(...pubKeyData, true, []) }
  528. )
  529. const privKeyData = keysToEcdhJwk(epub, epriv)
  530. const derived = await ecdhSubtle.importKey(...privKeyData, false, ['deriveKey'])
  531. .then(async (privKey) => {
  532. // privateKey scope doesn't leak out from here!
  533. const derivedKey = await ecdhSubtle.deriveKey(props, privKey, { name: 'AES-GCM', length: 256 }, true, [ 'encrypt', 'decrypt' ])
  534. return ecdhSubtle.exportKey('jwk', derivedKey).then(({ k }) => k)
  535. })
  536. const r = derived;
  537. if(cb){ try{ cb(r) }catch(e){console.log(e)} }
  538. return r;
  539. } catch(e) {
  540. SEA.err = e;
  541. if(SEA.throw){ throw e }
  542. if(cb){ cb() }
  543. return;
  544. }});
  545. const keysToEcdhJwk = (pub, d) => { // d === priv
  546. //const [ x, y ] = Buffer.from(pub, 'base64').toString('utf8').split(':') // old
  547. const [ x, y ] = pub.split('.') // new
  548. const jwk = d ? { d: d } : {}
  549. return [ // Use with spread returned value...
  550. 'jwk',
  551. Object.assign(
  552. jwk,
  553. { x: x, y: y, kty: 'EC', crv: 'P-256', ext: true }
  554. ), // ??? refactor
  555. S.ecdh
  556. ]
  557. }
  558. module.exports = SEA.secret;
  559. })(USE, './secret');
  560. ;USE(function(module){
  561. var shim = USE('./shim');
  562. // Practical examples about usage found from ./test/common.js
  563. var SEA = USE('./root');
  564. SEA.work = USE('./work');
  565. SEA.sign = USE('./sign');
  566. SEA.verify = USE('./verify');
  567. SEA.encrypt = USE('./encrypt');
  568. SEA.decrypt = USE('./decrypt');
  569. SEA.random = SEA.random || shim.random;
  570. // This is Buffer used in SEA and usable from Gun/SEA application also.
  571. // For documentation see https://nodejs.org/api/buffer.html
  572. SEA.Buffer = SEA.Buffer || USE('./buffer');
  573. // These SEA functions support now ony Promises or
  574. // async/await (compatible) code, use those like Promises.
  575. //
  576. // Creates a wrapper library around Web Crypto API
  577. // for various AES, ECDSA, PBKDF2 functions we called above.
  578. // Calculate public key KeyID aka PGPv4 (result: 8 bytes as hex string)
  579. SEA.keyid = SEA.keyid || (async (pub) => {
  580. try {
  581. // base64('base64(x):base64(y)') => Buffer(xy)
  582. const pb = Buffer.concat(
  583. pub.replace(/-/g, '+').replace(/_/g, '/').split('.')
  584. .map((t) => Buffer.from(t, 'base64'))
  585. )
  586. // id is PGPv4 compliant raw key
  587. const id = Buffer.concat([
  588. Buffer.from([0x99, pb.length / 0x100, pb.length % 0x100]), pb
  589. ])
  590. const sha1 = await sha1hash(id)
  591. const hash = Buffer.from(sha1, 'binary')
  592. return hash.toString('hex', hash.length - 8) // 16-bit ID as hex
  593. } catch (e) {
  594. console.log(e)
  595. throw e
  596. }
  597. });
  598. // all done!
  599. // Obviously it is missing MANY necessary features. This is only an alpha release.
  600. // Please experiment with it, audit what I've done so far, and complain about what needs to be added.
  601. // SEA should be a full suite that is easy and seamless to use.
  602. // Again, scroll naer the top, where I provide an EXAMPLE of how to create a user and sign in.
  603. // Once logged in, the rest of the code you just read handled automatically signing/validating data.
  604. // But all other behavior needs to be equally easy, like opinionated ways of
  605. // Adding friends (trusted public keys), sending private messages, etc.
  606. // Cheers! Tell me what you think.
  607. var Gun = (SEA.window||{}).Gun || USE('./gun', 1);
  608. Gun.SEA = SEA;
  609. SEA.GUN = SEA.Gun = Gun;
  610. module.exports = SEA
  611. })(USE, './sea');
  612. ;USE(function(module){
  613. var Gun = USE('./sea').Gun;
  614. Gun.chain.then = function(cb){
  615. var gun = this, p = (new Promise(function(res, rej){
  616. gun.once(res);
  617. }));
  618. return cb? p.then(cb) : p;
  619. }
  620. })(USE, './then');
  621. ;USE(function(module){
  622. var SEA = USE('./sea');
  623. var Gun = SEA.Gun;
  624. var then = USE('./then');
  625. function User(root){
  626. this._ = {$: this};
  627. }
  628. User.prototype = (function(){ function F(){}; F.prototype = Gun.chain; return new F() }()) // Object.create polyfill
  629. User.prototype.constructor = User;
  630. // let's extend the gun chain with a `user` function.
  631. // only one user can be logged in at a time, per gun instance.
  632. Gun.chain.user = function(pub){
  633. var gun = this, root = gun.back(-1), user;
  634. if(pub){ return root.get('~'+pub) }
  635. if(user = root.back('user')){ return user }
  636. var root = (root._), at = root, uuid = at.opt.uuid || Gun.state.lex;
  637. (at = (user = at.user = gun.chain(new User))._).opt = {};
  638. at.opt.uuid = function(cb){
  639. var id = uuid(), pub = root.user;
  640. if(!pub || !(pub = pub.is) || !(pub = pub.pub)){ return id }
  641. id = id + '~' + pub + '.';
  642. if(cb && cb.call){ cb(null, id) }
  643. return id;
  644. }
  645. return user;
  646. }
  647. Gun.User = User;
  648. module.exports = User;
  649. })(USE, './user');
  650. ;USE(function(module){
  651. // TODO: This needs to be split into all separate functions.
  652. // Not just everything thrown into 'create'.
  653. var SEA = USE('./sea');
  654. var User = USE('./user');
  655. var authsettings = USE('./settings');
  656. var Gun = SEA.Gun;
  657. var noop = function(){};
  658. // Well first we have to actually create a user. That is what this function does.
  659. User.prototype.create = function(alias, pass, cb, opt){
  660. var gun = this, cat = (gun._), root = gun.back(-1);
  661. cb = cb || noop;
  662. if(cat.ing){
  663. cb({err: Gun.log("User is already being created or authenticated!"), wait: true});
  664. return gun;
  665. }
  666. cat.ing = true;
  667. opt = opt || {};
  668. var act = {}, u;
  669. act.a = function(pubs){
  670. act.pubs = pubs;
  671. if(pubs && !opt.already){
  672. // If we can enforce that a user name is already taken, it might be nice to try, but this is not guaranteed.
  673. var ack = {err: Gun.log('User already created!')};
  674. cat.ing = false;
  675. cb(ack);
  676. gun.leave();
  677. return;
  678. }
  679. act.salt = Gun.text.random(64); // pseudo-randomly create a salt, then use PBKDF2 function to extend the password with it.
  680. SEA.work(pass, act.salt, act.b); // this will take some short amount of time to produce a proof, which slows brute force attacks.
  681. }
  682. act.b = function(proof){
  683. act.proof = proof;
  684. SEA.pair(act.c); // now we have generated a brand new ECDSA key pair for the user account.
  685. }
  686. act.c = function(pair){ var tmp;
  687. act.pair = pair || {};
  688. if(tmp = cat.root.user){
  689. tmp._.sea = pair;
  690. tmp.is = {pub: pair.pub, epub: pair.epub, alias: alias};
  691. }
  692. // the user's public key doesn't need to be signed. But everything else needs to be signed with it! // we have now automated it! clean up these extra steps now!
  693. act.data = {pub: pair.pub};
  694. SEA.sign(alias, pair, act.d);
  695. }
  696. act.d = function(sig){
  697. act.data.alias = alias || sig;
  698. SEA.sign(act.pair.epub, act.pair, act.e);
  699. }
  700. act.e = function(epub){
  701. act.data.epub = act.pair.epub || epub;
  702. SEA.encrypt({priv: act.pair.priv, epriv: act.pair.epriv}, act.proof, act.f); // to keep the private key safe, we AES encrypt it with the proof of work!
  703. }
  704. act.f = function(auth){
  705. act.data.auth = JSON.stringify({ek: auth, s: act.salt});
  706. SEA.sign({ek: auth, s: act.salt}, act.pair, act.g);
  707. }
  708. act.g = function(auth){ var tmp;
  709. act.data.auth = act.data.auth || auth;
  710. root.get(tmp = '~'+act.pair.pub).put(act.data); // awesome, now we can actually save the user with their public key as their ID.
  711. root.get('~@'+alias).put(Gun.obj.put({}, tmp, Gun.val.link.ify(tmp))); // next up, we want to associate the alias with the public key. So we add it to the alias list.
  712. setTimeout(function(){ // we should be able to delete this now, right?
  713. cat.ing = false;
  714. 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)
  715. if(noop === cb){ gun.auth(alias, pass) } // if no callback is passed, auto-login after signing up.
  716. },10);
  717. }
  718. root.get('~@'+alias).once(act.a);
  719. return gun;
  720. }
  721. // now that we have created a user, we want to authenticate them!
  722. User.prototype.auth = function(alias, pass, cb, opt){
  723. var gun = this, cat = (gun._), root = gun.back(-1);
  724. cb = cb || function(){};
  725. if(cat.ing){
  726. cb({err: Gun.log("User is already being created or authenticated!"), wait: true});
  727. return gun;
  728. }
  729. cat.ing = true;
  730. opt = opt || {};
  731. var pair = (alias && (alias.pub || alias.epub))? alias : (pass && (pass.pub || pass.epub))? pass : null;
  732. var act = {}, u;
  733. act.a = function(data){
  734. if(!data){ return act.b() }
  735. if(!data.pub){
  736. var tmp = [];
  737. Gun.node.is(data, function(v){ tmp.push(v) })
  738. return act.b(tmp);
  739. }
  740. if(act.name){ return act.f(data) }
  741. act.c((act.data = data).auth);
  742. }
  743. act.b = function(list){
  744. var get = (act.list = (act.list||[]).concat(list||[])).shift();
  745. if(u === get){
  746. if(act.name){ return act.err('Your user account is not published for dApps to access, please consider syncing it online, or allowing local access by adding your device as a peer.') }
  747. return act.err('Wrong user or password.')
  748. }
  749. root.get(get).once(act.a);
  750. }
  751. act.c = function(auth){
  752. if(u === auth){ return act.b() }
  753. if(Gun.text.is(auth)){ return act.c(Gun.obj.ify(auth)) } // new format
  754. SEA.work(pass, (act.auth = auth).s, act.d, act.enc); // the proof of work is evidence that we've spent some time/effort trying to log in, this slows brute force.
  755. }
  756. act.d = function(proof){
  757. SEA.decrypt(act.auth.ek, proof, act.e, act.enc);
  758. }
  759. act.e = function(half){
  760. if(u === half){
  761. if(!act.enc){ // try old format
  762. act.enc = {encode: 'utf8'};
  763. return act.c(act.auth);
  764. } act.enc = null; // end backwards
  765. return act.b();
  766. }
  767. act.half = half;
  768. act.f(act.data);
  769. }
  770. act.f = function(data){
  771. if(!data || !data.pub){ return act.b() }
  772. var tmp = act.half || {};
  773. act.g({pub: data.pub, epub: data.epub, priv: tmp.priv, epriv: tmp.epriv});
  774. }
  775. act.g = function(pair){
  776. act.pair = pair;
  777. var user = (root._).user, at = (user._);
  778. var tmp = at.tag;
  779. var upt = at.opt;
  780. at = user._ = root.get('~'+pair.pub)._;
  781. at.opt = upt;
  782. // add our credentials in-memory only to our root user instance
  783. user.is = {pub: pair.pub, epub: pair.epub, alias: alias};
  784. at.sea = act.pair;
  785. cat.ing = false;
  786. if(pass && !Gun.text.is(act.data.auth)){ opt.shuffle = opt.change = pass; } // migrate UTF8 + Shuffle! Test against NAB alias test_sea_shuffle + passw0rd
  787. opt.change? act.z() : cb(at);
  788. if(SEA.window && ((gun.back('user')._).opt||opt).remember){
  789. // TODO: this needs to be modular.
  790. try{var sS = {};
  791. sS = window.sessionStorage;
  792. sS.recall = true;
  793. sS.alias = alias;
  794. sS.tmp = pass;
  795. }catch(e){}
  796. }
  797. try{
  798. (root._).on('auth', at) // TODO: Deprecate this, emit on user instead! Update docs when you do.
  799. //at.on('auth', at) // Arrgh, this doesn't work without event "merge" code, but "merge" code causes stack overflow and crashes after logging in & trying to write data.
  800. }catch(e){
  801. Gun.log("Your 'auth' callback crashed with:", e);
  802. }
  803. }
  804. act.z = function(){
  805. // password update so encrypt private key using new pwd + salt
  806. act.salt = Gun.text.random(64); // pseudo-random
  807. SEA.work(opt.change, act.salt, act.y);
  808. }
  809. act.y = function(proof){
  810. SEA.encrypt({priv: act.pair.priv, epriv: act.pair.epriv}, proof, act.x);
  811. }
  812. act.x = function(auth){
  813. act.w(JSON.stringify({ek: auth, s: act.salt}));
  814. //SEA.sign({ek: auth, s: act.salt}, act.pair, act.w);
  815. }
  816. act.w = function(auth){
  817. if(opt.shuffle){ // delete in future!
  818. var tmp = Gun.obj.to(act.data);
  819. Gun.obj.del(tmp, '_');
  820. tmp.auth = auth;
  821. console.log('migrate core account from UTF8 & shuffle', tmp);
  822. root.get('~'+act.pair.pub).put(tmp);
  823. } // end delete
  824. root.get('~'+act.pair.pub).get('auth').put(auth, cb);
  825. }
  826. act.err = function(e){
  827. var ack = {err: Gun.log(e || 'User cannot be found!')};
  828. cat.ing = false;
  829. cb(ack);
  830. }
  831. act.plugin = function(name){
  832. if(!(act.name = name)){ return act.err() }
  833. var tmp = [name];
  834. if('~' !== name[0]){
  835. tmp[1] = '~'+name;
  836. tmp[2] = '~@'+name;
  837. }
  838. act.b(tmp);
  839. }
  840. if(pair){
  841. act.g(pair);
  842. } else
  843. if(alias){
  844. root.get('~@'+alias).once(act.a);
  845. } else
  846. if(!alias && !pass){
  847. SEA.name(act.plugin);
  848. }
  849. return gun;
  850. }
  851. User.prototype.pair = function(){
  852. console.log("user.pair() IS DEPRECATED AND WILL BE DELETED!!!");
  853. var user = this;
  854. if(!user.is){ return false }
  855. return user._.sea;
  856. }
  857. User.prototype.leave = function(opt, cb){
  858. var gun = this, user = (gun.back(-1)._).user;
  859. if(user){
  860. delete user.is;
  861. delete user._.is;
  862. delete user._.sea;
  863. }
  864. if(SEA.window){
  865. try{var sS = {};
  866. sS = window.sessionStorage;
  867. delete sS.alias;
  868. delete sS.tmp;
  869. delete sS.recall;
  870. }catch(e){};
  871. }
  872. return gun;
  873. }
  874. // If authenticated user wants to delete his/her account, let's support it!
  875. User.prototype.delete = async function(alias, pass, cb){
  876. var gun = this, root = gun.back(-1), user = gun.back('user');
  877. try {
  878. user.auth(alias, pass, function(ack){
  879. var pub = (user.is||{}).pub;
  880. // Delete user data
  881. user.map().once(function(){ this.put(null) });
  882. // Wipe user data from memory
  883. user.leave();
  884. (cb || noop)({ok: 0});
  885. });
  886. } catch (e) {
  887. Gun.log('User.delete failed! Error:', e);
  888. }
  889. return gun;
  890. }
  891. User.prototype.recall = function(opt, cb){
  892. var gun = this, root = gun.back(-1), tmp;
  893. opt = opt || {};
  894. if(opt && opt.sessionStorage){
  895. if(SEA.window){
  896. try{var sS = {};
  897. sS = window.sessionStorage;
  898. if(sS){
  899. (root._).opt.remember = true;
  900. ((gun.back('user')._).opt||opt).remember = true;
  901. if(sS.recall || (sS.alias && sS.tmp)){
  902. root.user().auth(sS.alias, sS.tmp, cb);
  903. }
  904. }
  905. }catch(e){}
  906. }
  907. return gun;
  908. }
  909. /*
  910. TODO: copy mhelander's expiry code back in.
  911. Although, we should check with community,
  912. should expiry be core or a plugin?
  913. */
  914. return gun;
  915. }
  916. User.prototype.alive = async function(){
  917. const gunRoot = this.back(-1)
  918. try {
  919. // All is good. Should we do something more with actual recalled data?
  920. await authRecall(gunRoot)
  921. return gunRoot._.user._
  922. } catch (e) {
  923. const err = 'No session!'
  924. Gun.log(err)
  925. throw { err }
  926. }
  927. }
  928. User.prototype.trust = async function(user){
  929. // TODO: BUG!!! SEA `node` read listener needs to be async, which means core needs to be async too.
  930. //gun.get('alice').get('age').trust(bob);
  931. if (Gun.is(user)) {
  932. user.get('pub').get((ctx, ev) => {
  933. console.log(ctx, ev)
  934. })
  935. }
  936. }
  937. User.prototype.grant = function(to, cb){
  938. console.log("`.grant` API MAY BE DELETED OR CHANGED OR RENAMED, DO NOT USE!");
  939. var gun = this, user = gun.back(-1).user(), pair = user.pair(), path = '';
  940. gun.back(function(at){ if(at.is){ return } path += (at.get||'') });
  941. (async function(){
  942. var enc, sec = await user.get('trust').get(pair.pub).get(path).then();
  943. sec = await SEA.decrypt(sec, pair);
  944. if(!sec){
  945. sec = SEA.random(16).toString();
  946. enc = await SEA.encrypt(sec, pair);
  947. user.get('trust').get(pair.pub).get(path).put(enc);
  948. }
  949. var pub = to.get('pub').then();
  950. var epub = to.get('epub').then();
  951. pub = await pub; epub = await epub;
  952. var dh = await SEA.secret(epub, pair);
  953. enc = await SEA.encrypt(sec, dh);
  954. user.get('trust').get(pub).get(path).put(enc, cb);
  955. }());
  956. return gun;
  957. }
  958. User.prototype.secret = function(data, cb){
  959. console.log("`.secret` API MAY BE DELETED OR CHANGED OR RENAMED, DO NOT USE!");
  960. var gun = this, user = gun.back(-1).user(), pair = user.pair(), path = '';
  961. gun.back(function(at){ if(at.is){ return } path += (at.get||'') });
  962. (async function(){
  963. var enc, sec = await user.get('trust').get(pair.pub).get(path).then();
  964. sec = await SEA.decrypt(sec, pair);
  965. if(!sec){
  966. sec = SEA.random(16).toString();
  967. enc = await SEA.encrypt(sec, pair);
  968. user.get('trust').get(pair.pub).get(path).put(enc);
  969. }
  970. enc = await SEA.encrypt(data, sec);
  971. gun.put(enc, cb);
  972. }());
  973. return gun;
  974. }
  975. module.exports = User
  976. })(USE, './create');
  977. ;USE(function(module){
  978. const SEA = USE('./sea')
  979. const Gun = SEA.Gun;
  980. // After we have a GUN extension to make user registration/login easy, we then need to handle everything else.
  981. // We do this with a GUN adapter, we first listen to when a gun instance is created (and when its options change)
  982. Gun.on('opt', function(at){
  983. if(!at.sea){ // only add SEA once per instance, on the "at" context.
  984. at.sea = {own: {}};
  985. at.on('in', security, at); // now listen to all input data, acting as a firewall.
  986. at.on('out', signature, at); // and output listeners, to encrypt outgoing data.
  987. at.on('node', each, at);
  988. }
  989. this.to.next(at); // make sure to call the "next" middleware adapter.
  990. });
  991. // Alright, this next adapter gets run at the per node level in the graph database.
  992. // This will let us verify that every property on a node has a value signed by a public key we trust.
  993. // If the signature does not match, the data is just `undefined` so it doesn't get passed on.
  994. // If it does match, then we transform the in-memory "view" of the data into its plain value (without the signature).
  995. // Now NOTE! Some data is "system" data, not user data. Example: List of public keys, aliases, etc.
  996. // This data is self-enforced (the value can only match its ID), but that is handled in the `security` function.
  997. // From the self-enforced data, we can see all the edges in the graph that belong to a public key.
  998. // Example: ~ASDF is the ID of a node with ASDF as its public key, signed alias and salt, and
  999. // its encrypted private key, but it might also have other signed values on it like `profile = <ID>` edge.
  1000. // Using that directed edge's ID, we can then track (in memory) which IDs belong to which keys.
  1001. // Here is a problem: Multiple public keys can "claim" any node's ID, so this is dangerous!
  1002. // This means we should ONLY trust our "friends" (our key ring) public keys, not any ones.
  1003. // I have not yet added that to SEA yet in this alpha release. That is coming soon, but beware in the meanwhile!
  1004. function each(msg){ // TODO: Warning: Need to switch to `gun.on('node')`! Do not use `Gun.on('node'` in your apps!
  1005. // NOTE: THE SECURITY FUNCTION HAS ALREADY VERIFIED THE DATA!!!
  1006. // WE DO NOT NEED TO RE-VERIFY AGAIN, JUST TRANSFORM IT TO PLAINTEXT.
  1007. var to = this.to, vertex = (msg.$._).put, c = 0, d;
  1008. Gun.node.is(msg.put, function(val, key, node){ c++; // for each property on the node
  1009. // TODO: consider async/await use here...
  1010. SEA.verify(val, false, function(data){ c--; // false just extracts the plain data.
  1011. var tmp = data;
  1012. data = SEA.opt.unpack(data, key, node);
  1013. node[key] = val = data; // transform to plain value.
  1014. if(d && !c && (c = -1)){ to.next(msg) }
  1015. });
  1016. });
  1017. d = true;
  1018. if(d && !c){ to.next(msg) }
  1019. return;
  1020. }
  1021. // signature handles data output, it is a proxy to the security function.
  1022. function signature(msg){
  1023. if(msg.user){
  1024. return this.to.next(msg);
  1025. }
  1026. var ctx = this.as;
  1027. msg.user = ctx.user;
  1028. security.call(this, msg);
  1029. }
  1030. // okay! The security function handles all the heavy lifting.
  1031. // It needs to deal read and write of input and output of system data, account/public key data, and regular data.
  1032. // This is broken down into some pretty clear edge cases, let's go over them:
  1033. function security(msg){
  1034. var at = this.as, sea = at.sea, to = this.to;
  1035. if(msg.get){
  1036. // if there is a request to read data from us, then...
  1037. var soul = msg.get['#'];
  1038. if(soul){ // for now, only allow direct IDs to be read.
  1039. if(typeof soul !== 'string'){ return to.next(msg) } // do not handle lexical cursors.
  1040. if('alias' === soul){ // Allow reading the list of usernames/aliases in the system?
  1041. return to.next(msg); // yes.
  1042. } else
  1043. if('~@' === soul.slice(0,2)){ // Allow reading the list of public keys associated with an alias?
  1044. return to.next(msg); // yes.
  1045. } else { // Allow reading everything?
  1046. return to.next(msg); // yes // TODO: No! Make this a callback/event that people can filter on.
  1047. }
  1048. }
  1049. }
  1050. if(msg.put){
  1051. // potentially parallel async operations!!!
  1052. var check = {}, each = {}, u;
  1053. each.node = function(node, soul){
  1054. if(Gun.obj.empty(node, '_')){ return check['node'+soul] = 0 } // ignore empty updates, don't reject them.
  1055. Gun.obj.map(node, each.way, {soul: soul, node: node});
  1056. };
  1057. each.way = function(val, key){
  1058. var soul = this.soul, node = this.node, tmp;
  1059. if('_' === key){ return } // ignore meta data
  1060. if('~@' === soul){ // special case for shared system data, the list of aliases.
  1061. each.alias(val, key, node, soul); return;
  1062. }
  1063. if('~@' === soul.slice(0,2)){ // special case for shared system data, the list of public keys for an alias.
  1064. each.pubs(val, key, node, soul); return;
  1065. }
  1066. if('~' === soul.slice(0,1) && 2 === (tmp = soul.slice(1)).split('.').length){ // special case, account data for a public key.
  1067. each.pub(val, key, node, soul, tmp, msg.user); return;
  1068. }
  1069. each.any(val, key, node, soul, msg.user); return;
  1070. return each.end({err: "No other data allowed!"});
  1071. };
  1072. each.alias = function(val, key, node, soul){ // Example: {_:#~@, ~@alice: {#~@alice}}
  1073. if(!val){ return each.end({err: "Data must exist!"}) } // data MUST exist
  1074. if('~@'+key === Gun.val.link.is(val)){ return check['alias'+key] = 0 } // in fact, it must be EXACTLY equal to itself
  1075. each.end({err: "Mismatching alias."}); // if it isn't, reject.
  1076. };
  1077. each.pubs = function(val, key, node, soul){ // Example: {_:#~@alice, ~asdf: {#~asdf}}
  1078. if(!val){ return each.end({err: "Alias must exist!"}) } // data MUST exist
  1079. if(key === Gun.val.link.is(val)){ return check['pubs'+soul+key] = 0 } // and the ID must be EXACTLY equal to its property
  1080. each.end({err: "Alias must match!"}); // that way nobody can tamper with the list of public keys.
  1081. };
  1082. each.pub = function(val, key, node, soul, pub, user){ // Example: {_:#~asdf, hello:SEA{'world',fdsa}}
  1083. if('pub' === key){
  1084. if(val === pub){ return (check['pub'+soul+key] = 0) } // the account MUST match `pub` property that equals the ID of the public key.
  1085. return each.end({err: "Account must match!"});
  1086. }
  1087. check['user'+soul+key] = 1;
  1088. if(user && user.is && pub === user.is.pub){
  1089. //var id = Gun.text.random(3);
  1090. SEA.sign([soul, key, val, Gun.state.is(node, key)], (user._).sea, function(data){ var rel;
  1091. if(u === data){ return each.end({err: SEA.err || 'Pub signature fail.'}) }
  1092. if(rel = Gun.val.link.is(val)){
  1093. (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true;
  1094. }
  1095. node[key] = data;
  1096. check['user'+soul+key] = 0;
  1097. each.end({ok: 1});
  1098. });
  1099. // TODO: Handle error!!!!
  1100. return;
  1101. }
  1102. SEA.verify(val, pub, function(data){ var rel, tmp;
  1103. data = SEA.opt.unpack(data, key, node);
  1104. if(u === data){ // make sure the signature matches the account it claims to be on.
  1105. return each.end({err: "Unverified data."}); // reject any updates that are signed with a mismatched account.
  1106. }
  1107. if((rel = Gun.val.link.is(data)) && pub === relpub(rel)){
  1108. (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true;
  1109. }
  1110. check['user'+soul+key] = 0;
  1111. each.end({ok: 1});
  1112. });
  1113. };
  1114. function relpub(s){
  1115. if(!s){ return }
  1116. s = s.split('~');
  1117. if(!s || !(s = s[1])){ return }
  1118. s = s.split('.');
  1119. if(!s || 2 > s.length){ return }
  1120. s = s.slice(0,2).join('.');
  1121. return s;
  1122. }
  1123. each.any = function(val, key, node, soul, user){ var tmp, pub;
  1124. if(!user || !user.is){
  1125. if(tmp = relpub(soul)){
  1126. check['any'+soul+key] = 1;
  1127. SEA.verify(val, pub = tmp, function(data){ var rel;
  1128. data = SEA.opt.unpack(data, key, node);
  1129. if(u === data){ return each.end({err: "Mismatched owner on '" + key + "'."}) } // thanks @rogowski !
  1130. if((rel = Gun.val.link.is(data)) && pub === relpub(rel)){
  1131. (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true;
  1132. }
  1133. check['any'+soul+key] = 0;
  1134. each.end({ok: 1});
  1135. });
  1136. return;
  1137. }
  1138. check['any'+soul+key] = 1;
  1139. at.on('secure', function(msg){ this.off();
  1140. check['any'+soul+key] = 0;
  1141. if(at.opt.secure){ msg = null }
  1142. each.end(msg || {err: "Data cannot be modified."});
  1143. }).on.on('secure', msg);
  1144. //each.end({err: "Data cannot be modified."});
  1145. return;
  1146. }
  1147. if(!(tmp = relpub(soul))){
  1148. if(at.opt.secure){
  1149. each.end({err: "Soul is missing public key at '" + key + "'."});
  1150. return;
  1151. }
  1152. if(val && val.slice && 'SEA{' === (val).slice(0,4)){
  1153. check['any'+soul+key] = 0;
  1154. each.end({ok: 1});
  1155. return;
  1156. }
  1157. //check['any'+soul+key] = 1;
  1158. //SEA.sign(val, user, function(data){
  1159. // if(u === data){ return each.end({err: 'Any signature failed.'}) }
  1160. // node[key] = data;
  1161. check['any'+soul+key] = 0;
  1162. each.end({ok: 1});
  1163. //});
  1164. return;
  1165. }
  1166. if(!msg.I || (pub = tmp) !== (user.is||noop).pub){
  1167. each.any(val, key, node, soul);
  1168. return;
  1169. }
  1170. /*var other = Gun.obj.map(at.sea.own[soul], function(v, p){
  1171. if((user.is||{}).pub !== p){ return p }
  1172. });
  1173. if(other){
  1174. each.any(val, key, node, soul);
  1175. return;
  1176. }*/
  1177. check['any'+soul+key] = 1;
  1178. SEA.sign([soul, key, val, Gun.state.is(node, key)], (user._).sea, function(data){
  1179. if(u === data){ return each.end({err: 'My signature fail.'}) }
  1180. node[key] = data;
  1181. check['any'+soul+key] = 0;
  1182. each.end({ok: 1});
  1183. });
  1184. }
  1185. each.end = function(ctx){ // TODO: Can't you just switch this to each.end = cb?
  1186. if(each.err){ return }
  1187. if((each.err = ctx.err) || ctx.no){
  1188. console.log('NO!', each.err, msg.put); // 451 mistmached data FOR MARTTI
  1189. return;
  1190. }
  1191. if(!each.end.ed){ return }
  1192. if(Gun.obj.map(check, function(no){
  1193. if(no){ return true }
  1194. })){ return }
  1195. to.next(msg);
  1196. };
  1197. Gun.obj.map(msg.put, each.node);
  1198. each.end({end: each.end.ed = true});
  1199. return; // need to manually call next after async.
  1200. }
  1201. to.next(msg); // pass forward any data we do not know how to handle or process (this allows custom security protocols).
  1202. }
  1203. SEA.opt.unpack = function(data, key, node){
  1204. if(u === data){ return }
  1205. var tmp = data, soul = Gun.node.soul(node), s = Gun.state.is(node, key);
  1206. if(tmp && 4 === tmp.length && soul === tmp[0] && key === tmp[1] && s === tmp[3]){
  1207. return tmp[2];
  1208. }
  1209. if(s < SEA.opt.shuffle_attack){
  1210. return data;
  1211. }
  1212. }
  1213. SEA.opt.shuffle_attack = 1546329600000; // Jan 1, 2019
  1214. var noop = {}, u;
  1215. })(USE, './index');
  1216. }());