Merge branch 'master' into dev

This commit is contained in:
Mark Nadal 2019-01-26 02:13:21 -08:00
commit 428f7e9d1c
36 changed files with 2044 additions and 1874 deletions

View File

@ -3,8 +3,6 @@ branches:
except:
- debug
node_js:
- 4
- 6
- 8
- 10
cache:

23
gun.js
View File

@ -959,7 +959,6 @@
function output(msg){
var put, get, at = this.as, back = at.back, root = at.root, tmp;
if(!msg.I){ msg.I = at.$ }
if(!msg.$){ msg.$ = at.$ }
this.to.next(msg);
if(get = msg.get){
@ -1215,7 +1214,6 @@
at.on('in', {get: at.get, put: Gun.val.link.ify(get['#']), $: at.$, '@': msg['@']});
return;
}
msg.$ = at.root.$;
Gun.on.put(msg, at.root.$);
}
var empty = {}, u;
@ -1312,7 +1310,7 @@
//else if(!cat.async && msg.put !== at.put && root.stop && root.stop[at.id]){ return } root.stop && (root.stop[at.id] = true);
//root.stop && (root.stop.ID = root.stop.ID || Gun.text.random(2));
//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((tmp = root.stop)){ if(tmp[at.id]){ return } tmp[at.id] = msg.root; } // temporary fix till a better solution?
@ -1806,13 +1804,13 @@
});
});
setTimeout(function(){
root.on('out', {put: send, '#': root.ask(ack), I: root.$});
root.on('out', {put: send, '#': root.ask(ack)});
},1);
}
root.on('out', function(msg){
if(msg.lS){ return }
if(msg.I && msg.put && !msg['@'] && !empty(opt.peers)){
if(Gun.is(msg.$) && msg.put && !msg['@'] && !empty(opt.peers)){
id = msg['#'];
Gun.graph.is(msg.put, null, map);
if(!to){ to = setTimeout(flush, opt.wait || 1) }
@ -1888,7 +1886,7 @@
return; // Hmm, what if we have peers but we are disconnected?
}
//console.log("lS get", lex, data);
root.on('in', {'@': msg['#'], put: Gun.graph.node(data), how: 'lS', lS: msg.I});
root.on('in', {'@': msg['#'], put: Gun.graph.node(data), how: 'lS', lS: msg.$ || root.$});
};
Gun.debug? setTimeout(to,1) : to();
});
@ -1938,8 +1936,8 @@
if((tmp = msg['@'])
&& (tmp = ctx.dup.s[tmp])
&& (tmp = tmp.it)
&& tmp.mesh){
mesh.say(msg, tmp.mesh.via, 1);
&& tmp._){
mesh.say(msg, (tmp._).via, 1);
tmp['##'] = msg['##'];
return;
}
@ -1971,9 +1969,9 @@
(tmp = dup.s)[hash] = tmp[id];
}
}
(msg.mesh = function(){}).via = peer;
(msg._ = function(){}).via = peer;
if((tmp = msg['><'])){
msg.mesh.to = Type.obj.map(tmp.split(','), function(k,i,m){m(k,true)});
(msg._).to = Type.obj.map(tmp.split(','), function(k,i,m){m(k,true)});
}
if(msg.dam){
if(tmp = mesh.hear[msg.dam]){
@ -1981,7 +1979,6 @@
}
return;
}
ctx.on('in', msg);
return;
@ -2012,7 +2009,7 @@
}
var tmp, wire = peer.wire || ((opt.wire) && opt.wire(peer)), msh, raw;// || open(peer, ctx); // TODO: Reopen!
if(!wire){ return }
msh = msg.mesh || empty;
msh = (msg._) || empty;
if(peer === msh.via){ return }
if(!(raw = msh.raw)){ raw = mesh.raw(msg) }
if((tmp = msg['@'])
@ -2063,7 +2060,7 @@
mesh.raw = function(msg){
if(!msg){ return '' }
var dup = ctx.dup, msh = msg.mesh || {}, put, hash, tmp;
var dup = ctx.dup, msh = (msg._) || {}, put, hash, tmp;
if(tmp = msh.raw){ return tmp }
if(typeof msg === 'string'){ return msg }
if(msg['@'] && (tmp = msg.put)){

3
gun.min.js vendored

File diff suppressed because one or more lines are too long

View File

@ -144,7 +144,7 @@
f.each = function(val, key, k, pre){
if(u !== val){ f.count++ }
if(opt.pack <= (val||'').length){ return cb("Record too big!"), true }
var enc = Radisk.encode(pre.length) +'#'+ Radisk.encode(k) + (u === val? '' : '='+ Radisk.encode(val)) +'\n';
var enc = Radisk.encode(pre.length) +'#'+ Radisk.encode(k) + (u === val? '' : ':'+ Radisk.encode(val)) +'\n';
if((opt.chunk < f.text.length + enc.length) && (1 < f.count) && !force){
f.text = '';
f.limit = Math.ceil(f.count/2);
@ -270,7 +270,7 @@
}
tmp = p.split(tmp[2])||'';
if('\n' == tmp[0]){ continue }
if('=' == tmp[0]){ v = tmp[1] }
if('=' == tmp[0] || ':' == tmp[0]){ v = tmp[1] }
if(u !== k && u !== v){ p.disk(pre.join(''), v) }
tmp = p.split(tmp[2]);
}

View File

@ -3,16 +3,11 @@ var Gun = (typeof window !== "undefined")? window.Gun : require('../gun');
Gun.on('create', function(root){
this.to.next(root);
var opt = root.opt, u;
if(typeof window !== "undefined"){
opt.window = window;
}
//if(true !== opt.radisk && (!opt.window && !process.env.RAD_ENV && !process.env.AWS_S3_BUCKET) && false !== opt.localStorage){ return }
//if(true !== opt.radisk){ return }
if(false === opt.radisk){ return }
var Radisk = (opt.window && opt.window.Radisk) || require('./radisk');
var Radisk = (Gun.window && Gun.window.Radisk) || require('./radisk');
var Radix = Radisk.Radix;
opt.store = opt.store || (!opt.window && require('./rfs')(opt));
opt.store = opt.store || (!Gun.window && require('./rfs')(opt));
var rad = Radisk(opt), esc = String.fromCharCode(27);
root.on('put', function(msg){
@ -41,8 +36,14 @@ Gun.on('create', function(root){
var id = msg['#'], soul = msg.get['#'], key = msg.get['.']||'', tmp = soul+'.'+key, node;
rad(tmp, function(err, val){
if(val){
if(val && typeof val !== 'string'){ Radix.map(val, each) }
if(!node){ each(val, key) }
if(val && typeof val !== 'string'){
if(key){
val = u;
} else {
Radix.map(val, each)
}
}
if(!node && val){ each(val, key) }
}
root.on('in', {'@': id, put: Gun.graph.node(node), err: err? err : u, rad: Radix});
});

View File

@ -20,7 +20,7 @@
opt.RTCPeerConnection = rtcpc;
opt.RTCSessionDescription = rtcsd;
opt.RTCIceCandidate = rtcic;
opt.webrtc = opt.webrtc || {'iceServers': [
opt.rtc = opt.rtc || {'iceServers': [
{url: 'stun:stun.l.google.com:19302'},
{url: "stun:stun.sipgate.net:3478"},
{url: "stun:stun.stunprotocol.org"},
@ -28,33 +28,33 @@
{url: "stun:217.10.68.152:10000"},
{url: 'stun:stun.services.mozilla.com'}
]};
opt.webrtc.dataChannel = opt.webrtc.dataChannel || {ordered: false, maxRetransmits: 2};
opt.webrtc.sdp = opt.webrtc.sdp || {mandatory: {OfferToReceiveAudio: false, OfferToReceiveVideo: false}};
opt.rtc.dataChannel = opt.rtc.dataChannel || {ordered: false, maxRetransmits: 2};
opt.rtc.sdp = opt.rtc.sdp || {mandatory: {OfferToReceiveAudio: false, OfferToReceiveVideo: false}};
var mesh = opt.mesh = opt.mesh || Gun.Mesh(root);
root.on('create', function(at){
this.to.next(at);
setTimeout(function(){ root.on('out', {webrtc: {id: opt.pid}}) },1); // announce ourself
setTimeout(function(){ root.on('out', {rtc: {id: opt.pid}}) },1); // announce ourself
});
root.on('in', function(msg){
if(msg.webrtc){ open(msg) }
if(msg.rtc){ open(msg) }
this.to.next(msg);
});
function open(msg){
var rtc = msg.webrtc, peer, tmp;
var rtc = msg.rtc, peer, tmp;
if(!rtc || !rtc.id){ return }
if(tmp = rtc.answer){
if(!(peer = opt.peers[rtc.id]) || peer.remoteSet){ return }
return peer.setRemoteDescription(peer.remoteSet = new opt.RTCSessionDescription(tmp));
}
if(tmp = rtc.candidate){
peer = opt.peers[rtc.id] || open({webrtc: {id: rtc.id}});
peer = opt.peers[rtc.id] || open({rtc: {id: rtc.id}});
return peer.addIceCandidate(new opt.RTCIceCandidate(tmp));
}
if(opt.peers[rtc.id]){ return }
(peer = new opt.RTCPeerConnection(opt.webrtc)).id = rtc.id;
var wire = peer.wire = peer.createDataChannel('dc', opt.webrtc.dataChannel);
(peer = new opt.RTCPeerConnection(opt.rtc)).id = rtc.id;
var wire = peer.wire = peer.createDataChannel('dc', opt.rtc.dataChannel);
mesh.hi(peer);
wire.onclose = function(){
mesh.bye(peer);
@ -77,7 +77,7 @@
};
peer.onicecandidate = function(e){ // source: EasyRTC!
if(!e.candidate){ return }
root.on('out', {'@': msg['#'], webrtc: {candidate: e.candidate, id: opt.pid}});
root.on('out', {'@': msg['#'], rtc: {candidate: e.candidate, id: opt.pid}});
}
peer.ondatachannel = function(e){
var rc = e.channel;
@ -89,14 +89,14 @@
peer.setRemoteDescription(new opt.RTCSessionDescription(tmp));
peer.createAnswer(function(answer){
peer.setLocalDescription(answer);
root.on('out', {'@': msg['#'], webrtc: {answer: answer, id: opt.pid}});
}, function(){}, opt.webrtc.sdp);
root.on('out', {'@': msg['#'], rtc: {answer: answer, id: opt.pid}});
}, function(){}, opt.rtc.sdp);
return;
}
peer.createOffer(function(offer){
peer.setLocalDescription(offer);
root.on('out', {'@': msg['#'], webrtc: {offer: offer, id: opt.pid}});
}, function(){}, opt.webrtc.sdp);
root.on('out', {'@': msg['#'], rtc: {offer: offer, id: opt.pid}});
}, function(){}, opt.rtc.sdp);
return peer;
}
});

4
nts.js
View File

@ -31,7 +31,7 @@
Gun.state.drift = Gun.state.drift || 0;
setTimeout(function ping(){
var NTS = {}, ack = Gun.text.random(), msg = {'#': ack, nts: true, gun: ctx.gun};
var NTS = {}, ack = Gun.text.random(), msg = {'#': ack, nts: true};
NTS.start = Gun.state();
ask[ack] = function(at){
NTS.end = Gun.state();
@ -46,4 +46,4 @@
}, 1);
});
// test by opening up examples/game/nts.html on devices that aren't NTP synced.
}());
}());

