sea.js 52 KB

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