UNPKG

libves

Version:

Simple, safe and secure end-to-end encryption at-rest

1,444 lines (1,427 loc) 189 kB
/*************************************************************************** * ___ ___ * / \ / \ VESvault * \__ / \ __/ Encrypt Everything without fear of losing the Key * \\ // https://vesvault.com https://ves.host * \\ // * ___ \\_// * / \ / \ libVES: VESvault API library * \__ / \ __/ * \\ // * \\ // * \\_// - Key Management and Exchange * / \ - Item Encryption and Sharing * \___/ - VESrecovery (TM) * * * (c) 2017 - 2022 VESvault Corp * Jim Zubov <jz@vesvault.com> * * GNU General Public License v3 * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is * furnished to do so, under the terms of the COPYING file. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * * @title libVES * @dev A JavaScript end-to-end encryption interface to VESvault REST API * @version 3.02 * * @dev Official source code: https://github.com/vesvault/libVES * * @author Jim Zubov <jz@vesvault.com> (VESvault Corp) * ***************************************************************************/ crypto = require('crypto'); crypto.subtle = require('subtle'); XMLHttpRequest = require('xhr2'); if (typeof(libVES) != 'function') function libVES(optns) { try { if (!crypto.subtle.digest) throw new libVES.Error('Init', 'crypto.subtle is unavailable or improperly implemented'); } catch (e) { if (e instanceof libVES.Error) throw e; throw new libVES.Error('Init', 'crypto.subtle is not available'); } for (var k in optns) this[k] = optns[k]; if (this.domain) this.type = 'secondary'; else if (this.user) this.type = 'primary'; else throw new libVES.Error('InvalidValue','Required parameters: user || domain'); this.unlockedKeys = {}; this.pendingKeys = {}; } libVES.prototype = { constructor: libVES, apiUrl: 'https://api.ves.host/v1/', pollUrl: 'https://poll.ves.host/v1/', wwwUrl: 'https://www.vesvault.com/', keyAlgo: 'ECDH', keyOptions: {namedCurve: 'P-521'}, textCipher: 'AES256GCMp', defaultHash: 'SHA256', propagators: null, request: function(method,uri,body,optns) { var self = this; if (!optns) optns = {}; return new Promise(function(resolve,reject) { var xhr = new XMLHttpRequest(); xhr.open(method, (uri.match(/^https\:/) ? uri : this.apiUrl + uri)); if (optns.abortFn) optns.abortFn(function() { return xhr.abort(); }); xhr.onreadystatechange = function() { switch(xhr.readyState) { case 4: if (xhr.response && typeof(xhr.response) == 'object') { if (xhr.response.errors) { var errs = xhr.response.errors.map(function(e) { return new libVES.Error(e.type,e.message,e); }); if (errs.length) { var retry = function(o) { return self.request(method, uri, body, (o || optns)); }; if (optns && optns.onerror) try { resolve(optns.onerror(errs, optns, retry)); } catch (e) { reject(e); } else if (self.onerror) try { resolve(self.onerror(errs, optns, retry)); } catch (e) { reject(e); } else reject(errs[0]); } } else resolve(xhr.response.result); } else reject(new libVES.Error('BadResponse','Empty response')); } }; if (body != null) xhr.setRequestHeader('Content-Type','application/json'); xhr.setRequestHeader('Accept','application/json'); if (this.user && optns.password) xhr.setRequestHeader('Authorization','Basic ' + btoa(this.user + ':' + optns.password)); else if (optns.token ?? this.token) xhr.setRequestHeader('Authorization','Bearer ' + (optns.token ? optns.token : this.token)); xhr.responseType = 'json'; xhr.send(body); }.bind(this)); }, get: function(uri,fields,optns) { return this.request('GET',this.uriWithFields(uri,fields),null,optns); }, post: function(uri,data,fields,optns) { return this.request('POST',this.uriWithFields(uri,fields),JSON.stringify(data),optns); }, uriWithFields: function(uri,fields) { return fields ? uri + (uri.match(/\?/) ? '&' : '?') + 'fields=' + this.uriListFields(fields) : uri; }, uriListFields: function(fields) { if (typeof(fields) == 'object') { var rs = []; if (fields[0]) rs = fields; else for (var k in fields) { if (fields[k]) rs.push(k + (typeof(fields[k]) == 'object' ? '(' + this.uriListFields(fields[k]) + ')' : '')); } return rs.join(','); } return ''; }, mergeFieldList: function(flds, flds2) { for (var k in flds2) if (flds[k] instanceof Object) flds[k] = this.mergeFieldList(flds[k], flds2[k]); else flds[k] = flds2[k]; return flds; }, elevateAuth: function(optns) { var self = this; if (optns && (optns.password || optns.token)) return Promise.resolve(optns); return (optns && optns.authVaultKey ? Promise.resolve(optns.authVaultKey) : self.getVaultKey()).then(function(vkey) { return vkey.getSessionToken().then(function(tkn) { if (!tkn) return optns; var o = {token: tkn}; if (optns) for (var k in optns) o[k] = optns[k]; return o; }); }); }, login: function(passwd) { if (this.token) return this.me(); var self = this; return this.userMe = Promise.resolve(passwd).then(function(passwd) { return self.get('me',{sessionToken: true},{password: passwd}).then(function(data) { if (!data.sessionToken) throw new libVES.Error('InvalidValue','Session Token is not received'); self.token = data.sessionToken; return new libVES.User(data,self); }); }); }, logout: function() { this.token = this.userMe = this.vaultKey = undefined; return this.lock(); }, delegate: function(optns) { var self = this; return libVES.getModule(libVES,'Delegate').then(function(dlg) { return dlg.login(self,null,optns).then(function() { return self; }); }); }, getVESflow: function() { var self = this; if (!this.VESflow) this.VESflow = libVES.getModule(libVES, 'Flow').then(function(fw) { var flow = new fw('VES'); var authf = self.authorize.bind(self); self.authorize = function(msg) { flow.setValue(msg); return authf(msg); }; var outf = self.logout.bind(self); self.logout = function() { flow.logout(); return outf(); }; return flow; }); return this.VESflow; }, flow: function(start, optns) { var self = this; return self.getVESflow().then(function(flow) { return flow.get().then(function(auth) { return self.authorize(auth); }).catch(function(e) { if (!start || (e && e.code == 'Reload')) throw e; var url = self.wwwUrl + 'vv/unlock?url=' + encodeURIComponent(document.location.href) + '&domain=' + encodeURIComponent(self.domain); if (optns) for (var k in optns) if (optns[k] != null) url += '&' + encodeURIComponent(k) + '=' + encodeURIComponent(optns[k]); flow.url = undefined; return flow.setValue(undefined).then(function() { return flow.addToken(url).then(function(url) { document.location.replace(url); throw new libVES.Error('Redirect', 'Starting VES Authorization...'); }); }); }); }); }, authorize: function(msg) { var self = this; return this.logout().then(function() { if (msg.token) self.token = msg.token; if (msg.domain) self.domain = msg.domain; if (msg.externalId) self.externalId = msg.externalId; return (msg.VESkey ? self.unlock(msg.VESkey) : self.me()).then(function() { return self; }); }); }, carry: function(optns) { var cr = {}; if (this.externalId) cr.externalId = this.externalId; if (this.domain) cr.domain = this.domain; if (this.token) cr.token = this.token; if (this.VESkey) cr.VESkey = this.VESkey; try { sessionStorage['libVES_carry'] = JSON.stringify(cr); } catch (e) { return Promise.reject(e); } return Promise.resolve(true); }, pick: function(optns) { try { if (!sessionStorage['libVES_carry']) throw new libVES.Error('NotFound','No libVES_carry in sessionStorage'); var cr = JSON.parse(sessionStorage['libVES_carry']); delete(sessionStorage['libVES_carry']); return this.authorize(cr); } catch (e) { return Promise.reject(e); } }, me: function() { var self = this; if (!this.userMe) this.userMe = this.get('me').then((function(data) { return new libVES.User(data,self); }).bind(this)); return this.userMe; }, unlock: function(veskey) { var self = this; return this.getVaultKey().then(function(vkey) { return vkey.unlock(veskey).then(function(cryptoKey) { if (self.VESkey === null) self.VESkey = veskey; if (!self.token && self.type == 'secondary') return vkey.getSessionToken().then(function(tkn) { self.token = tkn; return cryptoKey; }); return cryptoKey; }); }).then(function(ck) { return self.handleAttn().catch(function() {}).then(function() { return ck; }); }); }, lock: function() { var lock = []; for (var kid in this.unlockedKeys) lock.push(this.unlockedKeys[kid].then(function(k) { return k.lock(); }).catch(function() {})); this.propagators = null; return Promise.all(lock).then(function() { return true; }); }, reset: function(val) { this.userMe = undefined; return this.lock().then(function() { return val; }); }, getVaultKey: function() { var self = this; switch (this.type) { case 'primary': return this.me().then(function(me) { return me.getCurrentVaultKey(); }); case 'secondary': return (this.vaultKey || (this.vaultKey = this.prepareExternals({externalId: self.externalId}).then(function(ext) { var vKey = new libVES.VaultKey({type: 'secondary', externals: ext},self); return vKey.getField('encSessionToken').then(function(tk) { return vKey; }); }))); default: throw new libVES.Error('Internal','Invalid libVES.type: ' + this.type); } }, getShadowKey: function() { return this.me().then(function(me) { return me.getShadowVaultKey(); }); }, getVaultKeysById: function() { return this.me().then(function(me) { return me.getVaultKeys().then(function(vaultKeys) { return Promise.all(vaultKeys.map(function(e,i) { return e.getId(); })).then(function(ids) { var rs = {}; for (var i = 0; i < ids.length; i++) rs[ids[i]] = vaultKeys[i]; return rs; }); }); }); }, getItems: function(flds) { var self = this; return this.getVaultKey().then(function(k) { return k.getId().then(function(kid) { return k.getVaultEntries(self.mergeFieldList({type: true, deleted: true, file: {creator: true, externals: true}, vaultKey: {type: true, user: true}}, flds)).then(function(ves) { var vis = {}; var vlst = []; for (var i = 0; i < ves.length; i++) { var viid = ves[i].vaultItem.id; if (!vis[viid]) { var vi = ves[i].vaultItem; if (!vi.file) vi.file = undefined; if (!vi.vaultKey) vi.vaultKey = undefined; vi = vis[viid] = self.getItem(vi); vlst.push(vi); if (!ves[i].vaultKey) ves[i].vaultKey = {id: kid}; vi.vaultEntryByKey[kid] = ves[i]; } } return vlst; }); }); }); }, getItem: function(data) { return new libVES.VaultItem(data,this); }, postItem: function(data) { var vi = new libVES.VaultItem(data,this); return vi.validate().then(function() { return vi.post(); }); }, usersToKeys: function(users) { var self = this; return Promise.all(users.map(function(u) { if (typeof(u) == 'object') { if (u instanceof libVES.VaultKey) return [u]; else if (u instanceof libVES.External) return [new libVES.VaultKey({externals:[u]},self)]; else if (u instanceof libVES.User) return self.getUserKeys(u); else if (u instanceof Array || u.domain != null || u.externalId != null) return self._matchSecondaryKey(u, u.user, u.appUrl).then(function(vkey) { return [vkey]; }); } return self.getUserKeys(self._matchUser(u)); })).then(function(ks) { var rs = []; for (var i = 0; i < ks.length; i++) for (var j = 0; j < ks[i].length; j++) rs.push(ks[i][j]); return rs; }); }, _matchUser: function(u) { if (typeof(u) == 'object') { if (u instanceof libVES.User) return u; else return new libVES.User((u.id ? {id: u.id} : {email: u.email}), this); } else if (typeof(u) == 'string' && u.match(/^\S+\@\S+$/)) return new libVES.User({email: u},this); throw new libVES.Error('BadUser',"Cannot match user: " + u,{value: u}); }, _matchSecondaryKey: function(ext, user, appUrl) { var self = this; var m = function() { return libVES.getModule(libVES.Domain,ext.domain).catch(function(e) { return { userToVaultRef: function(u) { return u.getEmail().then(function(email) { return email.toLowerCase(); }); }, vaultRefToUser: function(ext) { var email = ext.externalId ? ext.externalId.replace(/\!.*/, '') : ''; if (!email.match(/\@/)) throw new libVES.Error('NotFound', 'externalId is not an email for non-existing vault'); return new libVES.User({email: email}, self); } }; }); }; return (ext.externalId ? self.prepareExternals(ext) : m().then(function(dom) { return Promise.resolve(user || self.me()).then(function(u) { return dom.userToVaultRef(u); }).then(function(ex) { return self.prepareExternals([ex]); }); }).catch(function(e) { throw new libVES.Error('NotFound', 'Cannot match externalId for domain:' + ext.domain + ', user:' + user + '. Define libVES.Domain.' + ext.domain + '.userToVaultRef(user) to return a valid reference.',{error: e}); })).then(function(exts) { var keyref = {externals: exts}; if (self.externalId && self.externalId[0] != '!') keyref.creator = self.me(); var vkey = new libVES.VaultKey(keyref, self); return vkey.getId().then(function() { return vkey; }).catch(function(e) { if (e.code != 'NotFound') throw e; if (!keyref.creator) throw new libVES.Error.InvalidKey('Anonymous vaults are not authorized to create temp keys'); return Promise.resolve(user || m().then(function(dom) { return dom.vaultRefToUser(exts[0]); })).catch(function(e) { throw new libVES.Error('NotFound', 'No matching secondary vault key',{error: e}); }).then(function(u) { return self.me().then(function(me) { return Promise.all([me.getId(),u.getId()]).then(function(ids) { if (ids[0] == ids[1]) return self.getSecondaryKey(exts,true); }).catch(function(e) { if (e.code != 'NotFound') throw e; }).then(function(rs) { return rs || self.createTempKey(self._matchUser(u)).then(function(vkey) { return vkey.setField('externals',exts).then(function() { if (appUrl) vkey.setField('appUrl', appUrl); return vkey; }); }); }); }); }); }); }); }, getPropagators: function() { if (!this.propagators) { let xid = (this.externalId ?? this.email); this.propagators = (xid?.match(/^[^\!]+\@\w/) ? Promise.resolve(xid.replace(/\!.*/, '')) : this.me().then((me) => me.getEmail())).then((xid) => { if (!xid) return []; let prop = new libVES.VaultKey({externals: {domain: '.propagate', externalId: xid.toLowerCase()}}, this); return prop.getId().then(() => [prop]); }).catch((er) => { if (er?.code == 'NotFound' || er?.code == 'Unauthorized') return []; throw er; }); } return Promise.resolve(this.propagators); }, getUserKeys: function(usr) { var self = this; return usr.getActiveVaultKeys().catch(function(e) { if (e.code == 'NotFound') return []; throw e; }).then(function(keys) { return Promise.all(keys.map(function(k,i) { return k.getPublicCryptoKey().then(function() { return k; }).catch(function() {}); })).then(function(keys) { var rs = []; for (var i = 0; i < keys.length; i++) if (keys[i]) rs.push(keys[i]); return rs; }); }).then(function(keys) { if (!keys.length) return self.createTempKey(usr).then(function(k) { return [k]; }); return keys; }); }, createTempKey: function(usr, optns) { var self = this; var key = new libVES.VaultKey({type: 'temp', algo: this.keyAlgo, user: usr}, self); var veskey = this.generateVESkey(usr); return key.generate(veskey, optns).then(function(k) { key.setField('vaultItems', veskey.then(function(v) { var vi = new libVES.VaultItem({type: 'password'}, self); return usr.getActiveVaultKeys().then(function(akeys) { return [self.me(), self.getVaultKey()].concat(akeys); }).catch(function(e) { if (e.code != 'NotFound') throw e; var sh = self.type == 'secondary' ? [self.me(), self.getVaultKey()] : [self.me()]; return self.getPropagators().then((props) => sh.concat(props)).catch((er) => sh); }).then(function(sh) { return Promise.all(sh); }).then(function(sh) { return vi.shareWith(sh, v, false).then(function() { return [vi]; }); }); })); key.setField('creator', self.me()); return key; }); }, generateVESkey: function(usr) { var buf = new Uint8Array(24); crypto.getRandomValues(buf); return Promise.resolve(libVES.Util.ByteArrayToB64(buf)); }, setVESkey: function(veskey,lost,options) { var self = this; return this.me().then(function(me) { return (new libVES.VaultKey({type: 'current', algo: self.keyAlgo, user: me},self)).generate(Promise.resolve(veskey),options).then(function(k) { return me.getCurrentVaultKey().then(function(cur) { return (cur ? cur.unlock().then(function() { return k.rekeyFrom(cur); }).catch(function(e) { if (!lost) throw e; }) : Promise.resolve(null)).then(function() { var r; if (cur && lost) r = cur.setField('type','lost').then(function() { k.user = undefined; return me.setField('vaultKeys',[cur,k]).then(function() { return me; }); }); else r = k; me.currentVaultKey = me.activeVaultKeys = me.shadowVaultKey = undefined; if (!cur || !lost) me.vaultKeys = undefined; return r; }); }).then(function(r) { return self.elevateAuth(options).then(function(optns) { return r.post(undefined, undefined, optns); }); }).catch(function(e) { self.reset(); throw e; }).then(function(post) { return self.reset(post); }).then(function() { return self.getVaultKey(); }); }); }); }, prepareExternals: function(ext) { var self = this; if (!ext) return Promise.reject(new libVES.Error('InvalidValue','External reference is required')); return Promise.resolve(ext).then(function(ext) { if (!(ext instanceof Array)) ext = [ext]; if (ext.length < 1) throw new libVES.Error('InvalidValue','External reference is required'); var rs = []; for (var i = 0; i < ext.length; i++) { rs[i] = (typeof(ext[i]) == 'object') ? {externalId: ext[i].externalId, domain: ext[i].domain} : {externalId: ext[i]}; if (!rs[i].domain && !(rs[i].domain = self.domain)) throw new libVES.Error('InvalidValue','External reference: domain is required'); if (!rs[i].externalId) throw new libVES.Error('InvalidValue','External reference: externalId is required'); } return rs; }); }, getSecondaryKey: function(ext, force) { var self = this; return this.prepareExternals(Promise.resolve(ext).then(function(e) { if (e.domain && !e.externalId) return libVES.getModule(libVES.Domain, e.domain).then(function(mod) { return self.me().then(function(me) { return mod.userToVaultRef(me, self); }); }); return e; })).then(function(ext) { var vkey = new libVES.VaultKey({externals: ext}, self); return vkey.getId().then(function(id) { return vkey; }).catch(function(e) { if (!force || e.code != 'NotFound') throw e; return self.setSecondaryKey(ext); }); }); }, setSecondaryKey: function(ext,veskey,optns) { var self = this; return this.prepareExternals(ext).then(function(ext) { if (!veskey) veskey = self.generateVESkey(); return self.me().then(function(me) { return (new libVES.VaultKey({type: 'secondary', algo: self.keyAlgo, user: me, externals: ext},self)).generate(veskey,optns).then(function(k) { return self.getSecondaryKey(ext).then(function(k2) { return k.rekeyFrom(k2); }).catch(function(e) { delete(k.vaultEntries); return k; }); }).then(function(k) { var vi = new libVES.VaultItem({type: "password"},self); k.setField('vaultItems',[vi]); return Promise.resolve(veskey).then(function(v) { if (!v) throw new libVES.Error('InvalidValue','VESkey cannot be empty'); return vi.shareWith([me],v,false).then(function() { return self.elevateAuth(optns); }).then(function(optns) { return k.post(undefined, undefined, optns).then(function(post) { return k.setField('id', post.id, false).then(function() { k.fieldUpdate = {id: true}; return k; }); }); }); }); }); }); }); }, setAnonymousKey: function(veskey, optns) { var self = this; return this.prepareExternals({externalId: self.externalId}).then(function(ext) { if (!veskey) throw new libVES.Error('InvalidValue','VESkey cannot be empty'); return (new libVES.VaultKey({type: 'secondary', algo: (optns && optns.algo ? optns.algo : self.keyAlgo), externals: ext},self)).generate(veskey, optns).then(function(k) { return k.post(undefined, ['encSessionToken'], optns).then(function(post) { return k.setField('id', post.id, false).then(function() { k.fieldUpdate = {id: true}; }); }).catch(function(e) { if (!e || e.code != 'Unauthorized') throw e; }).then(function() { self.vaultKey = k.lock().then(function() { return k; }); return self.unlock(veskey); }); }); }); }, setShadow: function(usrs,optns) { var self = this; if (!(usrs instanceof Array)) return Promise.reject(new libVES.Error('InvalidValue', 'usrs must be an array')); if (!usrs.length) { return self.getShadowKey().then(function(sh) { return (sh ? sh.getId().then(function(id) { return self.elevateAuth(optns).then(function(optns) { return self.post('vaultKeys/' + id, {'$op': 'delete'}, undefined, optns). then(function() { return null; }); }); }) : null); }); } if (!optns || !optns.n) return Promise.reject(new libVES.Error('InvalidValue','optns.n must be an integer')); var rkey = new Uint8Array(32); crypto.getRandomValues(rkey); var algo = optns.v ? libVES.Scramble.algo[optns.v] : libVES.Scramble.RDX; if (!algo) return Promise.reject(new libVES.Error('InvalidValue','Unknown scramble algorithm: ' + optns.v)); var s = new algo(optns.n); return s.explode(rkey,usrs.length,optns).then(function(tkns) { return self.me().then(function(me) { me.activeVaultKeys = undefined; return me.setField('shadowVaultKey',new libVES.VaultKey({type: 'shadow', user: me, algo: self.keyAlgo},self).generate(rkey,optns),false).then(function(k) { return me.getCurrentVaultKey().then(function(curr) { return k.rekeyFrom(curr).catch(function() {}).then(function() { libVES.Object._refs = {"#/":k}; k.setField('vaultItems',Promise.all(tkns.map(function(tk,i) { var vi = new libVES.VaultItem({type: 'secret'},self); return vi.shareWith([usrs[i]],tk,false).then(function() { return vi; }); })).then(function(vis) { delete(libVES.Object._refs); return vis; })); return self.elevateAuth(optns).then(function(optns) { return k.post(undefined, undefined, optns); }); }); }); }).catch(function(e) { me.shadowVaultKey = undefined; throw e; }).then(function() { me.currentVaultKey = me.shadowVaultKey = me.activeVaultKeys = undefined; return me.getShadowVaultKey(); }); }); }); }, getFile: function(fileRef) { var self = this; return self.prepareExternals(fileRef).then(function(ext) { return new libVES.File({externals: ext},self); }); }, getFileItem: function(fileRef) { var self = this; return self.getFile(fileRef).then(function(file) { return new libVES.VaultItem({file: file},self); }); }, getValue: function(fileRef) { return this.getFileItem(fileRef).then(function(vaultItem) { return vaultItem.get(); }); }, putValue: function(fileRef,value,shareWith) { var self = this; return this.getFileItem(fileRef).then(function(vaultItem) { return vaultItem.setField('type',libVES.VaultItem.Type._detect(value)).then(function() { return Promise.resolve(shareWith || self.getFileItem(fileRef).then(function(vi) { return vi.getShareList(); }).catch(function(e) { return self.usersToKeys([{domain: VES.domain, externalId: VES.externalId}]); })).then(function(shareWith) { return vaultItem.shareWith(shareWith,value); }); }); }); }, shareFile: function(fileRef,shareWith) { return this.getFileItem(fileRef).then(function(vaultItem) { return vaultItem.shareWith(shareWith); }); }, fileExists: function(fileRef) { return this.getFileItem(fileRef).then(function(vaultItem) { return vaultItem.getId().then(function(id) { return true; }).catch(function(e) { if (e.code == 'NotFound') return false; throw e; }); }); }, deleteFile: function(fileRef) { return this.getFileItem(fileRef).then(function(item) { return item.delete(); }); }, newSecret: function(cls) { if (!cls) cls = this.textCipher; else cls = cls.split('.')[0]; return libVES.getModule(libVES.Cipher,cls).then(function(ci) { return (new ci()).getSecret().then(function(buf) { return cls + '.' + libVES.Util.ByteArrayToB64W(buf); }); }); }, secretToCipher: function(secret) { var ss = secret.split('.'); return libVES.getModule(libVES.Cipher,ss[0]).then(function(cls) { return new cls(libVES.Util.B64ToByteArray(ss[1])); }); }, encryptText: function(openText,secret) { return this.secretToCipher(secret).then(function(ci) { return ci.encrypt(libVES.Util.StringToByteArray(openText),true).then(function(buf) { return libVES.Util.ByteArrayToB64W(buf); }); }); }, decryptText: function(cipherText,secret) { return this.secretToCipher(secret).then(function(ci) { return ci.decrypt(libVES.Util.B64ToByteArray(cipherText),true).then(function(buf) { return libVES.Util.ByteArrayToString(buf); }); }); }, hashText: function(text,cls) { if (cls) cls = cls.split('.')[0]; else cls = this.defaultHash; return libVES.getModule(libVES.Util,['Hash',cls]).then(function(mod) { return mod.hash(libVES.Util.StringToByteArray(text)).then(function(buf) { return cls + '.' + libVES.Util.ByteArrayToB64W(buf); }); }); }, found: function(veskeys,vaultKeys) { var self = this; return Promise.resolve(veskeys).then(function(veskeys) { var chain = Promise.resolve(0); if (veskeys && !(veskeys instanceof Array)) veskeys = [veskeys]; return (vaultKeys ? Promise.resolve(vaultKeys) : self.me().then(function(me) { var rs = []; return me.getVaultKeys().then(function(vaultKeys) { return Promise.all(vaultKeys.map(function(vaultKey,i) { return vaultKey.getType().then(function(t) { switch (t) { case 'temp': case 'lost': case 'recovery': rs.push(vaultKey); } }); })); }).then(function() { return rs; }); })).then(function(vaultKeys) { if (!(vaultKeys instanceof Array)) vaultKeys = [vaultKeys]; return Promise.all(vaultKeys.map(function(vaultKey,i) { return vaultKey.getRecovery().then(function(rcv) { return rcv.unlock(); }).catch(function(e) { var rs = vaultKey.unlock(); if (veskeys) veskeys.map(function(veskey,i) { rs = rs.catch(function() { return vaultKey.unlock(veskey); }); }); return rs; }).then(function() { chain = chain.then(function(ct) { return vaultKey.rekey().then(function() { return ct + 1; }); }).catch(function(e) {console.log(e);});; }).catch(function() {}); })); }).then(function() { return chain; }); }).then(function(ct) { if (ct) return ct + self.found(); }); }, getMyRecoveries: function() { var self = this; return self.me().then(function(me) { return me.getVaultKeys().then(function(vaultKeys) { return Promise.all(vaultKeys.map(function(e,i) { return e.getType(); })).then(function(types) { var rs = []; for (var i = 0; i < types.length; i++) switch (types[i]) { case 'recovery': case 'shadow': rs.push(vaultKeys[i].getRecovery()); } return Promise.all(rs); }); }); }); }, getFriendsRecoveries: function() { var self = this; return self.me().then(function(me) { return me.getFriendsKeyItems().then(function(vaultItems) { var vaultKeys = []; return Promise.all(vaultItems.map(function(e,i) { return e.getVaultKey().then(function(vk) { vaultKeys[i] = vk; if (vk) return vk.getType(); }); })).then(function(types) { var rs = []; for (var i = 0; i < types.length; i++) switch (types[i]) { case 'recovery': case 'shadow': rs.push(vaultKeys[i].getRecovery([vaultItems[i]])); } return Promise.all(rs); }); }); }); }, getUnlockableKeys: function() { var self = this; return self.getVaultKey().then(function(vk) { return vk.getField('unlockableVaultKeys').then(function(vks) { var rs = {}; return Promise.all((vks || []).map(function(e, i) { return e.getId().then(function(id) { rs[id] = e; }); })).then(function() { return rs; }); }); }); }, getAttn: function() { var self = this; var rs = {}; return self.get('attn').then(function(attn) { return attn ? Promise.all([(attn.vaultKeys ? Promise.all(attn.vaultKeys.map(function(vk, i) { return new libVES.VaultKey(vk, self); })).then(function(vks) { rs.vaultKeys = vks; }) : null)]) : null; }).then(function() { return rs; }); }, handleAttn: function(attn) { var self = this; return (attn ? Promise.resolve(attn) : self.getAttn()).then(function(attn) { if (attn) return Promise.all([(attn.vaultKeys ? self.attnVaultKeys(attn.vaultKeys) : null)]); }); }, attnVaultKeys: function(vkeys) { var self = this; return Promise.all(vkeys.map(function(vkey, i) { console.log('attnVaultKeys:', vkey); return vkey.getType().then(function(type) { switch (type) { case 'temp': case 'lost': return vkey.getUser().then(function(user) { return self.me().then(function(me) { return Promise.all([user.getId(), me.getId()]).then(function(ids) { if (ids[0] == ids[1]) return type == 'temp' ? vkey.rekey() : self.elevateAuth({authVaultKey: vkey}).then(function(optns) { return vkey.rekey(optns); }); else return vkey.getVaultItems().then(function(vis) { return Promise.all([user.getActiveVaultKeys(), vkey.getExternals().then((exts) => { let ckey = new libVES.VaultKey({externals: exts}, self); return ckey.getType().then(() => [ckey]).catch((er) => { if (er?.code != 'NotFound') throw er; return []; }); })]).then((klists) => { let ks = (klists[0] ?? []).concat(klists[1]); return Promise.all(ks.map((k) => k.getType())).then((ktypes) => ks.filter((k, i) => ktypes[i] != 'temp')); }).then((ks) => self.elevateAuth().then(function(optns) { return Promise.all(vis.map(function(vi, i) { return vi.reshareWith(ks, undefined, optns); })); })); }); }); }); }); case 'recovery': return vkey.getRecovery().then(function(rcv) { return rcv.recover(); }); } }); })); }, setKeyAlgo: function(optns) { if (typeof(optns) == 'string') { this.keyOptions = null; return this.keyAlgo = optns; } var algo = libVES.Algo.fromKeyOptions(optns); if (algo) { this.keyOptions = optns; return this.keyAlgo = algo; } } }; libVES.Error = function(code, msg, optns) { if (libVES.Error[code] && (libVES.Error[code].prototype instanceof libVES.Error)) return new (libVES.Error[code])(msg, optns); this.code = code; this.init(msg, optns); }; libVES.Error.prototype.init = function(msg, optns) { this.message = msg; if (optns) for (var k in optns) this[k] = optns[k]; }; libVES.Error.prototype.toString = function() { return this.message || this.code; }; libVES.Error.NotFound = function(msg, optns) { this.init(msg, optns); }; libVES.Error.NotFound.prototype = new libVES.Error('NotFound'); libVES.Error.InvalidValue = function(msg, optns) { this.init(msg, optns); }; libVES.Error.InvalidValue.prototype = new libVES.Error('InvalidValue'); libVES.Error.InvalidKey = function(msg, optns) { this.init(msg, optns); }; libVES.Error.InvalidKey.prototype = new libVES.Error('InvalidKey'); libVES.Error.Redirect = function(msg, optns) { this.init(msg, optns); }; libVES.Error.Redirect.prototype = new libVES.Error('Redirect'); libVES.Error.Unauthorized = function(msg, optns) { this.init(msg, optns); }; libVES.Error.Unauthorized.prototype = new libVES.Error('Unauthorized'); libVES.Error.Internal = function(msg, optns) { this.init(msg, optns); }; libVES.Error.Internal.prototype = new libVES.Error('Internal'); libVES.getModule = function(sectn,mods) { var mod; if (mods instanceof Array) mod = mods[0]; else mods = [mod = mods]; if (sectn[mod]) return mods.length > 1 ? libVES.getModule(sectn[mod],mods.slice(1)) : Promise.resolve(sectn[mod]); if (sectn.loadModule) { if (sectn.loadModule[mod]) return sectn.loadModule[mod]; } else sectn.loadModule = {}; return sectn.loadModule[mod] = libVES.loadModule(sectn,mod).then(function(m) { delete(sectn.loadModule[mod]); sectn[mod] = m; return ((mods instanceof Array) && mods.length > 1 ? libVES.getModule(m,mods.slice(1)) : m); }); }; libVES.getModuleFunc = function(sectn,mod,then) { return function() { var m = libVES.getModule(sectn,mod); return then ? m.then(then) : m; }; }; libVES.loadModule = function(sectn,mod) { return Promise.reject(new libVES.Error('Internal',"Cannot load " + sectn + '.' + mod)); }; libVES.maxKeyLen = 48896; libVES.maxEncDataLen = 32768; if (!libVES.Domain) libVES.Domain = {}; libVES.Util = { B64ToByteArray: function(s) { var buf = new Uint8Array(s.length); var boffs = 0; for (var i = 0; i < s.length; i++) { var p = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/-_".indexOf(s[i]); if (p >= 0) { if (p >= 64) p -= 2; buf[boffs >> 3] |= p << 2 >> (boffs & 7); boffs += 6; if ((boffs & 7) < 6) buf[boffs >> 3] |= p << (8 - (boffs & 7)); } } var l = boffs >> 3; var buf2 = new Uint8Array(l); for (var i = 0; i < l; i++) buf2[i] = buf[i]; return buf2.buffer; }, ByteArrayToB64D: function(b,dict) { var buf = new Uint8Array(b); var s = ""; var boffs = 0; while ((boffs >> 3) < buf.byteLength) { var c = (buf[boffs >> 3] << (boffs & 7)) & 0xfc; boffs += 6; if (((boffs & 7) < 6) && ((boffs >> 3) < buf.byteLength)) c |= (buf[boffs >> 3] >> (6 - (boffs & 7))); s += dict[c >> 2]; } for (; boffs & 7; boffs += 6) s += dict.substr(64); return s; }, ByteArrayToB64: function(b) { return libVES.Util.ByteArrayToB64D(b,"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="); }, ByteArrayToB64W: function(b) { return libVES.Util.ByteArrayToB64D(b,"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"); }, StringToByteArray: function(s) { if ((s instanceof ArrayBuffer) || (s instanceof Uint8Array)) return s; var rs = new Uint8Array(4 * s.length); var j = 0; for (var i = 0; i < s.length;i++) { var c = s.charCodeAt(i); if (c >= 0x80) { if (c >= 0x0800) { if (c >= 0x10000) { rs[j++] = (c >> 16) | 0xf0; rs[j++] = ((c >> 12) & 0x3f) | 0x80; } else rs[j++] = ((c >> 12) & 0x0f) | 0xe0; rs[j++] = ((c >> 6) & 0x3f) | 0x80; } else rs[j++] = ((c >> 6) & 0x1f) | 0xc0; rs[j++] = (c & 0x3f) | 0x80; } else rs[j++] = c; } return rs.slice(0,j).buffer; }, ByteArrayToString: function(b) { var buf = new Uint8Array(b); var rs = ''; var c; for (var i = 0; i < buf.length; i++) { var v = buf[i]; if (v & 0x80) { if (v & 0x40) { c = ((v & 0x1f) << 6) | (buf[++i] & 0x3f); if (v & 0x20) { c = (c << 6) | (buf[++i] & 0x3f); if (v & 0x10) c = ((c & 0xffff) << 6) | (buf[++i] & 0x3f); } } else c = -1; } else c = buf[i]; rs += String.fromCharCode(c); } return rs; }, fillUndefs: function(data, defs) { if (data instanceof Array) data.map((d) => libVES.Util.fillUndefs(d, defs)); else if (data && defs) for (var k in defs) if (defs[k]) { if (data[k] === undefined) data[k] = undefined; else if (data[k] instanceof Object) libVES.Util.fillUndefs(data[k], defs[k]); } return data; }, loadWasm: function(src) { return new Promise(function(resolve, reject) { var sc = document.createElement('script'); sc.async = false; sc.src = src; sc.onload = resolve; sc.onerror = reject; document.getElementsByTagName('head')[0].appendChild(sc); }); }, PEM: { toDER: function(pem) { var pp = pem.match(/-----BEGIN.*?-----\s*\r?\n([A-Za-z0-9\/\+\=\s\r\n]*)-----END/); if (!pp) throw new libVES.Error('Internal','PEM formatted key expected'); return new Uint8Array(libVES.Util.B64ToByteArray(pp[1])); }, decode: function(pem) { return libVES.Util.ASN1.decode(libVES.Util.PEM.toDER(pem)); }, import: function(pem,optns) { return libVES.Util.ASN1.import(libVES.Util.PEM.toDER(pem),optns); }, fromDER: function(der) { }, encode: function(der,sgn) { return '-----BEGIN ' + sgn + '-----\r\n' + libVES.Util.ByteArrayToB64(der).match(/.{1,64}/g).join("\r\n") + '\r\n-----END ' + sgn + '-----'; } }, ASN1: { decode: function(der,fStruct) { var p = 0; var data = function() { var l = der[p++]; var len; if (l < 128) len = l; else { len = 0; for (var i = 128; i < l; i++) len = (len << 8) | der[p++]; } if (p + len > der.length) throw new libVES.Error('Internal',"Invalid ASN.1 package"); return der.slice(p,p = p + len); }; var rs = []; for (; p < der.length;) { if (!fStruct && p) { rs.push(der.slice(p)); break; } var tag = der[p++]; switch (tag) { case 48: rs.push(libVES.Util.ASN1.decode(data(),true)); break; case 6: rs.push(new libVES.Util.OID(data())); break; case 2: var d = data(); var v = 0; for (var i = 0; i < d.length; i++) v = (v << 8) | d[i]; rs.push(v); break; case 5: data(); rs.push(null); break; default: rs.push(data()); break; } } return rs; }, encode: function(data,fStruct) { var i2a = function(v) { if (v < 0) throw new libVES.Error('Internal',"Negative value for ASN.1 integer!"); var rs = []; do { rs.push(v & 0xff); v >>= 8; } while (v > 0); return rs.reverse(); }; var bufs = []; var buf = function(tag,bf) { var b = new Uint8Array(bf); var l = b.length; var rs; if (l <= 127) { rs = new Uint8Array(l + 2); rs[1] = l; rs.set(b,2); } else { var lb = i2a(l); rs = new Uint8Array(l + lb.length + 2); rs[1] = 128 + lb.length; rs.set(lb,2); rs.set(b,2 + lb.length); } rs[0] = tag; bufs.push(rs); return rs; }; var d; for (var i = 0; i < data.length; i++) if (fStruct || !i) switch (typeof(d = data[i])) { case 'object': if (d == null) buf(5,new Uint8Array(0)); else if (d instanceof Array) buf(48,libVES.Util.ASN1.encode(d,true)); else if (d instanceof libVES.Util.OID) buf(6,d.getBuffer()); else if (d instanceof Uint8Array || d instanceof ArrayBuffer) buf((d.ASN1type || 4),d); else throw new libVES.Error('Internal',"ASN.1 encode - Unknown type"); break; case 'number': buf(2,i2a(d)); break; default: throw new libVES.Error('Internal',"ASN.1 encode - Unknown type"); } else bufs.push(d); var l = 0; for (var i = 0; i < bufs.length; i++) l += bufs[i].length; var der = new Uint8Array(l); var p = 0; for (i = 0, p = 0; i < bufs.length; p += bufs[i].length, i++) der.set(bufs[i],p); return der; }, import: function(der,optns) { var k = optns && optns.decoded || libVES.Util.ASN1.decode(der)[0]; if (!k) throw new libVES.Error('Internal','Empty ASN.1 package?'); var i = 0; if (typeof(k[i]) == 'number') (optns || (optns = {})).version = k[i++]; if (typeof(k[i]) == 'object' && (k[i][0] instanceof libVES.Util.OID)) return k[i][0].object().then(function(m) { return m.import(k[i][1],function(call,optns) { return new Promise(function(resolve,reject) { switch (call) { case 'container': return resolve(der); default: return resolve(k[i + 1]); } }); },optns); }); return libVES.Util.ASN1.import(libVES.Util.ASN1.encode([[0,[new libVES.Util.OID('1.2.840.113549.1.1.1'),null],der]])).catch(function(e) { throw new libVES.Error('Internal',"Unknown key format",{error: e}); }); }, setType: function(t,buf) { var rs = new Uint8Array(buf); rs.ASN1type = t; return rs; } }, OID: function(s) { if (s instanceof Uint8Array) { var rs = [ Math.floor(s[0] / 40), s[0] % 40 ]; var r = 0; for (var p = 1; p < s.length; p++) { var v = s[p]; r = (r << 7) | (v & 0x7f); if (!(v & 0x80)) { rs.push(r); r = 0; } } this.value = rs.join('.'); } else this.value = s; }, Key: { fromDER: function(der, callbk) { var asn = libVES.Util.ASN1.decode(der)[0]; if (typeof(asn[0]) == 'number') { var asn2 = libVES.Util.ASN1.decode(asn[2])[0]; var pub = libVES.Util.ASN1.decode(asn2[2]); console.log('pub', pub); if (pub) return callbk(pub[0].slice(1), asn2[1], pub[0][0]); else callbk(null, asn2[1]); } else return callbk(asn[1].slice(1), null, asn[1][0]); }, pubASN1: function(pub, optns) { var p = new Uint8Array(pub.byteLength + 1); p[0] = optns && optns.pubBits ? optns.pubBits : 0; p.set(new Uint8Array(pub), 1); p.ASN1type = 3; return p; }, toPKCS: function(pub, priv, asn1hdr, optns) { if (priv) { var seq = [1, priv]; if (pub) { p = libVES.Util.ASN1.encode([libVES.Util.Key.pubASN1(pub, optns)]); p.ASN1type = 0xa1; seq.push(p); } var buf = libVES.Util.ASN1.encode([seq]); return libVES.Util.ASN1.encode([[0, asn1hdr, buf]]); } return libVES.Util.ASN1.encode([[asn1hdr, libVES.Util.Key.pubASN1(pub, optns)]]); }, export: function(pub, priv, asn1hdr, optns) { var pkcs = libVES.Util.Key.toPKCS(pub, priv, asn1hdr, optns); if (priv) return libVES.Util.PKCS8.encode8(pkcs, optns); return libVES.Util.PEM.encode(pkcs, 'PUBLIC KEY'); } }, PKCS1: { import: function(args,chain,optns) { return chain('container',optns).then(function(der) { return crypto.subtle.importKey('spki',der,{name:'RSA-OAEP', hash:'SHA-1'},true,['encrypt']).catch(function(e) { return crypto.subtle.importKey('pkcs8',der,{name:'RSA-OAEP', hash:'SHA-1'},true,['decrypt']); }); }); }, encode: function(key,optns) { return crypto.subtle.exportKey('spki',key).then(function(der) { return libVES.Util.PEM.encode(der,'PUBLIC KEY'); }); } }, PKCS5: { import: function(args,chain,optns) { var f = chain; for (var i = args.length - 1; i >= 0; i--) f = (function(obj,fp) { if (obj[0] instanceof libVES.Util.OID) return function(call,optns) { return obj[0].object().then(function(m) { return m[call](obj[1],fp,optns); }); }; else return fp; })(args[i],f); return f('import',optns).then(function(der) { return libVES.Util.ASN1.import(new Uint8Array(der)); }).catch(function(e) { throw new libVES.Error('InvalidKey',"Cannot import the private key (Invalid VESkey?)"); }); }, export: function(chain,optns) { var args = []; var f = chain; if (!optns || !(optns.members instanceof Array)) throw new libVES.Error('Internal','PKCS#5: optns.members must be an array'); for (var i = optns.members.length - 1; i >= 0; i--) f = (function(obj,fp,idx) { return function(call,optns) { return obj[call](fp,optns).then(function(v) { if (call == 'export') args[idx] = v; return v; }); }; })(optns.members[i],f,i); return f('export',optns).then(function() { return [new libVES.Util.OID('1.2.840.113549.1.5.13'), args]; }); } }, PKCS8: { encode: function(key, optns) { return crypto.subtle.exportKey('pkcs8',key).catch(function(e) { console.log('PKCS8 failed, trying JWK...'); return crypto.subtle.exportKey('jwk', key).then(function(jwk) { return libVES.Util.PKCS8.fromJWK(jwk); }); }).then(function(pkcs8) { return libVES.Util.PKCS8.encode8(pkcs8, optns); }); }, encode8: function(pkcs8, optns) { var ops = {}; for (var k in optns) ops[k] = optns[k]; if (!ops.members) ops.members = [ libVES.getModule(libVES.Util,'PBKDF2'), libVES.getModule(libVES.Cipher,'AES256CBC') ]; return Promise.all(ops.members).then(function(ms) { ops.members = ms; ops.content = pkcs8; var rec = []; if (ops.password) return libVES.Util.PKCS5.export(function(call,optns) { rec[1] = optns.content; return Promise.resolve(); },ops).then(function(data) { rec[0] = data; return libVES.Util.PEM.encode(libVES.Util.ASN1.encode([rec]),'ENCRYPTED PRIVATE KEY'); }); else if (ops.opentext) return libVES.Util.PEM.encode(pkcs8,'PRIVATE KEY'); else throw new libVES.Error('Internal','No password for key export (opentext=true to export without password?)'); }); }, fromJWK: function(jwk) { if (jwk.kty != 'EC') throw new libVES.Error('Internal', 'kty=="EC" expected (workaround for Firefox)'); var pubx = libVES.Util.B64ToByteArray(jwk.x); var puby = libVES.Util.B64ToByteArray(jwk.y); var pub = new Uint8Array(pubx.byteLength + puby.byteLength + 2); pub[0] = 0; pub[1] = 4; pub.set(new Uint8Array(pubx), 2); pub.set(new Uint8Array(puby), pubx.byteLength + 2); pub.ASN1type = 3; pub = libVES.Util.ASN1.encode([pub]); pub.ASN1type = 0xa1; return libVES.Util.ASN1.encode([[ 0, [ new libVES.Util.OID('1.2.840.10045.2.1'), new libVES.Util.OID((function(crvs) { for (var k in crvs) if (crvs[k] == jwk.crv) return k; throw new libVES.Error('Internal', 'Unknown named curve: ' + jwk.crv); })(libVES.Util.EC.namedCurves)) ], libVES.Util.ASN1.encode([[ 1, libVES.Util.B64ToByteArray(jwk.d), pub ]]) ]]).buffer; }, toJWK: function(key) { var der = libVES.Util.ASN1.decode(key)[0]; var idx = 0; if (!(der[0] instanceof Array)) idx++; switch (String(der[idx][0])) { case '1.2.840.10045.2.1': case '1.3.132.1.12': case '1.3.132.112': // Firefox on Mac - apparently a misspelling of the former break; default: console.log(der); throw new libVES.Error('Internal', 'EC PKCS8 expected'); } var der2 = idx ? libVES.Util.ASN1.decode(der[idx + 1])[0] : null; var pub = idx ? (der2[2] ? libVES.Util.ASN1.decode(der2[2])[0] : null) : der[1]; var publ = 0; if (pub && pub.byteLength > 2 && pub[1] == 4) publ = pub.byteLength / 2 - 1; return { crv: libVES.Util.EC.namedCurves[der[idx][1]], d: (idx ? libVES.Util.ByteArrayToB64W(der2[1]) : undefined), ext: true, key_ops: (idx ? ['deriveKey', 'deriveBits'] : []), kty: 'EC', x: (publ ? libVES.Util.ByteArrayToB64W(pub.slice(2, publ + 2)) : ''), y: (publ ? libVES.Util.ByteArrayToB64W(pub.slice(publ + 2, 2 * publ + 2)) : '') }; } }, PBKDF2: { deriveKey: function(args, pwd, algo) { return crypto.subtle.importKey('raw', libVES.Util.StringToByteArray(pwd), 'PBKDF2', false, ['deriveKey']).then(function(k) { var keyargs = {name:'PBKDF2', salt:args[0], iterations:args[1]}; var ka; if (args[2] && (args[2][0] instanceof libVES.Util.OID)) ka = args[2][0].object().then(function(obj) { obj.setArgs(keyargs, args[2][1]); return keyargs; }); else { keyargs.hash = 'SHA-1'; ka = Promise.resolve(keyargs); } return ka.then(function(keyargs) { return crypto.subtle.deriveKey(keyargs, k, algo, true, ['encrypt', 'decrypt']); }); }); }, import: function(args,chain,optns) { if (!optns || !optns.password) throw new libVES.Error('InvalidKey',"VESkey is not supplied"); var pwd = (typeof(optns.password) == '