auto_encrypter.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. "use strict";
  2. var _a;
  3. Object.defineProperty(exports, "__esModule", { value: true });
  4. exports.AutoEncrypter = exports.AutoEncryptionLoggerLevel = void 0;
  5. const bson_1 = require("../bson");
  6. const deps_1 = require("../deps");
  7. const error_1 = require("../error");
  8. const mongo_client_1 = require("../mongo_client");
  9. const utils_1 = require("../utils");
  10. const cryptoCallbacks = require("./crypto_callbacks");
  11. const errors_1 = require("./errors");
  12. const mongocryptd_manager_1 = require("./mongocryptd_manager");
  13. const providers_1 = require("./providers");
  14. const state_machine_1 = require("./state_machine");
  15. /** @public */
  16. exports.AutoEncryptionLoggerLevel = Object.freeze({
  17. FatalError: 0,
  18. Error: 1,
  19. Warning: 2,
  20. Info: 3,
  21. Trace: 4
  22. });
  23. // Typescript errors if we index objects with `Symbol.for(...)`, so
  24. // to avoid TS errors we pull them out into variables. Then we can type
  25. // the objects (and class) that we expect to see them on and prevent TS
  26. // errors.
  27. /** @internal */
  28. const kDecorateResult = Symbol.for('@@mdb.decorateDecryptionResult');
  29. /** @internal */
  30. const kDecoratedKeys = Symbol.for('@@mdb.decryptedKeys');
  31. /**
  32. * @internal An internal class to be used by the driver for auto encryption
  33. * **NOTE**: Not meant to be instantiated directly, this is for internal use only.
  34. */
  35. class AutoEncrypter {
  36. /** @internal */
  37. static getMongoCrypt() {
  38. const encryption = (0, deps_1.getMongoDBClientEncryption)();
  39. if ('kModuleError' in encryption) {
  40. throw encryption.kModuleError;
  41. }
  42. return encryption.MongoCrypt;
  43. }
  44. /**
  45. * Create an AutoEncrypter
  46. *
  47. * **Note**: Do not instantiate this class directly. Rather, supply the relevant options to a MongoClient
  48. *
  49. * **Note**: Supplying `options.schemaMap` provides more security than relying on JSON Schemas obtained from the server.
  50. * It protects against a malicious server advertising a false JSON Schema, which could trick the client into sending unencrypted data that should be encrypted.
  51. * Schemas supplied in the schemaMap only apply to configuring automatic encryption for Client-Side Field Level Encryption.
  52. * Other validation rules in the JSON schema will not be enforced by the driver and will result in an error.
  53. *
  54. * @example <caption>Create an AutoEncrypter that makes use of mongocryptd</caption>
  55. * ```ts
  56. * // Enabling autoEncryption via a MongoClient using mongocryptd
  57. * const { MongoClient } = require('mongodb');
  58. * const client = new MongoClient(URL, {
  59. * autoEncryption: {
  60. * kmsProviders: {
  61. * aws: {
  62. * accessKeyId: AWS_ACCESS_KEY,
  63. * secretAccessKey: AWS_SECRET_KEY
  64. * }
  65. * }
  66. * }
  67. * });
  68. * ```
  69. *
  70. * await client.connect();
  71. * // From here on, the client will be encrypting / decrypting automatically
  72. * @example <caption>Create an AutoEncrypter that makes use of libmongocrypt's CSFLE shared library</caption>
  73. * ```ts
  74. * // Enabling autoEncryption via a MongoClient using CSFLE shared library
  75. * const { MongoClient } = require('mongodb');
  76. * const client = new MongoClient(URL, {
  77. * autoEncryption: {
  78. * kmsProviders: {
  79. * aws: {}
  80. * },
  81. * extraOptions: {
  82. * cryptSharedLibPath: '/path/to/local/crypt/shared/lib',
  83. * cryptSharedLibRequired: true
  84. * }
  85. * }
  86. * });
  87. * ```
  88. *
  89. * await client.connect();
  90. * // From here on, the client will be encrypting / decrypting automatically
  91. */
  92. constructor(client, options) {
  93. /**
  94. * Used by devtools to enable decorating decryption results.
  95. *
  96. * When set and enabled, `decrypt` will automatically recursively
  97. * traverse a decrypted document and if a field has been decrypted,
  98. * it will mark it as decrypted. Compass uses this to determine which
  99. * fields were decrypted.
  100. */
  101. this[_a] = false;
  102. this._client = client;
  103. this._bypassEncryption = options.bypassAutoEncryption === true;
  104. this._keyVaultNamespace = options.keyVaultNamespace || 'admin.datakeys';
  105. this._keyVaultClient = options.keyVaultClient || client;
  106. this._metaDataClient = options.metadataClient || client;
  107. this._proxyOptions = options.proxyOptions || {};
  108. this._tlsOptions = options.tlsOptions || {};
  109. this._kmsProviders = options.kmsProviders || {};
  110. const mongoCryptOptions = {
  111. cryptoCallbacks
  112. };
  113. if (options.schemaMap) {
  114. mongoCryptOptions.schemaMap = Buffer.isBuffer(options.schemaMap)
  115. ? options.schemaMap
  116. : (0, bson_1.serialize)(options.schemaMap);
  117. }
  118. if (options.encryptedFieldsMap) {
  119. mongoCryptOptions.encryptedFieldsMap = Buffer.isBuffer(options.encryptedFieldsMap)
  120. ? options.encryptedFieldsMap
  121. : (0, bson_1.serialize)(options.encryptedFieldsMap);
  122. }
  123. mongoCryptOptions.kmsProviders = !Buffer.isBuffer(this._kmsProviders)
  124. ? (0, bson_1.serialize)(this._kmsProviders)
  125. : this._kmsProviders;
  126. if (options.options?.logger) {
  127. mongoCryptOptions.logger = options.options.logger;
  128. }
  129. if (options.extraOptions && options.extraOptions.cryptSharedLibPath) {
  130. mongoCryptOptions.cryptSharedLibPath = options.extraOptions.cryptSharedLibPath;
  131. }
  132. if (options.bypassQueryAnalysis) {
  133. mongoCryptOptions.bypassQueryAnalysis = options.bypassQueryAnalysis;
  134. }
  135. this._bypassMongocryptdAndCryptShared = this._bypassEncryption || !!options.bypassQueryAnalysis;
  136. if (options.extraOptions && options.extraOptions.cryptSharedLibSearchPaths) {
  137. // Only for driver testing
  138. mongoCryptOptions.cryptSharedLibSearchPaths = options.extraOptions.cryptSharedLibSearchPaths;
  139. }
  140. else if (!this._bypassMongocryptdAndCryptShared) {
  141. mongoCryptOptions.cryptSharedLibSearchPaths = ['$SYSTEM'];
  142. }
  143. const MongoCrypt = AutoEncrypter.getMongoCrypt();
  144. this._mongocrypt = new MongoCrypt(mongoCryptOptions);
  145. this._contextCounter = 0;
  146. if (options.extraOptions &&
  147. options.extraOptions.cryptSharedLibRequired &&
  148. !this.cryptSharedLibVersionInfo) {
  149. throw new errors_1.MongoCryptInvalidArgumentError('`cryptSharedLibRequired` set but no crypt_shared library loaded');
  150. }
  151. // Only instantiate mongocryptd manager/client once we know for sure
  152. // that we are not using the CSFLE shared library.
  153. if (!this._bypassMongocryptdAndCryptShared && !this.cryptSharedLibVersionInfo) {
  154. this._mongocryptdManager = new mongocryptd_manager_1.MongocryptdManager(options.extraOptions);
  155. const clientOptions = {
  156. serverSelectionTimeoutMS: 10000
  157. };
  158. if (options.extraOptions == null || typeof options.extraOptions.mongocryptdURI !== 'string') {
  159. clientOptions.family = 4;
  160. }
  161. this._mongocryptdClient = new mongo_client_1.MongoClient(this._mongocryptdManager.uri, clientOptions);
  162. }
  163. }
  164. /**
  165. * Initializes the auto encrypter by spawning a mongocryptd and connecting to it.
  166. *
  167. * This function is a no-op when bypassSpawn is set or the crypt shared library is used.
  168. */
  169. async init() {
  170. if (this._bypassMongocryptdAndCryptShared || this.cryptSharedLibVersionInfo) {
  171. return;
  172. }
  173. if (!this._mongocryptdManager) {
  174. throw new error_1.MongoRuntimeError('Reached impossible state: mongocryptdManager is undefined when neither bypassSpawn nor the shared lib are specified.');
  175. }
  176. if (!this._mongocryptdClient) {
  177. throw new error_1.MongoRuntimeError('Reached impossible state: mongocryptdClient is undefined when neither bypassSpawn nor the shared lib are specified.');
  178. }
  179. if (!this._mongocryptdManager.bypassSpawn) {
  180. await this._mongocryptdManager.spawn();
  181. }
  182. try {
  183. const client = await this._mongocryptdClient.connect();
  184. return client;
  185. }
  186. catch (error) {
  187. const { message } = error;
  188. if (message && (message.match(/timed out after/) || message.match(/ENOTFOUND/))) {
  189. throw new error_1.MongoRuntimeError('Unable to connect to `mongocryptd`, please make sure it is running or in your PATH for auto-spawn', { cause: error });
  190. }
  191. throw error;
  192. }
  193. }
  194. /**
  195. * Cleans up the `_mongocryptdClient`, if present.
  196. */
  197. async teardown(force) {
  198. await this._mongocryptdClient?.close(force);
  199. }
  200. /**
  201. * Encrypt a command for a given namespace.
  202. */
  203. async encrypt(ns, cmd, options = {}) {
  204. if (this._bypassEncryption) {
  205. // If `bypassAutoEncryption` has been specified, don't encrypt
  206. return cmd;
  207. }
  208. const commandBuffer = Buffer.isBuffer(cmd) ? cmd : (0, bson_1.serialize)(cmd, options);
  209. const context = this._mongocrypt.makeEncryptionContext(utils_1.MongoDBCollectionNamespace.fromString(ns).db, commandBuffer);
  210. context.id = this._contextCounter++;
  211. context.ns = ns;
  212. context.document = cmd;
  213. const stateMachine = new state_machine_1.StateMachine({
  214. promoteValues: false,
  215. promoteLongs: false,
  216. proxyOptions: this._proxyOptions,
  217. tlsOptions: this._tlsOptions
  218. });
  219. return stateMachine.execute(this, context);
  220. }
  221. /**
  222. * Decrypt a command response
  223. */
  224. async decrypt(response, options = {}) {
  225. const buffer = Buffer.isBuffer(response) ? response : (0, bson_1.serialize)(response, options);
  226. const context = this._mongocrypt.makeDecryptionContext(buffer);
  227. context.id = this._contextCounter++;
  228. const stateMachine = new state_machine_1.StateMachine({
  229. ...options,
  230. proxyOptions: this._proxyOptions,
  231. tlsOptions: this._tlsOptions
  232. });
  233. const decorateResult = this[kDecorateResult];
  234. const result = await stateMachine.execute(this, context);
  235. if (decorateResult) {
  236. decorateDecryptionResult(result, response);
  237. }
  238. return result;
  239. }
  240. /**
  241. * Ask the user for KMS credentials.
  242. *
  243. * This returns anything that looks like the kmsProviders original input
  244. * option. It can be empty, and any provider specified here will override
  245. * the original ones.
  246. */
  247. async askForKMSCredentials() {
  248. return (0, providers_1.refreshKMSCredentials)(this._kmsProviders);
  249. }
  250. /**
  251. * Return the current libmongocrypt's CSFLE shared library version
  252. * as `{ version: bigint, versionStr: string }`, or `null` if no CSFLE
  253. * shared library was loaded.
  254. */
  255. get cryptSharedLibVersionInfo() {
  256. return this._mongocrypt.cryptSharedLibVersionInfo;
  257. }
  258. static get libmongocryptVersion() {
  259. return AutoEncrypter.getMongoCrypt().libmongocryptVersion;
  260. }
  261. }
  262. exports.AutoEncrypter = AutoEncrypter;
  263. _a = kDecorateResult;
  264. /**
  265. * Recurse through the (identically-shaped) `decrypted` and `original`
  266. * objects and attach a `decryptedKeys` property on each sub-object that
  267. * contained encrypted fields. Because we only call this on BSON responses,
  268. * we do not need to worry about circular references.
  269. *
  270. * @internal
  271. */
  272. function decorateDecryptionResult(decrypted, original, isTopLevelDecorateCall = true) {
  273. if (isTopLevelDecorateCall) {
  274. // The original value could have been either a JS object or a BSON buffer
  275. if (Buffer.isBuffer(original)) {
  276. original = (0, bson_1.deserialize)(original);
  277. }
  278. if (Buffer.isBuffer(decrypted)) {
  279. throw new error_1.MongoRuntimeError('Expected result of decryption to be deserialized BSON object');
  280. }
  281. }
  282. if (!decrypted || typeof decrypted !== 'object')
  283. return;
  284. for (const k of Object.keys(decrypted)) {
  285. const originalValue = original[k];
  286. // An object was decrypted by libmongocrypt if and only if it was
  287. // a BSON Binary object with subtype 6.
  288. if (originalValue && originalValue._bsontype === 'Binary' && originalValue.sub_type === 6) {
  289. if (!decrypted[kDecoratedKeys]) {
  290. Object.defineProperty(decrypted, kDecoratedKeys, {
  291. value: [],
  292. configurable: true,
  293. enumerable: false,
  294. writable: false
  295. });
  296. }
  297. // this is defined in the preceding if-statement
  298. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
  299. decrypted[kDecoratedKeys].push(k);
  300. // Do not recurse into this decrypted value. It could be a sub-document/array,
  301. // in which case there is no original value associated with its subfields.
  302. continue;
  303. }
  304. decorateDecryptionResult(decrypted[k], originalValue, false);
  305. }
  306. }
  307. //# sourceMappingURL=auto_encrypter.js.map