client_encryption.js 26 KB

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