Source: node_modules/mongodb-client-encryption/lib/autoEncrypter.js

  1. 'use strict';
  2. module.exports = function(modules) {
  3. const mc = require('bindings')('mongocrypt');
  4. const common = require('./common');
  5. const databaseNamespace = common.databaseNamespace;
  6. const StateMachine = modules.stateMachine.StateMachine;
  7. const MongocryptdManager = require('./mongocryptdManager').MongocryptdManager;
  8. const MongoClient = modules.mongodb.MongoClient;
  9. const MongoError = modules.mongodb.MongoError;
  10. const cryptoCallbacks = require('./cryptoCallbacks');
  11. /**
  12. * Configuration options for a automatic client encryption.
  13. *
  14. * @typedef {Object} AutoEncrypter~AutoEncryptionOptions
  15. * @property {MongoClient} [keyVaultClient] A `MongoClient` used to fetch keys from a key vault
  16. * @property {string} [keyVaultNamespace] The namespace where keys are stored in the key vault
  17. * @property {KMSProviders} [kmsProviders] Configuration options that are used by specific KMS providers during key generation, encryption, and decryption.
  18. * @property {object} [schemaMap] A map of namespaces to a local JSON schema for encryption
  19. * @property {boolean} [bypassAutoEncryption] Allows the user to bypass auto encryption, maintaining implicit decryption
  20. * @property {AutoEncrypter~logger} [options.logger] An optional hook to catch logging messages from the underlying encryption engine
  21. * @property {AutoEncrypter~AutoEncryptionExtraOptions} [extraOptions] Extra options related to the mongocryptd process
  22. */
  23. /**
  24. * Extra options related to the mongocryptd process
  25. * @typedef {object} AutoEncrypter~AutoEncryptionExtraOptions
  26. * @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
  27. * @property {boolean} [mongocryptdBypassSpawn=false] If true, autoEncryption will not attempt to spawn a mongocryptd before connecting
  28. * @property {string} [mongocryptdSpawnPath] The path to the mongocryptd executable on the system
  29. * @property {string[]} [mongocryptdSpawnArgs] Command line arguments to use when auto-spawning a mongocryptd
  30. */
  31. /**
  32. * @callback AutoEncrypter~logger
  33. * @description A callback that is invoked with logging information from
  34. * the underlying C++ Bindings.
  35. * @param {AutoEncrypter~logLevel} level The level of logging.
  36. * @param {string} message The message to log
  37. */
  38. /**
  39. * @name AutoEncrypter~logLevel
  40. * @enum {number}
  41. * @description
  42. * The level of severity of the log message
  43. *
  44. * | Value | Level |
  45. * |-------|-------|
  46. * | 0 | Fatal Error |
  47. * | 1 | Error |
  48. * | 2 | Warning |
  49. * | 3 | Info |
  50. * | 4 | Trace |
  51. */
  52. /**
  53. * @classdesc An internal class to be used by the driver for auto encryption
  54. * **NOTE**: Not meant to be instantiated directly, this is for internal use only.
  55. */
  56. class AutoEncrypter {
  57. /**
  58. * Create an AutoEncrypter
  59. *
  60. * **Note**: Do not instantiate this class directly. Rather, supply the relevant options to a MongoClient
  61. *
  62. * **Note**: Supplying `options.schemaMap` provides more security than relying on JSON Schemas obtained from the server.
  63. * It protects against a malicious server advertising a false JSON Schema, which could trick the client into sending unencrypted data that should be encrypted.
  64. * Schemas supplied in the schemaMap only apply to configuring automatic encryption for client side encryption.
  65. * Other validation rules in the JSON schema will not be enforced by the driver and will result in an error.
  66. * @param {MongoClient} client The client autoEncryption is enabled on
  67. * @param {AutoEncrypter~AutoEncryptionOptions} [options] Optional settings
  68. *
  69. * @example
  70. * // Enabling autoEncryption via a MongoClient
  71. * const { MongoClient } = require('mongodb');
  72. * const client = new MongoClient(URL, {
  73. * autoEncryption: {
  74. * kmsProviders: {
  75. * aws: {
  76. * accessKeyId: AWS_ACCESS_KEY,
  77. * secretAccessKey: AWS_SECRET_KEY
  78. * }
  79. * }
  80. * }
  81. * });
  82. *
  83. * await client.connect();
  84. * // From here on, the client will be encrypting / decrypting automatically
  85. */
  86. constructor(client, options) {
  87. this._client = client;
  88. this._bson = options.bson || client.topology.bson;
  89. this._mongocryptdManager = new MongocryptdManager(options.extraOptions);
  90. this._mongocryptdClient = new MongoClient(this._mongocryptdManager.uri, {
  91. useNewUrlParser: true,
  92. useUnifiedTopology: true,
  93. serverSelectionTimeoutMS: 1000
  94. });
  95. this._keyVaultNamespace = options.keyVaultNamespace || 'admin.datakeys';
  96. this._keyVaultClient = options.keyVaultClient || client;
  97. this._metaDataClient = options.metadataClient || client;
  98. this._bypassEncryption =
  99. typeof options.bypassAutoEncryption === 'boolean' ? options.bypassAutoEncryption : false;
  100. const mongoCryptOptions = {};
  101. if (options.schemaMap) {
  102. mongoCryptOptions.schemaMap = Buffer.isBuffer(options.schemaMap)
  103. ? options.schemaMap
  104. : this._bson.serialize(options.schemaMap);
  105. }
  106. if (options.kmsProviders) {
  107. mongoCryptOptions.kmsProviders = !Buffer.isBuffer(options.kmsProviders)
  108. ? this._bson.serialize(options.kmsProviders)
  109. : options.kmsProviders;
  110. }
  111. if (options.logger) {
  112. mongoCryptOptions.logger = options.logger;
  113. }
  114. Object.assign(mongoCryptOptions, { cryptoCallbacks });
  115. this._mongocrypt = new mc.MongoCrypt(mongoCryptOptions);
  116. this._contextCounter = 0;
  117. }
  118. /**
  119. * @ignore
  120. * @param {Function} callback Invoked when the mongocryptd client either successfully connects or errors
  121. */
  122. init(callback) {
  123. const _callback = (err, res) => {
  124. if (
  125. err &&
  126. err.message &&
  127. (err.message.match(/timed out after/) || err.message.match(/ENOTFOUND/))
  128. ) {
  129. callback(
  130. new MongoError(
  131. 'Unable to connect to `mongocryptd`, please make sure it is running or in your PATH for auto-spawn'
  132. )
  133. );
  134. return;
  135. }
  136. callback(err, res);
  137. };
  138. if (this._mongocryptdManager.bypassSpawn) {
  139. return this._mongocryptdClient.connect(_callback);
  140. }
  141. this._mongocryptdManager.spawn(() => this._mongocryptdClient.connect(_callback));
  142. }
  143. /**
  144. * @ignore
  145. * @param {Function} callback Invoked when the mongocryptd client either successfully disconnects or errors
  146. */
  147. teardown(force, callback) {
  148. this._mongocryptdClient.close(force, callback);
  149. }
  150. /**
  151. * @ignore
  152. * Encrypt a command for a given namespace.
  153. *
  154. * @param {string} ns The namespace for this encryption context
  155. * @param {object} cmd The command to encrypt
  156. * @param {Function} callback
  157. */
  158. encrypt(ns, cmd, options, callback) {
  159. if (typeof ns !== 'string') {
  160. throw new TypeError('Parameter `ns` must be a string');
  161. }
  162. if (typeof cmd !== 'object') {
  163. throw new TypeError('Parameter `cmd` must be an object');
  164. }
  165. if (typeof options === 'function' && callback == null) {
  166. callback = options;
  167. options = {};
  168. }
  169. // If `bypassAutoEncryption` has been specified, don't encrypt
  170. if (this._bypassEncryption) {
  171. callback(undefined, cmd);
  172. return;
  173. }
  174. const bson = this._bson;
  175. const commandBuffer = Buffer.isBuffer(cmd) ? cmd : bson.serialize(cmd, options);
  176. let context;
  177. try {
  178. context = this._mongocrypt.makeEncryptionContext(databaseNamespace(ns), commandBuffer);
  179. } catch (err) {
  180. callback(err, null);
  181. return;
  182. }
  183. // TODO: should these be accessors from the addon?
  184. context.id = this._contextCounter++;
  185. context.ns = ns;
  186. context.document = cmd;
  187. const stateMachine = new StateMachine(Object.assign({ bson }, options));
  188. stateMachine.execute(this, context, callback);
  189. }
  190. /**
  191. * @ignore
  192. * Decrypt a command response
  193. *
  194. * @param {Buffer} buffer
  195. * @param {Function} callback
  196. */
  197. decrypt(response, options, callback) {
  198. if (typeof options === 'function' && callback == null) {
  199. callback = options;
  200. options = {};
  201. }
  202. const bson = this._bson;
  203. const buffer = Buffer.isBuffer(response) ? response : bson.serialize(response, options);
  204. let context;
  205. try {
  206. context = this._mongocrypt.makeDecryptionContext(buffer);
  207. } catch (err) {
  208. callback(err, null);
  209. return;
  210. }
  211. // TODO: should this be an accessor from the addon?
  212. context.id = this._contextCounter++;
  213. const stateMachine = new StateMachine(Object.assign({ bson }, options));
  214. stateMachine.execute(this, context, callback);
  215. }
  216. }
  217. return { AutoEncrypter };
  218. };