UNPKG

web-enc-at-rest

Version:
181 lines (180 loc) 10.5 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.decryptObject = exports.encryptObject = exports.decryptBytes = exports.encryptBytes = exports.close = exports.open = exports.changeCredentialsAndReEncrypt = exports.dangerouslyDeInitialize = exports.isInitialized = void 0; const keyGen_1 = require("./keyGen"); const appDataEncryption_1 = require("./appDataEncryption"); const keyGenStore_1 = require("./keyGenStore"); const WearContext_1 = require("./WearContext"); const dataConvertUtil_1 = require("./dataConvertUtil"); const base64Util_1 = require("./base64Util"); /** Checks to see if a context was previously opened via open(). This can be useful * to present appropriate UI in the app for either request existing credentials (e.g. "log in") or * accept new credentials (e.g. "create account"). * * @global * * @return {boolean} True if context was previously opened, or false if not. */ function isInitialized() { return (0, keyGenStore_1.getCredentialProof)() !== null; } exports.isInitialized = isInitialized; /** Clears any information used to verify credentials or generate credential keys. Read the warning below before * calling this. * * WARNING: After this call, you won't be able to generate the same key from credentials. If you've got user data * encrypted with it, that data will be bricked. * * @global */ function dangerouslyDeInitialize() { (0, keyGenStore_1.setCredentialProof)(null); (0, keyGenStore_1.setDeriveKeySalt)(null); } exports.dangerouslyDeInitialize = dangerouslyDeInitialize; /** Returns a context derived from new credentials and calls a specified callback that will perform re-encryption. * The function's logic is meant as a safeguard to avoid bricking user data when credentials change. If app code * follows implementation instructions, this function will succeed or fail atomically, leaving user data in an * accessible state. * * @async * * @param {WearContext} oldContext Context containing key under which data is currently encrypted. oldContext will be closed * and be unusable if this function returns successfully. * @param {string} newUserName New user name, which would commonly be the same as previous user name, but doesn't have to be. * @param {string} newPassword Along with new user name, this comprises the credentials from which a new key will be derived. * @param {ReEncryptCallback} onReEncrypt Callback to app-supplied function which will perform re-encryption of user data. * @return {Promise<WearContext>} Promise resolving to new context generated from new credentials if everything was successful. */ function changeCredentialsAndReEncrypt(oldContext, newUserName, newPassword, onReEncrypt) { return __awaiter(this, void 0, void 0, function* () { if (oldContext.isClear()) throw Error('oldContext is unusable because it was closed.'); const newCredentialKey = yield (0, keyGen_1.generateCredentialKey)(newUserName, newPassword); const newContext = new WearContext_1.default(newCredentialKey); const newCredentialProof = yield (0, keyGen_1.generateCredentialProof)(newCredentialKey); if (!(yield onReEncrypt(oldContext, newContext))) throw Error('Re-encryption failed. The current context has not been changed.'); oldContext.clear(); (0, keyGenStore_1.setCredentialProof)(newCredentialProof); return newContext; }); } exports.changeCredentialsAndReEncrypt = changeCredentialsAndReEncrypt; /** Returns a context that is needed for passing to other APIs or null if passed credentials are incorrect. * * A natural time to call this is right after user has entered credentials and you've successfully performed any * authentication that your app requires. open() is idempotent and you can call it multiple times. * * @async * * @param {string} userName Uniquely identifies user. * @param {string} password Password for user. * @returns {Promise<WearContext>} Promise resolving to context that can be passed to other APIs. Treat this opaquely. * DO NOT store in any place but memory. */ function open(userName, password) { return __awaiter(this, void 0, void 0, function* () { const credentialKey = yield (0, keyGen_1.generateCredentialKey)(userName, password); if (!(yield (0, keyGen_1.matchOrCreateCredentialProof)(credentialKey))) return null; return new WearContext_1.default(credentialKey); }); } exports.open = open; /** Prevent any further encryption/decryption with the passed-in context. Useful for preventing attacks based on * physical access to the user's device, e.g. user leaves browser open on an unlocked, unattended laptop. * * A natural time to call this is whenever a user logs out. If you generated multiple contexts that * were stored in separate variables, then call `close()` on each. If the user closes the tab or browser before * you can call open(), there is no risk as the context is already cleared from memory. * * @param {WearContext} context From a previous call to open(). */ function close(context) { context.clear(); } exports.close = close; /** Encrypts byte array to a string that you can use for writing to persistent storage. * * @async * * @param {WearContext} context From a previous call to open(). * @param {Uint8Array} bytes Value to encrypt. * @return {Promise<string>} Promise resolving to base64-encoded string of encrypted data. */ function encryptBytes(context, bytes) { return __awaiter(this, void 0, void 0, function* () { if (context.isClear()) throw Error('Attempted to use a closed context.'); const credentialKey = context.dangerouslyGetKey(); return (0, base64Util_1.bytesToBase64)(yield (0, appDataEncryption_1.encryptAppData)(credentialKey, bytes)); }); } exports.encryptBytes = encryptBytes; /** Decrypts string to a byte array. * * @async * * @param {WearContext} context From a previous call to open(). The credentials that generated the context must match * credentials provided earlier in session where encryptedData was encrypted. * @param {string} encryptedData Must have been generated with a previous call to `encryptBytes()`. * @return {Promise<Uint8Array>} Promise resolving to Unencrypted data. */ function decryptBytes(context, encryptedData) { return __awaiter(this, void 0, void 0, function* () { if (context.isClear()) throw Error('Attempted to use a closed context.'); const credentialKey = context.dangerouslyGetKey(); const ciphertextBytes = (0, base64Util_1.base64ToBytes)(encryptedData); return yield (0, appDataEncryption_1.decryptAppData)(credentialKey, ciphertextBytes); }); } exports.decryptBytes = decryptBytes; /** Encrypts object to a ciphertext string that you can use for writing to persistent storage. * * If the object is not entirely representable in JSON, you'll need to pass a replacer function to handle * serialization, and pass a reviver function later to `decryptObject()` to symmetrically perform deserialization. * To understand if your object is JSON-representable, call `JSON.parse(JSON.stringify(yourObject))` and see if it * returns the same `yourObject` value. * * WEaR adds support for (de)serializing the following primitive values: null, Infinity, -Infinity, and NaN. * * @async * * @param {WearContext} context From a previous call to open(). * @param {object} object Value to encrypt. * @param {function} replacer Optional function to serialize values correctly. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#the_replacer_parameter * @return {Promise<string>} Promise resolving to ciphertext string. */ function encryptObject(context, object, replacer) { return __awaiter(this, void 0, void 0, function* () { const plainTextBytes = (0, dataConvertUtil_1.anyToBytes)(object, replacer); return yield encryptBytes(context, plainTextBytes); }); } exports.encryptObject = encryptObject; /** Decrypts ciphertext string to an object. * * If the object is not entirely representable in JSON, you'll need to pass a reviver function to handle * deserialization that matches a replacer function previously passed to `encryptObject()` for the same data. * To understand if your object is JSON-representable, call `JSON.parse(JSON.stringify(yourObject))` and see if it * returns the same `yourObject` value. * * WEaR adds support for (de)serializing the following primitive values: null, Infinity, -Infinity, and NaN. * * @async * * @param {WearContext} context From a previous call to open(). The credentials that generated the context must match * credentials provided earlier in session where encryptedData was encrypted. * @param {string} encryptedData Must have been generated with a previous call to `encryptObject()`. * @param {function} reviver Optional function to deserialize values correctly. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#using_the_reviver_parameter * @return {Promise<object>} Promise resolving to Unencrypted data. */ function decryptObject(context, encryptedData, reviver) { return __awaiter(this, void 0, void 0, function* () { const plainTextBytes = yield decryptBytes(context, encryptedData); return (0, dataConvertUtil_1.bytesToAny)(plainTextBytes, reviver); }); } exports.decryptObject = decryptObject;