mirror of
https://github.com/amark/gun.git
synced 2025-03-30 15:08:33 +00:00
Merge branch 'master' into dev
This commit is contained in:
commit
428f7e9d1c
@ -3,8 +3,6 @@ branches:
|
||||
except:
|
||||
- debug
|
||||
node_js:
|
||||
- 4
|
||||
- 6
|
||||
- 8
|
||||
- 10
|
||||
cache:
|
||||
|
23
gun.js
23
gun.js
@ -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
3
gun.min.js
vendored
File diff suppressed because one or more lines are too long
@ -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]);
|
||||
}
|
||||
|
19
lib/store.js
19
lib/store.js
@ -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});
|
||||
});
|
||||
|
@ -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
4
nts.js
@ -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
197
package-lock.json
generated
@ -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"
|
||||
}
|
||||
|
@ -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
488
sea.js
@ -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');
|
||||
}());
|
||||
}());
|
@ -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);
|
||||
|
@ -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() }
|
||||
|
@ -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() }
|
||||
|
153
sea/index.js
153
sea/index.js
@ -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.
|
||||
|
||||
|
17
sea/pair.js
17
sea/pair.js
@ -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) {
|
||||
|
11
sea/parse.js
11
sea/parse.js
@ -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
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
12
sea/shim.js
12
sea/shim.js
@ -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
|
||||
|
25
sea/sign.js
25
sea/sign.js
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
14
sea/work.js
14
sea/work.js
@ -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() }
|
||||
|
@ -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});
|
||||
}
|
||||
|
@ -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)){
|
||||
|
@ -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;
|
||||
|
@ -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))._);
|
||||
|
@ -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){
|
||||
|
@ -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)._);
|
||||
}
|
||||
|
@ -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)){
|
||||
|
@ -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.
|
||||
|
277
test/common.js
277
test/common.js
File diff suppressed because it is too large
Load Diff
@ -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){
|
||||
|
1255
test/sea/sea.js
1255
test/sea/sea.js
File diff suppressed because it is too large
Load Diff
@ -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
1009
test/sea/sea_old.js
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user