UNPKG

cryptobox-hd

Version:

High-level API with persistent storage for Proteus-HD.

328 lines (305 loc) 13.7 kB
"use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); Object.defineProperty(exports, "__esModule", { value: true }); var Proteus = require("proteus-hd"); var EventEmitter = require("events"); var LRUCache = require("wire-webapp-lru-cache"); var error_1 = require("./error"); var CryptoboxSession_1 = require("./CryptoboxSession"); var DecryptionError_1 = require("./DecryptionError"); var InvalidPreKeyFormatError_1 = require("./InvalidPreKeyFormatError"); var ReadOnlyStore_1 = require("./store/ReadOnlyStore"); var error_2 = require("./store/error"); var Cryptobox = (function (_super) { __extends(Cryptobox, _super); function Cryptobox(cryptoBoxStore, minimumAmountOfPreKeys) { if (minimumAmountOfPreKeys === void 0) { minimumAmountOfPreKeys = 1; } var _this = _super.call(this) || this; if (!cryptoBoxStore) { throw new Error("You cannot initialize Cryptobox without a storage component."); } if (minimumAmountOfPreKeys > Proteus.keys.PreKey.MAX_PREKEY_ID) { minimumAmountOfPreKeys = Proteus.keys.PreKey.MAX_PREKEY_ID; } _this.cachedPreKeys = []; _this.cachedSessions = new LRUCache(1000); _this.minimumAmountOfPreKeys = minimumAmountOfPreKeys; _this.store = cryptoBoxStore; _this.pk_store = new ReadOnlyStore_1.ReadOnlyStore(_this.store); var storageEngine = cryptoBoxStore.constructor.name; return _this; } Cryptobox.prototype.save_session_in_cache = function (session) { this.cachedSessions.set(session.id, session); return session; }; Cryptobox.prototype.load_session_from_cache = function (session_id) { return this.cachedSessions.get(session_id); }; Cryptobox.prototype.remove_session_from_cache = function (session_id) { this.cachedSessions.delete(session_id); }; Cryptobox.prototype.create = function () { var _this = this; return this.create_new_identity() .then(function (identity) { _this.identity = identity; return _this.create_last_resort_prekey(); }) .then(function (lastResortPreKey) { _this.cachedPreKeys = [lastResortPreKey]; return _this.init(); }); }; Cryptobox.prototype.load = function () { var _this = this; return this.store.load_identity() .then(function (identity) { if (identity) { _this.identity = identity; return _this.store.load_prekeys(); } throw new error_1.CryptoboxError('Failed to load local identity'); }) .then(function (preKeysFromStorage) { var lastResortPreKey = preKeysFromStorage.find(function (preKey) { return preKey.key_id === Proteus.keys.PreKey.MAX_PREKEY_ID; }); if (lastResortPreKey) { _this.lastResortPreKey = lastResortPreKey; _this.cachedPreKeys = preKeysFromStorage; return _this.init(); } throw new error_1.CryptoboxError('Failed to load last resort PreKey'); }); }; Cryptobox.prototype.init = function () { var _this = this; return this.refill_prekeys() .then(function () { var ids = _this.cachedPreKeys.map(function (preKey) { return preKey.key_id.toString(); }); return _this.cachedPreKeys.sort(function (a, b) { return a.key_id - b.key_id; }); }); }; Cryptobox.prototype.get_serialized_last_resort_prekey = function () { return Promise.resolve(this.serialize_prekey(this.lastResortPreKey)); }; Cryptobox.prototype.get_serialized_standard_prekeys = function () { var _this = this; var standardPreKeys = this.cachedPreKeys .map(function (preKey) { var isLastResortPreKey = preKey.key_id === Proteus.keys.PreKey.MAX_PREKEY_ID; return isLastResortPreKey ? undefined : _this.serialize_prekey(preKey); }) .filter(function (preKeyJson) { return preKeyJson; }); return Promise.resolve(standardPreKeys); }; Cryptobox.prototype.publish_event = function (topic, event) { this.emit(topic, event); }; Cryptobox.prototype.publish_prekeys = function (newPreKeys) { if (newPreKeys.length > 0) { this.publish_event(Cryptobox.TOPIC.NEW_PREKEYS, newPreKeys); } }; Cryptobox.prototype.publish_session_id = function (session) { this.publish_event(Cryptobox.TOPIC.NEW_SESSION, session.id); }; Cryptobox.prototype.refill_prekeys = function () { var _this = this; return Promise.resolve() .then(function () { var missingAmount = Math.max(0, _this.minimumAmountOfPreKeys - _this.cachedPreKeys.length); if (missingAmount > 0) { var startId = _this.cachedPreKeys .reduce(function (currentHighestValue, currentPreKey) { var isLastResortPreKey = currentPreKey.key_id === Proteus.keys.PreKey.MAX_PREKEY_ID; return isLastResortPreKey ? currentHighestValue : Math.max(currentPreKey.key_id + 1, currentHighestValue); }, 0); return _this.new_prekeys(startId, missingAmount); } return []; }) .then(function (newPreKeys) { if (newPreKeys.length > 0) { _this.cachedPreKeys = _this.cachedPreKeys.concat(newPreKeys); } return newPreKeys; }); }; Cryptobox.prototype.create_new_identity = function () { var _this = this; return Promise.resolve() .then(function () { return _this.store.delete_all(); }) .then(function () { var identity = Proteus.keys.IdentityKeyPair.new(); return _this.store.save_identity(identity); }); }; Cryptobox.prototype.session_from_prekey = function (session_id, pre_key_bundle) { var _this = this; var cachedSession = this.load_session_from_cache(session_id); if (cachedSession) { return Promise.resolve(cachedSession); } return Promise.resolve() .then(function () { var bundle; try { bundle = Proteus.keys.PreKeyBundle.deserialise(pre_key_bundle); } catch (error) { throw new InvalidPreKeyFormatError_1.InvalidPreKeyFormatError("PreKey bundle for session \"" + session_id + "\" has an unsupported format."); } return Proteus.session.Session.init_from_prekey(_this.identity, bundle) .then(function (session) { var cryptobox_session = new CryptoboxSession_1.CryptoboxSession(session_id, _this.pk_store, session); return _this.session_save(cryptobox_session); }) .catch(function (error) { if (error instanceof error_2.RecordAlreadyExistsError) { return _this.session_load(session_id); } throw error; }); }); }; Cryptobox.prototype.session_from_message = function (session_id, envelope) { var _this = this; var env = Proteus.message.Envelope.deserialise(envelope); return Proteus.session.Session.init_from_message(this.identity, this.pk_store, env) .then(function (tuple) { var session = tuple[0], decrypted = tuple[1]; var cryptoBoxSession = new CryptoboxSession_1.CryptoboxSession(session_id, _this.pk_store, session); return [cryptoBoxSession, decrypted]; }); }; Cryptobox.prototype.session_load = function (session_id) { var _this = this; var cachedSession = this.load_session_from_cache(session_id); if (cachedSession) { return Promise.resolve(cachedSession); } return this.store.read_session(this.identity, session_id) .then(function (session) { var cryptobox_session = new CryptoboxSession_1.CryptoboxSession(session_id, _this.pk_store, session); return _this.save_session_in_cache(cryptobox_session); }); }; Cryptobox.prototype.session_cleanup = function (session) { var _this = this; var preKeyDeletionPromises = this.pk_store.prekeys.map(function (preKeyId) { return _this.store.delete_prekey(preKeyId); }); return Promise.all(preKeyDeletionPromises) .then(function (deletedPreKeyIds) { _this.cachedPreKeys = _this.cachedPreKeys.filter(function (preKey) { return !deletedPreKeyIds.includes(preKey.key_id); }); _this.pk_store.release_prekeys(deletedPreKeyIds); return _this.refill_prekeys(); }) .then(function (newPreKeys) { _this.publish_prekeys(newPreKeys); return _this.save_session_in_cache(session); }) .then(function () { return session; }); }; Cryptobox.prototype.session_save = function (session) { var _this = this; return this.store.create_session(session.id, session.session) .then(function () { return _this.session_cleanup(session); }); }; Cryptobox.prototype.session_update = function (session) { var _this = this; return this.store.update_session(session.id, session.session) .then(function () { return _this.session_cleanup(session); }); }; Cryptobox.prototype.session_delete = function (session_id) { this.remove_session_from_cache(session_id); return this.store.delete_session(session_id); }; Cryptobox.prototype.create_last_resort_prekey = function () { var _this = this; return Promise.resolve() .then(function () { _this.lastResortPreKey = Proteus.keys.PreKey.last_resort(); return _this.store.save_prekeys([_this.lastResortPreKey]); }) .then(function (preKeys) { return preKeys[0]; }); }; Cryptobox.prototype.serialize_prekey = function (prekey) { return Proteus.keys.PreKeyBundle.new(this.identity.public_key, prekey).serialised_json(); }; Cryptobox.prototype.new_prekeys = function (start, size) { var _this = this; if (size === void 0) { size = 0; } if (size === 0) { return Promise.resolve([]); } return Promise.resolve() .then(function () { return Proteus.keys.PreKey.generate_prekeys(start, size); }) .then(function (newPreKeys) { return _this.store.save_prekeys(newPreKeys); }); }; Cryptobox.prototype.encrypt = function (session_id, payload, pre_key_bundle, confuse_pre_key_id) { var _this = this; var encryptedBuffer; var loadedSession; return Promise.resolve() .then(function () { if (pre_key_bundle) { return _this.session_from_prekey(session_id, pre_key_bundle); } return _this.session_load(session_id); }) .then(function (session) { loadedSession = session; return loadedSession.encrypt(payload, confuse_pre_key_id); }) .then(function (encrypted) { encryptedBuffer = encrypted; return _this.session_update(loadedSession); }) .then(function () { return encryptedBuffer; }); }; Cryptobox.prototype.decrypt = function (session_id, ciphertext) { var _this = this; var is_new_session = false; var message; var session; if (ciphertext.byteLength === 0) { return Promise.reject(new DecryptionError_1.DecryptionError('Cannot decrypt an empty ArrayBuffer.')); } return this.session_load(session_id) .catch(function () { return _this.session_from_message(session_id, ciphertext); }) .then(function (value) { var decrypted_message; if (value[0] !== undefined) { session = value[0], decrypted_message = value[1]; _this.publish_session_id(session); is_new_session = true; return decrypted_message; } session = value; return session.decrypt(ciphertext); }) .then(function (decrypted_message) { message = decrypted_message; if (is_new_session) { return _this.session_save(session); } return _this.session_update(session); }) .then(function () { return message; }); }; Cryptobox.TOPIC = { NEW_PREKEYS: "new-prekeys", NEW_SESSION: "new-session" }; return Cryptobox; }(EventEmitter)); exports.Cryptobox = Cryptobox; Cryptobox.prototype.VERSION = require('../package.json').version;