Finalized refactoring - much cleaner and easier to follow implementation

This commit is contained in:
mhelander 2018-02-03 15:24:34 +02:00
parent 2017bcc0d8
commit 5f29bd5a54

224
sea.js
View File

@ -195,14 +195,15 @@
} }
}) })
} }
// This is IndexedDB used by Gun SEA
const seaIndexedDb = new EasyIndexedDB('SEA', 'GunDB', 1) const seaIndexedDb = new EasyIndexedDB('SEA', 'GunDB', 1)
// Encryption parameters // Encryption parameters
const pbkdf2 = { hash: 'SHA-256', iter: 50000, ks: 64 } const pbKdf2 = { hash: 'SHA-256', iter: 50000, ks: 64 }
const ecdsasignprops = {name: 'ECDSA', hash: {name: 'SHA-256'}} const ecdsaSignProps = { name: 'ECDSA', hash: { name: 'SHA-256' } }
const ecdsakeyprops = {name: 'ECDSA', namedCurve: 'P-256'} const ecdsaKeyProps = { name: 'ECDSA', namedCurve: 'P-256' }
const ecdhkeyprops = {name: 'ECDH', namedCurve: 'P-256'} const ecdhKeyProps = { name: 'ECDH', namedCurve: 'P-256' }
const _initial_authsettings = { const _initial_authsettings = {
validity: 12 * 60 * 60, // internally in seconds : 12 hours validity: 12 * 60 * 60, // internally in seconds : 12 hours
@ -212,10 +213,13 @@
// These are used to persist user's authentication "session" // These are used to persist user's authentication "session"
const authsettings = Object.assign({}, _initial_authsettings) const authsettings = Object.assign({}, _initial_authsettings)
// This creates Web Cryptography API compliant JWK for sign/verify purposes // This creates Web Cryptography API compliant JWK for sign/verify purposes
const keystoecdsajwk = (pub, priv) => { const keysToEcdsaJwk = (pub, priv) => {
const [ x, y ] = Buffer.from(pub, 'base64').toString('utf8').split(':') const [ x, y ] = Buffer.from(pub, 'base64').toString('utf8').split(':')
const jwk = priv ? { d: priv, key_ops: ['sign'] } : { key_ops: ['verify'] } const jwk = priv ? { d: priv, key_ops: ['sign'] } : { key_ops: ['verify'] }
return Object.assign(jwk, { x, y, kty: 'EC', crv: 'P-256', ext: false }) return [ // Use with spread returned value...
'jwk',
Object.assign(jwk, { x, y, kty: 'EC', crv: 'P-256', ext: false })
]
} }
// let's extend the gun chain with a `user` function. // let's extend the gun chain with a `user` function.
@ -253,7 +257,7 @@
} }
// This is internal func queries public key(s) for alias. // This is internal func queries public key(s) for alias.
const querygunaliases = (alias,root) => new Promise((resolve, reject) => { const queryGunAliases = (alias, root) => new Promise((resolve, reject) => {
// load all public keys associated with the username alias we want to log in with. // load all public keys associated with the username alias we want to log in with.
root.get(`alias/${alias}`).get((rat, rev) => { root.get(`alias/${alias}`).get((rat, rev) => {
rev.off() rev.off()
@ -294,7 +298,7 @@
// This is internal User authentication func. // This is internal User authentication func.
const authenticate = async (alias, pass, root) => { const authenticate = async (alias, pass, root) => {
// load all public keys associated with the username alias we want to log in with. // load all public keys associated with the username alias we want to log in with.
const aliases = (await querygunaliases(alias, root)) const aliases = (await queryGunAliases(alias, root))
.filter(({ pub, at: { put } = {} } = {}) => !!pub && !!put) .filter(({ pub, at: { put } = {} } = {}) => !!pub && !!put)
// Got any? // Got any?
if (!aliases.length) { if (!aliases.length) {
@ -340,7 +344,7 @@
return user return user
} }
// This internal func finalizes User authentication // This internal func finalizes User authentication
const finalizelogin = async (alias, key, root, opts) => { const finalizeLogin = async (alias, key, root, opts) => {
const { user } = root._ const { user } = root._
// add our credentials in-memory only to our root gun instance // add our credentials in-memory only to our root gun instance
user._ = key.at.gun._ user._ = key.at.gun._
@ -351,7 +355,7 @@
Object.assign(user._, { alias, pub, epub, sea: { pub, priv, epub, epriv } }) Object.assign(user._, { alias, pub, epub, sea: { pub, priv, epub, epriv } })
//console.log("authorized", user._); //console.log("authorized", user._);
// persist authentication // persist authentication
await authpersist(user._, key.proof, opts) await authPersist(user._, key.proof, opts)
// emit an auth event, useful for page redirects and stuff. // emit an auth event, useful for page redirects and stuff.
try { try {
root._.on('auth', user._) root._.on('auth', user._)
@ -362,7 +366,7 @@
return user._ return user._
} }
// This updates sessionStorage & IndexedDB to persist authenticated "session" // This updates sessionStorage & IndexedDB to persist authenticated "session"
const updatestorage = (proof, key, pin) => async (props) => { const updateStorage = (proof, key, pin) => async (props) => {
if (!Gun.obj.has(props, 'alias')) { if (!Gun.obj.has(props, 'alias')) {
return // No 'alias' - we're done. return // No 'alias' - we're done.
} }
@ -403,7 +407,7 @@
} }
// This internal func persists User authentication if so configured // This internal func persists User authentication if so configured
const authpersist = async (user, proof, opts) => { const authPersist = async (user, proof, opts) => {
// opts = { pin: 'string' } // opts = { pin: 'string' }
// no opts.pin then uses random PIN // no opts.pin then uses random PIN
// How this works: // How this works:
@ -426,14 +430,14 @@
const key = { pub, priv, epub, epriv } const key = { pub, priv, epub, epriv }
if (props instanceof Promise) { if (props instanceof Promise) {
const asyncProps = await props.then() const asyncProps = await props.then()
return await updatestorage(proof, key, pin)(asyncProps) return await updateStorage(proof, key, pin)(asyncProps)
} }
return await updatestorage(proof, key, pin)(props) return await updateStorage(proof, key, pin)(props)
} }
return await updatestorage()({ alias: 'delete' }) return await updateStorage()({ alias: 'delete' })
} }
// This internal func recalls persisted User authentication if so configured // This internal func recalls persisted User authentication if so configured
const authrecall = async (root, authprops) => { const authRecall = async (root, authprops) => {
// window.sessionStorage only holds signed { alias, pin } !!! // window.sessionStorage only holds signed { alias, pin } !!!
const remember = authprops || sessionStorage.getItem('remember') const remember = authprops || sessionStorage.getItem('remember')
const { alias = sessionStorage.getItem('user'), pin: pIn } = authprops || {} const { alias = sessionStorage.getItem('user'), pin: pIn } = authprops || {}
@ -476,7 +480,7 @@
} }
} }
// Yes, let's get (all?) matching aliases // Yes, let's get (all?) matching aliases
const aliases = (await querygunaliases(alias, root)) const aliases = (await queryGunAliases(alias, root))
.filter(({ pub } = {}) => !!pub) .filter(({ pub } = {}) => !!pub)
// Got any? // Got any?
if (!aliases.length) { if (!aliases.length) {
@ -511,7 +515,7 @@
return return
} }
try { // Wipes IndexedDB silently try { // Wipes IndexedDB silently
await updatestorage()(data) await updateStorage()(data)
} catch (e) {} //eslint-disable-line no-empty } catch (e) {} //eslint-disable-line no-empty
err = 'Expired session!' err = 'Expired session!'
return return
@ -542,14 +546,14 @@
// now we have AES decrypted the private key, // now we have AES decrypted the private key,
// if we were successful, then that means we're logged in! // if we were successful, then that means we're logged in!
try { try {
await updatestorage(proof, key, newPin || pin)(key) await updateStorage(proof, key, newPin || pin)(key)
const user = Object.assign(key, { at, proof }) const user = Object.assign(key, { at, proof })
const pIN = newPin || pin const pIN = newPin || pin
const pinProp = pIN && { pin: Buffer.from(pIN, 'base64').toString('utf8') } const pinProp = pIN && { pin: Buffer.from(pIN, 'base64').toString('utf8') }
return await finalizelogin(alias, user, root, pinProp) return await finalizeLogin(alias, user, root, pinProp)
} catch (e) { // TODO: right log message ? } catch (e) { // TODO: right log message ?
Gun.log('Failed to finalize login with new password!') Gun.log('Failed to finalize login with new password!')
const { err = '' } = e || {} const { err = '' } = e || {}
@ -558,12 +562,12 @@
} }
// This internal func executes logout actions // This internal func executes logout actions
const authleave = async (root, alias = root._.user._.alias) => { const authLeave = async (root, alias = root._.user._.alias) => {
const { user = { _: {} } } = root._ const { user = { _: {} } } = root._
root._.user = null root._.user = null
// Removes persisted authentication & CryptoKeys // Removes persisted authentication & CryptoKeys
try { try {
await authpersist({ alias }) await authPersist({ alias })
} catch (e) {} //eslint-disable-line no-empty } catch (e) {} //eslint-disable-line no-empty
// TODO: is this correct way to 'logout' user from Gun.User ? // TODO: is this correct way to 'logout' user from Gun.User ?
[ 'alias', 'sea', 'pub' ].map((key) => delete user._[key]) [ 'alias', 'sea', 'pub' ].map((key) => delete user._[key])
@ -608,7 +612,7 @@
const sha256hash = async (mm) => { const sha256hash = async (mm) => {
const hashSubtle = subtleossl || subtle const hashSubtle = subtleossl || subtle
const m = parseProps(mm) const m = parseProps(mm)
const hash = await hashSubtle.digest(pbkdf2.hash, new TextEncoder().encode(m)) const hash = await hashSubtle.digest(pbKdf2.hash, new TextEncoder().encode(m))
return Buffer.from(hash) return Buffer.from(hash)
} }
// This internal func returns SHA-1 hashed data for KeyID generation // This internal func returns SHA-1 hashed data for KeyID generation
@ -618,11 +622,11 @@
function User(){} function User(){}
// Well first we have to actually create a user. That is what this function does. // Well first we have to actually create a user. That is what this function does.
Object.assign(User, { Object.assign(User, {
async create(alias, pass, cb) { async create(username, pass, cb) {
const root = this.back(-1) const root = this.back(-1)
return new Promise((resolve, reject) => { // Because no Promises or async return new Promise((resolve, reject) => { // Because no Promises or async
// Because more than 1 user might have the same username, we treat the alias as a list of those users. // 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(async (at, ev) => { root.get(`alias/${username}`).get(async (at, ev) => {
ev.off() ev.off()
if (at.put) { 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. // If we can enforce that a user name is already taken, it might be nice to try, but this is not guaranteed.
@ -638,28 +642,24 @@
const pairs = await SEA.pair() const pairs = await SEA.pair()
// now we have generated a brand new ECDSA key pair for the user account. // now we have generated a brand new ECDSA key pair for the user account.
const { pub, priv, epriv } = pairs 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! // the user's public key doesn't need to be signed. But everything else needs to be signed with it!
SEA.write(alias, pairs).then((alias) => const alias = await SEA.write(username, pairs)
Object.assign(user, {alias }) && SEA.write(pairs.epub, pairs) const epub = await SEA.write(pairs.epub, pairs)
).then((epub) => Object.assign(user, { epub })
// to keep the private key safe, we AES encrypt it with the proof of work! // to keep the private key safe, we AES encrypt it with the proof of work!
&& SEA.enc({ priv, epriv }, { pub: pairs.epub, key: proof }) const auth = await SEA.enc({ priv, epriv }, { pub: pairs.epub, key: proof })
).then((auth) => // TODO: So signedsalt isn't needed? .then((auth) => // TODO: So signedsalt isn't needed?
// SEA.write(salt, pairs).then((signedsalt) => // SEA.write(salt, pairs).then((signedsalt) =>
SEA.write({ salt, auth }, pairs) SEA.write({ salt, auth }, pairs)
// ) // )
).then((auth) => { ).catch((e) => { Gun.log('SEA.en or SEA.write calls failed!'); reject(e) })
Object.assign(user, { auth }) const user = { alias, pub, epub, auth }
const tmp = `pub/${pairs.pub}` 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.
// awesome, now we can actually save the user with their public key as their ID. root.get(tmp).put(user)
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.
// next up, we want to associate the alias with the public key. So we add it to the alias list. root.get(`alias/${username}`).put(Gun.obj.put({}, tmp, Gun.val.rel.ify(tmp)))
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)
// 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.
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) { } catch (e) {
Gun.log('SEA.create failed!') Gun.log('SEA.create failed!')
reject(e) reject(e)
@ -674,65 +674,67 @@
if (!pass && pin) { if (!pass && pin) {
try { try {
return await authrecall(root, { alias, pin }) return await authRecall(root, { alias, pin })
} catch (e) { } catch (e) {
throw { err: 'Auth attempt failed! Reason: No session data for alias & PIN' } throw { err: 'Auth attempt failed! Reason: No session data for alias & PIN' }
} }
} }
try { const putErr = (msg) => (e) => {
const putErr = (msg) => (e) => { const { message, err = message || '' } = e
const { message, err = message || '' } = e Gun.log(msg)
Gun.log(msg) throw { err: `${msg} Reason: ${err}` }
throw { err: `${msg} Reason: ${err}` } }
}
return authenticate(alias, pass, root).then((keys) => { try {
if (!keys) { const keys = await authenticate(alias, pass, root)
return putErr('Auth attempt failed!')({ message: 'No keys' }) if (!keys) {
} return putErr('Auth attempt failed!')({ message: 'No keys' })
const { pub, priv, epub, epriv } = keys }
// we're logged in! const { pub, priv, epub, epriv } = keys
if (newpass) { // we're logged in!
// password update so encrypt private key using new pwd + salt if (newpass) {
// password update so encrypt private key using new pwd + salt
try {
const salt = Gun.text.random(64) const salt = Gun.text.random(64)
return SEA.proof(newpass, salt).then((key) => const encSigAuth = await SEA.proof(newpass, salt)
.then((key) =>
SEA.enc({ priv, epriv }, { pub, key, set: true }) SEA.enc({ priv, epriv }, { pub, key, set: true })
.then((auth) => SEA.write({ salt, auth }, keys)) .then((auth) => SEA.write({ salt, auth }, keys))
).then((encSigAuth) => )
SEA.write(epub, keys).then((signedEpub) => const signedEpub = await SEA.write(epub, keys)
SEA.write(alias, keys).then((signedAlias) => ({ const signedAlias = await SEA.write(alias, keys)
pub, const user = {
alias: signedAlias, pub,
auth: encSigAuth, alias: signedAlias,
epub: signedEpub auth: encSigAuth,
})) epub: signedEpub
) }
).then((user) => { // awesome, now we can update the user using public key ID.
// awesome, now we can update the user using public key ID. root.get(`pub/${user.pub}`).put(user)
root.get(`pub/${user.pub}`).put(user) // then we're done
// then we're done return finalizeLogin(alias, keys, root, { pin })
return finalizelogin(alias, keys, root, { pin }) .catch(putErr('Failed to finalize login with new password!'))
.catch(putErr('Failed to finalize login with new password!')) } catch (e) {
}).catch(putErr('Password set attempt failed!')) putErr('Password set attempt failed!')(e)
} else {
return finalizelogin(alias, keys, root, { pin })
.catch(putErr('Finalizing login failed!'))
} }
}).catch(putErr('Auth attempt failed!')) } else {
return finalizeLogin(alias, keys, root, { pin })
.catch(putErr('Finalizing login failed!'))
}
} catch (e) { } catch (e) {
putErr('Auth attempt failed!') putErr('Auth attempt failed!')(e)
} }
}, },
async leave() { async leave() {
return await authleave(this.back(-1)) return await authLeave(this.back(-1))
}, },
// If authenticated user wants to delete his/her account, let's support it! // If authenticated user wants to delete his/her account, let's support it!
async delete(alias, pass) { async delete(alias, pass) {
const root = this.back(-1) const root = this.back(-1)
try { try {
const { pub } = await authenticate(alias, pass, root) const { pub } = await authenticate(alias, pass, root)
await authleave(root, alias) await authLeave(root, alias)
// Delete user data // Delete user data
root.get(`pub/${pub}`).put(null) root.get(`pub/${pub}`).put(null)
// Wipe user data from memory // Wipe user data from memory
@ -762,8 +764,8 @@
opts = options opts = options
validity = setvalidity * 60 // minutes to seconds validity = setvalidity * 60 // minutes to seconds
} }
// TODO: for some reasong authrecall doesn't work with await here...
return await new Promise((resolve, reject) => { try {
// opts = { hook: function({ iat, exp, alias, proof }) } // opts = { hook: function({ iat, exp, alias, proof }) }
// iat == Date.now() when issued, exp == seconds to expire from iat // iat == Date.now() when issued, exp == seconds to expire from iat
// How this works: // How this works:
@ -775,18 +777,20 @@
authsettings.hook = (Gun.obj.has(opts, 'hook') && typeof opts.hook === 'function') 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? // All is good. Should we do something more with actual recalled data?
authrecall(root).then(resolve).catch((e) => { return await authRecall(root)
const err = 'No session!' } catch (e) {
Gun.log(err) const err = 'No session!'
resolve({ err: (e && e.err) || err }) Gun.log(err)
}) // NOTE! It's fine to resolve recall with reason why not successful
}) // instead of rejecting...
return { err: (e && e.err) || err }
}
}, },
async alive() { async alive() {
const root = this.back(-1) const root = this.back(-1)
try { try {
// All is good. Should we do something more with actual recalled data? // All is good. Should we do something more with actual recalled data?
await authrecall(root) await authRecall(root)
return root._.user._ return root._.user._
} catch (e) { } catch (e) {
const err = 'No session!' const err = 'No session!'
@ -813,7 +817,7 @@
if(!at.sea){ // only add SEA once per instance, on the "at" context. if(!at.sea){ // only add SEA once per instance, on the "at" context.
at.sea = {own: {}}; at.sea = {own: {}};
var uuid = at.opt.uuid || Gun.state.lex; var uuid = at.opt.uuid || Gun.state.lex;
at.opt.uuid = function(cb){ at.opt.uuid = function(cb){ // TODO: consider async/await and drop callback pattern...
if(!cb){ return } if(!cb){ return }
var id = uuid(), pair = at.user && (at.user._).sea; var id = uuid(), pair = at.user && (at.user._).sea;
if(!pair){ return id } if(!pair){ return id }
@ -846,6 +850,7 @@
// WE DO NOT NEED TO RE-VERIFY AGAIN, JUST TRANSFORM IT TO PLAINTEXT. // WE DO NOT NEED TO RE-VERIFY AGAIN, JUST TRANSFORM IT TO PLAINTEXT.
var to = this.to, vertex = (msg.gun._).put, c = 0, d; var to = this.to, vertex = (msg.gun._).put, c = 0, d;
Gun.node.is(msg.put, function(val, key, node){ c++; // for each property on the node Gun.node.is(msg.put, function(val, key, node){ c++; // for each property on the node
// TODO: consider async/await use here...
SEA.read(val, false).then(function(data){ c--; // false just extracts the plain data. SEA.read(val, false).then(function(data){ c--; // false just extracts the plain data.
node[key] = val = data; // transform to plain value. node[key] = val = data; // transform to plain value.
if(d && !c && (c = -1)){ to.next(msg) } if(d && !c && (c = -1)){ to.next(msg) }
@ -957,6 +962,7 @@
}); });
return; return;
} }
// TODO: consider async/await and drop callback pattern...
SEA.read(val, pub).then(function(data){ var rel, tmp; SEA.read(val, pub).then(function(data){ var rel, tmp;
if(u === data){ // make sure the signature matches the account it claims to be on. 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. return each.end({err: "Unverified data."}); // reject any updates that are signed with a mismatched account.
@ -979,6 +985,7 @@
if(user = at.sea.own[soul]){ if(user = at.sea.own[soul]){
check['any'+soul+key] = 1; check['any'+soul+key] = 1;
user = Gun.obj.map(user, function(a,b){ return b }); user = Gun.obj.map(user, function(a,b){ return b });
// TODO: consider async/await and drop callback pattern...
SEA.read(val, user).then(function(data){ var rel; SEA.read(val, user).then(function(data){ var rel;
if(!data){ return each.end({err: "Mismatched owner on '" + key + "'.", }) } if(!data){ return each.end({err: "Mismatched owner on '" + key + "'.", }) }
if((rel = Gun.val.rel.is(data)) && (tmp = rel.split('~')) && 2 === tmp.length){ if((rel = Gun.val.rel.is(data)) && (tmp = rel.split('~')) && 2 === tmp.length){
@ -1021,6 +1028,7 @@
return; return;
} }
check['any'+soul+key] = 1; check['any'+soul+key] = 1;
// TODO: consider async/await and drop callback pattern...
SEA.verify(tmp[0], user.pub, tmp[1]).then(function(ok){ SEA.verify(tmp[0], user.pub, tmp[1]).then(function(ok){
if(!ok){ return each.end({err: "Signature did not match account at '" + key + "'."}) } if(!ok){ return each.end({err: "Signature did not match account at '" + key + "'."}) }
(at.sea.own[soul] = at.sea.own[soul] || {})[user.pub] = true; (at.sea.own[soul] = at.sea.own[soul] || {})[user.pub] = true;
@ -1075,10 +1083,10 @@
) )
const result = subtle.deriveBits({ const result = subtle.deriveBits({
name: 'PBKDF2', name: 'PBKDF2',
iterations: pbkdf2.iter, iterations: pbKdf2.iter,
salt: new TextEncoder().encode(salt), salt: new TextEncoder().encode(salt),
hash: pbkdf2.hash, hash: pbKdf2.hash,
}, key, pbkdf2.ks * 8) }, key, pbKdf2.ks * 8)
pass = getRandomBytes(pass.length) // Erase passphrase for app pass = getRandomBytes(pass.length) // Erase passphrase for app
return Buffer.from(result, 'binary').toString('base64') return Buffer.from(result, 'binary').toString('base64')
} }
@ -1086,9 +1094,9 @@
const hash = crypto.pbkdf2Sync( const hash = crypto.pbkdf2Sync(
pass, pass,
new TextEncoder().encode(salt), new TextEncoder().encode(salt),
pbkdf2.iter, pbKdf2.iter,
pbkdf2.ks, pbKdf2.ks,
pbkdf2.hash.replace('-', '').toLowerCase() pbKdf2.hash.replace('-', '').toLowerCase()
) )
pass = getRandomBytes(pass.length) // Erase passphrase for app pass = getRandomBytes(pass.length) // Erase passphrase for app
return hash && hash.toString('base64') return hash && hash.toString('base64')
@ -1121,7 +1129,7 @@
try { try {
const ecdhSubtle = subtleossl || subtle const ecdhSubtle = subtleossl || subtle
// First: ECDSA keys for signing/verifying... // First: ECDSA keys for signing/verifying...
const { pub, priv } = await subtle.generateKey(ecdsakeyprops, true, [ 'sign', 'verify' ]) const { pub, priv } = await subtle.generateKey(ecdsaKeyProps, true, [ 'sign', 'verify' ])
.then(async ({ publicKey, privateKey }) => { .then(async ({ publicKey, privateKey }) => {
const { d: priv } = await subtle.exportKey('jwk', privateKey) const { d: priv } = await subtle.exportKey('jwk', privateKey)
// privateKey scope doesn't leak out from here! // privateKey scope doesn't leak out from here!
@ -1132,7 +1140,7 @@
// To include PGPv4 kind of keyId: // To include PGPv4 kind of keyId:
// const pubId = await SEA.keyid(keys.pub) // const pubId = await SEA.keyid(keys.pub)
// Next: ECDH keys for encryption/decryption... // Next: ECDH keys for encryption/decryption...
const { epub, epriv } = await ecdhSubtle.generateKey(ecdhkeyprops, true, ['deriveKey']) const { epub, epriv } = await ecdhSubtle.generateKey(ecdhKeyProps, true, ['deriveKey'])
.then(async ({ publicKey, privateKey }) => { .then(async ({ publicKey, privateKey }) => {
// privateKey scope doesn't leak out from here! // privateKey scope doesn't leak out from here!
const { d: epriv } = await ecdhSubtle.exportKey('jwk', privateKey) const { d: epriv } = await ecdhSubtle.exportKey('jwk', privateKey)
@ -1161,9 +1169,9 @@
y y
}) })
} }
const public = await importKey('jwk', keystoecdhjwk(pub), ecdhkeyprops, false, ['deriveKey']) const public = await importKey('jwk', keystoecdhjwk(pub), ecdhKeyProps, false, ['deriveKey'])
const props = Object.assign({}, ecdhkeyprops, { public }) const props = Object.assign({}, ecdhKeyProps, { public })
const derived = await importKey('jwk', keystoecdhjwk(epub, epriv), ecdhkeyprops, false, ['deriveKey']) const derived = await importKey('jwk', keystoecdhjwk(epub, epriv), ecdhKeyProps, false, ['deriveKey'])
.then(async (privKey) => { .then(async (privKey) => {
// privateKey scope doesn't leak out from here! // privateKey scope doesn't leak out from here!
const derivedKey = await deriveKey(props, privKey, { name: 'AES-CBC', length: 256 }, true, [ 'encrypt', 'decrypt' ]) const derivedKey = await deriveKey(props, privKey, { name: 'AES-CBC', length: 256 }, true, [ 'encrypt', 'decrypt' ])
@ -1177,11 +1185,11 @@
}, },
async sign(data, { pub, priv }) { async sign(data, { pub, priv }) {
try { try {
const jwk = keystoecdsajwk(pub, priv) const jwk = keysToEcdsaJwk(pub, priv)
const hash = await sha256hash(data) const hash = await sha256hash(data)
// privateKey scope doesn't leak out from here! // privateKey scope doesn't leak out from here!
const binSig = await subtle.importKey('jwk', jwk, ecdsakeyprops, false, ['sign']) const binSig = await subtle.importKey(...jwk, ecdsaKeyProps, false, ['sign'])
.then((privKey) => subtle.sign(ecdsasignprops, privKey, new Uint8Array(hash))) .then((privKey) => subtle.sign(ecdsaSignProps, privKey, new Uint8Array(hash)))
return Buffer.from(binSig, 'binary').toString('base64') return Buffer.from(binSig, 'binary').toString('base64')
} catch (e) { } catch (e) {
Gun.log(e) Gun.log(e)
@ -1190,11 +1198,11 @@
}, },
async verify(data, pub, sig) { async verify(data, pub, sig) {
try { try {
const jwk = keystoecdsajwk(pub) const jwk = keysToEcdsaJwk(pub)
const key = await subtle.importKey('jwk', jwk, ecdsakeyprops, false, ['verify']) const key = await subtle.importKey(...jwk, ecdsaKeyProps, false, ['verify'])
const hash = await sha256hash(data) const hash = await sha256hash(data)
const ss = new Uint8Array(Buffer.from(sig, 'base64')) const ss = new Uint8Array(Buffer.from(sig, 'base64'))
return await subtle.verify(ecdsasignprops, key, ss, new Uint8Array(hash)) return await subtle.verify(ecdsaSignProps, key, ss, new Uint8Array(hash))
} catch (e) { } catch (e) {
Gun.log(e) Gun.log(e)
throw e throw e