197
package-lock.json generated
View File

@ -1,33 +1,9 @@
{
"name": "gun",
"version": "0.9.99996",
"version": "0.9.999998",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@trust/keyto": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/@trust/keyto/-/keyto-0.3.4.tgz",
"integrity": "sha512-OAqKvuSEPIu2zCnIHzBthvGnV8nKmpv7cBlRMngLzJZzZI9CanyuSfnEI1xC4sH4TwqA0XJR7Mb0oX4bwymXIw==",
"dev": true,
"requires": {
"asn1.js": "^4.9.1",
"base64url": "^3.0.0",
"elliptic": "^6.4.0"
}
},
"@trust/webcrypto": {
"version": "0.9.2",
"resolved": "https://registry.npmjs.org/@trust/webcrypto/-/webcrypto-0.9.2.tgz",
"integrity": "sha512-5iMAVcGYKhqLJGjefB1nzuQSqUJTru0nG4CytpBT/GGp1Piz/MVnj2jORdYf4JBYzggCIa8WZUr2rchP2Ngn/w==",
"dev": true,
"requires": {
"@trust/keyto": "^0.3.4",
"base64url": "^3.0.0",
"elliptic": "^6.4.0",
"node-rsa": "^0.4.0",
"text-encoding": "^0.6.1"
}
},
"accepts": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz",
@ -56,23 +32,6 @@
"integrity": "sha1-8zshWfBTKj8xB6JywMz70a0peco=",
"dev": true
},
"asn1": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz",
"integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=",
"dev": true
},
"asn1.js": {
"version": "4.10.1",
"resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz",
"integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==",
"dev": true,
"requires": {
"bn.js": "^4.0.0",
"inherits": "^2.0.1",
"minimalistic-assert": "^1.0.0"
}
},
"async-limiter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
@ -126,12 +85,6 @@
"integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=",
"dev": true
},
"base64url": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.0.tgz",
"integrity": "sha512-LIVmqIrIWuiqTvn4RzcrwCOuHo2DD6tKmKBPXXlr4p4n4l6BZBkwFTIa3zu1XkX5MbZgro4a6BvPi+n2Mns5Gg==",
"dev": true
},
"better-assert": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz",
@ -153,12 +106,6 @@
"integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==",
"dev": true
},
"bn.js": {
"version": "4.11.8",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==",
"dev": true
},
"body-parser": {
"version": "1.18.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz",
@ -187,12 +134,6 @@
"concat-map": "0.0.1"
}
},
"brorand": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
"integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=",
"dev": true
},
"browser-stdout": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
@ -223,9 +164,9 @@
"dev": true
},
"commander": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz",
"integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==",
"version": "2.15.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
"integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==",
"dev": true
},
"component-bind": {
@ -309,21 +250,6 @@
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=",
"dev": true
},
"elliptic": {
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.1.tgz",
"integrity": "sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ==",
"dev": true,
"requires": {
"bn.js": "^4.4.0",
"brorand": "^1.0.1",
"hash.js": "^1.0.0",
"hmac-drbg": "^1.0.0",
"inherits": "^2.0.1",
"minimalistic-assert": "^1.0.0",
"minimalistic-crypto-utils": "^1.0.0"
}
},
"encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
@ -572,9 +498,9 @@
}
},
"growl": {
"version": "1.10.3",
"resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz",
"integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==",
"version": "1.10.5",
"resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz",
"integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==",
"dev": true
},
"has-binary": {
@ -601,38 +527,17 @@
"dev": true
},
"has-flag": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
"integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
"dev": true
},
"hash.js": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.5.tgz",
"integrity": "sha512-eWI5HG9Np+eHV1KQhisXWwM+4EPPYe5dFX1UZZH7k/E3JzDEazVH+VGlZi6R94ZqImq+A3D1mCEtrFIfg/E7sA==",
"dev": true,
"requires": {
"inherits": "^2.0.3",
"minimalistic-assert": "^1.0.1"
}
},
"he": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz",
"integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=",
"dev": true
},
"hmac-drbg": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
"integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
"dev": true,
"requires": {
"hash.js": "^1.0.3",
"minimalistic-assert": "^1.0.0",
"minimalistic-crypto-utils": "^1.0.1"
}
},
"http-errors": {
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
@ -760,18 +665,6 @@
"mime-db": "~1.33.0"
}
},
"minimalistic-assert": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
"dev": true
},
"minimalistic-crypto-utils": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
"integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=",
"dev": true
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
@ -784,35 +677,33 @@
"minimist": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
"dev": true
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
},
"mkdirp": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"dev": true,
"requires": {
"minimist": "0.0.8"
}
},
"mocha": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/mocha/-/mocha-5.1.1.tgz",
"integrity": "sha512-kKKs/H1KrMMQIEsWNxGmb4/BGsmj0dkeyotEvbrAuQ01FcWRLssUNXCEUZk6SZtyJBi6EE7SL0zDDtItw1rGhw==",
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz",
"integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==",
"dev": true,
"requires": {
"browser-stdout": "1.3.1",
"commander": "2.11.0",
"commander": "2.15.1",
"debug": "3.1.0",
"diff": "3.5.0",
"escape-string-regexp": "1.0.5",
"glob": "7.1.2",
"growl": "1.10.3",
"growl": "1.10.5",
"he": "1.1.1",
"minimatch": "3.0.4",
"mkdirp": "0.5.1",
"supports-color": "4.4.0"
"supports-color": "5.4.0"
},
"dependencies": {
"debug": {
@ -833,10 +724,9 @@
"dev": true
},
"nan": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.11.0.tgz",
"integrity": "sha512-F4miItu2rGnV2ySkXOQoA8FKz/SR2Q2sWP0sbTxNxz/tuokeC8WxOhPMcwi0qIyGtVn/rrSeLbvVkznqCdwYnw==",
"dev": true
"version": "2.12.1",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.12.1.tgz",
"integrity": "sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw=="
},
"negotiator": {
"version": "0.6.1",
@ -844,25 +734,15 @@
"integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=",
"dev": true
},
"node-rsa": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/node-rsa/-/node-rsa-0.4.2.tgz",
"integrity": "sha1-1jkXKewWqDDtWjgEKzFX0tXXJTA=",
"dev": true,
"requires": {
"asn1": "0.2.3"
}
},
"node-webcrypto-ossl": {
"version": "1.0.38",
"resolved": "https://registry.npmjs.org/node-webcrypto-ossl/-/node-webcrypto-ossl-1.0.38.tgz",
"integrity": "sha512-UiQcDiBDNzaZbP0WVgz4QvVTVI4uR4jrFAtOtFsKbDDNOMFWc9+3mVeiF1hVvdLlv3ILC0ODgs8Wp/hp7SMoLA==",
"dev": true,
"version": "1.0.39",
"resolved": "https://registry.npmjs.org/node-webcrypto-ossl/-/node-webcrypto-ossl-1.0.39.tgz",
"integrity": "sha512-cEq67y6GJ5jcKdANi5XqejqMvM/eIGxuOOE8F+c0XS950jSpvOcjUNHLmIe3/dN/UKyUkb+dri0BU4OgmCJd2g==",
"requires": {
"mkdirp": "^0.5.1",
"nan": "^2.10.0",
"tslib": "^1.9.0",
"webcrypto-core": "^0.1.22"
"nan": "^2.11.1",
"tslib": "^1.9.3",
"webcrypto-core": "^0.1.25"
}
},
"object-assign": {
@ -1261,19 +1141,18 @@
"dev": true
},
"supports-color": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz",
"integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==",
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz",
"integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==",
"dev": true,
"requires": {
"has-flag": "^2.0.0"
"has-flag": "^3.0.0"
}
},
"text-encoding": {
"version": "0.6.4",
"resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz",
"integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=",
"dev": true
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.7.0.tgz",
"integrity": "sha512-oJQ3f1hrOnbRLOcwKz0Liq2IcrvDeZRHXhd9RgLrsT+DjWY/nty1Hi7v3dtkaEYbPYe0mUoOfzRrMwfXXwgPUA=="
},
"to-array": {
"version": "0.1.4",
@ -1284,8 +1163,7 @@
"tslib": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
"integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==",
"dev": true
"integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ=="
},
"type-is": {
"version": "1.6.16",
@ -1350,10 +1228,9 @@
"dev": true
},
"webcrypto-core": {
"version": "0.1.22",
"resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-0.1.22.tgz",
"integrity": "sha512-UoNP+/3I74foiQLe1m4ToxoN3oloWnH3Na0TPWNzu/ALhDl1MbhgS0QEezdNNQbkj/6i9cf59k7LeOAAvd0hzg==",
"dev": true,
"version": "0.1.26",
"resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-0.1.26.tgz",
"integrity": "sha512-BZVgJZkkHyuz8loKvsaOKiBDXDpmMZf5xG4QAOlSeYdXlFUl9c1FRrVnAXcOdb4fTHMG+TRu81odJwwSfKnWTA==",
"requires": {
"tslib": "^1.7.1"
}

View File

@ -1,6 +1,6 @@
{
"name": "gun",
"version": "0.9.999995",
"version": "0.9.999998",
"description": "A realtime, decentralized, offline-first, graph data synchronization engine.",
"main": "index.js",
"browser": "gun.min.js",
@ -48,9 +48,8 @@
"node": ">=0.8.4"
},
"dependencies": {
"@trust/webcrypto": "^0.9.2",
"text-encoding": "^0.6.4",
"node-webcrypto-ossl": "^1.0.38",
"text-encoding": "^0.7.0",
"node-webcrypto-ossl": "^1.0.39",
"ws": "~>5.2.0"
},
"devDependencies": {
@ -58,7 +57,7 @@
"concat-map": "^0.0.1",
"express": ">=4.15.2",
"ip": "^1.1.5",
"mocha": ">=3.2.0",
"mocha": "^5.2.0",
"panic-manager": "^1.2.0",
"panic-server": "^1.1.1",
"uglify-js": ">=2.8.22"

488
sea.js
View File

@ -165,96 +165,73 @@
}
if(!api.crypto){try{
var crypto = USE('crypto', 1);
const { subtle } = USE('@trust/webcrypto', 1) // All but ECDH
const { TextEncoder, TextDecoder } = USE('text-encoding', 1)
Object.assign(api, {
crypto,
subtle,
//subtle,
TextEncoder,
TextDecoder,
random: (len) => Buffer.from(crypto.randomBytes(len))
});
//try{
const WebCrypto = USE('node-webcrypto-ossl', 1)
api.ossl = new WebCrypto({directory: 'ossl'}).subtle // ECDH
const WebCrypto = USE('node-webcrypto-ossl', 1);
api.ossl = api.subtle = new WebCrypto({directory: 'ossl'}).subtle // ECDH
//}catch(e){
//console.log("node-webcrypto-ossl is optionally needed for ECDH, please install if needed.");
//}
}catch(e){
console.log("@trust/webcrypto and text-encoding are not included by default, you must add it to your package.json!");
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).");
TRUST_WEBCRYPTO_OR_TEXT_ENCODING_NOT_INSTALLED;
console.log("node-webcrypto-ossl and text-encoding may not be included by default, please add it to your package.json!");
OSSL_WEBCRYPTO_OR_TEXT_ENCODING_NOT_INSTALLED;
}}
module.exports = api
})(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)
};
s.check = function(t){ return (typeof t == 'string') && ('SEA{' === t.slice(0,4)) }
s.parse = 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) {}
return t;
}
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
} catch (e) {} //eslint-disable-line no-empty
return props
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);
}
})(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)
}
module.exports = sha256hash
})(USE, './sha256');
;USE(function(module){
@ -281,25 +258,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 +290,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 +305,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 +330,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 +346,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 +364,7 @@
var SEA = USE('./root');
var shim = USE('./shim');
var S = USE('./settings');
var sha256hash = USE('./sha256');
var sha = USE('./sha256');
var u;
SEA.sign = SEA.sign || (async (data, pair, cb, opt) => { try {
@ -396,13 +372,24 @@
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.' }
var json = S.parse(data);
var check = opt.check = opt.check || json;
if(SEA.verify && (SEA.opt.check(check) || (check && check.s && check.m))
&& u !== await SEA.verify(check, pair)){ // don't sign if we already signed it.
var r = S.parse(check);
if(!opt.raw){ r = 'SEA'+JSON.stringify(r) }
if(cb){ try{ cb(r) }catch(e){console.log(e)} }
return r;
}
var pub = pair.pub;
var priv = pair.priv;
var jwk = S.jwk(pub, priv);
var hash = await sha(json);
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 = {m: json, s: shim.Buffer.from(sig, 'binary').toString(opt.encode || 'base64')}
if(!opt.raw){ r = 'SEA'+JSON.stringify(r) }
if(cb){ try{ cb(r) }catch(e){console.log(e)} }
return r;
@ -421,38 +408,32 @@
var SEA = USE('./root');
var shim = USE('./shim');
var S = USE('./settings');
var sha256hash = USE('./sha256');
var parse = USE('./parse');
var sha = USE('./sha256');
var u;
SEA.verify = SEA.verify || (async (data, pair, cb, opt) => { try {
const json = parse(data)
var json = S.parse(data);
if(false === pair){ // don't verify!
const raw = (json !== data)?
(json.s && json.m)? parse(json.m) : data
: json;
var raw = S.parse(json.m);
if(cb){ try{ cb(raw) }catch(e){console.log(e)} }
return raw;
}
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 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))
var pub = pair.pub || pair;
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']);
var hash = await sha(json.m);
var buf, sig, check, tmp; 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));
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))
if(!check){ throw "Signature did not match." }
if(SEA.opt.fallback){
return await SEA.opt.fall_verify(data, pair, cb, opt);
}
}
const r = check? parse(json.m) : u;
var r = check? S.parse(json.m) : u;
if(cb){ try{ cb(r) }catch(e){console.log(e)} }
return r;
@ -465,6 +446,38 @@
}});
module.exports = SEA.verify;
// legacy & ossl leak mitigation:
var knownKeys = {};
var keyForPair = SEA.opt.slow_leak = 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.opt.fall_verify = async function(data, pair, cb, opt, f){
if(f === SEA.opt.fallback){ throw "Signature did not match" } f = f || 1;
var json = S.parse(data), pub = pair.pub || pair, key = await SEA.opt.slow_leak(pub);
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.
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))
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))
if(!check){ throw "Signature did not match." }
}
var r = check? S.parse(json.m) : u;
if(cb){ try{ cb(r) }catch(e){console.log(e)} }
return r;
}
SEA.opt.fallback = 2;
})(USE, './verify');
;USE(function(module){
@ -486,29 +499,32 @@
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 = {
ct: shim.Buffer.from(ct, 'binary').toString(opt.encode || 'base64'),
iv: rand.iv.toString(opt.encode || 'base64'),
s: rand.s.toString(opt.encode || 'base64')
});
}
if(!opt.raw){ r = 'SEA'+JSON.stringify(r) }
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() }
@ -523,7 +539,6 @@
var shim = USE('./shim');
var S = USE('./settings');
var aeskey = USE('./aeskey');
var parse = USE('./parse');
SEA.decrypt = SEA.decrypt || (async (data, pair, cb, opt) => { try {
opt = opt || {};
@ -532,23 +547,26 @@
pair = await SEA.I(null, {what: data, how: 'decrypt', why: opt.why});
key = pair.epriv || pair;
}
const json = parse(data)
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!
}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!
}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...
name: opt.name || 'AES-GCM', iv: new Uint8Array(bufiv)
}, aes, new Uint8Array(bufct)))
const r = parse(new shim.TextDecoder('utf8').decode(ct))
var json = S.parse(data);
var buf, bufiv, bufct; try{
buf = shim.Buffer.from(json.s, opt.encode || 'base64');
bufiv = shim.Buffer.from(json.iv, opt.encode || 'base64');
bufct = shim.Buffer.from(json.ct, opt.encode || 'base64');
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)));
}catch(e){
if('utf8' === opt.encode){ throw "Could not decrypt" }
if(SEA.opt.fallback){
opt.encode = 'utf8';
return await SEA.decrypt(data, pair, cb, opt);
}
}
var r = S.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 +586,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(
@ -757,19 +773,19 @@
}
// 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!
act.data = {pub: pair.pub};
SEA.sign(alias, pair, act.d);
act.d();
}
act.d = function(sig){
act.data.alias = alias || sig;
SEA.sign(act.pair.epub, act.pair, act.e);
act.d = function(){
act.data.alias = alias;
act.e();
}
act.e = function(epub){
act.data.epub = act.pair.epub || epub;
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!
act.e = function(){
act.data.epub = act.pair.epub;
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!
}
act.f = function(auth){
act.data.auth = JSON.stringify({ek: auth, s: act.salt});
SEA.sign({ek: auth, s: act.salt}, act.pair, act.g);
act.g(act.data.auth);
}
act.g = function(auth){ var tmp;
act.data.auth = act.data.auth || auth;
@ -816,7 +832,7 @@
}
act.c = function(auth){
if(u === auth){ return act.b() }
if(Gun.text.is(auth)){ return act.c(Gun.obj.ify(auth)) } // new format
if(Gun.text.is(auth)){ return act.c(Gun.obj.ify(auth)) } // in case of legacy
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.
}
act.d = function(proof){
@ -849,7 +865,7 @@
user.is = {pub: pair.pub, epub: pair.epub, alias: alias};
at.sea = act.pair;
cat.ing = false;
if(pass && !Gun.text.is(act.data.auth)){ opt.shuffle = opt.change = pass; } // migrate UTF8 + Shuffle! Test against NAB alias test_sea_shuffle + passw0rd
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!
opt.change? act.z() : cb(at);
if(SEA.window && ((gun.back('user')._).opt||opt).remember){
// TODO: this needs to be modular.
@ -873,18 +889,17 @@
SEA.work(opt.change, act.salt, act.y);
}
act.y = function(proof){
SEA.encrypt({priv: act.pair.priv, epriv: act.pair.epriv}, proof, act.x);
SEA.encrypt({priv: act.pair.priv, epriv: act.pair.epriv}, proof, act.x, {raw:1});
}
act.x = function(auth){
act.w(JSON.stringify({ek: auth, s: act.salt}));
//SEA.sign({ek: auth, s: act.salt}, act.pair, act.w);
}
act.w = function(auth){
if(opt.shuffle){ // delete in future!
console.log('migrate core account from UTF8 & shuffle');
var tmp = Gun.obj.to(act.data);
Gun.obj.del(tmp, '_');
tmp.auth = auth;
console.log('migrate core account from UTF8 & shuffle', tmp);
root.get('~'+act.pair.pub).put(tmp);
} // end delete
root.get('~'+act.pair.pub).get('auth').put(auth, cb);
@ -1074,27 +1089,30 @@
// NOTE: THE SECURITY FUNCTION HAS ALREADY VERIFIED THE DATA!!!
// WE DO NOT NEED TO RE-VERIFY AGAIN, JUST TRANSFORM IT TO PLAINTEXT.
var to = this.to, vertex = (msg.$._).put, c = 0, d;
Gun.node.is(msg.put, function(val, key, node){ c++; // for each property on the node
// TODO: consider async/await use here...
Gun.node.is(msg.put, function(val, key, node){
// only process if SEA formatted?
var tmp = Gun.obj.ify(val) || noop;
if(u !== tmp[':']){
node[key] = SEA.opt.unpack(tmp);
return;
}
if(!SEA.opt.check(val)){ return }
c++; // for each property on the node
SEA.verify(val, false, function(data){ c--; // false just extracts the plain data.
var tmp = data;
data = SEA.opt.unpack(data, key, node);
node[key] = val = data; // transform to plain value.
node[key] = SEA.opt.unpack(data, key, node);; // transform to plain value.
if(d && !c && (c = -1)){ to.next(msg) }
});
});
d = true;
if(d && !c){ to.next(msg) }
return;
if((d = true) && !c){ to.next(msg) }
}
// signature handles data output, it is a proxy to the security function.
function signature(msg){
if(msg.user){
if((msg._||noop).user){
return this.to.next(msg);
}
var ctx = this.as;
msg.user = ctx.user;
(msg._||(msg._=function(){})).user = ctx.user;
security.call(this, msg);
}
@ -1135,9 +1153,9 @@
each.pubs(val, key, node, soul); return;
}
if('~' === soul.slice(0,1) && 2 === (tmp = soul.slice(1)).split('.').length){ // special case, account data for a public key.
each.pub(val, key, node, soul, tmp, msg.user); return;
each.pub(val, key, node, soul, tmp, (msg._||noop).user); return;
}
each.any(val, key, node, soul, msg.user); return;
each.any(val, key, node, soul, (msg._||noop).user); return;
return each.end({err: "No other data allowed!"});
};
each.alias = function(val, key, node, soul){ // Example: {_:#~@, ~@alice: {#~@alice}}
@ -1150,62 +1168,43 @@
if(key === Gun.val.link.is(val)){ return check['pubs'+soul+key] = 0 } // and the ID must be EXACTLY equal to its property
each.end({err: "Alias must match!"}); // that way nobody can tamper with the list of public keys.
};
each.pub = function(val, key, node, soul, pub, user){ // Example: {_:#~asdf, hello:SEA{'world',fdsa}}
each.pub = function(val, key, node, soul, pub, user){ var tmp; // Example: {_:#~asdf, hello:'world'~fdsa}}
if('pub' === key){
if(val === pub){ return (check['pub'+soul+key] = 0) } // the account MUST match `pub` property that equals the ID of the public key.
return each.end({err: "Account must match!"});
}
check['user'+soul+key] = 1;
if(user && user.is && pub === user.is.pub){
//var id = Gun.text.random(3);
SEA.sign([soul, key, val, Gun.state.is(node, key)], (user._).sea, function(data){ var rel;
if(Gun.is(msg.$) && user && user.is && pub === user.is.pub){
SEA.sign(SEA.opt.prep(tmp = SEA.opt.parse(val), key, node, soul), (user._).sea, function(data){ var rel;
if(u === data){ return each.end({err: SEA.err || 'Pub signature fail.'}) }
if(rel = Gun.val.link.is(val)){
(at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true;
}
node[key] = data;
node[key] = JSON.stringify({':': SEA.opt.unpack(data.m), '~': data.s});
check['user'+soul+key] = 0;
each.end({ok: 1});
});
// TODO: Handle error!!!!
}, {check: SEA.opt.pack(tmp, key, node, soul), raw: 1});
return;
}
SEA.verify(val, pub, function(data){ var rel, tmp;
SEA.verify(SEA.opt.pack(val,key,node,soul), pub, function(data){ var rel, tmp;
data = SEA.opt.unpack(data, key, node);
if(u === data){ // make sure the signature matches the account it claims to be on.
return each.end({err: "Unverified data."}); // reject any updates that are signed with a mismatched account.
}
if((rel = Gun.val.link.is(data)) && pub === relpub(rel)){
if((rel = Gun.val.link.is(data)) && pub === SEA.opt.pub(rel)){
(at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true;
}
check['user'+soul+key] = 0;
each.end({ok: 1});
});
};
function relpub(s){
if(!s){ return }
s = s.split('~');
if(!s || !(s = s[1])){ return }
s = s.split('.');
if(!s || 2 > s.length){ return }
s = s.slice(0,2).join('.');
return s;
}
each.any = function(val, key, node, soul, user){ var tmp, pub;
if(!user || !user.is){
if(tmp = relpub(soul)){
check['any'+soul+key] = 1;
SEA.verify(val, pub = tmp, function(data){ var rel;
data = SEA.opt.unpack(data, key, node);
if(u === data){ return each.end({err: "Mismatched owner on '" + key + "'."}) } // thanks @rogowski !
if((rel = Gun.val.link.is(data)) && pub === relpub(rel)){
(at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true;
}
check['any'+soul+key] = 0;
each.end({ok: 1});
});
if(!(pub = SEA.opt.pub(soul))){
if(at.opt.secure){
each.end({err: "Soul is missing public key at '" + key + "'."});
return;
}
// TODO: Ask community if should auto-sign non user-graph data.
check['any'+soul+key] = 1;
at.on('secure', function(msg){ this.off();
check['any'+soul+key] = 0;
@ -1215,40 +1214,30 @@
//each.end({err: "Data cannot be modified."});
return;
}
if(!(tmp = relpub(soul))){
if(at.opt.secure){
each.end({err: "Soul is missing public key at '" + key + "'."});
if(Gun.is(msg.$) && user && user.is && pub === user.is.pub){
/*var other = Gun.obj.map(at.sea.own[soul], function(v, p){
if((user.is||{}).pub !== p){ return p }
});
if(other){
each.any(val, key, node, soul);
return;
}
if(val && val.slice && 'SEA{' === (val).slice(0,4)){
}*/
check['any'+soul+key] = 1;
SEA.sign(SEA.opt.prep(tmp = SEA.opt.parse(val), key, node, soul), (user._).sea, function(data){
if(u === data){ return each.end({err: 'My signature fail.'}) }
node[key] = JSON.stringify({':': SEA.opt.unpack(data.m), '~': data.s});
check['any'+soul+key] = 0;
each.end({ok: 1});
return;
}
//check['any'+soul+key] = 1;
//SEA.sign(val, user, function(data){
// if(u === data){ return each.end({err: 'Any signature failed.'}) }
// node[key] = data;
check['any'+soul+key] = 0;
each.end({ok: 1});
//});
}, {check: SEA.opt.pack(tmp, key, node, soul), raw: 1});
return;
}
if(!msg.I || (pub = tmp) !== (user.is||noop).pub){
each.any(val, key, node, soul);
return;
}
/*var other = Gun.obj.map(at.sea.own[soul], function(v, p){
if((user.is||{}).pub !== p){ return p }
});
if(other){
each.any(val, key, node, soul);
return;
}*/
check['any'+soul+key] = 1;
SEA.sign([soul, key, val, Gun.state.is(node, key)], (user._).sea, function(data){
if(u === data){ return each.end({err: 'My signature fail.'}) }
node[key] = data;
SEA.verify(SEA.opt.pack(val,key,node,soul), pub, function(data){ var rel;
data = SEA.opt.unpack(data, key, node);
if(u === data){ return each.end({err: "Mismatched owner on '" + key + "'."}) } // thanks @rogowski !
if((rel = Gun.val.link.is(data)) && pub === SEA.opt.pub(rel)){
(at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true;
}
check['any'+soul+key] = 0;
each.end({ok: 1});
});
@ -1263,6 +1252,7 @@
if(Gun.obj.map(check, function(no){
if(no){ return true }
})){ return }
(msg._||{}).user = at.user || security; // already been through firewall, does not need to again on out.
to.next(msg);
};
Gun.obj.map(msg.put, each.node);
@ -1271,18 +1261,42 @@
}
to.next(msg); // pass forward any data we do not know how to handle or process (this allows custom security protocols).
}
SEA.opt.unpack = function(data, key, node){
if(u === data){ return }
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]){
return tmp[2];
SEA.opt.pub = function(s){
if(!s){ return }
s = s.split('~');
if(!s || !(s = s[1])){ return }
s = s.split('.');
if(!s || 2 > s.length){ return }
s = s.slice(0,2).join('.');
return s;
}
SEA.opt.prep = function(d,k, n,s){ // prep for signing
return {'#':s,'.':k,':':SEA.opt.parse(d),'>':Gun.state.is(n, k)};
}
SEA.opt.pack = function(d,k, n,s){ // pack for verifying
if(SEA.opt.check(d)){ return d }
var meta = (Gun.obj.ify(d)||noop), sig = meta['~'];
return sig? {m: {'#':s,'.':k,':':meta[':'],'>':Gun.state.is(n, k)}, s: sig} : d;
}
SEA.opt.unpack = function(d, k, n){ var tmp;
if(u === d){ return }
if(d && (u !== (tmp = d[':']))){ return tmp }
if(!k || !n){ return }
if(d === n[k]){ return d }
if(!SEA.opt.check(n[k])){ return d }
var soul = Gun.node.soul(n), s = Gun.state.is(n, k);
if(d && 4 === d.length && soul === d[0] && k === d[1] && fl(s) === fl(d[3])){
return d[2];
}
if(s < SEA.opt.shuffle_attack){
return data;
return d;
}
}
SEA.opt.shuffle_attack = 1546329600000; // Jan 1, 2019
var noop = {}, u;
var noop = function(){}, 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');
}());
}());

View File

@ -45,19 +45,19 @@
}
// 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!
act.data = {pub: pair.pub};
SEA.sign(alias, pair, act.d);
act.d();
}
act.d = function(sig){
act.data.alias = alias || sig;
SEA.sign(act.pair.epub, act.pair, act.e);
act.d = function(){
act.data.alias = alias;
act.e();
}
act.e = function(epub){
act.data.epub = act.pair.epub || epub;
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!
act.e = function(){
act.data.epub = act.pair.epub;
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!
}
act.f = function(auth){
act.data.auth = JSON.stringify({ek: auth, s: act.salt});
SEA.sign({ek: auth, s: act.salt}, act.pair, act.g);
act.g(act.data.auth);
}
act.g = function(auth){ var tmp;
act.data.auth = act.data.auth || auth;
@ -104,7 +104,7 @@
}
act.c = function(auth){
if(u === auth){ return act.b() }
if(Gun.text.is(auth)){ return act.c(Gun.obj.ify(auth)) } // new format
if(Gun.text.is(auth)){ return act.c(Gun.obj.ify(auth)) } // in case of legacy
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.
}
act.d = function(proof){
@ -137,7 +137,7 @@
user.is = {pub: pair.pub, epub: pair.epub, alias: alias};
at.sea = act.pair;
cat.ing = false;
if(pass && !Gun.text.is(act.data.auth)){ opt.shuffle = opt.change = pass; } // migrate UTF8 + Shuffle! Test against NAB alias test_sea_shuffle + passw0rd
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!
opt.change? act.z() : cb(at);
if(SEA.window && ((gun.back('user')._).opt||opt).remember){
// TODO: this needs to be modular.
@ -161,18 +161,17 @@
SEA.work(opt.change, act.salt, act.y);
}
act.y = function(proof){
SEA.encrypt({priv: act.pair.priv, epriv: act.pair.epriv}, proof, act.x);
SEA.encrypt({priv: act.pair.priv, epriv: act.pair.epriv}, proof, act.x, {raw:1});
}
act.x = function(auth){
act.w(JSON.stringify({ek: auth, s: act.salt}));
//SEA.sign({ek: auth, s: act.salt}, act.pair, act.w);
}
act.w = function(auth){
if(opt.shuffle){ // delete in future!
console.log('migrate core account from UTF8 & shuffle');
var tmp = Gun.obj.to(act.data);
Gun.obj.del(tmp, '_');
tmp.auth = auth;
console.log('migrate core account from UTF8 & shuffle', tmp);
root.get('~'+act.pair.pub).put(tmp);
} // end delete
root.get('~'+act.pair.pub).get('auth').put(auth, cb);

View File

@ -3,7 +3,6 @@
var shim = require('./shim');
var S = require('./settings');
var aeskey = require('./aeskey');
var parse = require('./parse');
SEA.decrypt = SEA.decrypt || (async (data, pair, cb, opt) => { try {
opt = opt || {};
@ -12,23 +11,26 @@
pair = await SEA.I(null, {what: data, how: 'decrypt', why: opt.why});
key = pair.epriv || pair;
}
const json = parse(data)
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!
}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!
}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...
name: opt.name || 'AES-GCM', iv: new Uint8Array(bufiv)
}, aes, new Uint8Array(bufct)))
const r = parse(new shim.TextDecoder('utf8').decode(ct))
var json = S.parse(data);
var buf, bufiv, bufct; try{
buf = shim.Buffer.from(json.s, opt.encode || 'base64');
bufiv = shim.Buffer.from(json.iv, opt.encode || 'base64');
bufct = shim.Buffer.from(json.ct, opt.encode || 'base64');
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)));
}catch(e){
if('utf8' === opt.encode){ throw "Could not decrypt" }
if(SEA.opt.fallback){
opt.encode = 'utf8';
return await SEA.decrypt(data, pair, cb, opt);
}
}
var r = S.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() }

View File

@ -3,29 +3,32 @@
var shim = require('./shim');
var S = require('./settings');
var aeskey = require('./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 = {
ct: shim.Buffer.from(ct, 'binary').toString(opt.encode || 'base64'),
iv: rand.iv.toString(opt.encode || 'base64'),
s: rand.s.toString(opt.encode || 'base64')
});
}
if(!opt.raw){ r = 'SEA'+JSON.stringify(r) }
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() }

View File

@ -31,27 +31,30 @@
// NOTE: THE SECURITY FUNCTION HAS ALREADY VERIFIED THE DATA!!!
// WE DO NOT NEED TO RE-VERIFY AGAIN, JUST TRANSFORM IT TO PLAINTEXT.
var to = this.to, vertex = (msg.$._).put, c = 0, d;
Gun.node.is(msg.put, function(val, key, node){ c++; // for each property on the node
// TODO: consider async/await use here...
Gun.node.is(msg.put, function(val, key, node){
// only process if SEA formatted?
var tmp = Gun.obj.ify(val) || noop;
if(u !== tmp[':']){
node[key] = SEA.opt.unpack(tmp);
return;
}
if(!SEA.opt.check(val)){ return }
c++; // for each property on the node
SEA.verify(val, false, function(data){ c--; // false just extracts the plain data.
var tmp = data;
data = SEA.opt.unpack(data, key, node);
node[key] = val = data; // transform to plain value.
node[key] = SEA.opt.unpack(data, key, node);; // transform to plain value.
if(d && !c && (c = -1)){ to.next(msg) }
});
});
d = true;
if(d && !c){ to.next(msg) }
return;
if((d = true) && !c){ to.next(msg) }
}
// signature handles data output, it is a proxy to the security function.
function signature(msg){
if(msg.user){
if((msg._||noop).user){
return this.to.next(msg);
}
var ctx = this.as;
msg.user = ctx.user;
(msg._||(msg._=function(){})).user = ctx.user;
security.call(this, msg);
}
@ -92,9 +95,9 @@
each.pubs(val, key, node, soul); return;
}
if('~' === soul.slice(0,1) && 2 === (tmp = soul.slice(1)).split('.').length){ // special case, account data for a public key.
each.pub(val, key, node, soul, tmp, msg.user); return;
each.pub(val, key, node, soul, tmp, (msg._||noop).user); return;
}
each.any(val, key, node, soul, msg.user); return;
each.any(val, key, node, soul, (msg._||noop).user); return;
return each.end({err: "No other data allowed!"});
};
each.alias = function(val, key, node, soul){ // Example: {_:#~@, ~@alice: {#~@alice}}
@ -107,62 +110,43 @@
if(key === Gun.val.link.is(val)){ return check['pubs'+soul+key] = 0 } // and the ID must be EXACTLY equal to its property
each.end({err: "Alias must match!"}); // that way nobody can tamper with the list of public keys.
};
each.pub = function(val, key, node, soul, pub, user){ // Example: {_:#~asdf, hello:SEA{'world',fdsa}}
each.pub = function(val, key, node, soul, pub, user){ var tmp; // Example: {_:#~asdf, hello:'world'~fdsa}}
if('pub' === key){
if(val === pub){ return (check['pub'+soul+key] = 0) } // the account MUST match `pub` property that equals the ID of the public key.
return each.end({err: "Account must match!"});
}
check['user'+soul+key] = 1;
if(user && user.is && pub === user.is.pub){
//var id = Gun.text.random(3);
SEA.sign([soul, key, val, Gun.state.is(node, key)], (user._).sea, function(data){ var rel;
if(Gun.is(msg.$) && user && user.is && pub === user.is.pub){
SEA.sign(SEA.opt.prep(tmp = SEA.opt.parse(val), key, node, soul), (user._).sea, function(data){ var rel;
if(u === data){ return each.end({err: SEA.err || 'Pub signature fail.'}) }
if(rel = Gun.val.link.is(val)){
(at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true;
}
node[key] = data;
node[key] = JSON.stringify({':': SEA.opt.unpack(data.m), '~': data.s});
check['user'+soul+key] = 0;
each.end({ok: 1});
});
// TODO: Handle error!!!!
}, {check: SEA.opt.pack(tmp, key, node, soul), raw: 1});
return;
}
SEA.verify(val, pub, function(data){ var rel, tmp;
SEA.verify(SEA.opt.pack(val,key,node,soul), pub, function(data){ var rel, tmp;
data = SEA.opt.unpack(data, key, node);
if(u === data){ // make sure the signature matches the account it claims to be on.
return each.end({err: "Unverified data."}); // reject any updates that are signed with a mismatched account.
}
if((rel = Gun.val.link.is(data)) && pub === relpub(rel)){
if((rel = Gun.val.link.is(data)) && pub === SEA.opt.pub(rel)){
(at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true;
}
check['user'+soul+key] = 0;
each.end({ok: 1});
});
};
function relpub(s){
if(!s){ return }
s = s.split('~');
if(!s || !(s = s[1])){ return }
s = s.split('.');
if(!s || 2 > s.length){ return }
s = s.slice(0,2).join('.');
return s;
}
each.any = function(val, key, node, soul, user){ var tmp, pub;
if(!user || !user.is){
if(tmp = relpub(soul)){
check['any'+soul+key] = 1;
SEA.verify(val, pub = tmp, function(data){ var rel;
data = SEA.opt.unpack(data, key, node);
if(u === data){ return each.end({err: "Mismatched owner on '" + key + "'."}) } // thanks @rogowski !
if((rel = Gun.val.link.is(data)) && pub === relpub(rel)){
(at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true;
}
check['any'+soul+key] = 0;
each.end({ok: 1});
});
if(!(pub = SEA.opt.pub(soul))){
if(at.opt.secure){
each.end({err: "Soul is missing public key at '" + key + "'."});
return;
}
// TODO: Ask community if should auto-sign non user-graph data.
check['any'+soul+key] = 1;
at.on('secure', function(msg){ this.off();
check['any'+soul+key] = 0;
@ -172,40 +156,30 @@
//each.end({err: "Data cannot be modified."});
return;
}
if(!(tmp = relpub(soul))){
if(at.opt.secure){
each.end({err: "Soul is missing public key at '" + key + "'."});
if(Gun.is(msg.$) && user && user.is && pub === user.is.pub){
/*var other = Gun.obj.map(at.sea.own[soul], function(v, p){
if((user.is||{}).pub !== p){ return p }
});
if(other){
each.any(val, key, node, soul);
return;
}
if(val && val.slice && 'SEA{' === (val).slice(0,4)){
}*/
check['any'+soul+key] = 1;
SEA.sign(SEA.opt.prep(tmp = SEA.opt.parse(val), key, node, soul), (user._).sea, function(data){
if(u === data){ return each.end({err: 'My signature fail.'}) }
node[key] = JSON.stringify({':': SEA.opt.unpack(data.m), '~': data.s});
check['any'+soul+key] = 0;
each.end({ok: 1});
return;
}
//check['any'+soul+key] = 1;
//SEA.sign(val, user, function(data){
// if(u === data){ return each.end({err: 'Any signature failed.'}) }
// node[key] = data;
check['any'+soul+key] = 0;
each.end({ok: 1});
//});
}, {check: SEA.opt.pack(tmp, key, node, soul), raw: 1});
return;
}
if(!msg.I || (pub = tmp) !== (user.is||noop).pub){
each.any(val, key, node, soul);
return;
}
/*var other = Gun.obj.map(at.sea.own[soul], function(v, p){
if((user.is||{}).pub !== p){ return p }
});
if(other){
each.any(val, key, node, soul);
return;
}*/
check['any'+soul+key] = 1;
SEA.sign([soul, key, val, Gun.state.is(node, key)], (user._).sea, function(data){
if(u === data){ return each.end({err: 'My signature fail.'}) }
node[key] = data;
SEA.verify(SEA.opt.pack(val,key,node,soul), pub, function(data){ var rel;
data = SEA.opt.unpack(data, key, node);
if(u === data){ return each.end({err: "Mismatched owner on '" + key + "'."}) } // thanks @rogowski !
if((rel = Gun.val.link.is(data)) && pub === SEA.opt.pub(rel)){
(at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true;
}
check['any'+soul+key] = 0;
each.end({ok: 1});
});
@ -220,6 +194,7 @@
if(Gun.obj.map(check, function(no){
if(no){ return true }
})){ return }
(msg._||{}).user = at.user || security; // already been through firewall, does not need to again on out.
to.next(msg);
};
Gun.obj.map(msg.put, each.node);
@ -228,17 +203,41 @@
}
to.next(msg); // pass forward any data we do not know how to handle or process (this allows custom security protocols).
}
SEA.opt.unpack = function(data, key, node){
if(u === data){ return }
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]){
return tmp[2];
SEA.opt.pub = function(s){
if(!s){ return }
s = s.split('~');
if(!s || !(s = s[1])){ return }
s = s.split('.');
if(!s || 2 > s.length){ return }
s = s.slice(0,2).join('.');
return s;
}
SEA.opt.prep = function(d,k, n,s){ // prep for signing
return {'#':s,'.':k,':':SEA.opt.parse(d),'>':Gun.state.is(n, k)};
}
SEA.opt.pack = function(d,k, n,s){ // pack for verifying
if(SEA.opt.check(d)){ return d }
var meta = (Gun.obj.ify(d)||noop), sig = meta['~'];
return sig? {m: {'#':s,'.':k,':':meta[':'],'>':Gun.state.is(n, k)}, s: sig} : d;
}
SEA.opt.unpack = function(d, k, n){ var tmp;
if(u === d){ return }
if(d && (u !== (tmp = d[':']))){ return tmp }
if(!k || !n){ return }
if(d === n[k]){ return d }
if(!SEA.opt.check(n[k])){ return d }
var soul = Gun.node.soul(n), s = Gun.state.is(n, k);
if(d && 4 === d.length && soul === d[0] && k === d[1] && fl(s) === fl(d[3])){
return d[2];
}
if(s < SEA.opt.shuffle_attack){
return data;
return d;
}
}
SEA.opt.shuffle_attack = 1546329600000; // Jan 1, 2019
var noop = {}, u;
var noop = function(){}, 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.

View File

@ -2,7 +2,6 @@
var SEA = require('./root');
var shim = require('./shim');
var S = require('./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)} }
@ -18,17 +17,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.
@ -43,11 +42,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.
@ -59,7 +58,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) {

View File

@ -1,11 +0,0 @@
module.exports = (props) => {
try {
if(props.slice && 'SEA{' === props.slice(0,4)){
props = props.slice(3);
}
return props.slice ? JSON.parse(props) : props
} catch (e) {} //eslint-disable-line no-empty
return props
}

View File

@ -8,36 +8,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(

View File

@ -1,41 +1,37 @@
const SEA = require('./root');
const Buffer = require('./buffer')
const settings = {}
// Encryption parameters
const pbkdf2 = { hash: 'SHA-256', iter: 100000, ks: 64 }
var SEA = require('./root');
var Buffer = require('./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)
};
s.check = function(t){ return (typeof t == 'string') && ('SEA{' === t.slice(0,4)) }
s.parse = 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) {}
return t;
}
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

View File

@ -1,13 +1,8 @@
const shim = require('./shim');
const Buffer = require('./buffer')
const parse = require('./parse')
const { pbkdf2 } = require('./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 = require('./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

View File

@ -13,25 +13,23 @@
}
if(!api.crypto){try{
var crypto = require('crypto', 1);
const { subtle } = require('@trust/webcrypto', 1) // All but ECDH
const { TextEncoder, TextDecoder } = require('text-encoding', 1)
Object.assign(api, {
crypto,
subtle,
//subtle,
TextEncoder,
TextDecoder,
random: (len) => Buffer.from(crypto.randomBytes(len))
});
//try{
const WebCrypto = require('node-webcrypto-ossl', 1)
api.ossl = new WebCrypto({directory: 'ossl'}).subtle // ECDH
const WebCrypto = require('node-webcrypto-ossl', 1);
api.ossl = api.subtle = new WebCrypto({directory: 'ossl'}).subtle // ECDH
//}catch(e){
//console.log("node-webcrypto-ossl is optionally needed for ECDH, please install if needed.");
//}
}catch(e){
console.log("@trust/webcrypto and text-encoding are not included by default, you must add it to your package.json!");
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).");
TRUST_WEBCRYPTO_OR_TEXT_ENCODING_NOT_INSTALLED;
console.log("node-webcrypto-ossl and text-encoding may not be included by default, please add it to your package.json!");
OSSL_WEBCRYPTO_OR_TEXT_ENCODING_NOT_INSTALLED;
}}
module.exports = api

View File

@ -2,7 +2,7 @@
var SEA = require('./root');
var shim = require('./shim');
var S = require('./settings');
var sha256hash = require('./sha256');
var sha = require('./sha256');
var u;
SEA.sign = SEA.sign || (async (data, pair, cb, opt) => { try {
@ -10,13 +10,24 @@
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.' }
var json = S.parse(data);
var check = opt.check = opt.check || json;
if(SEA.verify && (SEA.opt.check(check) || (check && check.s && check.m))
&& u !== await SEA.verify(check, pair)){ // don't sign if we already signed it.
var r = S.parse(check);
if(!opt.raw){ r = 'SEA'+JSON.stringify(r) }
if(cb){ try{ cb(r) }catch(e){console.log(e)} }
return r;
}
var pub = pair.pub;
var priv = pair.priv;
var jwk = S.jwk(pub, priv);
var hash = await sha(json);
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 = {m: json, s: shim.Buffer.from(sig, 'binary').toString(opt.encode || 'base64')}
if(!opt.raw){ r = 'SEA'+JSON.stringify(r) }
if(cb){ try{ cb(r) }catch(e){console.log(e)} }
return r;

View File

@ -2,38 +2,32 @@
var SEA = require('./root');
var shim = require('./shim');
var S = require('./settings');
var sha256hash = require('./sha256');
var parse = require('./parse');
var sha = require('./sha256');
var u;
SEA.verify = SEA.verify || (async (data, pair, cb, opt) => { try {
const json = parse(data)
var json = S.parse(data);
if(false === pair){ // don't verify!
const raw = (json !== data)?
(json.s && json.m)? parse(json.m) : data
: json;
var raw = S.parse(json.m);
if(cb){ try{ cb(raw) }catch(e){console.log(e)} }
return raw;
}
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 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))
var pub = pair.pub || pair;
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']);
var hash = await sha(json.m);
var buf, sig, check, tmp; 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));
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))
if(!check){ throw "Signature did not match." }
if(SEA.opt.fallback){
return await SEA.opt.fall_verify(data, pair, cb, opt);
}
}
const r = check? parse(json.m) : u;
var r = check? S.parse(json.m) : u;
if(cb){ try{ cb(r) }catch(e){console.log(e)} }
return r;
@ -46,4 +40,36 @@
}});
module.exports = SEA.verify;
// legacy & ossl leak mitigation:
var knownKeys = {};
var keyForPair = SEA.opt.slow_leak = 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.opt.fall_verify = async function(data, pair, cb, opt, f){
if(f === SEA.opt.fallback){ throw "Signature did not match" } f = f || 1;
var json = S.parse(data), pub = pair.pub || pair, key = await SEA.opt.slow_leak(pub);
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.
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))
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))
if(!check){ throw "Signature did not match." }
}
var r = check? S.parse(json.m) : u;
if(cb){ try{ cb(r) }catch(e){console.log(e)} }
return r;
}
SEA.opt.fallback = 2;

View File

@ -13,25 +13,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() }

View File

@ -36,7 +36,7 @@ Gun.on('create', function(root){
root.on('out', function(msg){
if(msg.lS){ return }
if(msg.I && msg.put && !msg['@'] && !empty(opt.peers)){
if(Gun.is(msg.$) && msg.put && !msg['@'] && !empty(opt.peers)){
id = msg['#'];
Gun.graph.is(msg.put, null, map);
if(!to){ to = setTimeout(flush, opt.wait || 1) }
@ -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);
@ -112,7 +112,7 @@ Gun.on('create', function(root){
return; // Hmm, what if we have peers but we are disconnected?
}
//console.log("lS get", lex, data);
root.on('in', {'@': msg['#'], put: Gun.graph.node(data), how: 'lS', lS: msg.I});
root.on('in', {'@': msg['#'], put: Gun.graph.node(data), how: 'lS', lS: msg.$ || root.$});
};
Gun.debug? setTimeout(to,1) : to();
});
@ -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});
}

View File

@ -14,8 +14,8 @@ function Mesh(ctx){
if((tmp = msg['@'])
&& (tmp = ctx.dup.s[tmp])
&& (tmp = tmp.it)
&& tmp.mesh){
mesh.say(msg, tmp.mesh.via, 1);
&& tmp._){
mesh.say(msg, (tmp._).via, 1);
tmp['##'] = msg['##'];
return;
}
@ -47,9 +47,9 @@ function Mesh(ctx){
(tmp = dup.s)[hash] = tmp[id];
}
}
(msg.mesh = function(){}).via = peer;
(msg._ = function(){}).via = peer;
if((tmp = msg['><'])){
msg.mesh.to = Type.obj.map(tmp.split(','), function(k,i,m){m(k,true)});
(msg._).to = Type.obj.map(tmp.split(','), function(k,i,m){m(k,true)});
}
if(msg.dam){
if(tmp = mesh.hear[msg.dam]){
@ -58,7 +58,7 @@ function Mesh(ctx){
return;
}
ctx.on('in', msg);
return;
} else
if('[' === tmp){
@ -87,7 +87,7 @@ function Mesh(ctx){
}
var tmp, wire = peer.wire || ((opt.wire) && opt.wire(peer)), msh, raw;// || open(peer, ctx); // TODO: Reopen!
if(!wire){ return }
msh = msg.mesh || empty;
msh = (msg._) || empty;
if(peer === msh.via){ return }
if(!(raw = msh.raw)){ raw = mesh.raw(msg) }
if((tmp = msg['@'])
@ -138,7 +138,7 @@ function Mesh(ctx){
mesh.raw = function(msg){
if(!msg){ return '' }
var dup = ctx.dup, msh = msg.mesh || {}, put, hash, tmp;
var dup = ctx.dup, msh = (msg._) || {}, put, hash, tmp;
if(tmp = msh.raw){ return tmp }
if(typeof msg === 'string'){ return msg }
if(msg['@'] && (tmp = msg.put)){

View File

@ -16,7 +16,6 @@ Gun.chain.chain = function(sub){
function output(msg){
var put, get, at = this.as, back = at.back, root = at.root, tmp;
if(!msg.I){ msg.I = at.$ }
if(!msg.$){ msg.$ = at.$ }
this.to.next(msg);
if(get = msg.get){
@ -139,7 +138,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 +151,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 +203,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', {
@ -272,7 +271,6 @@ function ack(msg, ev){
at.on('in', {get: at.get, put: Gun.val.link.ify(get['#']), $: at.$, '@': msg['@']});
return;
}
msg.$ = at.root.$;
Gun.on.put(msg, at.root.$);
}
var empty = {}, u;

View File

@ -86,9 +86,9 @@ function use(msg){
//else if(!cat.async && msg.put !== at.put && root.stop && root.stop[at.id]){ return } root.stop && (root.stop[at.id] = true);
//root.stop && (root.stop.ID = root.stop.ID || Gun.text.random(2));
//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))._);

View File

@ -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){

View File

@ -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)._);
}

View File

@ -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)){

View File

@ -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.

File diff suppressed because it is too large Load Diff

View File

@ -17,13 +17,21 @@
<script src="./expect.js"></script>
<script></script>
<script src="../gun.js"></script>
<!-- script
src="https://cdn.jsdelivr.net/npm/gun/sea.js">
</script -->
<script
src="../sea.js">
</script>
<script src="./common.js"></script>
<script src="./sea/sea.js"></script>
<script>
if(location.search){
Gun.debug = true;
console.log('async?', Gun.debug);
}
mocha.run(function(a,b,c){
//document.body.prepend("MARK! REMEMBER TO REMOVE RETURN!");return;
var yes = confirm("REFRESH BROWSER FOR ASYNC TESTS?");
if(yes){
if(location.search){

File diff suppressed because it is too large Load Diff

View File

@ -1,61 +0,0 @@
/* global Gun,describe,expect,it,beforeEach */
/*eslint max-len: ["error", 95, { "ignoreComments": true }]*/
/*eslint semi: ["error", "always", { "omitLastInOneLineBlock": true}]*/
/*eslint object-curly-spacing: ["error", "never"]*/
/*eslint node/no-deprecated-api: [error, {ignoreModuleItems: ["new buffer.Buffer()"]}] */
var root;
var Gun;
(function(env){
root = env.window ? env.window : global;
env.window && root.localStorage && root.localStorage.clear();
try{ require('fs').unlinkSync('data.json') }catch(e){}
//root.Gun = root.Gun || require('../gun');
if(root.Gun){
//Gun = root.Gun = root.Gun;
} else {
var expect = global.expect = require("../expect");
root.Gun = require('../../gun');
//Gun.serve = require('../../lib/serve');
//require('./s3');
//require('./uws');
//require('./wsp/server');
require('../../lib/file');
require('../../sea.js');
}
}(this));
;(function(){
Gun = root.Gun
const SEA = Gun.SEA
if(!SEA){ return }
describe('SEA', function(){
var user;
var gun;
it('is instantiable', done => {
gun = Gun({ localStorage: true, radisk: false })
user = gun.user()
done()
})
it('register users', async done => {
user.create('bob', 'test123', ack => {
expect(ack.err).to.not.be.ok();
setTimeout(done, 30)
})
})
it('login users', async done => {
console.log("------------------");
user.auth('bob', 'test123', ack => {
console.log("?????", ack, SEA.err);
expect(ack.err).to.not.be.ok();
done()
})
})
})
})()

1009
test/sea/sea_old.js Normal file

File diff suppressed because it is too large Load Diff