Refactoring in-progress and some more clarity

This commit is contained in:
mhelander 2018-01-24 22:21:41 +02:00
parent 2935a21a19
commit ea886af1b4

214
sea.js
View File

@ -44,13 +44,13 @@
return { TextEncoder, TextDecoder, sessionStorage, localStorage }
}
indexedDB = require('fake-indexeddb')
}
const { TextEncoder, TextDecoder, sessionStorage, localStorage } = funcsSetter()
if (typeof global !== 'undefined') {
global.sessionStorage = sessionStorage
global.localStorage = localStorage
}
}
const { TextEncoder, TextDecoder, sessionStorage, localStorage } = funcsSetter()
// This is Array extended to have .toString(['utf8'|'hex'|'base64'])
function SeaArray() {}
Object.assign(SeaArray, { from: Array.from })
@ -588,7 +588,7 @@
// How does it work?
function User(){}
// Well first we have to actually create a user. That is what this function does.
Object.assign(User, {
Object.assign(User, {
create(alias, pass, cb) {
const root = this.back(-1)
const doIt = (resolve, reject) => {
@ -725,23 +725,27 @@ Object.assign(User, {
},
// If authentication is to be remembered over reloads or browser closing,
// set validity time in minutes.
recall(v,cb,o){
var root = this.back(-1);
var validity, callback, opts;
if(!o && typeof cb !== 'function' && !Gun.val.is(cb)){
opts = cb;
recall(setvalidity, cb, options) {
const root = this.back(-1)
let validity
let callback
let opts
if (!options && typeof cb !== 'function' && !Gun.val.is(cb)) {
opts = cb
} else {
callback = cb;
callback = cb
}
if(!callback){
if(typeof v === 'function'){
callback = v;
validity = _initial_authsettings.validity;
} else if(!Gun.val.is(v)){
opts = v;
validity = _initial_authsettings.validity;
if (!callback) {
if (typeof setvalidity === 'function') {
callback = setvalidity
validity = _initial_authsettings.validity
} else if (!Gun.val.is(setvalidity)) {
opts = setvalidity
validity = _initial_authsettings.validity
} else {
validity = v * 60; // minutes to seconds
validity = setvalidity * 60 // minutes to seconds
}
}
@ -752,34 +756,33 @@ Object.assign(User, {
// called when app bootstraps, with wanted options
// IF authsettings.validity === 0 THEN no remember-me, ever
// IF PIN then signed 'remember' to window.sessionStorage and 'auth' to IndexedDB
authsettings.validity = typeof validity !== 'undefined' ? validity
: _initial_authsettings.validity;
authsettings.validity = typeof validity !== 'undefined'
? validity : _initial_authsettings.validity
authsettings.hook = (Gun.obj.has(opts, 'hook') && typeof opts.hook === 'function')
? opts.hook : _initial_authsettings.hook;
? opts.hook : _initial_authsettings.hook
// All is good. Should we do something more with actual recalled data?
authrecall(root).then(resolve).catch(function(e){
var err = 'No session!';
Gun.log(err);
resolve({err: (e && e.err) || err});
});
};
if(callback){doIt(callback, callback)} else { return new Promise(doIt) }
authrecall(root).then(resolve).catch((e) => {
const err = 'No session!'
Gun.log(err)
resolve({ err: (e && e.err) || err })
})
}
if (callback) { doIt(callback, callback) } else { return new Promise(doIt) }
},
alive(cb) {
var root = this.back(-1);
var doIt = function(resolve, reject){
authrecall(root).then(function(){
const root = this.back(-1)
const doIt = (resolve, reject) => {
// All is good. Should we do something more with actual recalled data?
resolve(root._.user._);
}).catch(function(e){
var err = 'No session!';
Gun.log(err);
reject({err: err});
});
};
if(cb){doIt(cb, cb)} else { return new Promise(doIt) }
authrecall(root).then(() => resolve(root._.user._))
.catch((e) => {
const err = 'No session!'
Gun.log(err)
reject({ err })
})
}
})
if (cb) { doIt(cb, cb) } else { return new Promise(doIt) }
}
})
Gun.chain.trust = function(user) {
// TODO: BUG!!! SEA `node` read listener needs to be async, which means core needs to be async too.
@ -1052,20 +1055,20 @@ Object.assign(User, {
proof(pass, salt, cb) {
const nodeJsPbkdf2 = (resolve, reject) => {
try { // For NodeJS crypto.pkdf2 rocks
var hash = crypto.pbkdf2Sync(
const hash = crypto.pbkdf2Sync(
pass,
new TextEncoder().encode(salt),
pbkdf2.iter,
pbkdf2.ks,
pbkdf2.hash.replace('-', '').toLowerCase()
);
pass = getRandomBytes(pass.length)
)
pass = getRandomBytes(pass.length) // Erase passphrase for app
resolve(hash && hash.toString('base64'))
} catch (e) { reject(e) }
}
const doIt = (resolve, reject) => (typeof window !== 'undefined'
&& subtle.importKey( // For browser subtle works fine
'raw', new TextEncoder().encode(pass), {name: 'PBKDF2'}, false, ['deriveBits']
'raw', new TextEncoder().encode(pass), { name: 'PBKDF2' }, false, ['deriveBits']
).then((key) => subtle.deriveBits({
name: 'PBKDF2',
iterations: pbkdf2.iter,
@ -1077,19 +1080,19 @@ Object.assign(User, {
return Buffer.from(result, 'binary').toString('base64')
}).then(resolve).catch((e) => { Gun.log(e); reject(e) })
) || nodeJsPbkdf2(resolve, reject)
if (cb) { doIt(cb, function(){cb()}) } else { return new Promise(doIt) }
if (cb) { doIt(cb, () => cb()) } else { return new Promise(doIt) }
},
// Calculate public key KeyID aka PGPv4 (result: 8 bytes as hex string)
keyid(p, cb) {
keyid(pub, cb) {
const doIt = (resolve, reject) => {
// base64('base64(x):base64(y)') => Buffer(xy)
const pb = Buffer.concat(
Buffer.from(p, 'base64').toString('utf8').split(':')
Buffer.from(pub, 'base64').toString('utf8').split(':')
.map((t) => Buffer.from(t, 'base64'))
)
// id is PGPv4 compliant raw key
const id = Buffer.concat([
Buffer.from([0x99, pb.length/0x100, pb.length%0x100]), pb
Buffer.from([0x99, pb.length / 0x100, pb.length % 0x100]), pb
])
sha1hash(id).then((sha1) => {
const hash = Buffer.from(sha1, 'binary')
@ -1103,7 +1106,7 @@ Object.assign(User, {
const catcher = (e) => { Gun.log(e); reject(e) }
const ecdhSubtle = subtleossl || subtle
// First: ECDSA keys for signing/verifying...
subtle.generateKey(ecdsakeyprops, true, ['sign', 'verify'])
subtle.generateKey(ecdsakeyprops, true, [ 'sign', 'verify' ])
.then(({ publicKey, privateKey }) => subtle.exportKey('jwk', privateKey)
// privateKey scope doesn't leak out from here!
.then(({ d: priv }) => ({ priv }))
@ -1130,9 +1133,10 @@ Object.assign(User, {
).then(resolve)
.catch(catcher)
}
if (cb){ doIt(cb, function(){cb()}) } else { return new Promise(doIt) }
if (cb){ doIt(cb, () => cb()) } else { return new Promise(doIt) }
},
derive(m, p, cb) {
// Derive shared secret from other's pub and my epub/epriv
derive(pub, { epub, epriv }, cb) {
const ecdhSubtle = subtleossl || subtle
const keystoecdhjwk = (pub, priv) => {
const [ x, y ] = Buffer.from(pub, 'base64').toString('utf8').split(':')
@ -1146,66 +1150,68 @@ Object.assign(User, {
})
}
const doIt = (resolve, reject) => {
ecdhSubtle.importKey('jwk', keystoecdhjwk(m), ecdhkeyprops, false, ['deriveKey'])
.then((pub) => {
const catcher = (e) => { Gun.log(e); reject(e) }
ecdhSubtle.importKey('jwk', keystoecdhjwk(pub), ecdhkeyprops, false, ['deriveKey'])
.then((public) => {
ecdhSubtle.importKey(
'jwk', keystoecdhjwk(p.epub, p.epriv), ecdhkeyprops, false, ['deriveKey']
'jwk', keystoecdhjwk(epub, epriv), ecdhkeyprops, false, ['deriveKey']
).then((privkey) => {
const props = Object.assign({}, ecdhkeyprops, { pub })
const props = Object.assign({}, ecdhkeyprops, { public })
ecdhSubtle.deriveKey(
props, privkey, {name: 'AES-CBC', length: 256}, true, ['encrypt', 'decrypt']
props, privkey, { name: 'AES-CBC', length: 256 }, true, [ 'encrypt', 'decrypt' ]
).then((derivedkey) => ecdhSubtle.exportKey('jwk', derivedkey)
.then(({ k }) => resolve(k))
).catch((e) => { Gun.log(e); reject(e) })
}).catch((e) => { Gun.log(e); reject(e) })
}).catch((e) => { Gun.log(e); reject(e) })
).catch(catcher)
}).catch(catcher)
}).catch(catcher)
}
if (cb){ doIt(cb, function(){cb()}) } else { return new Promise(doIt) }
if (cb) { doIt(cb, () => cb()) } else { return new Promise(doIt) }
},
sign(m, { pub, priv }, cb) {
const doIt = (resolve, reject) => {
const jwk = keystoecdsajwk(pub, priv)
sha256hash(m).then((mm) =>
subtle.importKey('jwk', jwk, ecdsakeyprops, false, ['sign'])
.then((key) => subtle.sign(ecdsasignprops, key, new Uint8Array(mm))
.then((s) => resolve(Buffer.from(s, 'binary').toString('base64')))
.catch((e) => { Gun.log(e); reject(e) })
).catch((e) => { Gun.log(e); reject(e) })
)
}
if (cb) { doIt(cb, () => { cb() }) } else { return new Promise(doIt) }
},
verify(m, p, s, cb) {
sign(data, { pub, priv }, cb) {
const doIt = (resolve, reject) => {
const catcher = (e) => { Gun.log(e); reject(e) }
subtle.importKey('jwk', keystoecdsajwk(p), ecdsakeyprops, false, ['verify'])
.then((key) => sha256hash(m).then((hash) => ({ key, hash })))
const jwk = keystoecdsajwk(pub, priv)
sha256hash(data).then((hash) =>
subtle.importKey('jwk', jwk, ecdsakeyprops, false, ['sign'])
.then((key) => subtle.sign(ecdsasignprops, key, new Uint8Array(hash))
.then((sig) => resolve(Buffer.from(sig, 'binary').toString('base64')))
.catch(catcher)
).catch(catcher)
)
}
if (cb) { doIt(cb, () => cb()) } else { return new Promise(doIt) }
},
verify(data, pub, sig, cb) {
const doIt = (resolve, reject) => {
const catcher = (e) => { Gun.log(e); reject(e) }
subtle.importKey('jwk', keystoecdsajwk(pub), ecdsakeyprops, false, ['verify'])
.then((key) => sha256hash(data).then((hash) => ({ key, hash })))
.then(({ key, hash }) => {
const ss = new Uint8Array(Buffer.from(s, 'base64'))
const ss = new Uint8Array(Buffer.from(sig, 'base64'))
subtle.verify(ecdsasignprops, key, ss, new Uint8Array(hash))
.then(resolve).catch(catcher)
}).catch(catcher)
}
if (cb) { doIt(cb, () => cb()) } else { return new Promise(doIt) }
},
enc(m, p, cb) {
enc(data, priv, cb) {
const doIt = (resolve, reject) => {
const rands = { s: getRandomBytes(8), iv: getRandomBytes(16) }
const r = Object.keys(rands)
.reduce((obj, key) => Object.assign(obj, { [key]: rands[key].toString('hex') }), {})
try {
m = (m.slice && m) || JSON.stringify(m)
data = (data.slice && data) || JSON.stringify(data)
} catch(e) {} //eslint-disable-line no-empty
try {
recallCryptoKey(p, rands.s).then((aesKey) =>
recallCryptoKey(priv, rands.s).then((aesKey) =>
subtle.encrypt({
name: 'AES-CBC', iv: new Uint8Array(rands.iv)
}, aesKey, new TextEncoder().encode(m)).then((ct) => {
}, aesKey, new TextEncoder().encode(data)).then((ct) => {
/*
MARK TO @mhelander : webcrypto has nu handle
*/
// aesKey.handle.fill(0)
r.ct = Buffer.from(ct, 'binary').toString('base64')
Object.assign(r, { ct: Buffer.from(ct, 'binary').toString('base64') })
resolve(JSON.stringify(r))
}).catch((e) => {
/*
@ -1222,15 +1228,15 @@ Object.assign(User, {
}
if (cb) { doIt(cb, () => cb()) } else { return new Promise(doIt) }
},
dec(m, p, cb) {
dec(data, priv, cb) {
const doIt = (resolve, reject) => {
const { s, iv, ct } = parseProps(m)
const { s, iv, ct } = parseProps(data)
const mm = { s, iv, ct }
const rands = [ 'iv', 's' ].reduce((obj, key) => Object.assign(obj, {
[key]: new Uint8Array(Buffer.from(mm[key], 'hex'))
}), {})
try {
recallCryptoKey(p, rands.s).then((aesKey) =>
recallCryptoKey(priv, rands.s).then((aesKey) =>
subtle.decrypt({
name: 'AES-CBC', iv: rands.iv
}, aesKey, new Uint8Array(Buffer.from(mm.ct, 'base64'))).then(
@ -1245,16 +1251,16 @@ Object.assign(User, {
).catch((e) => { Gun.log(e); reject(e) })
} catch (e) { Gun.log(e); reject(e) }
}
if (cb) { doIt(cb, function(){cb()}) } else { return new Promise(doIt) }
if (cb) { doIt(cb, () => cb()) } else { return new Promise(doIt) }
},
write(mm, p, cb) {
write(data, keys, cb) {
const doIt = (resolve, reject) => {
// TODO: something's bugging double 'SEA[]' treatment to mm...
let m = mm
let m = data
if (m && m.slice && 'SEA[' === m.slice(0, 4)) {
return resolve(m)
}
if (mm && mm.slice) {
if (data && data.slice) {
// Needs to remove previous signature envelope
while ('SEA[' === m.slice(0, 4)) {
try {
@ -1265,39 +1271,35 @@ Object.assign(User, {
}
}
m = (m && m.slice) ? m : JSON.stringify(m)
seaSign(m, p).then(
seaSign(m, keys).then(
(signature) => resolve(`SEA${JSON.stringify([ m, signature ])}`)
).catch((e) => { Gun.log(e); reject(e) })
}
if (cb){ doIt(cb, function(){cb()}) } else { return new Promise(doIt) }
if (cb) { doIt(cb, () => cb()) } else { return new Promise(doIt) }
},
read(m, p, cb) {
read(data, pub, cb) {
const doIt = (resolve, reject) => {
let d
if (!m) {
if (false === p) {
return resolve(m)
if (!data) {
if (false === pub) {
return resolve(data)
}
return resolve()
}
if (!m.slice || 'SEA[' !== m.slice(0, 4)) {
if (false === p) {
return resolve(m)
if (!data.slice || 'SEA[' !== data.slice(0, 4)) {
if (false === pub) {
return resolve(data)
}
return resolve()
}
m = parseProps(m.slice(3))
m = parseProps(data.slice(3))
m = m || ''
d = parseProps(m[0])
if (false === p){
if (false === pub) {
resolve(d)
}
seaVerify(m[0], p, m[1]).then(function(ok){
if (!ok) {
return resolve()
}
resolve(d);
}).catch((e) => { reject(e) })
seaVerify(m[0], pub, m[1]).then((ok) => resolve(ok ? d : undefined))
.catch((e) => reject(e))
}
if (cb && typeof cb === 'function') { doIt(cb, () => cb()) } else {
return new Promise(doIt)