From f9974d55083aa3ba25e4d07e86f5f38ee45dc672 Mon Sep 17 00:00:00 2001 From: Mark Nadal Date: Fri, 4 Jan 2019 04:01:02 -0800 Subject: [PATCH 01/11] fix SEA shuffle to ignore GUN data --- package.json | 2 +- sea.js | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 263a6d05..397f2712 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gun", - "version": "0.9.999995", + "version": "0.9.999996", "description": "A realtime, decentralized, offline-first, graph data synchronization engine.", "main": "index.js", "browser": "gun.min.js", diff --git a/sea.js b/sea.js index 6a053585..73f8f8ad 100644 --- a/sea.js +++ b/sea.js @@ -1273,8 +1273,10 @@ } SEA.opt.unpack = function(data, key, node){ if(u === data){ return } + if(data === node[key]){ return data } + if(data && data['#'] && rel_is(data) === rel_is(node[key])){ return data } var tmp = data, soul = Gun.node.soul(node), s = Gun.state.is(node, key); - if(tmp && 4 === tmp.length && soul === tmp[0] && key === tmp[1] && s === tmp[3]){ + if(tmp && 4 === tmp.length && soul === tmp[0] && key === tmp[1] && fl(s) === fl(tmp[3])){ return tmp[2]; } if(s < SEA.opt.shuffle_attack){ @@ -1283,6 +1285,9 @@ } SEA.opt.shuffle_attack = 1546329600000; // Jan 1, 2019 var noop = {}, u; + var fl = Math.floor; // TODO: Still need to fix inconsistent state issue. + var rel_is = Gun.val.rel.is; + // TODO: Potential bug? If pub/priv key starts with `-`? IDK how possible. })(USE, './index'); }()); From 925aff85864e57858423231b99f6dd297dc18176 Mon Sep 17 00:00:00 2001 From: Mark Nadal Date: Fri, 4 Jan 2019 04:02:35 -0800 Subject: [PATCH 02/11] fix SEA shuffle to ignore good GUN data --- sea/index.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sea/index.js b/sea/index.js index a2434594..cf3cb07a 100644 --- a/sea/index.js +++ b/sea/index.js @@ -230,8 +230,10 @@ } SEA.opt.unpack = function(data, key, node){ if(u === data){ return } + if(data === node[key]){ return data } + if(data && data['#'] && rel_is(data) === rel_is(node[key])){ return data } var tmp = data, soul = Gun.node.soul(node), s = Gun.state.is(node, key); - if(tmp && 4 === tmp.length && soul === tmp[0] && key === tmp[1] && s === tmp[3]){ + if(tmp && 4 === tmp.length && soul === tmp[0] && key === tmp[1] && fl(s) === fl(tmp[3])){ return tmp[2]; } if(s < SEA.opt.shuffle_attack){ @@ -240,5 +242,8 @@ } SEA.opt.shuffle_attack = 1546329600000; // Jan 1, 2019 var noop = {}, u; + var fl = Math.floor; // TODO: Still need to fix inconsistent state issue. + var rel_is = Gun.val.rel.is; + // TODO: Potential bug? If pub/priv key starts with `-`? IDK how possible. \ No newline at end of file From cd0b12d1d27ba33591c6b2a9a347961ea987ed6b Mon Sep 17 00:00:00 2001 From: go1dfish Date: Fri, 4 Jan 2019 06:03:22 -0800 Subject: [PATCH 03/11] OSSL Memory leak mitigation Mitigates this leak: https://github.com/PeculiarVentures/node-webcrypto-ossl/issues/136 --- sea/verify.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/sea/verify.js b/sea/verify.js index 909bed30..849161e4 100644 --- a/sea/verify.js +++ b/sea/verify.js @@ -6,6 +6,15 @@ var parse = require('./parse'); var u; + + var knownKeys = {}; + var keyForPair = pair => { + if (knownKeys[pair]) return knownKeys[pair]; + const jwk = S.jwk(pair); + knownKeys[pair] = (shim.ossl || shim.subtle).importKey("jwk", jwk, S.ecdsa.pair, false, ["verify"]); + return knownKeys[pair]; + }; + SEA.verify = SEA.verify || (async (data, pair, cb, opt) => { try { const json = parse(data) if(false === pair){ // don't verify! @@ -18,9 +27,8 @@ opt = opt || {}; // SEA.I // verify is free! Requires no user permission. if(json === data){ throw "No signature on data." } - const pub = pair.pub || pair - const jwk = S.jwk(pub) - const key = await (shim.ossl || shim.subtle).importKey('jwk', jwk, S.ecdsa.pair, false, ['verify']) + const pub = pair.pub || pair; + const key = await keyForPair(pub); const hash = await sha256hash(json.m) var buf; var sig; var check; try{ buf = shim.Buffer.from(json.s, opt.encode || 'base64') // NEW DEFAULT! @@ -46,4 +54,4 @@ }}); module.exports = SEA.verify; - \ No newline at end of file + From 9f9b3e557c1b9f9ff3d774edcec30271a596d19a Mon Sep 17 00:00:00 2001 From: Mark Nadal Date: Fri, 4 Jan 2019 20:38:26 -0800 Subject: [PATCH 04/11] unbuild --- src/adapters/localStorage.js | 4 ++-- src/adapters/mesh.js | 3 ++- src/chain.js | 6 +++--- src/get.js | 2 +- src/onto.js | 4 ++-- src/root.js | 2 +- src/state.js | 4 ++-- src/val.js | 2 +- 8 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/adapters/localStorage.js b/src/adapters/localStorage.js index 5c8f0302..d2a170f7 100644 --- a/src/adapters/localStorage.js +++ b/src/adapters/localStorage.js @@ -84,7 +84,7 @@ Gun.on('create', function(root){ var disk = Gun.obj.ify(store.getItem(opt.prefix)) || {}; var lS = function(){}, u; root.on('localStorage', disk); // NON-STANDARD EVENT! - + root.on('put', function(at){ this.to.next(at); Gun.graph.is(at.put, null, map); @@ -130,7 +130,7 @@ Gun.on('create', function(root){ acks = {}; if(data){ disk = data } try{store.setItem(opt.prefix, JSON.stringify(disk)); - }catch(e){ + }catch(e){ Gun.log(err = (e || "localStorage failure") + " Consider using GUN's IndexedDB plugin for RAD for more storage space, temporary example at https://github.com/amark/gun/blob/master/test/tmp/indexedDB.html ."); root.on('localStorage:error', {err: err, file: opt.prefix, flush: disk, retry: flush}); } diff --git a/src/adapters/mesh.js b/src/adapters/mesh.js index 790beaed..f26e819f 100644 --- a/src/adapters/mesh.js +++ b/src/adapters/mesh.js @@ -57,8 +57,9 @@ function Mesh(ctx){ } return; } + ctx.on('in', msg); - + return; } else if('[' === tmp){ diff --git a/src/chain.js b/src/chain.js index c2a1d20d..0105cd07 100644 --- a/src/chain.js +++ b/src/chain.js @@ -139,7 +139,7 @@ function input(msg){ //if(tmp[cat.id]){ return } tmp.is = tmp.is || at.put; tmp[cat.id] = at.put || true; - //if(root.stop){ + //if(root.stop){ eve.to.next(msg) //} relate(cat, msg, at, rel); @@ -152,7 +152,7 @@ function relate(at, msg, from, rel){ var tmp = (at.root.$.get(rel)._); if(at.has){ from = tmp; - } else + } else if(from.has){ relate(from, msg, from, rel); } @@ -204,7 +204,7 @@ function map(data, key){ // Map over only the changes on every update. if(tmp = via.$){ tmp = (chain = via.$.get(key))._; if(u === tmp.put || !Gun.val.link.is(data)){ - tmp.put = data; + tmp.put = data; } } at.on('in', { diff --git a/src/get.js b/src/get.js index 6f5ab51d..a71e08ce 100644 --- a/src/get.js +++ b/src/get.js @@ -88,7 +88,7 @@ function use(msg){ //root.stop && (root.stop.ID = root.stop.ID || Gun.text.random(2)); //if((tmp = root.stop) && (tmp = tmp[at.id] || (tmp[at.id] = {})) && tmp[cat.id]){ return } tmp && (tmp[cat.id] = true); - if(eve.seen && at.id && eve.seen[at.id]){ return eve.to.next(msg) } + if(eve.seen && at.id && eve.seen[at.id]){ return eve.to.next(msg) } //if((tmp = root.stop)){ if(tmp[at.id]){ return } tmp[at.id] = msg.root; } // temporary fix till a better solution? if((tmp = data) && tmp[rel._] && (tmp = rel.is(tmp))){ tmp = ((msg.$$ = at.root.gun.get(tmp))._); diff --git a/src/onto.js b/src/onto.js index 78827257..4851d32b 100644 --- a/src/onto.js +++ b/src/onto.js @@ -5,13 +5,13 @@ module.exports = function onto(tag, arg, as){ var u, tag = (this.tag || (this.tag = {}))[tag] || (this.tag[tag] = {tag: tag, to: onto._ = { next: function(arg){ var tmp; - if((tmp = this.to)){ + if((tmp = this.to)){ tmp.next(arg); }} }}); if(arg instanceof Function){ var be = { - off: onto.off || + off: onto.off || (onto.off = function(){ if(this.next === onto._.next){ return !0 } if(this === this.the.last){ diff --git a/src/root.js b/src/root.js index 242f53ee..642a96b7 100644 --- a/src/root.js +++ b/src/root.js @@ -104,7 +104,7 @@ Gun.dup = require('./dup'); if(!at){ if(!(cat.opt||empty).super){ ctx.souls[soul] = false; - return; + return; } at = (ctx.$.get(soul)._); } diff --git a/src/state.js b/src/state.js index 16ef2ca6..1cddd3f1 100644 --- a/src/state.js +++ b/src/state.js @@ -26,10 +26,10 @@ State.lex = function(){ return State().toString(36).replace('.','') } State.ify = function(n, k, s, v, soul){ // put a key's state on a node. if(!n || !n[N_]){ // reject if it is not node-like. if(!soul){ // unless they passed a soul - return; + return; } n = Node.soul.ify(n, soul); // then make it so! - } + } var tmp = obj_as(n[N_], State._); // grab the states data. if(u !== k && k !== N_){ if(num_is(s)){ diff --git a/src/val.js b/src/val.js index fa4ac8f7..fb7cf4ee 100644 --- a/src/val.js +++ b/src/val.js @@ -7,7 +7,7 @@ Val.is = function(v){ // Valid values are a subset of JSON: null, binary, number if(v === Infinity){ return false } // we want this to be, but JSON does not support it, sad face. if(text_is(v) // by "text" we mean strings. || bi_is(v) // by "binary" we mean boolean. - || num_is(v)){ // by "number" we mean integers or decimals. + || num_is(v)){ // by "number" we mean integers or decimals. return true; // simple values are valid. } return Val.rel.is(v) || false; // is the value a soul relation? Then it is valid and return it. If not, everything else remaining is an invalid data type. Custom extensions can be built on top of these primitives to support other types. From 2c110df3959caa859680932574d55a5122a8f5bf Mon Sep 17 00:00:00 2001 From: Mark Nadal Date: Sun, 6 Jan 2019 01:26:26 -0800 Subject: [PATCH 05/11] initial tests for SEA corrections --- sea.js | 232 +++++---- test/mocha.html | 2 + test/sea/sea.js | 1132 ++++++------------------------------------- test/sea/sea2.js | 61 --- test/sea/sea_old.js | 1009 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 1274 insertions(+), 1162 deletions(-) delete mode 100644 test/sea/sea2.js create mode 100644 test/sea/sea_old.js diff --git a/sea.js b/sea.js index 73f8f8ad..2384fbbb 100644 --- a/sea.js +++ b/sea.js @@ -190,71 +190,51 @@ })(USE, './shim'); ;USE(function(module){ - const SEA = USE('./root'); - const Buffer = USE('./buffer') - const settings = {} - // Encryption parameters - const pbkdf2 = { hash: 'SHA-256', iter: 100000, ks: 64 } + var SEA = USE('./root'); + var Buffer = USE('./buffer'); + var s = {}; + s.pbkdf2 = {hash: 'SHA-256', iter: 100000, ks: 64}; + s.ecdsa = { + pair: {name: 'ECDSA', namedCurve: 'P-256'}, + sign: {name: 'ECDSA', hash: {name: 'SHA-256'}} + }; + s.ecdh = {name: 'ECDH', namedCurve: 'P-256'}; - const ecdsaSignProps = { name: 'ECDSA', hash: { name: 'SHA-256' } } - const ecdsaKeyProps = { name: 'ECDSA', namedCurve: 'P-256' } - const ecdhKeyProps = { name: 'ECDH', namedCurve: 'P-256' } - - const _initial_authsettings = { - validity: 12 * 60 * 60, // internally in seconds : 12 hours - hook: (props) => props // { iat, exp, alias, remember } - // or return new Promise((resolve, reject) => resolve(props) - } - // These are used to persist user's authentication "session" - const authsettings = Object.assign({}, _initial_authsettings) // This creates Web Cryptography API compliant JWK for sign/verify purposes - const keysToEcdsaJwk = (pub, d) => { // d === priv - //const [ x, y ] = Buffer.from(pub, 'base64').toString('utf8').split(':') // old - const [ x, y ] = pub.split('.') // new - var jwk = { kty: "EC", crv: "P-256", x: x, y: y, ext: true } + s.jwk = function(pub, d){ // d === priv + pub = pub.split('.'); + var x = pub[0], y = pub[1]; + var jwk = {kty: "EC", crv: "P-256", x: x, y: y, ext: true}; jwk.key_ops = d ? ['sign'] : ['verify']; if(d){ jwk.d = d } return jwk; - } + }; + s.recall = { + validity: 12 * 60 * 60, // internally in seconds : 12 hours + hook: function(props){ return props } // { iat, exp, alias, remember } // or return new Promise((resolve, reject) => resolve(props) + }; - Object.assign(settings, { - pbkdf2: pbkdf2, - ecdsa: { - pair: ecdsaKeyProps, - sign: ecdsaSignProps - }, - ecdh: ecdhKeyProps, - jwk: keysToEcdsaJwk, - recall: authsettings - }) - SEA.opt = settings; - module.exports = settings + SEA.opt = s; + module.exports = s })(USE, './settings'); ;USE(function(module){ - module.exports = (props) => { - try { - if(props.slice && 'SEA{' === props.slice(0,4)){ - props = props.slice(3); - } - return props.slice ? JSON.parse(props) : props + module.exports = function p(t){ try { + var yes = (typeof t == 'string'); + if(yes && 'SEA{' === t.slice(0,4)){ t = t.slice(3) } + return yes ? JSON.parse(t) : t; } catch (e) {} //eslint-disable-line no-empty - return props + return t; } })(USE, './parse'); ;USE(function(module){ - const shim = USE('./shim'); - const Buffer = USE('./buffer') - const parse = USE('./parse') - const { pbkdf2 } = USE('./settings') - // This internal func returns SHA-256 hashed data for signing - const sha256hash = async (mm) => { - const m = parse(mm) - const hash = await shim.subtle.digest({name: pbkdf2.hash}, new shim.TextEncoder().encode(m)) - return Buffer.from(hash) + var shim = USE('./shim'); + module.exports = async function(d, o){ + var t = (typeof d == 'string')? d : JSON.stringify(d); + var hash = await shim.subtle.digest({name: o||'SHA-256'}, new shim.TextEncoder().encode(t)); + return shim.Buffer.from(hash); } - module.exports = sha256hash })(USE, './sha256'); ;USE(function(module){ @@ -281,25 +261,25 @@ salt = u; } salt = salt || shim.random(9); - if('SHA-256' === opt.name){ - var rsha = shim.Buffer.from(await sha(data), 'binary').toString(opt.encode || 'base64') + data = (typeof data == 'string')? data : JSON.stringify(data); + if('sha' === (opt.name||'').toLowerCase().slice(0,3)){ + var rsha = shim.Buffer.from(await sha(data, opt.name), 'binary').toString(opt.encode || 'base64') if(cb){ try{ cb(rsha) }catch(e){console.log(e)} } return rsha; } - const key = await (shim.ossl || shim.subtle).importKey( - 'raw', new shim.TextEncoder().encode(data), { name: opt.name || 'PBKDF2' }, false, ['deriveBits'] - ) - const result = await (shim.ossl || shim.subtle).deriveBits({ + var key = await (shim.ossl || shim.subtle).importKey('raw', new shim.TextEncoder().encode(data), {name: opt.name || 'PBKDF2'}, false, ['deriveBits']); + var work = await (shim.ossl || shim.subtle).deriveBits({ name: opt.name || 'PBKDF2', iterations: opt.iterations || S.pbkdf2.iter, salt: new shim.TextEncoder().encode(opt.salt || salt), hash: opt.hash || S.pbkdf2.hash, }, key, opt.length || (S.pbkdf2.ks * 8)) data = shim.random(data.length) // Erase data in case of passphrase - const r = shim.Buffer.from(result, 'binary').toString(opt.encode || 'base64') + var r = shim.Buffer.from(work, 'binary').toString(opt.encode || 'base64') if(cb){ try{ cb(r) }catch(e){console.log(e)} } return r; } catch(e) { + console.log(e); SEA.err = e; if(SEA.throw){ throw e } if(cb){ cb() } @@ -313,7 +293,6 @@ var SEA = USE('./root'); var shim = USE('./shim'); var S = USE('./settings'); - var Buff = (typeof Buffer !== 'undefined')? Buffer : shim.Buffer; SEA.name = SEA.name || (async (cb, opt) => { try { if(cb){ try{ cb() }catch(e){console.log(e)} } @@ -329,17 +308,17 @@ //SEA.pair = async (data, proof, cb) => { try { SEA.pair = SEA.pair || (async (cb, opt) => { try { - const ecdhSubtle = shim.ossl || shim.subtle + var ecdhSubtle = shim.ossl || shim.subtle; // First: ECDSA keys for signing/verifying... var sa = await shim.subtle.generateKey(S.ecdsa.pair, true, [ 'sign', 'verify' ]) .then(async (keys) => { // privateKey scope doesn't leak out from here! //const { d: priv } = await shim.subtle.exportKey('jwk', keys.privateKey) - const key = {}; + var key = {}; key.priv = (await shim.subtle.exportKey('jwk', keys.privateKey)).d; - const pub = await shim.subtle.exportKey('jwk', keys.publicKey) + var pub = await shim.subtle.exportKey('jwk', keys.publicKey); //const pub = Buff.from([ x, y ].join(':')).toString('base64') // old - key.pub = pub.x+'.'+pub.y // new + key.pub = pub.x+'.'+pub.y; // new // x and y are already base64 // pub is UTF8 but filename/URL safe (https://www.ietf.org/rfc/rfc3986.txt) // but split on a non-base64 letter. @@ -354,11 +333,11 @@ var dh = await ecdhSubtle.generateKey(S.ecdh, true, ['deriveKey']) .then(async (keys) => { // privateKey scope doesn't leak out from here! - const key = {}; + var key = {}; key.epriv = (await ecdhSubtle.exportKey('jwk', keys.privateKey)).d; - const pub = await ecdhSubtle.exportKey('jwk', keys.publicKey) + var pub = await ecdhSubtle.exportKey('jwk', keys.publicKey); //const epub = Buff.from([ ex, ey ].join(':')).toString('base64') // old - key.epub = pub.x+'.'+pub.y // new + key.epub = pub.x+'.'+pub.y; // new // ex and ey are already base64 // epub is UTF8 but filename/URL safe (https://www.ietf.org/rfc/rfc3986.txt) // but split on a non-base64 letter. @@ -370,7 +349,7 @@ else { throw e } } dh = dh || {}; - const r = { pub: sa.pub, priv: sa.priv, /* pubId, */ epub: dh.epub, epriv: dh.epriv } + var r = { pub: sa.pub, priv: sa.priv, /* pubId, */ epub: dh.epub, epriv: dh.epriv } if(cb){ try{ cb(r) }catch(e){console.log(e)} } return r; } catch(e) { @@ -388,7 +367,8 @@ var SEA = USE('./root'); var shim = USE('./shim'); var S = USE('./settings'); - var sha256hash = USE('./sha256'); + var sha = USE('./sha256'); + var parse = USE('./parse'); var u; SEA.sign = SEA.sign || (async (data, pair, cb, opt) => { try { @@ -396,13 +376,15 @@ if(!(pair||opt).priv){ pair = await SEA.I(null, {what: data, how: 'sign', why: opt.why}); } - const pub = pair.pub - const priv = pair.priv - const jwk = S.jwk(pub, priv) - const hash = await sha256hash(JSON.stringify(data)) - const sig = await (shim.ossl || shim.subtle).importKey('jwk', jwk, S.ecdsa.pair, false, ['sign']) + if(u === data){ throw '`undefined` not allowed.' } + data = parse(data); + var pub = pair.pub; + var priv = pair.priv; + var jwk = S.jwk(pub, priv); + var hash = await sha(data); + var sig = await (shim.ossl || shim.subtle).importKey('jwk', jwk, S.ecdsa.pair, false, ['sign']) .then((key) => (shim.ossl || shim.subtle).sign(S.ecdsa.sign, key, new Uint8Array(hash))) // privateKey scope doesn't leak out from here! - const r = 'SEA'+JSON.stringify({m: data, s: shim.Buffer.from(sig, 'binary').toString(opt.encode || 'base64')}); + var r = 'SEA'+JSON.stringify({m: data, s: shim.Buffer.from(sig, 'binary').toString(opt.encode || 'base64')}); if(cb){ try{ cb(r) }catch(e){console.log(e)} } return r; @@ -421,14 +403,22 @@ var SEA = USE('./root'); var shim = USE('./shim'); var S = USE('./settings'); - var sha256hash = USE('./sha256'); + var sha = USE('./sha256'); var parse = USE('./parse'); var u; + var knownKeys = {}; + var keyForPair = pair => { + if (knownKeys[pair]) return knownKeys[pair]; + var jwk = S.jwk(pair); + knownKeys[pair] = (shim.ossl || shim.subtle).importKey("jwk", jwk, S.ecdsa.pair, false, ["verify"]); + return knownKeys[pair]; + }; + SEA.verify = SEA.verify || (async (data, pair, cb, opt) => { try { - const json = parse(data) + var json = parse(data); if(false === pair){ // don't verify! - const raw = (json !== data)? + var raw = (json !== data)? (json.s && json.m)? parse(json.m) : data : json; if(cb){ try{ cb(raw) }catch(e){console.log(e)} } @@ -436,23 +426,21 @@ } opt = opt || {}; // SEA.I // verify is free! Requires no user permission. - if(json === data){ throw "No signature on data." } - const pub = pair.pub || pair - const jwk = S.jwk(pub) - const key = await (shim.ossl || shim.subtle).importKey('jwk', jwk, S.ecdsa.pair, false, ['verify']) - const hash = await sha256hash(json.m) + var pub = pair.pub || pair; + var key = await keyForPair(pub); + var hash = await sha(json.m); var buf; var sig; var check; try{ - buf = shim.Buffer.from(json.s, opt.encode || 'base64') // NEW DEFAULT! - sig = new Uint8Array(buf) - check = await (shim.ossl || shim.subtle).verify(S.ecdsa.sign, key, sig, new Uint8Array(hash)) + buf = shim.Buffer.from(json.s, opt.encode || 'base64'); // NEW DEFAULT! + sig = new Uint8Array(buf); + check = await (shim.ossl || shim.subtle).verify(S.ecdsa.sign, key, sig, new Uint8Array(hash)); if(!check){ throw "Signature did not match." } }catch(e){ - buf = shim.Buffer.from(json.s, 'utf8') // AUTO BACKWARD OLD UTF8 DATA! - sig = new Uint8Array(buf) - check = await (shim.ossl || shim.subtle).verify(S.ecdsa.sign, key, sig, new Uint8Array(hash)) + buf = shim.Buffer.from(json.s, 'utf8'); // AUTO BACKWARD OLD UTF8 DATA! + sig = new Uint8Array(buf); + check = await (shim.ossl || shim.subtle).verify(S.ecdsa.sign, key, sig, new Uint8Array(hash)); if(!check){ throw "Signature did not match." } } - const r = check? parse(json.m) : u; + var r = check? parse(json.m) : u; if(cb){ try{ cb(r) }catch(e){console.log(e)} } return r; @@ -486,21 +474,22 @@ var shim = USE('./shim'); var S = USE('./settings'); var aeskey = USE('./aeskey'); + var u; SEA.encrypt = SEA.encrypt || (async (data, pair, cb, opt) => { try { opt = opt || {}; var key = (pair||opt).epriv || pair; + if(u === data){ throw '`undefined` not allowed.' } if(!key){ pair = await SEA.I(null, {what: data, how: 'encrypt', why: opt.why}); key = pair.epriv || pair; } - const msg = JSON.stringify(data) - const rand = {s: shim.random(8), iv: shim.random(16)}; - const ct = await aeskey(key, rand.s, opt) - .then((aes) => (/*shim.ossl ||*/ shim.subtle).encrypt({ // Keeping the AES key scope as private as possible... + var msg = (typeof data == 'string')? data : JSON.stringify(data); + var rand = {s: shim.random(8), iv: shim.random(16)}; + var ct = await aeskey(key, rand.s, opt).then((aes) => (/*shim.ossl ||*/ shim.subtle).encrypt({ // Keeping the AES key scope as private as possible... name: opt.name || 'AES-GCM', iv: new Uint8Array(rand.iv) - }, aes, new shim.TextEncoder().encode(msg))) - const r = 'SEA'+JSON.stringify({ + }, aes, new shim.TextEncoder().encode(msg))); + var r = 'SEA'+JSON.stringify({ ct: shim.Buffer.from(ct, 'binary').toString(opt.encode || 'base64'), iv: rand.iv.toString(opt.encode || 'base64'), s: rand.s.toString(opt.encode || 'base64') @@ -509,6 +498,7 @@ if(cb){ try{ cb(r) }catch(e){console.log(e)} } return r; } catch(e) { + console.log(e); SEA.err = e; if(SEA.throw){ throw e } if(cb){ cb() } @@ -532,23 +522,23 @@ pair = await SEA.I(null, {what: data, how: 'decrypt', why: opt.why}); key = pair.epriv || pair; } - const json = parse(data) + var json = parse(data); - var buf; try{buf = shim.Buffer.from(json.s, opt.encode || 'base64') // NEW DEFAULT! + var buf; try{buf = shim.Buffer.from(json.s, opt.encode || 'base64'); // NEW DEFAULT! }catch(e){buf = shim.Buffer.from(json.s, 'utf8')} // AUTO BACKWARD OLD UTF8 DATA! - var bufiv; try{bufiv = shim.Buffer.from(json.iv, opt.encode || 'base64') // NEW DEFAULT! + var bufiv; try{bufiv = shim.Buffer.from(json.iv, opt.encode || 'base64'); // NEW DEFAULT! }catch(e){bufiv = shim.Buffer.from(json.iv, 'utf8')} // AUTO BACKWARD OLD UTF8 DATA! - var bufct; try{bufct = shim.Buffer.from(json.ct, opt.encode || 'base64') // NEW DEFAULT! + var bufct; try{bufct = shim.Buffer.from(json.ct, opt.encode || 'base64'); // NEW DEFAULT! }catch(e){bufct = shim.Buffer.from(json.ct, 'utf8')} // AUTO BACKWARD OLD UTF8 DATA! - const ct = await aeskey(key, buf, opt) - .then((aes) => (/*shim.ossl ||*/ shim.subtle).decrypt({ // Keeping aesKey scope as private as possible... + var ct = await aeskey(key, buf, opt).then((aes) => (/*shim.ossl ||*/ shim.subtle).decrypt({ // Keeping aesKey scope as private as possible... name: opt.name || 'AES-GCM', iv: new Uint8Array(bufiv) - }, aes, new Uint8Array(bufct))) - const r = parse(new shim.TextDecoder('utf8').decode(ct)) + }, aes, new Uint8Array(bufct))); + var r = parse(new shim.TextDecoder('utf8').decode(ct)); if(cb){ try{ cb(r) }catch(e){console.log(e)} } return r; } catch(e) { + console.log(e); SEA.err = e; if(SEA.throw){ throw e } if(cb){ cb() } @@ -568,36 +558,34 @@ if(!pair || !pair.epriv || !pair.epub){ pair = await SEA.I(null, {what: key, how: 'secret', why: opt.why}); } - const pub = key.epub || key - const epub = pair.epub - const epriv = pair.epriv - const ecdhSubtle = shim.ossl || shim.subtle - const pubKeyData = keysToEcdhJwk(pub) - const props = Object.assign( - S.ecdh, - { public: await ecdhSubtle.importKey(...pubKeyData, true, []) } - ) - const privKeyData = keysToEcdhJwk(epub, epriv) - const derived = await ecdhSubtle.importKey(...privKeyData, false, ['deriveKey']) - .then(async (privKey) => { + var pub = key.epub || key; + var epub = pair.epub; + var epriv = pair.epriv; + var ecdhSubtle = shim.ossl || shim.subtle; + var pubKeyData = keysToEcdhJwk(pub); + var props = Object.assign(S.ecdh, { public: await ecdhSubtle.importKey(...pubKeyData, true, []) }); + var privKeyData = keysToEcdhJwk(epub, epriv); + var derived = await ecdhSubtle.importKey(...privKeyData, false, ['deriveKey']).then(async (privKey) => { // privateKey scope doesn't leak out from here! - const derivedKey = await ecdhSubtle.deriveKey(props, privKey, { name: 'AES-GCM', length: 256 }, true, [ 'encrypt', 'decrypt' ]) - return ecdhSubtle.exportKey('jwk', derivedKey).then(({ k }) => k) + var derivedKey = await ecdhSubtle.deriveKey(props, privKey, { name: 'AES-GCM', length: 256 }, true, [ 'encrypt', 'decrypt' ]); + return ecdhSubtle.exportKey('jwk', derivedKey).then(({ k }) => k); }) - const r = derived; + var r = derived; if(cb){ try{ cb(r) }catch(e){console.log(e)} } return r; - } catch(e) { + } catch(e) { + console.log(e); SEA.err = e; if(SEA.throw){ throw e } if(cb){ cb() } return; }}); - const keysToEcdhJwk = (pub, d) => { // d === priv - //const [ x, y ] = Buffer.from(pub, 'base64').toString('utf8').split(':') // old - const [ x, y ] = pub.split('.') // new - const jwk = d ? { d: d } : {} + // can this be replaced with settings.jwk? + var keysToEcdhJwk = (pub, d) => { // d === priv + //var [ x, y ] = Buffer.from(pub, 'base64').toString('utf8').split(':') // old + var [ x, y ] = pub.split('.') // new + var jwk = d ? { d: d } : {} return [ // Use with spread returned value... 'jwk', Object.assign( diff --git a/test/mocha.html b/test/mocha.html index 86e4d79e..13aee439 100644 --- a/test/mocha.html +++ b/test/mocha.html @@ -17,7 +17,9 @@ + + - + +