UNPKG

etebase

Version:

Etebase TypeScript API for the web and node

552 lines 22.8 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.EncryptedCollectionItem = exports.EncryptedCollection = exports.getMainCryptoManager = exports.StorageCryptoManager = exports.CollectionItemCryptoManager = exports.CollectionCryptoManager = exports.MinimalCollectionCryptoManager = exports.AccountCryptoManager = exports.MainCryptoManager = exports.CollectionAccessLevel = void 0; const Constants = __importStar(require("./Constants")); const Crypto_1 = require("./Crypto"); const Exceptions_1 = require("./Exceptions"); const Helpers_1 = require("./Helpers"); var CollectionAccessLevel; (function (CollectionAccessLevel) { CollectionAccessLevel[CollectionAccessLevel["ReadOnly"] = 0] = "ReadOnly"; CollectionAccessLevel[CollectionAccessLevel["Admin"] = 1] = "Admin"; CollectionAccessLevel[CollectionAccessLevel["ReadWrite"] = 2] = "ReadWrite"; })(CollectionAccessLevel = exports.CollectionAccessLevel || (exports.CollectionAccessLevel = {})); function genUidBase64() { return Helpers_1.toBase64(Helpers_1.randomBytes(24)); } class MainCryptoManager extends Crypto_1.CryptoManager { constructor(key, version = Constants.CURRENT_VERSION) { super(key, "Main", version); this.Main = true; // So classes are different } getLoginCryptoManager() { return Crypto_1.LoginCryptoManager.keygen(this.asymKeySeed); } getAccountCryptoManager(privkey) { return new AccountCryptoManager(privkey, this.version); } getIdentityCryptoManager(privkey) { return Crypto_1.BoxCryptoManager.fromPrivkey(privkey); } } exports.MainCryptoManager = MainCryptoManager; class AccountCryptoManager extends Crypto_1.CryptoManager { constructor(key, version = Constants.CURRENT_VERSION) { super(key, "Acct", version); this.Account = true; // So classes are different this.colTypePadSize = 32; } colTypeToUid(colType) { return this.deterministicEncrypt(Helpers_1.bufferPadFixed(Helpers_1.fromString(colType), this.colTypePadSize)); } colTypeFromUid(colTypeUid) { return Helpers_1.toString(Helpers_1.bufferUnpadFixed(this.deterministicDecrypt(colTypeUid), this.colTypePadSize)); } } exports.AccountCryptoManager = AccountCryptoManager; class MinimalCollectionCryptoManager extends Crypto_1.CryptoManager { constructor(key, version = Constants.CURRENT_VERSION) { super(key, "Col", version); this.Collection = true; // So classes are different } } exports.MinimalCollectionCryptoManager = MinimalCollectionCryptoManager; class CollectionCryptoManager extends MinimalCollectionCryptoManager { constructor(accountCryptoManager, key, version = Constants.CURRENT_VERSION) { super(key, version); this.accountCryptoManager = accountCryptoManager; } } exports.CollectionCryptoManager = CollectionCryptoManager; class CollectionItemCryptoManager extends Crypto_1.CryptoManager { constructor(key, version = Constants.CURRENT_VERSION) { super(key, "ColItem", version); this.CollectionItem = true; // So classes are different } } exports.CollectionItemCryptoManager = CollectionItemCryptoManager; class StorageCryptoManager extends Crypto_1.CryptoManager { constructor(key, version = Constants.CURRENT_VERSION) { super(key, "Stor", version); this.Storage = true; // So classes are different } } exports.StorageCryptoManager = StorageCryptoManager; function getMainCryptoManager(mainEncryptionKey, version) { return new MainCryptoManager(mainEncryptionKey, version); } exports.getMainCryptoManager = getMainCryptoManager; class EncryptedRevision { constructor() { this.deleted = false; } static async create(cryptoManager, additionalData, meta, content) { const ret = new EncryptedRevision(); ret.chunks = []; ret.setMeta(cryptoManager, additionalData, meta); await ret.setContent(cryptoManager, additionalData, content); return ret; } static deserialize(json) { const { uid, meta, chunks, deleted } = json; const ret = new EncryptedRevision(); ret.uid = uid; ret.meta = meta; ret.deleted = deleted; ret.chunks = chunks.map((chunk) => { var _a; return [chunk[0], (_a = chunk[1]) !== null && _a !== void 0 ? _a : undefined]; }); return ret; } serialize() { const ret = { uid: this.uid, meta: this.meta, deleted: this.deleted, chunks: this.chunks.map((chunk) => { var _a; return [chunk[0], (_a = chunk[1]) !== null && _a !== void 0 ? _a : undefined]; }), }; return ret; } static cacheLoad(cached_) { const cached = Helpers_1.msgpackDecode(cached_); const ret = new EncryptedRevision(); ret.uid = Helpers_1.toBase64(cached[0]); ret.meta = cached[1]; ret.deleted = cached[2]; ret.chunks = cached[3].map((chunk) => { var _a; return [ Helpers_1.toBase64(chunk[0]), (_a = chunk[1]) !== null && _a !== void 0 ? _a : undefined, ]; }); return ret; } cacheSave(saveContent) { return Helpers_1.msgpackEncode([ Helpers_1.fromBase64(this.uid), this.meta, this.deleted, ((saveContent) ? this.chunks.map((chunk) => { var _a; return [Helpers_1.fromBase64(chunk[0]), (_a = chunk[1]) !== null && _a !== void 0 ? _a : null]; }) : this.chunks.map((chunk) => [Helpers_1.fromBase64(chunk[0])])), ]); } verify(cryptoManager, additionalData) { const adHash = this.calculateAdHash(cryptoManager, additionalData); const mac = Helpers_1.fromBase64(this.uid); try { cryptoManager.verify(this.meta, mac, adHash); return true; } catch (e) { throw new Exceptions_1.IntegrityError(`mac verification failed.`); } } calculateAdHash(cryptoManager, additionalData) { const cryptoMac = cryptoManager.getCryptoMac(); cryptoMac.update(new Uint8Array([(this.deleted) ? 1 : 0])); cryptoMac.updateWithLenPrefix(additionalData); // We hash the chunks separately so that the server can (in the future) return just the hash instead of the full // chunk list if requested - useful for asking for collection updates const chunksHash = cryptoManager.getCryptoMac(false); this.chunks.forEach((chunk) => chunksHash.update(Helpers_1.fromBase64(chunk[0]))); cryptoMac.update(chunksHash.finalize()); return cryptoMac.finalize(); } setMeta(cryptoManager, additionalData, meta) { const adHash = this.calculateAdHash(cryptoManager, additionalData); const encContent = cryptoManager.encryptDetached(Helpers_1.bufferPadSmall(Helpers_1.msgpackEncode(meta)), adHash); this.meta = encContent[1]; this.uid = Helpers_1.toBase64(encContent[0]); } getMeta(cryptoManager, additionalData) { const mac = Helpers_1.fromBase64(this.uid); const adHash = this.calculateAdHash(cryptoManager, additionalData); return Helpers_1.msgpackDecode(Helpers_1.bufferUnpad(cryptoManager.decryptDetached(this.meta, mac, adHash))); } async setContent(cryptoManager, additionalData, content) { const meta = this.getMeta(cryptoManager, additionalData); let chunks = []; const minChunk = 1 << 14; const maxChunk = 1 << 16; let chunkStart = 0; // Only try chunking if our content is larger than the minimum chunk size if (content.length > minChunk) { // FIXME: figure out what to do with mask - should it be configurable? const buzhash = cryptoManager.getChunker(); const mask = (1 << 12) - 1; let pos = 0; while (pos < content.length) { buzhash.update(content[pos]); if (pos - chunkStart >= minChunk) { if ((pos - chunkStart >= maxChunk) || (buzhash.split(mask))) { const buf = content.subarray(chunkStart, pos); const hash = Helpers_1.toBase64(cryptoManager.calculateMac(buf)); chunks.push([hash, buf]); chunkStart = pos; } } pos++; } } if (chunkStart < content.length) { const buf = content.subarray(chunkStart); const hash = Helpers_1.toBase64(cryptoManager.calculateMac(buf)); chunks.push([hash, buf]); } // Shuffle the items and save the ordering if we have more than one if (chunks.length > 0) { const indices = Helpers_1.shuffle(chunks); // Filter duplicates and construct the indice list. const uidIndices = new Map(); chunks = chunks.filter((chunk, i) => { const uid = chunk[0]; const previousIndex = uidIndices.get(uid); if (previousIndex !== undefined) { indices[i] = previousIndex; return false; } else { uidIndices.set(uid, i); return true; } }); // If we have more than one chunk we need to encode the mapping header in the last chunk if (indices.length > 1) { // We encode it in an array so we can extend it later on if needed const buf = Helpers_1.msgpackEncode([indices]); const hash = Helpers_1.toBase64(cryptoManager.calculateMac(buf)); chunks.push([hash, buf]); } } // Encrypt all of the chunks this.chunks = chunks.map((chunk) => [chunk[0], cryptoManager.encrypt(Helpers_1.bufferPad(chunk[1]))]); this.setMeta(cryptoManager, additionalData, meta); } async getContent(cryptoManager) { let indices = [0]; const decryptedChunks = this.chunks.map((chunk) => { if (!chunk[1]) { throw new Exceptions_1.MissingContentError("Missing content for item. Please download it using `downloadContent`"); } const buf = Helpers_1.bufferUnpad(cryptoManager.decrypt(chunk[1])); const hash = cryptoManager.calculateMac(buf); if (!Helpers_1.memcmp(hash, Helpers_1.fromBase64(chunk[0]))) { throw new Exceptions_1.IntegrityError(`The content's mac is different to the expected mac (${chunk[0]})`); } return buf; }); // If we have more than one chunk we have the mapping header in the last chunk if (this.chunks.length > 1) { const lastChunk = Helpers_1.msgpackDecode(decryptedChunks.pop()); indices = lastChunk[0]; } // We need to unshuffle the chunks if (indices.length > 1) { const sortedChunks = []; for (const index of indices) { sortedChunks.push(decryptedChunks[index]); } return Crypto_1.concatArrayBuffersArrays(sortedChunks); } else if (decryptedChunks.length > 0) { return decryptedChunks[0]; } else { return new Uint8Array(); } } delete(cryptoManager, additionalData, preserveContent) { const meta = this.getMeta(cryptoManager, additionalData); if (!preserveContent) { this.chunks = []; } this.deleted = true; this.setMeta(cryptoManager, additionalData, meta); } clone() { const rev = new EncryptedRevision(); rev.uid = this.uid; rev.meta = this.meta; rev.chunks = this.chunks; rev.deleted = this.deleted; return rev; } } class EncryptedCollection { static async create(parentCryptoManager, collectionTypeName, meta, content) { const ret = new EncryptedCollection(); ret.collectionType = parentCryptoManager.colTypeToUid(collectionTypeName); ret.collectionKey = parentCryptoManager.encrypt(Helpers_1.randomBytes(Helpers_1.symmetricKeyLength), ret.collectionType); ret.accessLevel = CollectionAccessLevel.Admin; ret.stoken = null; const cryptoManager = ret.getCryptoManager(parentCryptoManager, Constants.CURRENT_VERSION); ret.item = await EncryptedCollectionItem.create(cryptoManager, meta, content); return ret; } static deserialize(json) { const { stoken, accessLevel, collectionType, collectionKey } = json; const ret = new EncryptedCollection(); ret.collectionKey = collectionKey; ret.item = EncryptedCollectionItem.deserialize(json.item); ret.collectionType = collectionType; ret.accessLevel = accessLevel; ret.stoken = stoken; return ret; } serialize() { const ret = { item: this.item.serialize(), collectionType: this.collectionType, collectionKey: this.collectionKey, }; return ret; } static cacheLoad(cached_) { const cached = Helpers_1.msgpackDecode(cached_); const ret = new EncryptedCollection(); ret.collectionKey = cached[1]; ret.accessLevel = cached[2]; ret.stoken = cached[3]; ret.item = EncryptedCollectionItem.cacheLoad(cached[4]); ret.collectionType = cached[5]; return ret; } cacheSave(saveContent) { return Helpers_1.msgpackEncode([ 1, this.collectionKey, this.accessLevel, this.stoken, this.item.cacheSave(saveContent), this.collectionType, ]); } __markSaved() { this.item.__markSaved(); } verify(cryptoManager) { const itemCryptoManager = this.item.getCryptoManager(cryptoManager); return this.item.verify(itemCryptoManager); } setMeta(cryptoManager, meta) { const itemCryptoManager = this.item.getCryptoManager(cryptoManager); this.item.setMeta(itemCryptoManager, meta); } getMeta(cryptoManager) { this.verify(cryptoManager); const itemCryptoManager = this.item.getCryptoManager(cryptoManager); return this.item.getMeta(itemCryptoManager); } async setContent(cryptoManager, content) { const itemCryptoManager = this.item.getCryptoManager(cryptoManager); return this.item.setContent(itemCryptoManager, content); } async getContent(cryptoManager) { this.verify(cryptoManager); const itemCryptoManager = this.item.getCryptoManager(cryptoManager); return this.item.getContent(itemCryptoManager); } delete(cryptoManager, preserveContent) { const itemCryptoManager = this.item.getCryptoManager(cryptoManager); this.item.delete(itemCryptoManager, preserveContent); } get isDeleted() { return this.item.isDeleted; } get uid() { return this.item.uid; } get etag() { return this.item.etag; } get lastEtag() { return this.item.lastEtag; } get version() { return this.item.version; } getCollectionType(parentCryptoManager) { // FIXME: remove this condition "collection-type-migration" is done if (!this.collectionType) { const cryptoManager = this.getCryptoManager(parentCryptoManager); const meta = this.getMeta(cryptoManager); return meta.type; } return parentCryptoManager.colTypeFromUid(this.collectionType); } async createInvitation(parentCryptoManager, identCryptoManager, username, pubkey, accessLevel) { const uid = Helpers_1.randomBytes(32); const encryptionKey = this.getCollectionKey(parentCryptoManager); const collectionType = this.getCollectionType(parentCryptoManager); const content = { encryptionKey, collectionType }; const rawContent = Helpers_1.bufferPadSmall(Helpers_1.msgpackEncode(content)); const signedEncryptionKey = identCryptoManager.encrypt(rawContent, pubkey); const ret = { version: Constants.CURRENT_VERSION, uid: Helpers_1.toBase64(uid), username, collection: this.uid, accessLevel, signedEncryptionKey, }; return ret; } getCryptoManager(parentCryptoManager, version) { const encryptionKey = this.getCollectionKey(parentCryptoManager); return new CollectionCryptoManager(parentCryptoManager, encryptionKey, version !== null && version !== void 0 ? version : this.version); } getCollectionKey(parentCryptoManager) { var _a; // FIXME: remove the ?? null once "collection-type-migration" is done return parentCryptoManager.decrypt(this.collectionKey, (_a = this.collectionType) !== null && _a !== void 0 ? _a : null).subarray(0, Helpers_1.symmetricKeyLength); } } exports.EncryptedCollection = EncryptedCollection; class EncryptedCollectionItem { static async create(parentCryptoManager, meta, content) { const ret = new EncryptedCollectionItem(); ret.uid = genUidBase64(); ret.version = Constants.CURRENT_VERSION; ret.encryptionKey = null; ret.lastEtag = null; const cryptoManager = ret.getCryptoManager(parentCryptoManager); ret.content = await EncryptedRevision.create(cryptoManager, ret.getAdditionalMacData(), meta, content); return ret; } static deserialize(json) { const { uid, version, encryptionKey, content } = json; const ret = new EncryptedCollectionItem(); ret.uid = uid; ret.version = version; ret.encryptionKey = encryptionKey !== null && encryptionKey !== void 0 ? encryptionKey : null; ret.content = EncryptedRevision.deserialize(content); ret.lastEtag = ret.content.uid; return ret; } serialize() { var _a; const ret = { uid: this.uid, version: this.version, encryptionKey: (_a = this.encryptionKey) !== null && _a !== void 0 ? _a : undefined, etag: this.lastEtag, content: this.content.serialize(), }; return ret; } static cacheLoad(cached_) { const cached = Helpers_1.msgpackDecode(cached_); const ret = new EncryptedCollectionItem(); ret.uid = Helpers_1.toBase64(cached[1]); ret.version = cached[2]; ret.encryptionKey = cached[3]; ret.lastEtag = (cached[4]) ? Helpers_1.toBase64(cached[4]) : null; ret.content = EncryptedRevision.cacheLoad(cached[5]); return ret; } cacheSave(saveContent) { return Helpers_1.msgpackEncode([ 1, Helpers_1.fromBase64(this.uid), this.version, this.encryptionKey, (this.lastEtag) ? Helpers_1.fromBase64(this.lastEtag) : null, this.content.cacheSave(saveContent), ]); } __markSaved() { this.lastEtag = this.content.uid; } __getPendingChunks() { return this.content.chunks; } __getMissingChunks() { return this.content.chunks.filter(([_uid, content]) => !content); } isLocallyChanged() { return this.lastEtag !== this.content.uid; } verify(cryptoManager) { return this.content.verify(cryptoManager, this.getAdditionalMacData()); } setMeta(cryptoManager, meta) { let rev = this.content; if (!this.isLocallyChanged()) { rev = this.content.clone(); } rev.setMeta(cryptoManager, this.getAdditionalMacData(), meta); this.content = rev; } getMeta(cryptoManager) { this.verify(cryptoManager); return this.content.getMeta(cryptoManager, this.getAdditionalMacData()); } async setContent(cryptoManager, content) { let rev = this.content; if (!this.isLocallyChanged()) { rev = this.content.clone(); } await rev.setContent(cryptoManager, this.getAdditionalMacData(), content); this.content = rev; } async getContent(cryptoManager) { this.verify(cryptoManager); return this.content.getContent(cryptoManager); } delete(cryptoManager, preserveContent) { let rev = this.content; if (!this.isLocallyChanged()) { rev = this.content.clone(); } rev.delete(cryptoManager, this.getAdditionalMacData(), preserveContent); this.content = rev; } get isDeleted() { return this.content.deleted; } get etag() { return this.content.uid; } get isMissingContent() { return this.content.chunks.some(([_uid, content]) => !content); } getCryptoManager(parentCryptoManager) { const encryptionKey = (this.encryptionKey) ? parentCryptoManager.decrypt(this.encryptionKey) : parentCryptoManager.deriveSubkey(Helpers_1.fromString(this.uid)); return new CollectionItemCryptoManager(encryptionKey, this.version); } getHierarchicalCryptoManager(parentCryptoManager) { const encryptionKey = (this.encryptionKey) ? parentCryptoManager.decrypt(this.encryptionKey) : parentCryptoManager.deriveSubkey(Helpers_1.fromString(this.uid)); return new MinimalCollectionCryptoManager(encryptionKey, this.version); } getAdditionalMacData() { return Helpers_1.fromString(this.uid); } } exports.EncryptedCollectionItem = EncryptedCollectionItem; //# sourceMappingURL=EncryptedModels.js.map