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._bypassEncryption =
  98. typeof options.bypassAutoEncryption === 'boolean' ? options.bypassAutoEncryption : false;
  99. const mongoCryptOptions = {};
  100. if (options.schemaMap) {
  101. mongoCryptOptions.schemaMap = Buffer.isBuffer(options.schemaMap)
  102. ? options.schemaMap
  103. : this._bson.serialize(options.schemaMap);
  104. }
  105. if (options.kmsProviders) {
  106. mongoCryptOptions.kmsProviders = options.kmsProviders;
  107. }
  108. if (options.logger) {
  109. mongoCryptOptions.logger = options.logger;
  110. }
  111. Object.assign(mongoCryptOptions, { cryptoCallbacks });
  112. this._mongocrypt = new mc.MongoCrypt(mongoCryptOptions);
  113. this._contextCounter = 0;
  114. }
  115. /**
  116. * @ignore
  117. * @param {Function} callback Invoked when the mongocryptd client either successfully connects or errors
  118. */
  119. init(callback) {
  120. const _callback = (err, res) => {
  121. if (err && err.message && err.message.match(/timed out after/)) {
  122. callback(
  123. new MongoError(
  124. 'Unable to connect to `mongocryptd`, please make sure it is running or in your PATH for auto-spawn'
  125. )
  126. );
  127. return;
  128. }
  129. callback(err, res);
  130. };
  131. if (this._mongocryptdManager.bypassSpawn) {
  132. return this._mongocryptdClient.connect(_callback);
  133. }
  134. this._mongocryptdManager.spawn(() => this._mongocryptdClient.connect(_callback));
  135. }
  136. /**
  137. * @ignore
  138. * @param {Function} callback Invoked when the mongocryptd client either successfully disconnects or errors
  139. */
  140. teardown(force, callback) {
  141. this._mongocryptdClient.close(force, callback);
  142. }
  143. /**
  144. * @ignore
  145. * Encrypt a command for a given namespace.
  146. *
  147. * @param {string} ns The namespace for this encryption context
  148. * @param {object} cmd The command to encrypt
  149. * @param {Function} callback
  150. */
  151. encrypt(ns, cmd, options, callback) {
  152. if (typeof ns !== 'string') {
  153. throw new TypeError('Parameter `ns` must be a string');
  154. }
  155. if (typeof cmd !== 'object') {
  156. throw new TypeError('Parameter `cmd` must be an object');
  157. }
  158. if (typeof options === 'function' && callback == null) {
  159. callback = options;
  160. options = {};
  161. }
  162. // If `bypassAutoEncryption` has been specified, don't encrypt
  163. if (this._bypassEncryption) {
  164. callback(undefined, cmd);
  165. return;
  166. }
  167. const bson = this._bson;
  168. const commandBuffer = Buffer.isBuffer(cmd) ? cmd : bson.serialize(cmd, options);
  169. let context;
  170. try {
  171. context = this._mongocrypt.makeEncryptionContext(databaseNamespace(ns), commandBuffer);
  172. } catch (err) {
  173. callback(err, null);
  174. return;
  175. }
  176. // TODO: should these be accessors from the addon?
  177. context.id = this._contextCounter++;
  178. context.ns = ns;
  179. context.document = cmd;
  180. const stateMachine = new StateMachine(Object.assign({ bson }, options));
  181. stateMachine.execute(this, context, callback);
  182. }
  183. /**
  184. * @ignore
  185. * Decrypt a command response
  186. *
  187. * @param {Buffer} buffer
  188. * @param {Function} callback
  189. */
  190. decrypt(response, options, callback) {
  191. if (typeof options === 'function' && callback == null) {
  192. callback = options;
  193. options = {};
  194. }
  195. const bson = this._bson;
  196. const buffer = Buffer.isBuffer(response) ? response : bson.serialize(response, options);
  197. let context;
  198. try {
  199. context = this._mongocrypt.makeDecryptionContext(buffer);
  200. } catch (err) {
  201. callback(err, null);
  202. return;
  203. }
  204. // TODO: should this be an accessor from the addon?
  205. context.id = this._contextCounter++;
  206. const stateMachine = new StateMachine(Object.assign({ bson }, options));
  207. stateMachine.execute(this, context, callback);
  208. }
  209. }
  210. return { AutoEncrypter };
  211. };