UNPKG

@azure/cosmos

Version:
362 lines (361 loc) • 14.6 kB
var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var EncryptionProcessor_exports = {}; __export(EncryptionProcessor_exports, { EncryptionProcessor: () => EncryptionProcessor }); module.exports = __toCommonJS(EncryptionProcessor_exports); var import_common = require("../common/index.js"); var import_request = require("../request/index.js"); var import_diagnostics = require("../utils/diagnostics.js"); class EncryptionProcessor { constructor(containerId, containerRid, database, clientContext, encryptionManager) { this.containerId = containerId; this.containerRid = containerRid; this.database = database; this.clientContext = clientContext; this.encryptionManager = encryptionManager; } async encrypt(body) { if (!body) { throw new import_request.ErrorResponse("Input body is null or undefined."); } let propertiesEncryptedCount = 0; const encryptionSettings = await this.getEncryptionSetting(); if (!encryptionSettings) return { body, propertiesEncryptedCount }; for (const pathToEncrypt of encryptionSettings.pathsToEncrypt) { const propertyName = pathToEncrypt.slice(1); if (!Object.prototype.hasOwnProperty.call(body, propertyName)) { continue; } const settingForProperty = encryptionSettings.getEncryptionSettingForProperty(pathToEncrypt); if (!settingForProperty) { throw new import_request.ErrorResponse("Invalid Encryption Setting for the Property: " + propertyName); } body[propertyName] = await this.encryptToken( body[propertyName], settingForProperty, propertyName === "id" ); propertiesEncryptedCount++; } return { body, propertiesEncryptedCount }; } async isPathEncrypted(path) { path = (0, import_common.extractPath)(path); const encryptionSettings = await this.getEncryptionSetting(); const settingForProperty = encryptionSettings.getEncryptionSettingForProperty(path); if (!settingForProperty) return false; return true; } async encryptProperty(path, value) { path = (0, import_common.extractPath)(path); const encryptionSettings = await this.getEncryptionSetting(); if (!encryptionSettings) return value; const settingForProperty = encryptionSettings.getEncryptionSettingForProperty(path); if (!settingForProperty) { return value; } value = await this.encryptToken(value, settingForProperty, path === "/id"); return value; } async getEncryptedPartitionKeyValue(partitionKeyList) { const encryptionSettings = await this.getEncryptionSetting(); let encryptedCount = 0; if (!encryptionSettings) return { partitionKeyList, encryptedCount }; const partitionKeyPaths = encryptionSettings.partitionKeyPaths; for (let i = 0; i < partitionKeyPaths.length; i++) { const partitionKeyPath = (0, import_common.extractPath)(partitionKeyPaths[i]); if (encryptionSettings.pathsToEncrypt.includes(partitionKeyPath)) { const settingForProperty = encryptionSettings.getEncryptionSettingForProperty(partitionKeyPath); partitionKeyList[i] = await this.encryptToken( partitionKeyList[i], settingForProperty, partitionKeyPath === "/id" ); encryptedCount++; } } return { partitionKeyList, encryptedCount }; } async getEncryptedUrl(id) { const parts = id.split("/"); const lastPart = parts[parts.length - 1]; const encryptedLastPart = await this.getEncryptedId(lastPart); parts[parts.length - 1] = encryptedLastPart; return parts.join("/"); } async getEncryptedId(id) { const encryptionSettings = await this.getEncryptionSetting(); if (!encryptionSettings) return id; const settingForProperty = encryptionSettings.getEncryptionSettingForProperty("/id"); if (!settingForProperty) return id; id = await this.encryptToken(id, settingForProperty, true); return id; } async encryptQueryParameter(path, value, isValueId, type) { if (value === null) { return value; } path = (0, import_common.extractPath)(path); const encryptionSettings = await this.getEncryptionSetting(); if (!encryptionSettings) return value; const settingForProperty = encryptionSettings.getEncryptionSettingForProperty(path); if (!settingForProperty) { return value; } return this.encryptToken(value, settingForProperty, isValueId, type); } async encryptToken(valueToEncrypt, propertySetting, isValueId, type) { if (typeof valueToEncrypt === "object" && valueToEncrypt !== null) { for (const key in valueToEncrypt) { if (Object.prototype.hasOwnProperty.call(valueToEncrypt, key)) { valueToEncrypt[key] = await this.encryptToken( valueToEncrypt[key], propertySetting, isValueId, type ); } } } else if (Array.isArray(type)) { for (let i = 0; i < valueToEncrypt.length; i++) { valueToEncrypt[i] = await this.encryptToken( valueToEncrypt[i], propertySetting, isValueId, type ); } } else { valueToEncrypt = await this.serializeAndEncryptValue( valueToEncrypt, propertySetting, isValueId, type ); } return valueToEncrypt; } async serializeAndEncryptValue(valueToEncrypt, propertySetting, isValueId, type) { if (valueToEncrypt === null) { return valueToEncrypt; } const [typeMarker, serializer] = (0, import_common.createSerializer)(valueToEncrypt, type); const plainText = serializer.serialize(valueToEncrypt); const encryptionAlgorithm = await this.buildEncryptionAlgorithm(propertySetting); const cipherText = encryptionAlgorithm.encrypt(plainText); if (isValueId) { if (typeof valueToEncrypt !== "string") { throw new import_request.ErrorResponse("The id should be of string type."); } } const cipherTextWithTypeMarker = Buffer.alloc(cipherText.length + 1); cipherTextWithTypeMarker[0] = typeMarker; cipherText.forEach((value, index) => { cipherTextWithTypeMarker[index + 1] = value; }); let encryptedValue = Buffer.from(cipherTextWithTypeMarker).toString("base64"); if (isValueId) { encryptedValue = encryptedValue.replace(/\//g, "_").replace(/\+/g, "-"); } return encryptedValue; } async decrypt(body) { let propertiesDecryptedCount = 0; if (body == null) { return { body, propertiesDecryptedCount }; } const encryptionSettings = await this.getEncryptionSetting(); if (!encryptionSettings) return { body, propertiesDecryptedCount }; for (const pathToEncrypt of encryptionSettings.pathsToEncrypt) { const propertyName = pathToEncrypt.slice(1); if (!Object.prototype.hasOwnProperty.call(body, propertyName)) { continue; } const settingForProperty = encryptionSettings.getEncryptionSettingForProperty(pathToEncrypt); if (settingForProperty == null) { throw new import_request.ErrorResponse("Invalid Encryption Setting for the Path: " + pathToEncrypt); } body[propertyName] = await this.decryptToken( body[propertyName], settingForProperty, propertyName === "id" ); propertiesDecryptedCount++; } return { body, propertiesDecryptedCount }; } async decryptToken(valueToDecrypt, propertySetting, isValueId) { if (typeof valueToDecrypt === "object") { for (const key in valueToDecrypt) { if (Object.prototype.hasOwnProperty.call(valueToDecrypt, key)) { valueToDecrypt[key] = await this.decryptToken( valueToDecrypt[key], propertySetting, isValueId ); } } } else if (Array.isArray(valueToDecrypt)) { for (let i = 0; i < valueToDecrypt.length; i++) { valueToDecrypt[i] = await this.decryptToken(valueToDecrypt[i], propertySetting, isValueId); } } else { valueToDecrypt = await this.deserializeAndDecryptValue( valueToDecrypt, propertySetting, isValueId ); } return valueToDecrypt; } async deserializeAndDecryptValue(valueToDecrypt, propertySetting, isValueId) { if (isValueId) { valueToDecrypt = valueToDecrypt.replace(/_/g, "/").replace(/-/g, "+"); } const cipherTextWithTypeMarker = Buffer.from(valueToDecrypt, "base64"); if (cipherTextWithTypeMarker === null) { return null; } let cipherText = Buffer.alloc(cipherTextWithTypeMarker.length - 1); cipherText = Buffer.from(cipherTextWithTypeMarker.slice(1)); const encryptionAlgorithm = await this.buildEncryptionAlgorithm(propertySetting); const plainText = encryptionAlgorithm.decrypt(cipherText); if (plainText === null) { throw new import_request.ErrorResponse("returned null plain text"); } const serializer = (0, import_common.createDeserializer)(cipherTextWithTypeMarker[0]); return serializer.deserialize(plainText); } async getEncryptionSetting(forceRefresh) { const key = this.database._rid + "/" + this.containerRid; const encryptionSetting = this.encryptionManager.encryptionSettingsCache.get(key); if (forceRefresh || !encryptionSetting) { return (0, import_diagnostics.withDiagnostics)(async (diagnosticNode) => { const path = `/dbs/${this.database.id}/colls/${this.containerId}`; const id = `dbs/${this.database.id}/colls/${this.containerId}`; const response = await this.clientContext.read({ path, resourceType: import_common.ResourceType.container, resourceId: id, diagnosticNode }); if (!response || !response.result) { throw new import_request.ErrorResponse("Failed to fetch container definition"); } const containerRid = response.result._rid; const clientEncryptionPolicy = response.result.clientEncryptionPolicy; const partitionKeyPaths = response.result.partitionKey.paths; const updatedEncryptionSetting = await this.encryptionManager.encryptionSettingsCache.create( key, containerRid, partitionKeyPaths, clientEncryptionPolicy ); return updatedEncryptionSetting; }, this.clientContext); } return encryptionSetting; } async buildEncryptionAlgorithm(propertySetting) { const key = `${this.database._rid}/${propertySetting.encryptionKeyId}`; let clientEncryptionKeyProperties = this.encryptionManager.clientEncryptionKeyPropertiesCache.get(key); if (!clientEncryptionKeyProperties) { clientEncryptionKeyProperties = await this.fetchClientEncryptionKey( propertySetting.encryptionKeyId ); } try { return await propertySetting.buildEncryptionAlgorithm( clientEncryptionKeyProperties, this.encryptionManager ); } catch (err) { if (err.statusCode !== import_common.StatusCodes.Forbidden) throw err; clientEncryptionKeyProperties = await this.fetchClientEncryptionKey( propertySetting.encryptionKeyId ); try { return await propertySetting.buildEncryptionAlgorithm( clientEncryptionKeyProperties, this.encryptionManager, true ); } catch (retryErr) { if (retryErr.statusCode !== import_common.StatusCodes.Forbidden) throw retryErr; clientEncryptionKeyProperties = await this.fetchClientEncryptionKey( propertySetting.encryptionKeyId, clientEncryptionKeyProperties.etag ); return propertySetting.buildEncryptionAlgorithm( clientEncryptionKeyProperties, this.encryptionManager ); } } } async fetchClientEncryptionKey(cekId, cekEtag) { return (0, import_diagnostics.withDiagnostics)(async (diagnosticNode) => { const path = `/dbs/${this.database.id}/clientencryptionkeys/${cekId}`; const id = `dbs/${this.database.id}/clientencryptionkeys/${cekId}`; const options = {}; if (cekEtag) { options.accessCondition = { type: import_common.Constants.HttpHeaders.IfNoneMatch, condition: cekEtag }; } options.databaseRid = this.database._rid; const response = await this.clientContext.read({ path, resourceType: import_common.ResourceType.clientencryptionkey, resourceId: id, options, diagnosticNode }); if (!response) { throw new import_request.ErrorResponse(`Failed to fetch client encryption key ${cekId}`); } if (response.code === import_common.StatusCodes.NotModified) { throw new import_request.ErrorResponse( `The Client Encryption Key with key id: ${cekId} on database: ${this.database.id} needs to be rewrapped with a valid Key Encryption Key using rewrapClientEncryptionKey. The Key Encryption Key used to wrap the Client Encryption Key has been revoked` ); } const clientEncryptionKeyProperties = { id: response.result.id, encryptionAlgorithm: response.result.encryptionAlgorithm, wrappedDataEncryptionKey: new Uint8Array( Buffer.from(response.result.wrappedDataEncryptionKey, "base64") ), encryptionKeyWrapMetadata: response.result.keyWrapMetadata, etag: response.result._etag }; const key = this.database._rid + "/" + cekId; this.encryptionManager.clientEncryptionKeyPropertiesCache.set( key, clientEncryptionKeyProperties ); return clientEncryptionKeyProperties; }, this.clientContext); } } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { EncryptionProcessor });