client_encryption.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.ClientEncryption = void 0;
  4. exports.autoSelectSocketOptions = autoSelectSocketOptions;
  5. const bson_1 = require("../bson");
  6. const deps_1 = require("../deps");
  7. const timeout_1 = require("../timeout");
  8. const utils_1 = require("../utils");
  9. const cryptoCallbacks = require("./crypto_callbacks");
  10. const errors_1 = require("./errors");
  11. const index_1 = require("./providers/index");
  12. const state_machine_1 = require("./state_machine");
  13. /**
  14. * @public
  15. * The public interface for explicit in-use encryption
  16. */
  17. class ClientEncryption {
  18. /** @internal */
  19. static getMongoCrypt() {
  20. const encryption = (0, deps_1.getMongoDBClientEncryption)();
  21. if ('kModuleError' in encryption) {
  22. throw encryption.kModuleError;
  23. }
  24. return encryption.MongoCrypt;
  25. }
  26. /**
  27. * Create a new encryption instance
  28. *
  29. * @example
  30. * ```ts
  31. * new ClientEncryption(mongoClient, {
  32. * keyVaultNamespace: 'client.encryption',
  33. * kmsProviders: {
  34. * local: {
  35. * key: masterKey // The master key used for encryption/decryption. A 96-byte long Buffer
  36. * }
  37. * }
  38. * });
  39. * ```
  40. *
  41. * @example
  42. * ```ts
  43. * new ClientEncryption(mongoClient, {
  44. * keyVaultNamespace: 'client.encryption',
  45. * kmsProviders: {
  46. * aws: {
  47. * accessKeyId: AWS_ACCESS_KEY,
  48. * secretAccessKey: AWS_SECRET_KEY
  49. * }
  50. * }
  51. * });
  52. * ```
  53. */
  54. constructor(client, options) {
  55. this._client = client;
  56. this._proxyOptions = options.proxyOptions ?? {};
  57. this._tlsOptions = options.tlsOptions ?? {};
  58. this._kmsProviders = options.kmsProviders || {};
  59. const { timeoutMS } = (0, utils_1.resolveTimeoutOptions)(client, options);
  60. this._timeoutMS = timeoutMS;
  61. this._credentialProviders = options.credentialProviders;
  62. if (options.credentialProviders?.aws && !(0, index_1.isEmptyCredentials)('aws', this._kmsProviders)) {
  63. throw new errors_1.MongoCryptInvalidArgumentError('Can only provide a custom AWS credential provider when the state machine is configured for automatic AWS credential fetching');
  64. }
  65. if (options.keyVaultNamespace == null) {
  66. throw new errors_1.MongoCryptInvalidArgumentError('Missing required option `keyVaultNamespace`');
  67. }
  68. const mongoCryptOptions = {
  69. ...options,
  70. cryptoCallbacks,
  71. kmsProviders: !Buffer.isBuffer(this._kmsProviders)
  72. ? (0, bson_1.serialize)(this._kmsProviders)
  73. : this._kmsProviders
  74. };
  75. this._keyVaultNamespace = options.keyVaultNamespace;
  76. this._keyVaultClient = options.keyVaultClient || client;
  77. const MongoCrypt = ClientEncryption.getMongoCrypt();
  78. this._mongoCrypt = new MongoCrypt(mongoCryptOptions);
  79. }
  80. /**
  81. * Creates a data key used for explicit encryption and inserts it into the key vault namespace
  82. *
  83. * @example
  84. * ```ts
  85. * // Using async/await to create a local key
  86. * const dataKeyId = await clientEncryption.createDataKey('local');
  87. * ```
  88. *
  89. * @example
  90. * ```ts
  91. * // Using async/await to create an aws key
  92. * const dataKeyId = await clientEncryption.createDataKey('aws', {
  93. * masterKey: {
  94. * region: 'us-east-1',
  95. * key: 'xxxxxxxxxxxxxx' // CMK ARN here
  96. * }
  97. * });
  98. * ```
  99. *
  100. * @example
  101. * ```ts
  102. * // Using async/await to create an aws key with a keyAltName
  103. * const dataKeyId = await clientEncryption.createDataKey('aws', {
  104. * masterKey: {
  105. * region: 'us-east-1',
  106. * key: 'xxxxxxxxxxxxxx' // CMK ARN here
  107. * },
  108. * keyAltNames: [ 'mySpecialKey' ]
  109. * });
  110. * ```
  111. */
  112. async createDataKey(provider, options = {}) {
  113. if (options.keyAltNames && !Array.isArray(options.keyAltNames)) {
  114. throw new errors_1.MongoCryptInvalidArgumentError(`Option "keyAltNames" must be an array of strings, but was of type ${typeof options.keyAltNames}.`);
  115. }
  116. let keyAltNames = undefined;
  117. if (options.keyAltNames && options.keyAltNames.length > 0) {
  118. keyAltNames = options.keyAltNames.map((keyAltName, i) => {
  119. if (typeof keyAltName !== 'string') {
  120. throw new errors_1.MongoCryptInvalidArgumentError(`Option "keyAltNames" must be an array of strings, but item at index ${i} was of type ${typeof keyAltName}`);
  121. }
  122. return (0, bson_1.serialize)({ keyAltName });
  123. });
  124. }
  125. let keyMaterial = undefined;
  126. if (options.keyMaterial) {
  127. keyMaterial = (0, bson_1.serialize)({ keyMaterial: options.keyMaterial });
  128. }
  129. const dataKeyBson = (0, bson_1.serialize)({
  130. provider,
  131. ...options.masterKey
  132. });
  133. const context = this._mongoCrypt.makeDataKeyContext(dataKeyBson, {
  134. keyAltNames,
  135. keyMaterial
  136. });
  137. const stateMachine = new state_machine_1.StateMachine({
  138. proxyOptions: this._proxyOptions,
  139. tlsOptions: this._tlsOptions,
  140. socketOptions: autoSelectSocketOptions(this._client.s.options)
  141. });
  142. const timeoutContext = options?.timeoutContext ??
  143. timeout_1.TimeoutContext.create((0, utils_1.resolveTimeoutOptions)(this._client, { timeoutMS: this._timeoutMS }));
  144. const dataKey = (0, bson_1.deserialize)(await stateMachine.execute(this, context, { timeoutContext }));
  145. const { db: dbName, collection: collectionName } = utils_1.MongoDBCollectionNamespace.fromString(this._keyVaultNamespace);
  146. const { insertedId } = await this._keyVaultClient
  147. .db(dbName)
  148. .collection(collectionName)
  149. .insertOne(dataKey, {
  150. writeConcern: { w: 'majority' },
  151. timeoutMS: timeoutContext?.csotEnabled()
  152. ? timeoutContext?.getRemainingTimeMSOrThrow()
  153. : undefined
  154. });
  155. return insertedId;
  156. }
  157. /**
  158. * 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.
  159. *
  160. * If no matches are found, then no bulk write is performed.
  161. *
  162. * @example
  163. * ```ts
  164. * // rewrapping all data data keys (using a filter that matches all documents)
  165. * const filter = {};
  166. *
  167. * const result = await clientEncryption.rewrapManyDataKey(filter);
  168. * if (result.bulkWriteResult != null) {
  169. * // keys were re-wrapped, results will be available in the bulkWrite object.
  170. * }
  171. * ```
  172. *
  173. * @example
  174. * ```ts
  175. * // attempting to rewrap all data keys with no matches
  176. * const filter = { _id: new Binary() } // assume _id matches no documents in the database
  177. * const result = await clientEncryption.rewrapManyDataKey(filter);
  178. *
  179. * if (result.bulkWriteResult == null) {
  180. * // no keys matched, `bulkWriteResult` does not exist on the result object
  181. * }
  182. * ```
  183. */
  184. async rewrapManyDataKey(filter, options) {
  185. let keyEncryptionKeyBson = undefined;
  186. if (options) {
  187. const keyEncryptionKey = Object.assign({ provider: options.provider }, options.masterKey);
  188. keyEncryptionKeyBson = (0, bson_1.serialize)(keyEncryptionKey);
  189. }
  190. const filterBson = (0, bson_1.serialize)(filter);
  191. const context = this._mongoCrypt.makeRewrapManyDataKeyContext(filterBson, keyEncryptionKeyBson);
  192. const stateMachine = new state_machine_1.StateMachine({
  193. proxyOptions: this._proxyOptions,
  194. tlsOptions: this._tlsOptions,
  195. socketOptions: autoSelectSocketOptions(this._client.s.options)
  196. });
  197. const timeoutContext = timeout_1.TimeoutContext.create((0, utils_1.resolveTimeoutOptions)(this._client, { timeoutMS: this._timeoutMS }));
  198. const { v: dataKeys } = (0, bson_1.deserialize)(await stateMachine.execute(this, context, { timeoutContext }));
  199. if (dataKeys.length === 0) {
  200. return {};
  201. }
  202. const { db: dbName, collection: collectionName } = utils_1.MongoDBCollectionNamespace.fromString(this._keyVaultNamespace);
  203. const replacements = dataKeys.map((key) => ({
  204. updateOne: {
  205. filter: { _id: key._id },
  206. update: {
  207. $set: {
  208. masterKey: key.masterKey,
  209. keyMaterial: key.keyMaterial
  210. },
  211. $currentDate: {
  212. updateDate: true
  213. }
  214. }
  215. }
  216. }));
  217. const result = await this._keyVaultClient
  218. .db(dbName)
  219. .collection(collectionName)
  220. .bulkWrite(replacements, {
  221. writeConcern: { w: 'majority' },
  222. timeoutMS: timeoutContext.csotEnabled() ? timeoutContext?.remainingTimeMS : undefined
  223. });
  224. return { bulkWriteResult: result };
  225. }
  226. /**
  227. * Deletes the key with the provided id from the keyvault, if it exists.
  228. *
  229. * @example
  230. * ```ts
  231. * // delete a key by _id
  232. * const id = new Binary(); // id is a bson binary subtype 4 object
  233. * const { deletedCount } = await clientEncryption.deleteKey(id);
  234. *
  235. * if (deletedCount != null && deletedCount > 0) {
  236. * // successful deletion
  237. * }
  238. * ```
  239. *
  240. */
  241. async deleteKey(_id) {
  242. const { db: dbName, collection: collectionName } = utils_1.MongoDBCollectionNamespace.fromString(this._keyVaultNamespace);
  243. return await this._keyVaultClient
  244. .db(dbName)
  245. .collection(collectionName)
  246. .deleteOne({ _id }, { writeConcern: { w: 'majority' }, timeoutMS: this._timeoutMS });
  247. }
  248. /**
  249. * Finds all the keys currently stored in the keyvault.
  250. *
  251. * This method will not throw.
  252. *
  253. * @returns a FindCursor over all keys in the keyvault.
  254. * @example
  255. * ```ts
  256. * // fetching all keys
  257. * const keys = await clientEncryption.getKeys().toArray();
  258. * ```
  259. */
  260. getKeys() {
  261. const { db: dbName, collection: collectionName } = utils_1.MongoDBCollectionNamespace.fromString(this._keyVaultNamespace);
  262. return this._keyVaultClient
  263. .db(dbName)
  264. .collection(collectionName)
  265. .find({}, { readConcern: { level: 'majority' }, timeoutMS: this._timeoutMS });
  266. }
  267. /**
  268. * Finds a key in the keyvault with the specified _id.
  269. *
  270. * Returns a promise that either resolves to a {@link DataKey} if a document matches the key or null if no documents
  271. * match the id. The promise rejects with an error if an error is thrown.
  272. * @example
  273. * ```ts
  274. * // getting a key by id
  275. * const id = new Binary(); // id is a bson binary subtype 4 object
  276. * const key = await clientEncryption.getKey(id);
  277. * if (!key) {
  278. * // key is null if there was no matching key
  279. * }
  280. * ```
  281. */
  282. async getKey(_id) {
  283. const { db: dbName, collection: collectionName } = utils_1.MongoDBCollectionNamespace.fromString(this._keyVaultNamespace);
  284. return await this._keyVaultClient
  285. .db(dbName)
  286. .collection(collectionName)
  287. .findOne({ _id }, { readConcern: { level: 'majority' }, timeoutMS: this._timeoutMS });
  288. }
  289. /**
  290. * Finds a key in the keyvault which has the specified keyAltName.
  291. *
  292. * @param keyAltName - a keyAltName to search for a key
  293. * @returns Returns a promise that either resolves to a {@link DataKey} if a document matches the key or null if no documents
  294. * match the keyAltName. The promise rejects with an error if an error is thrown.
  295. * @example
  296. * ```ts
  297. * // get a key by alt name
  298. * const keyAltName = 'keyAltName';
  299. * const key = await clientEncryption.getKeyByAltName(keyAltName);
  300. * if (!key) {
  301. * // key is null if there is no matching key
  302. * }
  303. * ```
  304. */
  305. async getKeyByAltName(keyAltName) {
  306. const { db: dbName, collection: collectionName } = utils_1.MongoDBCollectionNamespace.fromString(this._keyVaultNamespace);
  307. return await this._keyVaultClient
  308. .db(dbName)
  309. .collection(collectionName)
  310. .findOne({ keyAltNames: keyAltName }, { readConcern: { level: 'majority' }, timeoutMS: this._timeoutMS });
  311. }
  312. /**
  313. * Adds a keyAltName to a key identified by the provided _id.
  314. *
  315. * This method resolves to/returns the *old* key value (prior to adding the new altKeyName).
  316. *
  317. * @param _id - The id of the document to update.
  318. * @param keyAltName - a keyAltName to search for a key
  319. * @returns Returns a promise that either resolves to a {@link DataKey} if a document matches the key or null if no documents
  320. * match the id. The promise rejects with an error if an error is thrown.
  321. * @example
  322. * ```ts
  323. * // adding an keyAltName to a data key
  324. * const id = new Binary(); // id is a bson binary subtype 4 object
  325. * const keyAltName = 'keyAltName';
  326. * const oldKey = await clientEncryption.addKeyAltName(id, keyAltName);
  327. * if (!oldKey) {
  328. * // null is returned if there is no matching document with an id matching the supplied id
  329. * }
  330. * ```
  331. */
  332. async addKeyAltName(_id, keyAltName) {
  333. const { db: dbName, collection: collectionName } = utils_1.MongoDBCollectionNamespace.fromString(this._keyVaultNamespace);
  334. const value = await this._keyVaultClient
  335. .db(dbName)
  336. .collection(collectionName)
  337. .findOneAndUpdate({ _id }, { $addToSet: { keyAltNames: keyAltName } }, { writeConcern: { w: 'majority' }, returnDocument: 'before', timeoutMS: this._timeoutMS });
  338. return value;
  339. }
  340. /**
  341. * Adds a keyAltName to a key identified by the provided _id.
  342. *
  343. * This method resolves to/returns the *old* key value (prior to removing the new altKeyName).
  344. *
  345. * If the removed keyAltName is the last keyAltName for that key, the `altKeyNames` property is unset from the document.
  346. *
  347. * @param _id - The id of the document to update.
  348. * @param keyAltName - a keyAltName to search for a key
  349. * @returns Returns a promise that either resolves to a {@link DataKey} if a document matches the key or null if no documents
  350. * match the id. The promise rejects with an error if an error is thrown.
  351. * @example
  352. * ```ts
  353. * // removing a key alt name from a data key
  354. * const id = new Binary(); // id is a bson binary subtype 4 object
  355. * const keyAltName = 'keyAltName';
  356. * const oldKey = await clientEncryption.removeKeyAltName(id, keyAltName);
  357. *
  358. * if (!oldKey) {
  359. * // null is returned if there is no matching document with an id matching the supplied id
  360. * }
  361. * ```
  362. */
  363. async removeKeyAltName(_id, keyAltName) {
  364. const { db: dbName, collection: collectionName } = utils_1.MongoDBCollectionNamespace.fromString(this._keyVaultNamespace);
  365. const pipeline = [
  366. {
  367. $set: {
  368. keyAltNames: {
  369. $cond: [
  370. {
  371. $eq: ['$keyAltNames', [keyAltName]]
  372. },
  373. '$$REMOVE',
  374. {
  375. $filter: {
  376. input: '$keyAltNames',
  377. cond: {
  378. $ne: ['$$this', keyAltName]
  379. }
  380. }
  381. }
  382. ]
  383. }
  384. }
  385. }
  386. ];
  387. const value = await this._keyVaultClient
  388. .db(dbName)
  389. .collection(collectionName)
  390. .findOneAndUpdate({ _id }, pipeline, {
  391. writeConcern: { w: 'majority' },
  392. returnDocument: 'before',
  393. timeoutMS: this._timeoutMS
  394. });
  395. return value;
  396. }
  397. /**
  398. * A convenience method for creating an encrypted collection.
  399. * This method will create data keys for any encryptedFields that do not have a `keyId` defined
  400. * and then create a new collection with the full set of encryptedFields.
  401. *
  402. * @param db - A Node.js driver Db object with which to create the collection
  403. * @param name - The name of the collection to be created
  404. * @param options - Options for createDataKey and for createCollection
  405. * @returns created collection and generated encryptedFields
  406. * @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.
  407. * @throws MongoCryptCreateEncryptedCollectionError - If creating the collection fails, an error will be rejected that has the entire `encryptedFields` that were created.
  408. */
  409. async createEncryptedCollection(db, name, options) {
  410. const { provider, masterKey, createCollectionOptions: { encryptedFields: { ...encryptedFields }, ...createCollectionOptions } } = options;
  411. const timeoutContext = this._timeoutMS != null
  412. ? timeout_1.TimeoutContext.create((0, utils_1.resolveTimeoutOptions)(this._client, { timeoutMS: this._timeoutMS }))
  413. : undefined;
  414. if (Array.isArray(encryptedFields.fields)) {
  415. const createDataKeyPromises = encryptedFields.fields.map(async (field) => field == null || typeof field !== 'object' || field.keyId != null
  416. ? field
  417. : {
  418. ...field,
  419. keyId: await this.createDataKey(provider, {
  420. masterKey,
  421. // clone the timeoutContext
  422. // in order to avoid sharing the same timeout for server selection and connection checkout across different concurrent operations
  423. timeoutContext: timeoutContext?.csotEnabled() ? timeoutContext?.clone() : undefined
  424. })
  425. });
  426. const createDataKeyResolutions = await Promise.allSettled(createDataKeyPromises);
  427. encryptedFields.fields = createDataKeyResolutions.map((resolution, index) => resolution.status === 'fulfilled' ? resolution.value : encryptedFields.fields[index]);
  428. const rejection = createDataKeyResolutions.find((result) => result.status === 'rejected');
  429. if (rejection != null) {
  430. throw new errors_1.MongoCryptCreateDataKeyError(encryptedFields, { cause: rejection.reason });
  431. }
  432. }
  433. try {
  434. const collection = await db.createCollection(name, {
  435. ...createCollectionOptions,
  436. encryptedFields,
  437. timeoutMS: timeoutContext?.csotEnabled()
  438. ? timeoutContext?.getRemainingTimeMSOrThrow()
  439. : undefined
  440. });
  441. return { collection, encryptedFields };
  442. }
  443. catch (cause) {
  444. throw new errors_1.MongoCryptCreateEncryptedCollectionError(encryptedFields, { cause });
  445. }
  446. }
  447. /**
  448. * Explicitly encrypt a provided value. Note that either `options.keyId` or `options.keyAltName` must
  449. * be specified. Specifying both `options.keyId` and `options.keyAltName` is considered an error.
  450. *
  451. * @param value - The value that you wish to serialize. Must be of a type that can be serialized into BSON
  452. * @param options -
  453. * @returns a Promise that either resolves with the encrypted value, or rejects with an error.
  454. *
  455. * @example
  456. * ```ts
  457. * // Encryption with async/await api
  458. * async function encryptMyData(value) {
  459. * const keyId = await clientEncryption.createDataKey('local');
  460. * return clientEncryption.encrypt(value, { keyId, algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic' });
  461. * }
  462. * ```
  463. *
  464. * @example
  465. * ```ts
  466. * // Encryption using a keyAltName
  467. * async function encryptMyData(value) {
  468. * await clientEncryption.createDataKey('local', { keyAltNames: 'mySpecialKey' });
  469. * return clientEncryption.encrypt(value, { keyAltName: 'mySpecialKey', algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic' });
  470. * }
  471. * ```
  472. */
  473. async encrypt(value, options) {
  474. return await this._encrypt(value, false, options);
  475. }
  476. /**
  477. * Encrypts a Match Expression or Aggregate Expression to query a range index.
  478. *
  479. * Only supported when queryType is "range" and algorithm is "Range".
  480. *
  481. * @param expression - a BSON document of one of the following forms:
  482. * 1. A Match Expression of this form:
  483. * `{$and: [{<field>: {$gt: <value1>}}, {<field>: {$lt: <value2> }}]}`
  484. * 2. An Aggregate Expression of this form:
  485. * `{$and: [{$gt: [<fieldpath>, <value1>]}, {$lt: [<fieldpath>, <value2>]}]}`
  486. *
  487. * `$gt` may also be `$gte`. `$lt` may also be `$lte`.
  488. *
  489. * @param options -
  490. * @returns Returns a Promise that either resolves with the encrypted value or rejects with an error.
  491. */
  492. async encryptExpression(expression, options) {
  493. return await this._encrypt(expression, true, options);
  494. }
  495. /**
  496. * Explicitly decrypt a provided encrypted value
  497. *
  498. * @param value - An encrypted value
  499. * @returns a Promise that either resolves with the decrypted value, or rejects with an error
  500. *
  501. * @example
  502. * ```ts
  503. * // Decrypting value with async/await API
  504. * async function decryptMyValue(value) {
  505. * return clientEncryption.decrypt(value);
  506. * }
  507. * ```
  508. */
  509. async decrypt(value) {
  510. const valueBuffer = (0, bson_1.serialize)({ v: value });
  511. const context = this._mongoCrypt.makeExplicitDecryptionContext(valueBuffer);
  512. const stateMachine = new state_machine_1.StateMachine({
  513. proxyOptions: this._proxyOptions,
  514. tlsOptions: this._tlsOptions,
  515. socketOptions: autoSelectSocketOptions(this._client.s.options)
  516. });
  517. const timeoutContext = this._timeoutMS != null
  518. ? timeout_1.TimeoutContext.create((0, utils_1.resolveTimeoutOptions)(this._client, { timeoutMS: this._timeoutMS }))
  519. : undefined;
  520. const { v } = (0, bson_1.deserialize)(await stateMachine.execute(this, context, { timeoutContext }));
  521. return v;
  522. }
  523. /**
  524. * @internal
  525. * Ask the user for KMS credentials.
  526. *
  527. * This returns anything that looks like the kmsProviders original input
  528. * option. It can be empty, and any provider specified here will override
  529. * the original ones.
  530. */
  531. async askForKMSCredentials() {
  532. return await (0, index_1.refreshKMSCredentials)(this._kmsProviders, this._credentialProviders);
  533. }
  534. static get libmongocryptVersion() {
  535. return ClientEncryption.getMongoCrypt().libmongocryptVersion;
  536. }
  537. /**
  538. * @internal
  539. * A helper that perform explicit encryption of values and expressions.
  540. * Explicitly encrypt a provided value. Note that either `options.keyId` or `options.keyAltName` must
  541. * be specified. Specifying both `options.keyId` and `options.keyAltName` is considered an error.
  542. *
  543. * @param value - The value that you wish to encrypt. Must be of a type that can be serialized into BSON
  544. * @param expressionMode - a boolean that indicates whether or not to encrypt the value as an expression
  545. * @param options - options to pass to encrypt
  546. * @returns the raw result of the call to stateMachine.execute(). When expressionMode is set to true, the return
  547. * value will be a bson document. When false, the value will be a BSON Binary.
  548. *
  549. */
  550. async _encrypt(value, expressionMode, options) {
  551. const { algorithm, keyId, keyAltName, contentionFactor, queryType, rangeOptions, textOptions } = options;
  552. const contextOptions = {
  553. expressionMode,
  554. algorithm
  555. };
  556. if (keyId) {
  557. contextOptions.keyId = keyId.buffer;
  558. }
  559. if (keyAltName) {
  560. if (keyId) {
  561. throw new errors_1.MongoCryptInvalidArgumentError(`"options" cannot contain both "keyId" and "keyAltName"`);
  562. }
  563. if (typeof keyAltName !== 'string') {
  564. throw new errors_1.MongoCryptInvalidArgumentError(`"options.keyAltName" must be of type string, but was of type ${typeof keyAltName}`);
  565. }
  566. contextOptions.keyAltName = (0, bson_1.serialize)({ keyAltName });
  567. }
  568. if (typeof contentionFactor === 'number' || typeof contentionFactor === 'bigint') {
  569. contextOptions.contentionFactor = contentionFactor;
  570. }
  571. if (typeof queryType === 'string') {
  572. contextOptions.queryType = queryType;
  573. }
  574. if (typeof rangeOptions === 'object') {
  575. contextOptions.rangeOptions = (0, bson_1.serialize)(rangeOptions);
  576. }
  577. if (typeof textOptions === 'object') {
  578. contextOptions.textOptions = (0, bson_1.serialize)(textOptions);
  579. }
  580. const valueBuffer = (0, bson_1.serialize)({ v: value });
  581. const stateMachine = new state_machine_1.StateMachine({
  582. proxyOptions: this._proxyOptions,
  583. tlsOptions: this._tlsOptions,
  584. socketOptions: autoSelectSocketOptions(this._client.s.options)
  585. });
  586. const context = this._mongoCrypt.makeExplicitEncryptionContext(valueBuffer, contextOptions);
  587. const timeoutContext = this._timeoutMS != null
  588. ? timeout_1.TimeoutContext.create((0, utils_1.resolveTimeoutOptions)(this._client, { timeoutMS: this._timeoutMS }))
  589. : undefined;
  590. const { v } = (0, bson_1.deserialize)(await stateMachine.execute(this, context, { timeoutContext }));
  591. return v;
  592. }
  593. }
  594. exports.ClientEncryption = ClientEncryption;
  595. /**
  596. * Get the socket options from the client.
  597. * @param baseOptions - The mongo client options.
  598. * @returns ClientEncryptionSocketOptions
  599. */
  600. function autoSelectSocketOptions(baseOptions) {
  601. const options = { autoSelectFamily: true };
  602. if ('autoSelectFamily' in baseOptions) {
  603. options.autoSelectFamily = baseOptions.autoSelectFamily;
  604. }
  605. if ('autoSelectFamilyAttemptTimeout' in baseOptions) {
  606. options.autoSelectFamilyAttemptTimeout = baseOptions.autoSelectFamilyAttemptTimeout;
  607. }
  608. return options;
  609. }
  610. //# sourceMappingURL=client_encryption.js.map