Source: node_modules/mongodb-client-encryption/lib/clientEncryption.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 collectionNamespace = common.collectionNamespace;
  7. const promiseOrCallback = common.promiseOrCallback;
  8. const StateMachine = modules.stateMachine.StateMachine;
  9. const cryptoCallbacks = require('./cryptoCallbacks');
  10. function sanitizeDataKeyOptions(bson, options) {
  11. options = Object.assign({}, options);
  12. // To avoid using libbson inside the bindings, we pre-serialize
  13. // any keyAltNames here.
  14. if (options.keyAltNames) {
  15. if (!Array.isArray(options.keyAltNames)) {
  16. throw new TypeError(
  17. `Option "keyAltNames" must be an array of string, but was of type ${typeof options.keyAltNames}.`
  18. );
  19. }
  20. const serializedKeyAltNames = [];
  21. for (let i = 0; i < options.keyAltNames.length; i += 1) {
  22. const item = options.keyAltNames[i];
  23. const itemType = typeof item;
  24. if (itemType !== 'string') {
  25. throw new TypeError(
  26. `Option "keyAltNames" must be an array of string, but item at index ${i} was of type ${itemType} `
  27. );
  28. }
  29. serializedKeyAltNames.push(bson.serialize({ keyAltName: item }));
  30. }
  31. options.keyAltNames = serializedKeyAltNames;
  32. } else if (options.keyAltNames == null) {
  33. // If keyAltNames is null or undefined, we can assume the intent of
  34. // the user is to not pass in the value. B/c Nan::Has will still
  35. // register a value of null or undefined as present as long
  36. // as the key is present, we delete it off of the options
  37. // object here.
  38. delete options.keyAltNames;
  39. }
  40. return options;
  41. }
  42. /**
  43. * @typedef {object} KMSProviders
  44. * @description Configuration options that are used by specific KMS providers during key generation, encryption, and decryption.
  45. * @prop {object} [aws] Configuration options for using 'aws' as your KMS provider
  46. * @prop {string} [aws.accessKeyId] The access key used for the AWS KMS provider
  47. * @prop {string} [aws.secretAccessKey] The secret access key used for the AWS KMS provider
  48. * @prop {object} [local] Configuration options for using 'local' as your KMS provider
  49. * @prop {Buffer} [local.key] The master key used to encrypt/decrypt data keys. A 96-byte long Buffer.
  50. */
  51. /**
  52. * The public interface for explicit client side encryption
  53. */
  54. class ClientEncryption {
  55. /**
  56. * Create a new encryption instance
  57. *
  58. * @param {MongoClient} client The client used for encryption
  59. * @param {object} options Optional settings
  60. * @param {string} options.keyVaultNamespace The namespace of the key vault, used to store encryption keys
  61. * @param {MongoClient} [options.keyVaultClient] A `MongoClient` used to fetch keys from a key vault. Defaults to `client`
  62. * @param {KMSProviders} [options.kmsProviders] options for specific KMS providers to use
  63. *
  64. * @example
  65. * new ClientEncryption(mongoClient, {
  66. * keyVaultNamespace: 'client.encryption',
  67. * kmsProviders: {
  68. * local: {
  69. * key: masterKey // The master key used for encryption/decryption. A 96-byte long Buffer
  70. * }
  71. * }
  72. * });
  73. *
  74. * @example
  75. * new ClientEncryption(mongoClient, {
  76. * keyVaultNamespace: 'client.encryption',
  77. * kmsProviders: {
  78. * aws: {
  79. * accessKeyId: AWS_ACCESS_KEY,
  80. * secretAccessKey: AWS_SECRET_KEY
  81. * }
  82. * }
  83. * });
  84. */
  85. constructor(client, options) {
  86. this._client = client;
  87. this._bson = options.bson || client.topology.bson;
  88. if (options.keyVaultNamespace == null) {
  89. throw new TypeError('Missing required option `keyVaultNamespace`');
  90. }
  91. Object.assign(options, { cryptoCallbacks });
  92. this._keyVaultNamespace = options.keyVaultNamespace;
  93. this._keyVaultClient = options.keyVaultClient || client;
  94. this._mongoCrypt = new mc.MongoCrypt(options);
  95. }
  96. /**
  97. * @typedef {Binary} ClientEncryption~dataKeyId
  98. * @description The id of an existing dataKey. Is a bson Binary value.
  99. * Can be used for {@link ClientEncryption.encrypt}, and can be used to directly
  100. * query for the data key itself against the key vault namespace.
  101. */
  102. /**
  103. * @callback ClientEncryption~createDataKeyCallback
  104. * @param {Error} [error] If present, indicates an error that occurred in the creation of the data key
  105. * @param {ClientEncryption~dataKeyId} [dataKeyId] If present, returns the id of the created data key
  106. */
  107. /**
  108. * Creates a data key used for explicit encryption and inserts it into the key vault namespace
  109. *
  110. * @param {string} provider The KMS provider used for this data key. Must be `'aws'` or `'local'`
  111. * @param {object} [options] Options for creating the data key
  112. * @param {object} [options.masterKey] Idenfities a new KMS-specific key used to encrypt the new data key. If the kmsProvider is "aws" it is required.
  113. * @param {string} [options.masterKey.region] The AWS region of the KMS
  114. * @param {string} [options.masterKey.key] The Amazon Resource Name (ARN) to the AWS customer master key (CMK)
  115. * @param {string} [options.masterKey.endpoint] An alternate host to send KMS requests to. May include port number.
  116. * @param {string[]} [options.keyAltNames] An optional list of string alternate names used to reference a key. If a key is created with alternate names, then encryption may refer to the key by the unique alternate name instead of by _id.
  117. * @param {ClientEncryption~createDataKeyCallback} [callback] Optional callback to invoke when key is created
  118. * @returns {Promise|void} If no callback is provided, returns a Promise that either resolves with {@link ClientEncryption~dataKeyId the id of the created data key}, or rejects with an error. If a callback is provided, returns nothing.
  119. * @example
  120. * // Using callbacks to create a local key
  121. * clientEncryption.createDataKey('local', (err, dataKey) => {
  122. * if (err) {
  123. * // This means creating the key failed.
  124. * } else {
  125. * // key creation succeeded
  126. * }
  127. * });
  128. *
  129. * @example
  130. * // Using async/await to create a local key
  131. * const dataKeyId = await clientEncryption.createDataKey('local');
  132. *
  133. * @example
  134. * // Using async/await to create an aws key
  135. * const dataKeyId = await clientEncryption.createDataKey('aws', {
  136. * masterKey: {
  137. * region: 'us-east-1',
  138. * key: 'xxxxxxxxxxxxxx' // CMK ARN here
  139. * }
  140. * });
  141. *
  142. * @example
  143. * // Using async/await to create an aws key with a keyAltName
  144. * const dataKeyId = await clientEncryption.createDataKey('aws', {
  145. * masterKey: {
  146. * region: 'us-east-1',
  147. * key: 'xxxxxxxxxxxxxx' // CMK ARN here
  148. * },
  149. * keyAltNames: [ 'mySpecialKey' ]
  150. * });
  151. */
  152. createDataKey(provider, options, callback) {
  153. if (typeof options === 'function') (callback = options), (options = {});
  154. const bson = this._bson;
  155. options = sanitizeDataKeyOptions(bson, options);
  156. const context = this._mongoCrypt.makeDataKeyContext(provider, options);
  157. const stateMachine = new StateMachine({ bson });
  158. return promiseOrCallback(callback, cb => {
  159. stateMachine.execute(this, context, (err, dataKey) => {
  160. if (err) {
  161. cb(err, null);
  162. return;
  163. }
  164. const dbName = databaseNamespace(this._keyVaultNamespace);
  165. const collectionName = collectionNamespace(this._keyVaultNamespace);
  166. this._keyVaultClient
  167. .db(dbName)
  168. .collection(collectionName)
  169. .insertOne(dataKey, { w: 'majority' }, (err, result) => {
  170. if (err) {
  171. cb(err, null);
  172. return;
  173. }
  174. cb(null, result.insertedId);
  175. });
  176. });
  177. });
  178. }
  179. /**
  180. * @callback ClientEncryption~encryptCallback
  181. * @param {Error} [err] If present, indicates an error that occurred in the process of encryption
  182. * @param {Buffer} [result] If present, is the encrypted result
  183. */
  184. /**
  185. * Explicitly encrypt a provided value. Note that either `options.keyId` or `options.keyAltName` must
  186. * be specified. Specifying both `options.keyId` and `options.keyAltName` is considered an error.
  187. *
  188. * @param {*} value The value that you wish to serialize. Must be of a type that can be serialized into BSON
  189. * @param {object} options
  190. * @param {ClientEncryption~dataKeyId} [options.keyId] The id of the Binary dataKey to use for encryption
  191. * @param {string} [options.keyAltName] A unique string name corresponding to an already existing dataKey.
  192. * @param {} options.algorithm The algorithm to use for encryption. Must be either `'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'` or `AEAD_AES_256_CBC_HMAC_SHA_512-Random'`
  193. * @param {ClientEncryption~encryptCallback} [callback] Optional callback to invoke when value is encrypted
  194. * @returns {Promise|void} If no callback is provided, returns a Promise that either resolves with the encrypted value, or rejects with an error. If a callback is provided, returns nothing.
  195. *
  196. * @example
  197. * // Encryption with callback API
  198. * function encryptMyData(value, callback) {
  199. * clientEncryption.createDataKey('local', (err, keyId) => {
  200. * if (err) {
  201. * return callback(err);
  202. * }
  203. * clientEncryption.encrypt(value, { keyId, algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic' }, callback);
  204. * });
  205. * }
  206. *
  207. * @example
  208. * // Encryption with async/await api
  209. * async function encryptMyData(value) {
  210. * const keyId = await clientEncryption.createDataKey('local');
  211. * return clientEncryption.encrypt(value, { keyId, algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic' });
  212. * }
  213. *
  214. * @example
  215. * // Encryption using a keyAltName
  216. * async function encryptMyData(value) {
  217. * await clientEncryption.createDataKey('local', { keyAltNames: 'mySpecialKey' });
  218. * return clientEncryption.encrypt(value, { keyAltName: 'mySpecialKey', algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic' });
  219. * }
  220. */
  221. encrypt(value, options, callback) {
  222. const bson = this._bson;
  223. const valueBuffer = bson.serialize({ v: value });
  224. const contextOptions = Object.assign({}, options);
  225. if (options.keyId) {
  226. contextOptions.keyId = options.keyId.buffer;
  227. }
  228. if (options.keyAltName) {
  229. const keyAltName = options.keyAltName;
  230. if (options.keyId) {
  231. throw new TypeError(`"options" cannot contain both "keyId" and "keyAltName"`);
  232. }
  233. const keyAltNameType = typeof keyAltName;
  234. if (keyAltNameType !== 'string') {
  235. throw new TypeError(
  236. `"options.keyAltName" must be of type string, but was of type ${keyAltNameType}`
  237. );
  238. }
  239. contextOptions.keyAltName = bson.serialize({ keyAltName });
  240. }
  241. const stateMachine = new StateMachine({ bson });
  242. const context = this._mongoCrypt.makeExplicitEncryptionContext(valueBuffer, contextOptions);
  243. return promiseOrCallback(callback, cb => {
  244. stateMachine.execute(this, context, (err, result) => {
  245. if (err) {
  246. cb(err, null);
  247. return;
  248. }
  249. cb(null, result.v);
  250. });
  251. });
  252. }
  253. /**
  254. * @callback ClientEncryption~decryptCallback
  255. * @param {Error} [err] If present, indicates an error that occurred in the process of decryption
  256. * @param {object} [result] If present, is the decrypted result
  257. */
  258. /**
  259. * Explicitly decrypt a provided encrypted value
  260. *
  261. * @param {Buffer} value An encrypted value
  262. * @param {ClientEncryption~decryptCallback} callback Optional callback to invoke when value is decrypted
  263. * @returns {Promise|void} If no callback is provided, returns a Promise that either resolves with the decryped value, or rejects with an error. If a callback is provided, returns nothing.
  264. *
  265. * @example
  266. * // Decrypting value with callback API
  267. * function decryptMyValue(value, callback) {
  268. * clientEncryption.decrypt(value, callback);
  269. * }
  270. *
  271. * @example
  272. * // Decrypting value with async/await API
  273. * async function decryptMyValue(value) {
  274. * return clientEncryption.decrypt(value);
  275. * }
  276. */
  277. decrypt(value, callback) {
  278. const bson = this._bson;
  279. const valueBuffer = bson.serialize({ v: value });
  280. const context = this._mongoCrypt.makeExplicitDecryptionContext(valueBuffer);
  281. const stateMachine = new StateMachine({ bson });
  282. return promiseOrCallback(callback, cb => {
  283. stateMachine.execute(this, context, (err, result) => {
  284. if (err) {
  285. cb(err, null);
  286. return;
  287. }
  288. cb(null, result.v);
  289. });
  290. });
  291. }
  292. }
  293. return { ClientEncryption };
  294. };