etebase
Version:
Etebase TypeScript API for the web and node
552 lines • 22.8 kB
JavaScript
;
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