diff --git a/.eslintignore b/.eslintignore
index fdc4ad443c6..39a231fa1fc 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -2,6 +2,3 @@
lib
test/disabled
!etc/docs
-
-src/client-side-encryption
-test/unit/client-side-encryption
diff --git a/.eslintrc.json b/.eslintrc.json
index ba4f33a112e..5c38c6c90a9 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -9,6 +9,7 @@
"import",
"@typescript-eslint",
"prettier",
+ "unused-imports",
"tsdoc"
],
"extends": [
@@ -275,6 +276,16 @@
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-empty-function": "off"
}
+ },
+ {
+ // Settings for generated definition files
+ "files": [
+ "mongodb.d.ts"
+ ],
+ "parser": "@typescript-eslint/parser",
+ "rules": {
+ "unused-imports/no-unused-imports": "error"
+ }
}
]
}
diff --git a/package-lock.json b/package-lock.json
index c7db725d21d..d9a5f0d05a9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -41,12 +41,13 @@
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-tsdoc": "^0.2.17",
+ "eslint-plugin-unused-imports": "^2.0.0",
"express": "^4.18.2",
"gcp-metadata": "^5.2.0",
"js-yaml": "^4.1.0",
"mocha": "^10.2.0",
"mocha-sinon": "^2.1.2",
- "mongodb-client-encryption": "^6.0.0-alpha.0",
+ "mongodb-client-encryption": "^6.0.0-alpha.1",
"mongodb-legacy": "^5.0.0",
"nyc": "^15.1.0",
"prettier": "^2.8.8",
@@ -74,7 +75,7 @@
"@mongodb-js/zstd": "^1.1.0",
"gcp-metadata": "^5.2.0",
"kerberos": "^2.0.1",
- "mongodb-client-encryption": ">=6.0.0-alpha.0 <7",
+ "mongodb-client-encryption": ">=6.0.0-alpha.1 <7",
"snappy": "^7.2.2",
"socks": "^2.7.1"
},
@@ -4511,6 +4512,36 @@
"@microsoft/tsdoc-config": "0.16.2"
}
},
+ "node_modules/eslint-plugin-unused-imports": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-2.0.0.tgz",
+ "integrity": "sha512-3APeS/tQlTrFa167ThtP0Zm0vctjr4M44HMpeg1P4bK6wItarumq0Ma82xorMKdFsWpphQBlRPzw/pxiVELX1A==",
+ "dev": true,
+ "dependencies": {
+ "eslint-rule-composer": "^0.3.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/eslint-plugin": "^5.0.0",
+ "eslint": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@typescript-eslint/eslint-plugin": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-rule-composer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz",
+ "integrity": "sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==",
+ "dev": true,
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
"node_modules/eslint-rule-docs": {
"version": "1.1.235",
"resolved": "https://registry.npmjs.org/eslint-rule-docs/-/eslint-rule-docs-1.1.235.tgz",
@@ -6598,9 +6629,9 @@
}
},
"node_modules/mongodb-client-encryption": {
- "version": "6.0.0-alpha.0",
- "resolved": "https://registry.npmjs.org/mongodb-client-encryption/-/mongodb-client-encryption-6.0.0-alpha.0.tgz",
- "integrity": "sha512-lwkwJcjgXnxtd3A5otzTchxtqS+aVmsGpVaYnpnrL2m2s59uWXJpVStPQBt54SYDPt0Eu7pcT8nrWcVvZGZFfg==",
+ "version": "6.0.0-alpha.1",
+ "resolved": "https://registry.npmjs.org/mongodb-client-encryption/-/mongodb-client-encryption-6.0.0-alpha.1.tgz",
+ "integrity": "sha512-SaYli844l5TN8oog4nJW8KKWpSPwSx2auojv30JtDQv8hgWV979Bnc4bwF2pf+R9fquqgoLr27CWxganGY0Zfg==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
@@ -6609,7 +6640,7 @@
"prebuild-install": "^7.1.1"
},
"engines": {
- "node": ">=12.9.0"
+ "node": ">=16.20.1"
}
},
"node_modules/mongodb-connection-string-url": {
diff --git a/package.json b/package.json
index ac7ffaa6d93..815a924d6f6 100644
--- a/package.json
+++ b/package.json
@@ -36,7 +36,7 @@
"@mongodb-js/zstd": "^1.1.0",
"gcp-metadata": "^5.2.0",
"kerberos": "^2.0.1",
- "mongodb-client-encryption": ">=6.0.0-alpha.0 <7",
+ "mongodb-client-encryption": ">=6.0.0-alpha.1 <7",
"snappy": "^7.2.2",
"socks": "^2.7.1"
},
@@ -92,12 +92,13 @@
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-tsdoc": "^0.2.17",
+ "eslint-plugin-unused-imports": "^2.0.0",
"express": "^4.18.2",
"gcp-metadata": "^5.2.0",
"js-yaml": "^4.1.0",
"mocha": "^10.2.0",
"mocha-sinon": "^2.1.2",
- "mongodb-client-encryption": "^6.0.0-alpha.0",
+ "mongodb-client-encryption": "^6.0.0-alpha.1",
"mongodb-legacy": "^5.0.0",
"nyc": "^15.1.0",
"prettier": "^2.8.8",
@@ -125,7 +126,7 @@
"scripts": {
"build:evergreen": "node .evergreen/generate_evergreen_tasks.js",
"build:ts": "node ./node_modules/typescript/bin/tsc",
- "build:dts": "npm run build:ts && api-extractor run && node etc/clean_definition_files.cjs",
+ "build:dts": "npm run build:ts && api-extractor run && node etc/clean_definition_files.cjs && eslint mongodb.d.ts --fix",
"build:docs": "./etc/docs/build.ts",
"build:typedoc": "typedoc",
"build:nightly": "node ./.github/scripts/nightly.mjs",
diff --git a/src/client-side-encryption/autoEncrypter.js b/src/client-side-encryption/autoEncrypter.js
deleted file mode 100644
index d6fafe0e5d3..00000000000
--- a/src/client-side-encryption/autoEncrypter.js
+++ /dev/null
@@ -1,428 +0,0 @@
-import { databaseNamespace } from './common';
-import { StateMachine } from './stateMachine';
-import { MongocryptdManager } from './mongocryptdManager';
-import {MongoClient} from '../mongo_client';
-import {MongoError} from '../error';
-import { loadCredentials } from './providers';
-import * as cryptoCallbacks from './cryptoCallbacks';
-import { serialize, deserialize } from '../bson';
-import { getMongoDBClientEncryption } from '../deps';
-
-/**
- * Configuration options for a automatic client encryption.
- *
- * @typedef {Object} AutoEncrypter~AutoEncryptionOptions
- * @property {MongoClient} [keyVaultClient] A `MongoClient` used to fetch keys from a key vault
- * @property {string} [keyVaultNamespace] The namespace where keys are stored in the key vault
- * @property {KMSProviders} [kmsProviders] Configuration options that are used by specific KMS providers during key generation, encryption, and decryption.
- * @property {object} [schemaMap] A map of namespaces to a local JSON schema for encryption
- * @property {boolean} [bypassAutoEncryption] Allows the user to bypass auto encryption, maintaining implicit decryption
- * @property {AutoEncrypter~logger} [options.logger] An optional hook to catch logging messages from the underlying encryption engine
- * @property {AutoEncrypter~AutoEncryptionExtraOptions} [extraOptions] Extra options related to the mongocryptd process
- */
-
-/**
- * Extra options related to the mongocryptd process
- * \* _Available in MongoDB 6.0 or higher._
- * @typedef {object} AutoEncrypter~AutoEncryptionExtraOptions
- * @property {string} [mongocryptdURI] A local process the driver communicates with to determine how to encrypt values in a command. Defaults to "mongodb://%2Fvar%2Fmongocryptd.sock" if domain sockets are available or "mongodb://localhost:27020" otherwise
- * @property {boolean} [mongocryptdBypassSpawn=false] If true, autoEncryption will not attempt to spawn a mongocryptd before connecting
- * @property {string} [mongocryptdSpawnPath] The path to the mongocryptd executable on the system
- * @property {string[]} [mongocryptdSpawnArgs] Command line arguments to use when auto-spawning a mongocryptd
- * @property {string} [cryptSharedLibPath] Full path to a MongoDB Crypt shared library on the system. If specified, autoEncryption will not attempt to spawn a mongocryptd, but makes use of the shared library file specified. Note that the path must point to the shared libary file itself, not the folder which contains it \*
- * @property {boolean} [cryptSharedLibRequired] If true, never use mongocryptd and fail when the MongoDB Crypt shared libary cannot be loaded. Defaults to true if [cryptSharedLibPath] is specified and false otherwise \*
- */
-
-/**
- * @callback AutoEncrypter~logger
- * @description A callback that is invoked with logging information from
- * the underlying C++ Bindings.
- * @param {AutoEncrypter~logLevel} level The level of logging.
- * @param {string} message The message to log
- */
-
-/**
- * @name AutoEncrypter~logLevel
- * @enum {number}
- * @description
- * The level of severity of the log message
- *
- * | Value | Level |
- * |-------|-------|
- * | 0 | Fatal Error |
- * | 1 | Error |
- * | 2 | Warning |
- * | 3 | Info |
- * | 4 | Trace |
- */
-
-/**
- * @internal An internal class to be used by the driver for auto encryption
- * **NOTE**: Not meant to be instantiated directly, this is for internal use only.
- */
-export class AutoEncrypter {
- /**
- * Create an AutoEncrypter
- *
- * **Note**: Do not instantiate this class directly. Rather, supply the relevant options to a MongoClient
- *
- * **Note**: Supplying `options.schemaMap` provides more security than relying on JSON Schemas obtained from the server.
- * It protects against a malicious server advertising a false JSON Schema, which could trick the client into sending unencrypted data that should be encrypted.
- * Schemas supplied in the schemaMap only apply to configuring automatic encryption for Client-Side Field Level Encryption.
- * Other validation rules in the JSON schema will not be enforced by the driver and will result in an error.
- * @param {MongoClient} client The client autoEncryption is enabled on
- * @param {AutoEncrypter~AutoEncryptionOptions} [options] Optional settings
- *
- * @example
Create an AutoEncrypter that makes use of mongocryptd
- * // Enabling autoEncryption via a MongoClient using mongocryptd
- * const { MongoClient } = require('mongodb');
- * const client = new MongoClient(URL, {
- * autoEncryption: {
- * kmsProviders: {
- * aws: {
- * accessKeyId: AWS_ACCESS_KEY,
- * secretAccessKey: AWS_SECRET_KEY
- * }
- * }
- * }
- * });
- *
- * await client.connect();
- * // From here on, the client will be encrypting / decrypting automatically
- * @example
Create an AutoEncrypter that makes use of libmongocrypt's CSFLE shared library
- * // Enabling autoEncryption via a MongoClient using CSFLE shared library
- * const { MongoClient } = require('mongodb');
- * const client = new MongoClient(URL, {
- * autoEncryption: {
- * kmsProviders: {
- * aws: {}
- * },
- * extraOptions: {
- * cryptSharedLibPath: '/path/to/local/crypt/shared/lib',
- * cryptSharedLibRequired: true
- * }
- * }
- * });
- *
- * await client.connect();
- * // From here on, the client will be encrypting / decrypting automatically
- */
- constructor(client, options) {
- this._client = client;
- this._bypassEncryption = options.bypassAutoEncryption === true;
-
- this._keyVaultNamespace = options.keyVaultNamespace || 'admin.datakeys';
- this._keyVaultClient = options.keyVaultClient || client;
- this._metaDataClient = options.metadataClient || client;
- this._proxyOptions = options.proxyOptions || {};
- this._tlsOptions = options.tlsOptions || {};
- this._onKmsProviderRefresh = options.onKmsProviderRefresh;
- this._kmsProviders = options.kmsProviders || {};
-
- const mongoCryptOptions = {};
- if (options.schemaMap) {
- mongoCryptOptions.schemaMap = Buffer.isBuffer(options.schemaMap)
- ? options.schemaMap
- : serialize(options.schemaMap);
- }
-
- if (options.encryptedFieldsMap) {
- mongoCryptOptions.encryptedFieldsMap = Buffer.isBuffer(options.encryptedFieldsMap)
- ? options.encryptedFieldsMap
- : serialize(options.encryptedFieldsMap);
- }
-
- mongoCryptOptions.kmsProviders = !Buffer.isBuffer(this._kmsProviders)
- ? serialize(this._kmsProviders)
- : this._kmsProviders;
-
- if (options.logger) {
- mongoCryptOptions.logger = options.logger;
- }
-
- if (options.extraOptions && options.extraOptions.cryptSharedLibPath) {
- mongoCryptOptions.cryptSharedLibPath = options.extraOptions.cryptSharedLibPath;
- }
-
- if (options.bypassQueryAnalysis) {
- mongoCryptOptions.bypassQueryAnalysis = options.bypassQueryAnalysis;
- }
-
- this._bypassMongocryptdAndCryptShared = this._bypassEncryption || options.bypassQueryAnalysis;
-
- if (options.extraOptions && options.extraOptions.cryptSharedLibSearchPaths) {
- // Only for driver testing
- mongoCryptOptions.cryptSharedLibSearchPaths =
- options.extraOptions.cryptSharedLibSearchPaths;
- } else if (!this._bypassMongocryptdAndCryptShared) {
- mongoCryptOptions.cryptSharedLibSearchPaths = ['$SYSTEM'];
- }
-
- Object.assign(mongoCryptOptions, { cryptoCallbacks });
- const { MongoCrypt } = getMongoDBClientEncryption();
- this._mongocrypt = new MongoCrypt(mongoCryptOptions);
- this._contextCounter = 0;
-
- if (
- options.extraOptions &&
- options.extraOptions.cryptSharedLibRequired &&
- !this.cryptSharedLibVersionInfo
- ) {
- throw new MongoError('`cryptSharedLibRequired` set but no crypt_shared library loaded');
- }
-
- // Only instantiate mongocryptd manager/client once we know for sure
- // that we are not using the CSFLE shared library.
- if (!this._bypassMongocryptdAndCryptShared && !this.cryptSharedLibVersionInfo) {
- this._mongocryptdManager = new MongocryptdManager(options.extraOptions);
- const clientOptions = {
- serverSelectionTimeoutMS: 10000
- };
-
- if (
- options.extraOptions == null ||
- typeof options.extraOptions.mongocryptdURI !== 'string'
- ) {
- clientOptions.family = 4;
- }
-
- this._mongocryptdClient = new MongoClient(this._mongocryptdManager.uri, clientOptions);
- }
- }
-
- /**
- * @ignore
- * @param {Function} callback Invoked when the mongocryptd client either successfully connects or errors
- */
- init(callback) {
- if (this._bypassMongocryptdAndCryptShared || this.cryptSharedLibVersionInfo) {
- return callback();
- }
- const _callback = (err, res) => {
- if (
- err &&
- err.message &&
- (err.message.match(/timed out after/) || err.message.match(/ENOTFOUND/))
- ) {
- callback(
- new MongoError(
- 'Unable to connect to `mongocryptd`, please make sure it is running or in your PATH for auto-spawn'
- )
- );
- return;
- }
-
- callback(err, res);
- };
-
- if (this._mongocryptdManager.bypassSpawn) {
- return this._mongocryptdClient.connect().then(
- result => {
- return _callback(null, result);
- },
- error => {
- _callback(error, null);
- }
- );
- }
-
- this._mongocryptdManager.spawn(() => {
- this._mongocryptdClient.connect().then(
- result => {
- return _callback(null, result);
- },
- error => {
- _callback(error, null);
- }
- );
- });
- }
-
- /**
- * @ignore
- * @param {Function} callback Invoked when the mongocryptd client either successfully disconnects or errors
- */
- teardown(force, callback) {
- if (this._mongocryptdClient) {
- this._mongocryptdClient.close(force).then(
- result => {
- return callback(null, result);
- },
- error => {
- callback(error);
- }
- );
- } else {
- callback();
- }
- }
-
- /**
- * @ignore
- * Encrypt a command for a given namespace.
- *
- * @param {string} ns The namespace for this encryption context
- * @param {object} cmd The command to encrypt
- * @param {Function} callback
- */
- encrypt(ns, cmd, options, callback) {
- if (typeof ns !== 'string') {
- throw new TypeError('Parameter `ns` must be a string');
- }
-
- if (typeof cmd !== 'object') {
- throw new TypeError('Parameter `cmd` must be an object');
- }
-
- if (typeof options === 'function' && callback == null) {
- callback = options;
- options = {};
- }
-
- // If `bypassAutoEncryption` has been specified, don't encrypt
- if (this._bypassEncryption) {
- callback(undefined, cmd);
- return;
- }
-
- const commandBuffer = Buffer.isBuffer(cmd) ? cmd : serialize(cmd, options);
-
- let context;
- try {
- context = this._mongocrypt.makeEncryptionContext(databaseNamespace(ns), commandBuffer);
- } catch (err) {
- callback(err, null);
- return;
- }
-
- // TODO: should these be accessors from the addon?
- context.id = this._contextCounter++;
- context.ns = ns;
- context.document = cmd;
-
- const stateMachine = new StateMachine({
- ...options,
- promoteValues: false,
- promoteLongs: false,
- proxyOptions: this._proxyOptions,
- tlsOptions: this._tlsOptions
- });
- stateMachine.execute(this, context, callback);
- }
-
- /**
- * @ignore
- * Decrypt a command response
- *
- * @param {Buffer} buffer
- * @param {Function} callback
- */
- decrypt(response, options, callback) {
- if (typeof options === 'function' && callback == null) {
- callback = options;
- options = {};
- }
-
- const buffer = Buffer.isBuffer(response) ? response : serialize(response, options);
-
- let context;
- try {
- context = this._mongocrypt.makeDecryptionContext(buffer);
- } catch (err) {
- callback(err, null);
- return;
- }
-
- // TODO: should this be an accessor from the addon?
- context.id = this._contextCounter++;
-
- const stateMachine = new StateMachine({
- ...options,
- proxyOptions: this._proxyOptions,
- tlsOptions: this._tlsOptions
- });
-
- const decorateResult = this[Symbol.for('@@mdb.decorateDecryptionResult')];
- stateMachine.execute(this, context, function (err, result) {
- // Only for testing/internal usage
- if (!err && result && decorateResult) {
- err = decorateDecryptionResult(result, response);
- if (err) return callback(err);
- }
- callback(err, result);
- });
- }
-
- /**
- * Ask the user for KMS credentials.
- *
- * This returns anything that looks like the kmsProviders original input
- * option. It can be empty, and any provider specified here will override
- * the original ones.
- */
- async askForKMSCredentials() {
- return this._onKmsProviderRefresh
- ? this._onKmsProviderRefresh()
- : loadCredentials(this._kmsProviders);
- }
-
- /**
- * Return the current libmongocrypt's CSFLE shared library version
- * as `{ version: bigint, versionStr: string }`, or `null` if no CSFLE
- * shared library was loaded.
- */
- get cryptSharedLibVersionInfo() {
- return this._mongocrypt.cryptSharedLibVersionInfo;
- }
-
- static get libmongocryptVersion() {
- const { MongoCrypt } = getMongoDBClientEncryption();
- return MongoCrypt.libmongocryptVersion;
- }
-}
-
-/**
- * Recurse through the (identically-shaped) `decrypted` and `original`
- * objects and attach a `decryptedKeys` property on each sub-object that
- * contained encrypted fields. Because we only call this on BSON responses,
- * we do not need to worry about circular references.
- *
- * @internal
- * @ignore
- */
-function decorateDecryptionResult(decrypted, original, isTopLevelDecorateCall = true) {
- const decryptedKeys = Symbol.for('@@mdb.decryptedKeys');
- if (isTopLevelDecorateCall) {
- // The original value could have been either a JS object or a BSON buffer
- if (Buffer.isBuffer(original)) {
- original = deserialize(original);
- }
- if (Buffer.isBuffer(decrypted)) {
- return new Error('Expected result of decryption to be deserialized BSON object');
- }
- }
-
- if (!decrypted || typeof decrypted !== 'object') return;
- for (const k of Object.keys(decrypted)) {
- const originalValue = original[k];
-
- // An object was decrypted by libmongocrypt if and only if it was
- // a BSON Binary object with subtype 6.
- if (originalValue && originalValue._bsontype === 'Binary' && originalValue.sub_type === 6) {
- if (!decrypted[decryptedKeys]) {
- Object.defineProperty(decrypted, decryptedKeys, {
- value: [],
- configurable: true,
- enumerable: false,
- writable: false
- });
- }
- decrypted[decryptedKeys].push(k);
- // Do not recurse into this decrypted value. It could be a subdocument/array,
- // in which case there is no original value associated with its subfields.
- continue;
- }
-
- decorateDecryptionResult(decrypted[k], originalValue, false);
- }
-}
diff --git a/src/client-side-encryption/auto_encrypter.ts b/src/client-side-encryption/auto_encrypter.ts
new file mode 100644
index 00000000000..5bcbd1c683c
--- /dev/null
+++ b/src/client-side-encryption/auto_encrypter.ts
@@ -0,0 +1,663 @@
+import {
+ type MongoCrypt,
+ type MongoCryptConstructor,
+ type MongoCryptOptions
+} from 'mongodb-client-encryption';
+
+import { deserialize, type Document, serialize } from '../bson';
+import { type CommandOptions, type ProxyOptions } from '../cmap/connection';
+import { getMongoDBClientEncryption } from '../deps';
+import { type AnyError, MongoRuntimeError } from '../error';
+import { MongoClient, type MongoClientOptions } from '../mongo_client';
+import { type Callback, MongoDBCollectionNamespace } from '../utils';
+import * as cryptoCallbacks from './crypto_callbacks';
+import { MongoCryptInvalidArgumentError } from './errors';
+import { MongocryptdManager } from './mongocryptd_manager';
+import { type KMSProviders, refreshKMSCredentials } from './providers';
+import {
+ type CSFLEKMSTlsOptions,
+ StateMachine,
+ type StateMachineExecutable
+} from './state_machine';
+
+/** @public */
+export interface AutoEncryptionOptions {
+ /** @internal client for metadata lookups */
+ metadataClient?: MongoClient;
+ /** A `MongoClient` used to fetch keys from a key vault */
+ keyVaultClient?: MongoClient;
+ /** The namespace where keys are stored in the key vault */
+ keyVaultNamespace?: string;
+ /** Configuration options that are used by specific KMS providers during key generation, encryption, and decryption. */
+ kmsProviders?: {
+ /** Configuration options for using 'aws' as your KMS provider */
+ aws?:
+ | {
+ /** The access key used for the AWS KMS provider */
+ accessKeyId: string;
+ /** The secret access key used for the AWS KMS provider */
+ secretAccessKey: string;
+ /**
+ * An optional AWS session token that will be used as the
+ * X-Amz-Security-Token header for AWS requests.
+ */
+ sessionToken?: string;
+ }
+ | Record;
+ /** Configuration options for using 'local' as your KMS provider */
+ local?: {
+ /**
+ * The master key used to encrypt/decrypt data keys.
+ * A 96-byte long Buffer or base64 encoded string.
+ */
+ key: Buffer | string;
+ };
+ /** Configuration options for using 'azure' as your KMS provider */
+ azure?:
+ | {
+ /** The tenant ID identifies the organization for the account */
+ tenantId: string;
+ /** The client ID to authenticate a registered application */
+ clientId: string;
+ /** The client secret to authenticate a registered application */
+ clientSecret: string;
+ /**
+ * If present, a host with optional port. E.g. "example.com" or "example.com:443".
+ * This is optional, and only needed if customer is using a non-commercial Azure instance
+ * (e.g. a government or China account, which use different URLs).
+ * Defaults to "login.microsoftonline.com"
+ */
+ identityPlatformEndpoint?: string | undefined;
+ }
+ | {
+ /**
+ * If present, an access token to authenticate with Azure.
+ */
+ accessToken: string;
+ }
+ | Record;
+ /** Configuration options for using 'gcp' as your KMS provider */
+ gcp?:
+ | {
+ /** The service account email to authenticate */
+ email: string;
+ /** A PKCS#8 encrypted key. This can either be a base64 string or a binary representation */
+ privateKey: string | Buffer;
+ /**
+ * If present, a host with optional port. E.g. "example.com" or "example.com:443".
+ * Defaults to "oauth2.googleapis.com"
+ */
+ endpoint?: string | undefined;
+ }
+ | {
+ /**
+ * If present, an access token to authenticate with GCP.
+ */
+ accessToken: string;
+ }
+ | Record;
+ /**
+ * Configuration options for using 'kmip' as your KMS provider
+ */
+ kmip?: {
+ /**
+ * The output endpoint string.
+ * The endpoint consists of a hostname and port separated by a colon.
+ * E.g. "example.com:123". A port is always present.
+ */
+ endpoint?: string;
+ };
+ };
+ /**
+ * A map of namespaces to a local JSON schema for encryption
+ *
+ * **NOTE**: Supplying options.schemaMap provides more security than relying on JSON Schemas obtained from the server.
+ * It protects against a malicious server advertising a false JSON Schema, which could trick the client into sending decrypted data that should be encrypted.
+ * Schemas supplied in the schemaMap only apply to configuring automatic encryption for Client-Side Field Level Encryption.
+ * Other validation rules in the JSON schema will not be enforced by the driver and will result in an error.
+ */
+ schemaMap?: Document;
+ /** Supply a schema for the encrypted fields in the document */
+ encryptedFieldsMap?: Document;
+ /** Allows the user to bypass auto encryption, maintaining implicit decryption */
+ bypassAutoEncryption?: boolean;
+ /** Allows users to bypass query analysis */
+ bypassQueryAnalysis?: boolean;
+ options?: {
+ /** An optional hook to catch logging messages from the underlying encryption engine */
+ logger?: (level: AutoEncryptionLoggerLevel, message: string) => void;
+ };
+ extraOptions?: {
+ /**
+ * A local process the driver communicates with to determine how to encrypt values in a command.
+ * Defaults to "mongodb://%2Fvar%2Fmongocryptd.sock" if domain sockets are available or "mongodb://localhost:27020" otherwise
+ */
+ mongocryptdURI?: string;
+ /** If true, autoEncryption will not attempt to spawn a mongocryptd before connecting */
+ mongocryptdBypassSpawn?: boolean;
+ /** The path to the mongocryptd executable on the system */
+ mongocryptdSpawnPath?: string;
+ /** Command line arguments to use when auto-spawning a mongocryptd */
+ mongocryptdSpawnArgs?: string[];
+ /**
+ * Full path to a MongoDB Crypt shared library to be used (instead of mongocryptd).
+ *
+ * This needs to be the path to the file itself, not a directory.
+ * It can be an absolute or relative path. If the path is relative and
+ * its first component is `$ORIGIN`, it will be replaced by the directory
+ * containing the mongodb-client-encryption native addon file. Otherwise,
+ * the path will be interpreted relative to the current working directory.
+ *
+ * Currently, loading different MongoDB Crypt shared library files from different
+ * MongoClients in the same process is not supported.
+ *
+ * If this option is provided and no MongoDB Crypt shared library could be loaded
+ * from the specified location, creating the MongoClient will fail.
+ *
+ * If this option is not provided and `cryptSharedLibRequired` is not specified,
+ * the AutoEncrypter will attempt to spawn and/or use mongocryptd according
+ * to the mongocryptd-specific `extraOptions` options.
+ *
+ * Specifying a path prevents mongocryptd from being used as a fallback.
+ *
+ * Requires the MongoDB Crypt shared library, available in MongoDB 6.0 or higher.
+ */
+ cryptSharedLibPath?: string;
+ /**
+ * If specified, never use mongocryptd and instead fail when the MongoDB Crypt
+ * shared library could not be loaded.
+ *
+ * This is always true when `cryptSharedLibPath` is specified.
+ *
+ * Requires the MongoDB Crypt shared library, available in MongoDB 6.0 or higher.
+ */
+ cryptSharedLibRequired?: boolean;
+ /**
+ * Search paths for a MongoDB Crypt shared library to be used (instead of mongocryptd)
+ * Only for driver testing!
+ * @internal
+ */
+ cryptSharedLibSearchPaths?: string[];
+ };
+ proxyOptions?: ProxyOptions;
+ /** The TLS options to use connecting to the KMS provider */
+ tlsOptions?: CSFLEKMSTlsOptions;
+}
+
+/**
+ * @public
+ *
+ * Extra options related to the mongocryptd process
+ * \* _Available in MongoDB 6.0 or higher._
+ */
+export type AutoEncryptionExtraOptions = NonNullable;
+
+/** @public */
+export const AutoEncryptionLoggerLevel = Object.freeze({
+ FatalError: 0,
+ Error: 1,
+ Warning: 2,
+ Info: 3,
+ Trace: 4
+} as const);
+
+/**
+ * @public
+ * The level of severity of the log message
+ *
+ * | Value | Level |
+ * |-------|-------|
+ * | 0 | Fatal Error |
+ * | 1 | Error |
+ * | 2 | Warning |
+ * | 3 | Info |
+ * | 4 | Trace |
+ */
+export type AutoEncryptionLoggerLevel =
+ (typeof AutoEncryptionLoggerLevel)[keyof typeof AutoEncryptionLoggerLevel];
+
+// Typescript errors if we index objects with `Symbol.for(...)`, so
+// to avoid TS errors we pull them out into variables. Then we can type
+// the objects (and class) that we expect to see them on and prevent TS
+// errors.
+/** @internal */
+const kDecorateResult = Symbol.for('@@mdb.decorateDecryptionResult');
+/** @internal */
+const kDecoratedKeys = Symbol.for('@@mdb.decryptedKeys');
+
+/**
+ * @internal An internal class to be used by the driver for auto encryption
+ * **NOTE**: Not meant to be instantiated directly, this is for internal use only.
+ */
+export class AutoEncrypter implements StateMachineExecutable {
+ _client: MongoClient;
+ _bypassEncryption: boolean;
+ _keyVaultNamespace: string;
+ _keyVaultClient: MongoClient;
+ _metaDataClient: MongoClient;
+ _proxyOptions: ProxyOptions;
+ _tlsOptions: CSFLEKMSTlsOptions;
+ _kmsProviders: KMSProviders;
+ _bypassMongocryptdAndCryptShared: boolean;
+ _contextCounter: number;
+
+ _mongocryptdManager?: MongocryptdManager;
+ _mongocryptdClient?: MongoClient;
+
+ /** @internal */
+ _mongocrypt: MongoCrypt;
+
+ /**
+ * Used by devtools to enable decorating decryption results.
+ *
+ * When set and enabled, `decrypt` will automatically recursively
+ * traverse a decrypted document and if a field has been decrypted,
+ * it will mark it as decrypted. Compass uses this to determine which
+ * fields were decrypted.
+ */
+ [kDecorateResult] = false;
+
+ /** @internal */
+ static getMongoCrypt(): MongoCryptConstructor {
+ const encryption = getMongoDBClientEncryption();
+ if ('kModuleError' in encryption) {
+ throw encryption.kModuleError;
+ }
+ return encryption.MongoCrypt;
+ }
+
+ /**
+ * Create an AutoEncrypter
+ *
+ * **Note**: Do not instantiate this class directly. Rather, supply the relevant options to a MongoClient
+ *
+ * **Note**: Supplying `options.schemaMap` provides more security than relying on JSON Schemas obtained from the server.
+ * It protects against a malicious server advertising a false JSON Schema, which could trick the client into sending unencrypted data that should be encrypted.
+ * Schemas supplied in the schemaMap only apply to configuring automatic encryption for Client-Side Field Level Encryption.
+ * Other validation rules in the JSON schema will not be enforced by the driver and will result in an error.
+ *
+ * @example
Create an AutoEncrypter that makes use of mongocryptd
+ * ```ts
+ * // Enabling autoEncryption via a MongoClient using mongocryptd
+ * const { MongoClient } = require('mongodb');
+ * const client = new MongoClient(URL, {
+ * autoEncryption: {
+ * kmsProviders: {
+ * aws: {
+ * accessKeyId: AWS_ACCESS_KEY,
+ * secretAccessKey: AWS_SECRET_KEY
+ * }
+ * }
+ * }
+ * });
+ * ```
+ *
+ * await client.connect();
+ * // From here on, the client will be encrypting / decrypting automatically
+ * @example
Create an AutoEncrypter that makes use of libmongocrypt's CSFLE shared library
+ * ```ts
+ * // Enabling autoEncryption via a MongoClient using CSFLE shared library
+ * const { MongoClient } = require('mongodb');
+ * const client = new MongoClient(URL, {
+ * autoEncryption: {
+ * kmsProviders: {
+ * aws: {}
+ * },
+ * extraOptions: {
+ * cryptSharedLibPath: '/path/to/local/crypt/shared/lib',
+ * cryptSharedLibRequired: true
+ * }
+ * }
+ * });
+ * ```
+ *
+ * await client.connect();
+ * // From here on, the client will be encrypting / decrypting automatically
+ */
+ constructor(client: MongoClient, options: AutoEncryptionOptions) {
+ this._client = client;
+ this._bypassEncryption = options.bypassAutoEncryption === true;
+
+ this._keyVaultNamespace = options.keyVaultNamespace || 'admin.datakeys';
+ this._keyVaultClient = options.keyVaultClient || client;
+ this._metaDataClient = options.metadataClient || client;
+ this._proxyOptions = options.proxyOptions || {};
+ this._tlsOptions = options.tlsOptions || {};
+ this._kmsProviders = options.kmsProviders || {};
+
+ const mongoCryptOptions: MongoCryptOptions = {
+ cryptoCallbacks
+ };
+ if (options.schemaMap) {
+ mongoCryptOptions.schemaMap = Buffer.isBuffer(options.schemaMap)
+ ? options.schemaMap
+ : (serialize(options.schemaMap) as Buffer);
+ }
+
+ if (options.encryptedFieldsMap) {
+ mongoCryptOptions.encryptedFieldsMap = Buffer.isBuffer(options.encryptedFieldsMap)
+ ? options.encryptedFieldsMap
+ : (serialize(options.encryptedFieldsMap) as Buffer);
+ }
+
+ mongoCryptOptions.kmsProviders = !Buffer.isBuffer(this._kmsProviders)
+ ? (serialize(this._kmsProviders) as Buffer)
+ : this._kmsProviders;
+
+ if (options.options?.logger) {
+ mongoCryptOptions.logger = options.options.logger;
+ }
+
+ if (options.extraOptions && options.extraOptions.cryptSharedLibPath) {
+ mongoCryptOptions.cryptSharedLibPath = options.extraOptions.cryptSharedLibPath;
+ }
+
+ if (options.bypassQueryAnalysis) {
+ mongoCryptOptions.bypassQueryAnalysis = options.bypassQueryAnalysis;
+ }
+
+ this._bypassMongocryptdAndCryptShared = this._bypassEncryption || !!options.bypassQueryAnalysis;
+
+ if (options.extraOptions && options.extraOptions.cryptSharedLibSearchPaths) {
+ // Only for driver testing
+ mongoCryptOptions.cryptSharedLibSearchPaths = options.extraOptions.cryptSharedLibSearchPaths;
+ } else if (!this._bypassMongocryptdAndCryptShared) {
+ mongoCryptOptions.cryptSharedLibSearchPaths = ['$SYSTEM'];
+ }
+
+ const MongoCrypt = AutoEncrypter.getMongoCrypt();
+ this._mongocrypt = new MongoCrypt(mongoCryptOptions);
+ this._contextCounter = 0;
+
+ if (
+ options.extraOptions &&
+ options.extraOptions.cryptSharedLibRequired &&
+ !this.cryptSharedLibVersionInfo
+ ) {
+ throw new MongoCryptInvalidArgumentError(
+ '`cryptSharedLibRequired` set but no crypt_shared library loaded'
+ );
+ }
+
+ // Only instantiate mongocryptd manager/client once we know for sure
+ // that we are not using the CSFLE shared library.
+ if (!this._bypassMongocryptdAndCryptShared && !this.cryptSharedLibVersionInfo) {
+ this._mongocryptdManager = new MongocryptdManager(options.extraOptions);
+ const clientOptions: MongoClientOptions = {
+ serverSelectionTimeoutMS: 10000
+ };
+
+ if (options.extraOptions == null || typeof options.extraOptions.mongocryptdURI !== 'string') {
+ clientOptions.family = 4;
+ }
+
+ this._mongocryptdClient = new MongoClient(this._mongocryptdManager.uri, clientOptions);
+ }
+ }
+
+ /**
+ * Initializes the auto encrypter by spawning a mongocryptd and connecting to it.
+ *
+ * This function is a no-op when bypassSpawn is set or the crypt shared library is used.
+ */
+ init(callback: Callback) {
+ if (this._bypassMongocryptdAndCryptShared || this.cryptSharedLibVersionInfo) {
+ return callback();
+ }
+ if (!this._mongocryptdManager) {
+ return callback(
+ new MongoRuntimeError(
+ 'Reached impossible state: mongocryptdManager is undefined when neither bypassSpawn nor the shared lib are specified.'
+ )
+ );
+ }
+ if (!this._mongocryptdClient) {
+ return callback(
+ new MongoRuntimeError(
+ 'Reached impossible state: mongocryptdClient is undefined when neither bypassSpawn nor the shared lib are specified.'
+ )
+ );
+ }
+ const _callback = (err?: AnyError, res?: MongoClient) => {
+ if (
+ err &&
+ err.message &&
+ (err.message.match(/timed out after/) || err.message.match(/ENOTFOUND/))
+ ) {
+ callback(
+ new MongoRuntimeError(
+ 'Unable to connect to `mongocryptd`, please make sure it is running or in your PATH for auto-spawn'
+ )
+ );
+ return;
+ }
+
+ callback(err, res);
+ };
+
+ if (this._mongocryptdManager.bypassSpawn) {
+ this._mongocryptdClient.connect().then(
+ result => {
+ return _callback(undefined, result);
+ },
+ error => {
+ _callback(error, undefined);
+ }
+ );
+ return;
+ }
+
+ this._mongocryptdManager.spawn(() => {
+ if (!this._mongocryptdClient) {
+ return callback(
+ new MongoRuntimeError(
+ 'Reached impossible state: mongocryptdClient is undefined after spawning libmongocrypt.'
+ )
+ );
+ }
+ this._mongocryptdClient.connect().then(
+ result => {
+ return _callback(undefined, result);
+ },
+ error => {
+ _callback(error, undefined);
+ }
+ );
+ });
+ }
+
+ /**
+ * Cleans up the `_mongocryptdClient`, if present.
+ */
+ teardown(force: boolean, callback: Callback) {
+ if (this._mongocryptdClient) {
+ this._mongocryptdClient.close(force).then(
+ result => {
+ return callback(undefined, result);
+ },
+ error => {
+ callback(error);
+ }
+ );
+ } else {
+ callback();
+ }
+ }
+
+ encrypt(ns: string, cmd: Document, callback: Callback): void;
+ encrypt(
+ ns: string,
+ cmd: Document,
+ options: CommandOptions,
+ callback: Callback
+ ): void;
+ /**
+ * Encrypt a command for a given namespace.
+ */
+ encrypt(
+ ns: string,
+ cmd: Document,
+ options?: CommandOptions | Callback,
+ callback?: Callback
+ ) {
+ callback = typeof options === 'function' ? options : callback;
+
+ if (callback == null) {
+ throw new MongoCryptInvalidArgumentError('Callback must be provided');
+ }
+
+ options = typeof options === 'function' ? {} : options;
+
+ // If `bypassAutoEncryption` has been specified, don't encrypt
+ if (this._bypassEncryption) {
+ callback(undefined, cmd);
+ return;
+ }
+
+ const commandBuffer = Buffer.isBuffer(cmd) ? cmd : serialize(cmd, options);
+
+ let context;
+ try {
+ context = this._mongocrypt.makeEncryptionContext(
+ MongoDBCollectionNamespace.fromString(ns).db,
+ commandBuffer
+ );
+ } catch (err) {
+ callback(err, undefined);
+ return;
+ }
+
+ context.id = this._contextCounter++;
+ context.ns = ns;
+ context.document = cmd;
+
+ const stateMachine = new StateMachine({
+ promoteValues: false,
+ promoteLongs: false,
+ proxyOptions: this._proxyOptions,
+ tlsOptions: this._tlsOptions
+ });
+ stateMachine.execute(this, context, callback);
+ }
+
+ /**
+ * Decrypt a command response
+ */
+ decrypt(
+ response: Uint8Array,
+ options: CommandOptions | Callback,
+ callback?: Callback
+ ) {
+ callback = typeof options === 'function' ? options : callback;
+
+ if (callback == null) {
+ throw new MongoCryptInvalidArgumentError('Callback must be provided');
+ }
+
+ options = typeof options === 'function' ? {} : options;
+
+ const buffer = Buffer.isBuffer(response) ? response : serialize(response, options);
+
+ let context;
+ try {
+ context = this._mongocrypt.makeDecryptionContext(buffer);
+ } catch (err) {
+ callback(err, undefined);
+ return;
+ }
+
+ context.id = this._contextCounter++;
+
+ const stateMachine = new StateMachine({
+ ...options,
+ proxyOptions: this._proxyOptions,
+ tlsOptions: this._tlsOptions
+ });
+
+ const decorateResult = this[kDecorateResult];
+ stateMachine.execute(this, context, function (error?: Error, result?: Document) {
+ // Only for testing/internal usage
+ if (!error && result && decorateResult) {
+ const error = decorateDecryptionResult(result, response);
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ if (error) return callback!(error);
+ }
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ callback!(error, result);
+ });
+ }
+
+ /**
+ * Ask the user for KMS credentials.
+ *
+ * This returns anything that looks like the kmsProviders original input
+ * option. It can be empty, and any provider specified here will override
+ * the original ones.
+ */
+ async askForKMSCredentials(): Promise {
+ return refreshKMSCredentials(this._kmsProviders);
+ }
+
+ /**
+ * Return the current libmongocrypt's CSFLE shared library version
+ * as `{ version: bigint, versionStr: string }`, or `null` if no CSFLE
+ * shared library was loaded.
+ */
+ get cryptSharedLibVersionInfo(): { version: bigint; versionStr: string } | null {
+ return this._mongocrypt.cryptSharedLibVersionInfo;
+ }
+
+ static get libmongocryptVersion(): string {
+ return AutoEncrypter.getMongoCrypt().libmongocryptVersion;
+ }
+}
+
+/**
+ * Recurse through the (identically-shaped) `decrypted` and `original`
+ * objects and attach a `decryptedKeys` property on each sub-object that
+ * contained encrypted fields. Because we only call this on BSON responses,
+ * we do not need to worry about circular references.
+ *
+ * @internal
+ */
+function decorateDecryptionResult(
+ decrypted: Document & { [kDecoratedKeys]?: Array },
+ original: Document,
+ isTopLevelDecorateCall = true
+): Error | void {
+ if (isTopLevelDecorateCall) {
+ // The original value could have been either a JS object or a BSON buffer
+ if (Buffer.isBuffer(original)) {
+ original = deserialize(original);
+ }
+ if (Buffer.isBuffer(decrypted)) {
+ return new MongoRuntimeError('Expected result of decryption to be deserialized BSON object');
+ }
+ }
+
+ if (!decrypted || typeof decrypted !== 'object') return;
+ for (const k of Object.keys(decrypted)) {
+ const originalValue = original[k];
+
+ // An object was decrypted by libmongocrypt if and only if it was
+ // a BSON Binary object with subtype 6.
+ if (originalValue && originalValue._bsontype === 'Binary' && originalValue.sub_type === 6) {
+ if (!decrypted[kDecoratedKeys]) {
+ Object.defineProperty(decrypted, kDecoratedKeys, {
+ value: [],
+ configurable: true,
+ enumerable: false,
+ writable: false
+ });
+ }
+ // this is defined in the preceeding if-statement
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ decrypted[kDecoratedKeys]!.push(k);
+ // Do not recurse into this decrypted value. It could be a subdocument/array,
+ // in which case there is no original value associated with its subfields.
+ continue;
+ }
+
+ decorateDecryptionResult(decrypted[k], originalValue, false);
+ }
+}
diff --git a/src/client-side-encryption/buffer_pool.js b/src/client-side-encryption/buffer_pool.js
deleted file mode 100644
index a1164ab1a31..00000000000
--- a/src/client-side-encryption/buffer_pool.js
+++ /dev/null
@@ -1,119 +0,0 @@
-/**
- * @internal
- * @ignore
- * */
-const kBuffers = Symbol('buffers');
-/**
- * @internal
- * @ignore
- *
- * */
-const kLength = Symbol('length');
-
-/**
- * A pool of Buffers which allow you to read them as if they were one
- * @internal
- * @ignore
- */
-export class BufferPool {
- // [kBuffers]: Buffer[];
- // [kLength]: number;
-
- constructor() {
- this[kBuffers] = [];
- this[kLength] = 0;
- }
-
- get length() {
- return this[kLength];
- }
-
- /**
- * Adds a buffer to the internal buffer pool list
- * @param {Buffer} buffer - buffer to append to the pool
- * @returns {void}
- */
- append(buffer) {
- this[kBuffers].push(buffer);
- this[kLength] += buffer.length;
- }
-
- /**
- * Returns the requested number of bytes without consuming them
- * @param {number} size - the number of bytes to return from the head of the pool
- * @returns {Buffer}
- */
- peek(size) {
- return this.read(size, false);
- }
-
- /**
- * Reads the requested number of bytes, optionally consuming them
- * @param {number} size - the number of bytes to return from the head of the pool
- * @param {boolean} [consume] - whether the bytes returned should be removed, defaults to true
- * @returns {Buffer}
- */
- read(size, consume = true) {
- if (typeof size !== 'number' || size < 0) {
- throw new Error('Argument "size" must be a non-negative number');
- }
-
- if (size > this[kLength]) {
- return Buffer.alloc(0);
- }
-
- let result;
-
- // read the whole buffer
- if (size === this.length) {
- result = Buffer.concat(this[kBuffers]);
-
- if (consume) {
- this[kBuffers] = [];
- this[kLength] = 0;
- }
- }
-
- // size is within first buffer, no need to concat
- else if (size <= this[kBuffers][0].length) {
- result = this[kBuffers][0].slice(0, size);
- if (consume) {
- this[kBuffers][0] = this[kBuffers][0].slice(size);
- this[kLength] -= size;
- }
- }
-
- // size is beyond first buffer, need to track and copy
- else {
- result = Buffer.allocUnsafe(size);
-
- let idx;
- let offset = 0;
- let bytesToCopy = size;
- for (idx = 0; idx < this[kBuffers].length; ++idx) {
- let bytesCopied;
- if (bytesToCopy > this[kBuffers][idx].length) {
- bytesCopied = this[kBuffers][idx].copy(result, offset, 0);
- offset += bytesCopied;
- } else {
- bytesCopied = this[kBuffers][idx].copy(result, offset, 0, bytesToCopy);
- if (consume) {
- this[kBuffers][idx] = this[kBuffers][idx].slice(bytesCopied);
- }
- offset += bytesCopied;
- break;
- }
-
- bytesToCopy -= bytesCopied;
- }
-
- // compact the internal buffer array
- if (consume) {
- this[kBuffers] = this[kBuffers].slice(idx);
- this[kLength] -= size;
- }
- }
-
- return result;
- }
-}
diff --git a/src/client-side-encryption/clientEncryption.js b/src/client-side-encryption/clientEncryption.js
deleted file mode 100644
index 5c6e734a09c..00000000000
--- a/src/client-side-encryption/clientEncryption.js
+++ /dev/null
@@ -1,803 +0,0 @@
-
-import { databaseNamespace, collectionNamespace, promiseOrCallback, maybeCallback } from './common';
-import { StateMachine } from './stateMachine';
-import {
- MongoCryptCreateEncryptedCollectionError,
- MongoCryptCreateDataKeyError
-} from './errors';
-import { loadCredentials } from './providers/index';
-import * as cryptoCallbacks from './cryptoCallbacks';
-import { promisify } from 'util';
-import { serialize, deserialize } from '../bson';
-import { getMongoDBClientEncryption } from '../deps';
-
-/** @typedef {*} BSONValue - any serializable BSON value */
-/** @typedef {BSON.Long} Long A 64 bit integer, represented by the js-bson Long type.*/
-
-/**
- * @typedef {object} KMSProviders Configuration options that are used by specific KMS providers during key generation, encryption, and decryption.
- * @property {object} [aws] Configuration options for using 'aws' as your KMS provider
- * @property {string} [aws.accessKeyId] The access key used for the AWS KMS provider
- * @property {string} [aws.secretAccessKey] The secret access key used for the AWS KMS provider
- * @property {object} [local] Configuration options for using 'local' as your KMS provider
- * @property {Buffer} [local.key] The master key used to encrypt/decrypt data keys. A 96-byte long Buffer.
- * @property {object} [azure] Configuration options for using 'azure' as your KMS provider
- * @property {string} [azure.tenantId] The tenant ID identifies the organization for the account
- * @property {string} [azure.clientId] The client ID to authenticate a registered application
- * @property {string} [azure.clientSecret] The client secret to authenticate a registered application
- * @property {string} [azure.identityPlatformEndpoint] If present, a host with optional port. E.g. "example.com" or "example.com:443". This is optional, and only needed if customer is using a non-commercial Azure instance (e.g. a government or China account, which use different URLs). Defaults to "login.microsoftonline.com"
- * @property {object} [gcp] Configuration options for using 'gcp' as your KMS provider
- * @property {string} [gcp.email] The service account email to authenticate
- * @property {string|Binary} [gcp.privateKey] A PKCS#8 encrypted key. This can either be a base64 string or a binary representation
- * @property {string} [gcp.endpoint] If present, a host with optional port. E.g. "example.com" or "example.com:443". Defaults to "oauth2.googleapis.com"
- */
-
-/**
- * @typedef {object} DataKey A data key as stored in the database.
- * @property {UUID} _id A unique identifier for the key.
- * @property {number} version A numeric identifier for the schema version of this document. Implicitly 0 if unset.
- * @property {string[]} [keyAltNames] Alternate names to search for keys by. Used for a per-document key scenario in support of GDPR scenarios.
- * @property {Binary} keyMaterial Encrypted data key material, BinData type General.
- * @property {Date} creationDate The datetime the wrapped data key material was imported into the Key Database.
- * @property {Date} updateDate The datetime the wrapped data key material was last modified. On initial import, this value will be set to creationDate.
- * @property {number} status 0 = enabled, 1 = disabled
- * @property {object} masterKey the encrypted master key
- */
-
-/**
- * @typedef {string} KmsProvider A string containing the name of a kms provider. Valid options are 'aws', 'azure', 'gcp', 'kmip', or 'local'
- */
-
-/**
- * @typedef {object} ClientSession The ClientSession class from the MongoDB Node driver (see https://mongodb.github.io/node-mongodb-native/4.8/classes/ClientSession.html)
- */
-
-/**
- * @typedef {object} DeleteResult The result of a delete operation from the MongoDB Node driver (see https://mongodb.github.io/node-mongodb-native/4.8/interfaces/DeleteResult.html)
- * @property {boolean} acknowledged Indicates whether this write result was acknowledged. If not, then all other members of this result will be undefined.
- * @property {number} deletedCount The number of documents that were deleted
- */
-
-/**
- * @typedef {object} BulkWriteResult The BulkWriteResult class from the MongoDB Node driver (https://mongodb.github.io/node-mongodb-native/4.8/classes/BulkWriteResult.html)
- */
-
-/**
- * @typedef {object} FindCursor The FindCursor class from the MongoDB Node driver (see https://mongodb.github.io/node-mongodb-native/4.8/classes/FindCursor.html)
- */
-
-/**
- * The public interface for explicit in-use encryption
- */
-export class ClientEncryption {
- /**
- * Create a new encryption instance
- *
- * @param {MongoClient} client The client used for encryption
- * @param {object} options Additional settings
- * @param {string} options.keyVaultNamespace The namespace of the key vault, used to store encryption keys
- * @param {object} options.tlsOptions An object that maps KMS provider names to TLS options.
- * @param {MongoClient} [options.keyVaultClient] A `MongoClient` used to fetch keys from a key vault. Defaults to `client`
- * @param {KMSProviders} [options.kmsProviders] options for specific KMS providers to use
- *
- * @example
- * new ClientEncryption(mongoClient, {
- * keyVaultNamespace: 'client.encryption',
- * kmsProviders: {
- * local: {
- * key: masterKey // The master key used for encryption/decryption. A 96-byte long Buffer
- * }
- * }
- * });
- *
- * @example
- * new ClientEncryption(mongoClient, {
- * keyVaultNamespace: 'client.encryption',
- * kmsProviders: {
- * aws: {
- * accessKeyId: AWS_ACCESS_KEY,
- * secretAccessKey: AWS_SECRET_KEY
- * }
- * }
- * });
- */
- constructor(client, options) {
- this._client = client;
- this._proxyOptions = options.proxyOptions;
- this._tlsOptions = options.tlsOptions;
- this._kmsProviders = options.kmsProviders || {};
-
- if (options.keyVaultNamespace == null) {
- throw new TypeError('Missing required option `keyVaultNamespace`');
- }
-
- const mongoCryptOptions = { ...options, cryptoCallbacks };
-
- mongoCryptOptions.kmsProviders = !Buffer.isBuffer(this._kmsProviders)
- ? serialize(this._kmsProviders)
- : this._kmsProviders;
-
- this._onKmsProviderRefresh = options.onKmsProviderRefresh;
- this._keyVaultNamespace = options.keyVaultNamespace;
- this._keyVaultClient = options.keyVaultClient || client;
- const { MongoCrypt } = getMongoDBClientEncryption();
- this._mongoCrypt = new MongoCrypt(mongoCryptOptions);
- }
-
- /**
- * @typedef {Binary} ClientEncryptionDataKeyId
- * The id of an existing dataKey. Is a bson Binary value.
- * Can be used for {@link ClientEncryption.encrypt}, and can be used to directly
- * query for the data key itself against the key vault namespace.
- */
-
- /**
- * @callback ClientEncryptionCreateDataKeyCallback
- * @param {Error} [error] If present, indicates an error that occurred in the creation of the data key
- * @param {ClientEncryption~dataKeyId} [dataKeyId] If present, returns the id of the created data key
- */
-
- /**
- * @typedef {object} AWSEncryptionKeyOptions Configuration options for making an AWS encryption key
- * @property {string} region The AWS region of the KMS
- * @property {string} key The Amazon Resource Name (ARN) to the AWS customer master key (CMK)
- * @property {string} [endpoint] An alternate host to send KMS requests to. May include port number
- */
-
- /**
- * @typedef {object} GCPEncryptionKeyOptions Configuration options for making a GCP encryption key
- * @property {string} projectId GCP project id
- * @property {string} location Location name (e.g. "global")
- * @property {string} keyRing Key ring name
- * @property {string} keyName Key name
- * @property {string} [keyVersion] Key version
- * @property {string} [endpoint] KMS URL, defaults to `https://www.googleapis.com/auth/cloudkms`
- */
-
- /**
- * @typedef {object} AzureEncryptionKeyOptions Configuration options for making an Azure encryption key
- * @property {string} keyName Key name
- * @property {string} keyVaultEndpoint Key vault URL, typically `.vault.azure.net`
- * @property {string} [keyVersion] Key version
- */
-
- /**
- * Creates a data key used for explicit encryption and inserts it into the key vault namespace
- *
- * @param {string} provider The KMS provider used for this data key. Must be `'aws'`, `'azure'`, `'gcp'`, or `'local'`
- * @param {object} [options] Options for creating the data key
- * @param {AWSEncryptionKeyOptions|AzureEncryptionKeyOptions|GCPEncryptionKeyOptions} [options.masterKey] Idenfities a new KMS-specific key used to encrypt the new data key
- * @param {string[]} [options.keyAltNames] An optional list of string alternate names used to reference a key. If a key is created with alternate names, then encryption may refer to the key by the unique alternate name instead of by _id.
- * @param {ClientEncryptionCreateDataKeyCallback} [callback] Optional callback to invoke when key is created
- * @returns {Promise|void} If no callback is provided, returns a Promise that either resolves with {@link ClientEncryption~dataKeyId the id of the created data key}, or rejects with an error. If a callback is provided, returns nothing.
- * @example
- * // Using callbacks to create a local key
- * clientEncryption.createDataKey('local', (err, dataKey) => {
- * if (err) {
- * // This means creating the key failed.
- * } else {
- * // key creation succeeded
- * }
- * });
- *
- * @example
- * // Using async/await to create a local key
- * const dataKeyId = await clientEncryption.createDataKey('local');
- *
- * @example
- * // Using async/await to create an aws key
- * const dataKeyId = await clientEncryption.createDataKey('aws', {
- * masterKey: {
- * region: 'us-east-1',
- * key: 'xxxxxxxxxxxxxx' // CMK ARN here
- * }
- * });
- *
- * @example
- * // Using async/await to create an aws key with a keyAltName
- * const dataKeyId = await clientEncryption.createDataKey('aws', {
- * masterKey: {
- * region: 'us-east-1',
- * key: 'xxxxxxxxxxxxxx' // CMK ARN here
- * },
- * keyAltNames: [ 'mySpecialKey' ]
- * });
- */
- createDataKey(provider, options, callback) {
- if (typeof options === 'function') {
- callback = options;
- options = {};
- }
- if (options == null) {
- options = {};
- }
-
- const dataKey = Object.assign({ provider }, options.masterKey);
-
- if (options.keyAltNames && !Array.isArray(options.keyAltNames)) {
- throw new TypeError(
- `Option "keyAltNames" must be an array of strings, but was of type ${typeof options.keyAltNames}.`
- );
- }
-
- let keyAltNames = undefined;
- if (options.keyAltNames && options.keyAltNames.length > 0) {
- keyAltNames = options.keyAltNames.map((keyAltName, i) => {
- if (typeof keyAltName !== 'string') {
- throw new TypeError(
- `Option "keyAltNames" must be an array of strings, but item at index ${i} was of type ${typeof keyAltName}`
- );
- }
-
- return serialize({ keyAltName });
- });
- }
-
- let keyMaterial = undefined;
- if (options.keyMaterial) {
- keyMaterial = serialize({ keyMaterial: options.keyMaterial });
- }
-
- const dataKeyBson = serialize(dataKey);
- const context = this._mongoCrypt.makeDataKeyContext(dataKeyBson, {
- keyAltNames,
- keyMaterial
- });
- const stateMachine = new StateMachine({
- proxyOptions: this._proxyOptions,
- tlsOptions: this._tlsOptions
- });
-
- return promiseOrCallback(callback, cb => {
- stateMachine.execute(this, context, (err, dataKey) => {
- if (err) {
- cb(err, null);
- return;
- }
-
- const dbName = databaseNamespace(this._keyVaultNamespace);
- const collectionName = collectionNamespace(this._keyVaultNamespace);
-
- this._keyVaultClient
- .db(dbName)
- .collection(collectionName)
- .insertOne(dataKey, { writeConcern: { w: 'majority' } })
- .then(
- result => {
- return cb(null, result.insertedId);
- },
- err => {
- cb(err, null);
- }
- );
- });
- });
- }
-
- /**
- * @typedef {object} RewrapManyDataKeyResult
- * @property {BulkWriteResult} [bulkWriteResult] An optional BulkWriteResult, if any keys were matched and attempted to be re-wrapped.
- */
-
- /**
- * Searches the keyvault for any data keys matching the provided filter. If there are matches, rewrapManyDataKey then attempts to re-wrap the data keys using the provided options.
- *
- * If no matches are found, then no bulk write is performed.
- *
- * @param {object} filter A valid MongoDB filter. Any documents matching this filter will be re-wrapped.
- * @param {object} [options]
- * @param {KmsProvider} options.provider The KMS provider to use when re-wrapping the data keys.
- * @param {AWSEncryptionKeyOptions | AzureEncryptionKeyOptions | GCPEncryptionKeyOptions} [options.masterKey]
- * @returns {Promise}
- *
- * @example
- * // rewrapping all data data keys (using a filter that matches all documents)
- * const filter = {};
- *
- * const result = await clientEncryption.rewrapManyDataKey(filter);
- * if (result.bulkWriteResult != null) {
- * // keys were re-wrapped, results will be available in the bulkWrite object.
- * }
- *
- * @example
- * // attempting to rewrap all data keys with no matches
- * const filter = { _id: new Binary() } // assume _id matches no documents in the database
- * const result = await clientEncryption.rewrapManyDataKey(filter);
- *
- * if (result.bulkWriteResult == null) {
- * // no keys matched, `bulkWriteResult` does not exist on the result object
- * }
- */
- async rewrapManyDataKey(filter, options) {
- let keyEncryptionKeyBson = undefined;
- if (options) {
- const keyEncryptionKey = Object.assign({ provider: options.provider }, options.masterKey);
- keyEncryptionKeyBson = serialize(keyEncryptionKey);
- } else {
- // Always make sure `options` is an object below.
- options = {};
- }
- const filterBson = serialize(filter);
- const context = this._mongoCrypt.makeRewrapManyDataKeyContext(
- filterBson,
- keyEncryptionKeyBson
- );
- const stateMachine = new StateMachine({
- proxyOptions: this._proxyOptions,
- tlsOptions: this._tlsOptions
- });
-
- const execute = promisify(stateMachine.execute.bind(stateMachine));
-
- const dataKey = await execute(this, context);
- if (!dataKey || dataKey.v.length === 0) {
- return {};
- }
-
- const dbName = databaseNamespace(this._keyVaultNamespace);
- const collectionName = collectionNamespace(this._keyVaultNamespace);
- const replacements = dataKey.v.map(key => ({
- updateOne: {
- filter: { _id: key._id },
- update: {
- $set: {
- masterKey: key.masterKey,
- keyMaterial: key.keyMaterial
- },
- $currentDate: {
- updateDate: true
- }
- }
- }
- }));
-
- const result = await this._keyVaultClient
- .db(dbName)
- .collection(collectionName)
- .bulkWrite(replacements, {
- writeConcern: { w: 'majority' }
- });
-
- return { bulkWriteResult: result };
- }
-
- /**
- * Deletes the key with the provided id from the keyvault, if it exists.
- *
- * @param {ClientEncryptionDataKeyId} _id - the id of the document to delete.
- * @returns {Promise} Returns a promise that either resolves to a {@link DeleteResult} or rejects with an error.
- *
- * @example
- * // delete a key by _id
- * const id = new Binary(); // id is a bson binary subtype 4 object
- * const { deletedCount } = await clientEncryption.deleteKey(id);
- *
- * if (deletedCount != null && deletedCount > 0) {
- * // successful deletion
- * }
- *
- */
- async deleteKey(_id) {
- const dbName = databaseNamespace(this._keyVaultNamespace);
- const collectionName = collectionNamespace(this._keyVaultNamespace);
- return await this._keyVaultClient
- .db(dbName)
- .collection(collectionName)
- .deleteOne({ _id }, { writeConcern: { w: 'majority' } });
- }
-
- /**
- * Finds all the keys currently stored in the keyvault.
- *
- * This method will not throw.
- *
- * @returns {FindCursor} a FindCursor over all keys in the keyvault.
- * @example
- * // fetching all keys
- * const keys = await clientEncryption.getKeys().toArray();
- */
- getKeys() {
- const dbName = databaseNamespace(this._keyVaultNamespace);
- const collectionName = collectionNamespace(this._keyVaultNamespace);
- return this._keyVaultClient
- .db(dbName)
- .collection(collectionName)
- .find({}, { readConcern: { level: 'majority' } });
- }
-
- /**
- * Finds a key in the keyvault with the specified _id.
- *
- * @param {ClientEncryptionDataKeyId} _id - the id of the document to delete.
- * @returns {Promise} Returns a promise that either resolves to a {@link DataKey} if a document matches the key or null if no documents
- * match the id. The promise rejects with an error if an error is thrown.
- * @example
- * // getting a key by id
- * const id = new Binary(); // id is a bson binary subtype 4 object
- * const key = await clientEncryption.getKey(id);
- * if (!key) {
- * // key is null if there was no matching key
- * }
- */
- async getKey(_id) {
- const dbName = databaseNamespace(this._keyVaultNamespace);
- const collectionName = collectionNamespace(this._keyVaultNamespace);
- return await this._keyVaultClient
- .db(dbName)
- .collection(collectionName)
- .findOne({ _id }, { readConcern: { level: 'majority' } });
- }
-
- /**
- * Finds a key in the keyvault which has the specified keyAltName.
- *
- * @param {string} keyAltName - a keyAltName to search for a key
- * @returns {Promise} Returns a promise that either resolves to a {@link DataKey} if a document matches the key or null if no documents
- * match the keyAltName. The promise rejects with an error if an error is thrown.
- * @example
- * // get a key by alt name
- * const keyAltName = 'keyAltName';
- * const key = await clientEncryption.getKeyByAltName(keyAltName);
- * if (!key) {
- * // key is null if there is no matching key
- * }
- */
- async getKeyByAltName(keyAltName) {
- const dbName = databaseNamespace(this._keyVaultNamespace);
- const collectionName = collectionNamespace(this._keyVaultNamespace);
- return await this._keyVaultClient
- .db(dbName)
- .collection(collectionName)
- .findOne({ keyAltNames: keyAltName }, { readConcern: { level: 'majority' } });
- }
-
- /**
- * Adds a keyAltName to a key identified by the provided _id.
- *
- * This method resolves to/returns the *old* key value (prior to adding the new altKeyName).
- *
- * @param {ClientEncryptionDataKeyId} _id The id of the document to update.
- * @param {string} keyAltName - a keyAltName to search for a key
- * @returns {Promise} Returns a promise that either resolves to a {@link DataKey} if a document matches the key or null if no documents
- * match the id. The promise rejects with an error if an error is thrown.
- * @example
- * // adding an keyAltName to a data key
- * const id = new Binary(); // id is a bson binary subtype 4 object
- * const keyAltName = 'keyAltName';
- * const oldKey = await clientEncryption.addKeyAltName(id, keyAltName);
- * if (!oldKey) {
- * // null is returned if there is no matching document with an id matching the supplied id
- * }
- */
- async addKeyAltName(_id, keyAltName) {
- const dbName = databaseNamespace(this._keyVaultNamespace);
- const collectionName = collectionNamespace(this._keyVaultNamespace);
- const { value } = await this._keyVaultClient
- .db(dbName)
- .collection(collectionName)
- .findOneAndUpdate(
- { _id },
- { $addToSet: { keyAltNames: keyAltName } },
- { writeConcern: { w: 'majority' }, returnDocument: 'before' }
- );
-
- return value;
- }
-
- /**
- * Adds a keyAltName to a key identified by the provided _id.
- *
- * This method resolves to/returns the *old* key value (prior to removing the new altKeyName).
- *
- * If the removed keyAltName is the last keyAltName for that key, the `altKeyNames` property is unset from the document.
- *
- * @param {ClientEncryptionDataKeyId} _id The id of the document to update.
- * @param {string} keyAltName - a keyAltName to search for a key
- * @returns {Promise} Returns a promise that either resolves to a {@link DataKey} if a document matches the key or null if no documents
- * match the id. The promise rejects with an error if an error is thrown.
- * @example
- * // removing a key alt name from a data key
- * const id = new Binary(); // id is a bson binary subtype 4 object
- * const keyAltName = 'keyAltName';
- * const oldKey = await clientEncryption.removeKeyAltName(id, keyAltName);
- *
- * if (!oldKey) {
- * // null is returned if there is no matching document with an id matching the supplied id
- * }
- */
- async removeKeyAltName(_id, keyAltName) {
- const dbName = databaseNamespace(this._keyVaultNamespace);
- const collectionName = collectionNamespace(this._keyVaultNamespace);
- const pipeline = [
- {
- $set: {
- keyAltNames: {
- $cond: [
- {
- $eq: ['$keyAltNames', [keyAltName]]
- },
- '$$REMOVE',
- {
- $filter: {
- input: '$keyAltNames',
- cond: {
- $ne: ['$$this', keyAltName]
- }
- }
- }
- ]
- }
- }
- }
- ];
- const { value } = await this._keyVaultClient
- .db(dbName)
- .collection(collectionName)
- .findOneAndUpdate({ _id }, pipeline, {
- writeConcern: { w: 'majority' },
- returnDocument: 'before'
- });
-
- return value;
- }
-
- /**
- * A convenience method for creating an encrypted collection.
- * This method will create data keys for any encryptedFields that do not have a `keyId` defined
- * and then create a new collection with the full set of encryptedFields.
- *
- * @template {TSchema} - Schema for the collection being created
- * @param {Db} db - A Node.js driver Db object with which to create the collection
- * @param {string} name - The name of the collection to be created
- * @param {object} options - Options for createDataKey and for createCollection
- * @param {string} options.provider - KMS provider name
- * @param {AWSEncryptionKeyOptions | AzureEncryptionKeyOptions | GCPEncryptionKeyOptions} [options.masterKey] - masterKey to pass to createDataKey
- * @param {CreateCollectionOptions} options.createCollectionOptions - options to pass to createCollection, must include `encryptedFields`
- * @returns {Promise<{ collection: Collection, encryptedFields: Document }>} - created collection and generated encryptedFields
- * @throws {MongoCryptCreateDataKeyError} - If part way through the process a createDataKey invocation fails, an error will be rejected that has the partial `encryptedFields` that were created.
- * @throws {MongoCryptCreateEncryptedCollectionError} - If creating the collection fails, an error will be rejected that has the entire `encryptedFields` that were created.
- */
- async createEncryptedCollection(db, name, options) {
- const {
- provider,
- masterKey,
- createCollectionOptions: {
- encryptedFields: { ...encryptedFields },
- ...createCollectionOptions
- }
- } = options;
-
- if (Array.isArray(encryptedFields.fields)) {
- const createDataKeyPromises = encryptedFields.fields.map(async field =>
- field == null || typeof field !== 'object' || field.keyId != null
- ? field
- : {
- ...field,
- keyId: await this.createDataKey(provider, { masterKey })
- }
- );
-
- const createDataKeyResolutions = await Promise.allSettled(createDataKeyPromises);
-
- encryptedFields.fields = createDataKeyResolutions.map((resolution, index) =>
- resolution.status === 'fulfilled' ? resolution.value : encryptedFields.fields[index]
- );
-
- const rejection = createDataKeyResolutions.find(({ status }) => status === 'rejected');
- if (rejection != null) {
- throw new MongoCryptCreateDataKeyError({ encryptedFields, cause: rejection.reason });
- }
- }
-
- try {
- const collection = await db.createCollection(name, {
- ...createCollectionOptions,
- encryptedFields
- });
- return { collection, encryptedFields };
- } catch (cause) {
- throw new MongoCryptCreateEncryptedCollectionError({ encryptedFields, cause });
- }
- }
-
- /**
- * @callback ClientEncryptionEncryptCallback
- * @param {Error} [err] If present, indicates an error that occurred in the process of encryption
- * @param {Buffer} [result] If present, is the encrypted result
- */
-
- /**
- * @typedef {object} RangeOptions
- * min, max, sparsity, and range must match the values set in the encryptedFields of the destination collection.
- * For double and decimal128, min/max/precision must all be set, or all be unset.
- * @property {BSONValue} min is required if precision is set.
- * @property {BSONValue} max is required if precision is set.
- * @property {BSON.Long} sparsity
- * @property {number | undefined} precision (may only be set for double or decimal128).
- */
-
- /**
- * @typedef {object} EncryptOptions Options to provide when encrypting data.
- * @property {ClientEncryptionDataKeyId} [keyId] The id of the Binary dataKey to use for encryption.
- * @property {string} [keyAltName] A unique string name corresponding to an already existing dataKey.
- * @property {string} [algorithm] The algorithm to use for encryption. Must be either `'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'`, `'AEAD_AES_256_CBC_HMAC_SHA_512-Random'`, `'Indexed'` or `'Unindexed'`
- * @property {bigint | number} [contentionFactor] - the contention factor.
- * @property {'equality' | 'rangePreview'} queryType - the query type supported. only the query type `equality` is stable at this time. queryType `rangePreview` is experimental.
- * @property {RangeOptions} [rangeOptions] (experimental) The index options for a Queryable Encryption field supporting "rangePreview" queries.
- */
-
- /**
- * Explicitly encrypt a provided value. Note that either `options.keyId` or `options.keyAltName` must
- * be specified. Specifying both `options.keyId` and `options.keyAltName` is considered an error.
- *
- * @param {*} value The value that you wish to serialize. Must be of a type that can be serialized into BSON
- * @param {EncryptOptions} options
- * @param {ClientEncryptionEncryptCallback} [callback] Optional callback to invoke when value is encrypted
- * @returns {Promise|void} If no callback is provided, returns a Promise that either resolves with the encrypted value, or rejects with an error. If a callback is provided, returns nothing.
- *
- * @example
- * // Encryption with callback API
- * function encryptMyData(value, callback) {
- * clientEncryption.createDataKey('local', (err, keyId) => {
- * if (err) {
- * return callback(err);
- * }
- * clientEncryption.encrypt(value, { keyId, algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic' }, callback);
- * });
- * }
- *
- * @example
- * // Encryption with async/await api
- * async function encryptMyData(value) {
- * const keyId = await clientEncryption.createDataKey('local');
- * return clientEncryption.encrypt(value, { keyId, algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic' });
- * }
- *
- * @example
- * // Encryption using a keyAltName
- * async function encryptMyData(value) {
- * await clientEncryption.createDataKey('local', { keyAltNames: 'mySpecialKey' });
- * return clientEncryption.encrypt(value, { keyAltName: 'mySpecialKey', algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic' });
- * }
- */
- encrypt(value, options, callback) {
- return maybeCallback(() => this._encrypt(value, false, options), callback);
- }
-
- /**
- * Encrypts a Match Expression or Aggregate Expression to query a range index.
- *
- * Only supported when queryType is "rangePreview" and algorithm is "RangePreview".
- *
- * @experimental The Range algorithm is experimental only. It is not intended for production use. It is subject to breaking changes.
- *
- * @param {object} expression a BSON document of one of the following forms:
- * 1. A Match Expression of this form:
- * `{$and: [{: {$gt: }}, {: {$lt: }}]}`
- * 2. An Aggregate Expression of this form:
- * `{$and: [{$gt: [, ]}, {$lt: [, ]}]}`
- *
- * `$gt` may also be `$gte`. `$lt` may also be `$lte`.
- *
- * @param {EncryptOptions} options
- * @returns {Promise