mirror of
https://github.com/amark/gun.git
synced 2025-06-05 21:56:51 +00:00
Refactoring in-progress and some more clarity
This commit is contained in:
parent
2935a21a19
commit
ea886af1b4
520
sea.js
520
sea.js
@ -44,13 +44,13 @@
|
||||
return { TextEncoder, TextDecoder, sessionStorage, localStorage }
|
||||
}
|
||||
indexedDB = require('fake-indexeddb')
|
||||
if (typeof global !== 'undefined') {
|
||||
global.sessionStorage = sessionStorage
|
||||
global.localStorage = localStorage
|
||||
}
|
||||
}
|
||||
const { TextEncoder, TextDecoder, sessionStorage, localStorage } = funcsSetter()
|
||||
|
||||
if (typeof global !== 'undefined') {
|
||||
global.sessionStorage = sessionStorage
|
||||
global.localStorage = localStorage
|
||||
}
|
||||
// This is Array extended to have .toString(['utf8'|'hex'|'base64'])
|
||||
function SeaArray() {}
|
||||
Object.assign(SeaArray, { from: Array.from })
|
||||
@ -588,198 +588,201 @@
|
||||
// How does it work?
|
||||
function User(){}
|
||||
// Well first we have to actually create a user. That is what this function does.
|
||||
Object.assign(User, {
|
||||
create(alias, pass, cb) {
|
||||
const root = this.back(-1)
|
||||
const doIt = (resolve, reject) => {
|
||||
// Because more than 1 user might have the same username, we treat the alias as a list of those users.
|
||||
root.get(`alias/${alias}`).get((at, ev) => {
|
||||
ev.off()
|
||||
if (at.put) {
|
||||
// If we can enforce that a user name is already taken, it might be nice to try, but this is not guaranteed.
|
||||
const err = 'User already created!'
|
||||
Gun.log(err)
|
||||
return reject({ err })
|
||||
}
|
||||
const salt = Gun.text.random(64)
|
||||
// pseudo-randomly create a salt, then use CryptoJS's PBKDF2 function to extend the password with it.
|
||||
seaProof(pass, salt).then((proof) => {
|
||||
// this will take some short amount of time to produce a proof, which slows brute force attacks.
|
||||
seaPair().then((pairs) => {
|
||||
// now we have generated a brand new ECDSA key pair for the user account.
|
||||
const { pub, priv, epriv } = pairs
|
||||
const user = { pub }
|
||||
// the user's public key doesn't need to be signed. But everything else needs to be signed with it!
|
||||
seaWrite(alias, pairs).then((alias) =>
|
||||
Object.assign(user, {alias }) && seaWrite(pairs.epub, pairs)
|
||||
).then((epub) => Object.assign(user, { epub })
|
||||
// to keep the private key safe, we AES encrypt it with the proof of work!
|
||||
&& seaEnc({ priv, epriv }, { pub: pairs.epub, key: proof })
|
||||
).then((auth) => // TODO: So signedsalt isn't needed?
|
||||
// seaWrite(salt, pairs).then((signedsalt) =>
|
||||
seaWrite({ salt, auth }, pairs)
|
||||
// )
|
||||
).then((auth) => {
|
||||
Object.assign(user, { auth })
|
||||
const tmp = `pub/${pairs.pub}`
|
||||
//console.log("create", user, pair.pub);
|
||||
// awesome, now we can actually save the user with their public key as their ID.
|
||||
root.get(tmp).put(user)
|
||||
// next up, we want to associate the alias with the public key. So we add it to the alias list.
|
||||
root.get(`alias/${alias}`).put(Gun.obj.put({}, tmp, Gun.val.rel.ify(tmp)))
|
||||
// callback that the user has been created. (Note: ok = 0 because we didn't wait for disk to ack)
|
||||
setTimeout(() => { resolve({ ok: 0, pub: pairs.pub}) }, 10) // TODO: BUG! If `.auth` happens synchronously after `create` finishes, auth won't work. This setTimeout is a temporary hack until we can properly fix it.
|
||||
}).catch((e) => { Gun.log('SEA.en or SEA.write calls failed!'); reject(e) })
|
||||
}).catch((e) => { Gun.log('SEA.pair call failed!'); reject(e) })
|
||||
})
|
||||
})
|
||||
}
|
||||
if (cb){ doIt(cb, cb) } else { return new Promise(doIt) }
|
||||
},
|
||||
// now that we have created a user, we want to authenticate them!
|
||||
auth(alias, pass, cb, opt) {
|
||||
const opts = opt || (typeof cb !== 'function' && cb)
|
||||
const { pin, newpass } = opts || {}
|
||||
const root = this.back(-1)
|
||||
cb = typeof cb === 'function' && cb
|
||||
|
||||
const doIt = (resolve, reject) => {
|
||||
const putErr = (msg) => (e) => {
|
||||
const { message, err = message || '' } = e
|
||||
Gun.log(msg)
|
||||
reject({ err: `${msg} Reason: ${err}` })
|
||||
}
|
||||
|
||||
if (!pass && pin) {
|
||||
return authrecall(root, { alias, pin }).then(resolve).catch((e) => {
|
||||
reject({ err: 'Auth attempt failed! Reason: No session data for alias & PIN' })
|
||||
})
|
||||
}
|
||||
authenticate(alias, pass, root).then((keys) => {
|
||||
if (!keys) {
|
||||
return putErr('Auth attempt failed!')({ message: 'No keys' })
|
||||
}
|
||||
const { pub, priv, epub, epriv } = keys
|
||||
// we're logged in!
|
||||
if (newpass) {
|
||||
// password update so encrypt private key using new pwd + salt
|
||||
Object.assign(User, {
|
||||
create(alias, pass, cb) {
|
||||
const root = this.back(-1)
|
||||
const doIt = (resolve, reject) => {
|
||||
// Because more than 1 user might have the same username, we treat the alias as a list of those users.
|
||||
root.get(`alias/${alias}`).get((at, ev) => {
|
||||
ev.off()
|
||||
if (at.put) {
|
||||
// If we can enforce that a user name is already taken, it might be nice to try, but this is not guaranteed.
|
||||
const err = 'User already created!'
|
||||
Gun.log(err)
|
||||
return reject({ err })
|
||||
}
|
||||
const salt = Gun.text.random(64)
|
||||
seaProof(newpass, salt).then((key) =>
|
||||
seaEnc({ priv, epriv }, { pub, key, set: true })
|
||||
.then((auth) => seaWrite({ salt, auth }, keys))
|
||||
).then((encSigAuth) =>
|
||||
seaWrite(epub, keys).then((signedEpub) =>
|
||||
seaWrite(alias, keys).then((signedAlias) => ({
|
||||
pub,
|
||||
alias: signedAlias,
|
||||
auth: encSigAuth,
|
||||
epub: signedEpub
|
||||
}))
|
||||
)
|
||||
).then((user) => {
|
||||
// awesome, now we can update the user using public key ID.
|
||||
root.get(`pub/${user.pub}`).put(user)
|
||||
// then we're done
|
||||
finalizelogin(alias, keys, root, { pin }).then(resolve)
|
||||
.catch(putErr('Failed to finalize login with new password!'))
|
||||
}).catch(putErr('Password set attempt failed!'))
|
||||
} else {
|
||||
finalizelogin(alias, keys, root, pin).then(resolve)
|
||||
.catch(putErr('Finalizing login failed!'))
|
||||
}
|
||||
}).catch(putErr('Auth attempt failed!'))
|
||||
}
|
||||
if (cb){ doIt(cb, cb) } else { return new Promise(doIt) }
|
||||
},
|
||||
leave(cb) {
|
||||
const doIt = (resolve) => authleave(this.back(-1)).then(resolve)
|
||||
if (cb) { doIt(cb) } else { return new Promise(doIt) }
|
||||
},
|
||||
// If authenticated user wants to delete his/her account, let's support it!
|
||||
delete(alias, pass, cb) {
|
||||
const root = this.back(-1)
|
||||
const doIt = (resolve, reject) => {
|
||||
authenticate(alias, pass, root)
|
||||
.then(({ pub }) => authleave(root, alias).then(() => pub))
|
||||
.then((pub) => {
|
||||
try {
|
||||
// Delete user data
|
||||
root.get(`pub/${pub}`).put(null)
|
||||
// Wipe user data from memory
|
||||
const { user = { _: {} } } = root._
|
||||
// TODO: is this correct way to 'logout' user from Gun.User ?
|
||||
[ 'alias', 'sea', 'pub' ].map((key) => delete user._[key])
|
||||
user._.is = user.is = {}
|
||||
root.user()
|
||||
resolve({ ok: 0 })
|
||||
} catch(e) {
|
||||
Gun.log('User.delete failed! Error:', e)
|
||||
reject({err: 'Delete attempt failed! Reason: ' + (e && e.err) || e || ''})
|
||||
}
|
||||
}).catch((e) => {
|
||||
Gun.log('User.delete authentication failed! Error:', e)
|
||||
reject({err: 'Delete attempt failed! Reason: ' + (e && e.err) || e || ''})
|
||||
})
|
||||
}
|
||||
if (cb) { doIt(cb, cb) } else { return new Promise(doIt) }
|
||||
},
|
||||
// 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;
|
||||
} else {
|
||||
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;
|
||||
} else {
|
||||
validity = v * 60; // minutes to seconds
|
||||
// pseudo-randomly create a salt, then use CryptoJS's PBKDF2 function to extend the password with it.
|
||||
seaProof(pass, salt).then((proof) => {
|
||||
// this will take some short amount of time to produce a proof, which slows brute force attacks.
|
||||
seaPair().then((pairs) => {
|
||||
// now we have generated a brand new ECDSA key pair for the user account.
|
||||
const { pub, priv, epriv } = pairs
|
||||
const user = { pub }
|
||||
// the user's public key doesn't need to be signed. But everything else needs to be signed with it!
|
||||
seaWrite(alias, pairs).then((alias) =>
|
||||
Object.assign(user, {alias }) && seaWrite(pairs.epub, pairs)
|
||||
).then((epub) => Object.assign(user, { epub })
|
||||
// to keep the private key safe, we AES encrypt it with the proof of work!
|
||||
&& seaEnc({ priv, epriv }, { pub: pairs.epub, key: proof })
|
||||
).then((auth) => // TODO: So signedsalt isn't needed?
|
||||
// seaWrite(salt, pairs).then((signedsalt) =>
|
||||
seaWrite({ salt, auth }, pairs)
|
||||
// )
|
||||
).then((auth) => {
|
||||
Object.assign(user, { auth })
|
||||
const tmp = `pub/${pairs.pub}`
|
||||
//console.log("create", user, pair.pub);
|
||||
// awesome, now we can actually save the user with their public key as their ID.
|
||||
root.get(tmp).put(user)
|
||||
// next up, we want to associate the alias with the public key. So we add it to the alias list.
|
||||
root.get(`alias/${alias}`).put(Gun.obj.put({}, tmp, Gun.val.rel.ify(tmp)))
|
||||
// callback that the user has been created. (Note: ok = 0 because we didn't wait for disk to ack)
|
||||
setTimeout(() => { resolve({ ok: 0, pub: pairs.pub}) }, 10) // TODO: BUG! If `.auth` happens synchronously after `create` finishes, auth won't work. This setTimeout is a temporary hack until we can properly fix it.
|
||||
}).catch((e) => { Gun.log('SEA.en or SEA.write calls failed!'); reject(e) })
|
||||
}).catch((e) => { Gun.log('SEA.pair call failed!'); reject(e) })
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
if (cb){ doIt(cb, cb) } else { return new Promise(doIt) }
|
||||
},
|
||||
// now that we have created a user, we want to authenticate them!
|
||||
auth(alias, pass, cb, opt) {
|
||||
const opts = opt || (typeof cb !== 'function' && cb)
|
||||
const { pin, newpass } = opts || {}
|
||||
const root = this.back(-1)
|
||||
cb = typeof cb === 'function' && cb
|
||||
|
||||
var doIt = function(resolve, reject){
|
||||
// opts = { hook: function({ iat, exp, alias, proof }) }
|
||||
// iat == Date.now() when issued, exp == seconds to expire from iat
|
||||
// How this works:
|
||||
// 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.hook = (Gun.obj.has(opts, 'hook') && typeof opts.hook === 'function')
|
||||
? 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) }
|
||||
},
|
||||
alive(cb) {
|
||||
var root = this.back(-1);
|
||||
var doIt = function(resolve, reject){
|
||||
authrecall(root).then(function(){
|
||||
const doIt = (resolve, reject) => {
|
||||
const putErr = (msg) => (e) => {
|
||||
const { message, err = message || '' } = e
|
||||
Gun.log(msg)
|
||||
reject({ err: `${msg} Reason: ${err}` })
|
||||
}
|
||||
|
||||
if (!pass && pin) {
|
||||
return authrecall(root, { alias, pin }).then(resolve).catch((e) => {
|
||||
reject({ err: 'Auth attempt failed! Reason: No session data for alias & PIN' })
|
||||
})
|
||||
}
|
||||
authenticate(alias, pass, root).then((keys) => {
|
||||
if (!keys) {
|
||||
return putErr('Auth attempt failed!')({ message: 'No keys' })
|
||||
}
|
||||
const { pub, priv, epub, epriv } = keys
|
||||
// we're logged in!
|
||||
if (newpass) {
|
||||
// password update so encrypt private key using new pwd + salt
|
||||
const salt = Gun.text.random(64)
|
||||
seaProof(newpass, salt).then((key) =>
|
||||
seaEnc({ priv, epriv }, { pub, key, set: true })
|
||||
.then((auth) => seaWrite({ salt, auth }, keys))
|
||||
).then((encSigAuth) =>
|
||||
seaWrite(epub, keys).then((signedEpub) =>
|
||||
seaWrite(alias, keys).then((signedAlias) => ({
|
||||
pub,
|
||||
alias: signedAlias,
|
||||
auth: encSigAuth,
|
||||
epub: signedEpub
|
||||
}))
|
||||
)
|
||||
).then((user) => {
|
||||
// awesome, now we can update the user using public key ID.
|
||||
root.get(`pub/${user.pub}`).put(user)
|
||||
// then we're done
|
||||
finalizelogin(alias, keys, root, { pin }).then(resolve)
|
||||
.catch(putErr('Failed to finalize login with new password!'))
|
||||
}).catch(putErr('Password set attempt failed!'))
|
||||
} else {
|
||||
finalizelogin(alias, keys, root, pin).then(resolve)
|
||||
.catch(putErr('Finalizing login failed!'))
|
||||
}
|
||||
}).catch(putErr('Auth attempt failed!'))
|
||||
}
|
||||
if (cb){ doIt(cb, cb) } else { return new Promise(doIt) }
|
||||
},
|
||||
leave(cb) {
|
||||
const doIt = (resolve) => authleave(this.back(-1)).then(resolve)
|
||||
if (cb) { doIt(cb) } else { return new Promise(doIt) }
|
||||
},
|
||||
// If authenticated user wants to delete his/her account, let's support it!
|
||||
delete(alias, pass, cb) {
|
||||
const root = this.back(-1)
|
||||
const doIt = (resolve, reject) => {
|
||||
authenticate(alias, pass, root)
|
||||
.then(({ pub }) => authleave(root, alias).then(() => pub))
|
||||
.then((pub) => {
|
||||
try {
|
||||
// Delete user data
|
||||
root.get(`pub/${pub}`).put(null)
|
||||
// Wipe user data from memory
|
||||
const { user = { _: {} } } = root._
|
||||
// TODO: is this correct way to 'logout' user from Gun.User ?
|
||||
[ 'alias', 'sea', 'pub' ].map((key) => delete user._[key])
|
||||
user._.is = user.is = {}
|
||||
root.user()
|
||||
resolve({ ok: 0 })
|
||||
} catch(e) {
|
||||
Gun.log('User.delete failed! Error:', e)
|
||||
reject({err: 'Delete attempt failed! Reason: ' + (e && e.err) || e || ''})
|
||||
}
|
||||
}).catch((e) => {
|
||||
Gun.log('User.delete authentication failed! Error:', e)
|
||||
reject({err: 'Delete attempt failed! Reason: ' + (e && e.err) || e || ''})
|
||||
})
|
||||
}
|
||||
if (cb) { doIt(cb, cb) } else { return new Promise(doIt) }
|
||||
},
|
||||
// If authentication is to be remembered over reloads or browser closing,
|
||||
// set validity time in minutes.
|
||||
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
|
||||
}
|
||||
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 = setvalidity * 60 // minutes to seconds
|
||||
}
|
||||
}
|
||||
|
||||
var doIt = function(resolve, reject){
|
||||
// opts = { hook: function({ iat, exp, alias, proof }) }
|
||||
// iat == Date.now() when issued, exp == seconds to expire from iat
|
||||
// How this works:
|
||||
// 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.hook = (Gun.obj.has(opts, 'hook') && typeof opts.hook === 'function')
|
||||
? opts.hook : _initial_authsettings.hook
|
||||
// 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).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) {
|
||||
const root = this.back(-1)
|
||||
const doIt = (resolve, reject) => {
|
||||
// All is good. Should we do something more with actual recalled data?
|
||||
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)
|
||||
@ -1341,4 +1343,4 @@ Object.assign(User, {
|
||||
try {
|
||||
module.exports = SEA
|
||||
} catch (e) {} //eslint-disable-line no-empty
|
||||
}());
|
||||
}());
|
||||
|
Loading…
x
Reference in New Issue
Block a user