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. * @property {object} [aws] Configuration options for using 'aws' as your KMS provider
  46. * @property {string} [aws.accessKeyId] The access key used for the AWS KMS provider
  47. * @property {string} [aws.secretAccessKey] The secret access key used for the AWS KMS provider
  48. * @property {object} [local] Configuration options for using 'local' as your KMS provider
  49. * @property {Buffer} [local.key] The master key used to encrypt/decrypt data keys. A 96-byte long Buffer.
  50. * @property {object} [azure] Configuration options for using 'azure' as your KMS provider
  51. * @property {string} [azure.tenantId] The tenant ID identifies the organization for the account
  52. * @property {string} [azure.clientId] The client ID to authenticate a registered application
  53. * @property {string} [azure.clientSecret] The client secret to authenticate a registered application
  54. * @property {string} [azure.identityPlatformEndpoint] If present, a host with optional port. E.g. "example.com" or "example.com:443". This is optional, and only needed if customer is using a non-commercial Azure instance (e.g. a government or China account, which use different URLs). Defaults to "login.microsoftonline.com"
  55. * @property {object} [gcp] Configuration options for using 'gcp' as your KMS provider
  56. * @property {string} [gcp.email] The service account email to authenticate
  57. * @property {string|Binary} [gcp.privateKey] A PKCS#8 encrypted key. This can either be a base64 string or a binary representation
  58. * @property {string} [gcp.endpoint] If present, a host with optional port. E.g. "example.com" or "example.com:443". Defaults to "oauth2.googleapis.com"
  59. */
  60. /**
  61. * The public interface for explicit client side encryption
  62. */
  63. class ClientEncryption {
  64. /**
  65. * Create a new encryption instance
  66. *
  67. * @param {MongoClient} client The client used for encryption
  68. * @param {object} options Additional settings
  69. * @param {string} options.keyVaultNamespace The namespace of the key vault, used to store encryption keys
  70. * @param {MongoClient} [options.keyVaultClient] A `MongoClient` used to fetch keys from a key vault. Defaults to `client`
  71. * @param {KMSProviders} [options.kmsProviders] options for specific KMS providers to use
  72. *
  73. * @example
  74. * new ClientEncryption(mongoClient, {
  75. * keyVaultNamespace: 'client.encryption',
  76. * kmsProviders: {
  77. * local: {
  78. * key: masterKey // The master key used for encryption/decryption. A 96-byte long Buffer
  79. * }
  80. * }
  81. * });
  82. *
  83. * @example
  84. * new ClientEncryption(mongoClient, {
  85. * keyVaultNamespace: 'client.encryption',
  86. * kmsProviders: {
  87. * aws: {
  88. * accessKeyId: AWS_ACCESS_KEY,
  89. * secretAccessKey: AWS_SECRET_KEY
  90. * }
  91. * }
  92. * });
  93. */
  94. constructor(client, options) {
  95. this._client = client;
  96. this._bson = options.bson || client.topology.bson;
  97. if (options.keyVaultNamespace == null) {
  98. throw new TypeError('Missing required option `keyVaultNamespace`');
  99. }
  100. Object.assign(options, { cryptoCallbacks });
  101. // kmsProviders will be parsed by libmongocrypt, must be provided as BSON binary data
  102. if (options.kmsProviders && !Buffer.isBuffer(options.kmsProviders)) {
  103. options.kmsProviders = this._bson.serialize(options.kmsProviders);
  104. }
  105. this._keyVaultNamespace = options.keyVaultNamespace;
  106. this._keyVaultClient = options.keyVaultClient || client;
  107. this._mongoCrypt = new mc.MongoCrypt(options);
  108. }
  109. /**
  110. * @typedef {Binary} ClientEncryption~dataKeyId
  111. * @description The id of an existing dataKey. Is a bson Binary value.
  112. * Can be used for {@link ClientEncryption.encrypt}, and can be used to directly
  113. * query for the data key itself against the key vault namespace.
  114. */
  115. /**
  116. * @callback ClientEncryption~createDataKeyCallback
  117. * @param {Error} [error] If present, indicates an error that occurred in the creation of the data key
  118. * @param {ClientEncryption~dataKeyId} [dataKeyId] If present, returns the id of the created data key
  119. */
  120. /**
  121. * @typedef {object} AWSEncryptionKeyOptions
  122. * @description Configuration options for making an AWS encryption key
  123. * @property {string} region The AWS region of the KMS
  124. * @property {string} key The Amazon Resource Name (ARN) to the AWS customer master key (CMK)
  125. * @property {string} [endpoint] An alternate host to send KMS requests to. May include port number
  126. */
  127. /**
  128. * @typedef {object} GCPEncryptionKeyOptions
  129. * @description Configuration options for making a GCP encryption key
  130. * @property {string} projectId GCP project id
  131. * @property {string} location Location name (e.g. "global")
  132. * @property {string} keyRing Key ring name
  133. * @property {string} keyName Key name
  134. * @property {string} [keyVersion] Key version
  135. * @property {string} [endpoint] KMS URL, defaults to `https://www.googleapis.com/auth/cloudkms`
  136. */
  137. /**
  138. * @typedef {object} AzureEncryptionKeyOptions
  139. * @description Configuration options for making an Azure encryption key
  140. * @property {string} keyName Key name
  141. * @property {string} keyVaultEndpoint Key vault URL, typically `<name>.vault.azure.net`
  142. * @property {string} [keyVersion] Key version
  143. */
  144. /**
  145. * Creates a data key used for explicit encryption and inserts it into the key vault namespace
  146. *
  147. * @param {string} provider The KMS provider used for this data key. Must be `'aws'`, `'azure'`, `'gcp'`, or `'local'`
  148. * @param {object} [options] Options for creating the data key
  149. * @param {AWSEncryptionKeyOptions|AzureEncryptionKeyOptions|GCPEncryptionKeyOptions} [options.masterKey] Idenfities a new KMS-specific key used to encrypt the new data key
  150. * @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.
  151. * @param {ClientEncryption~createDataKeyCallback} [callback] Optional callback to invoke when key is created
  152. * @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.
  153. * @example
  154. * // Using callbacks to create a local key
  155. * clientEncryption.createDataKey('local', (err, dataKey) => {
  156. * if (err) {
  157. * // This means creating the key failed.
  158. * } else {
  159. * // key creation succeeded
  160. * }
  161. * });
  162. *
  163. * @example
  164. * // Using async/await to create a local key
  165. * const dataKeyId = await clientEncryption.createDataKey('local');
  166. *
  167. * @example
  168. * // Using async/await to create an aws key
  169. * const dataKeyId = await clientEncryption.createDataKey('aws', {
  170. * masterKey: {
  171. * region: 'us-east-1',
  172. * key: 'xxxxxxxxxxxxxx' // CMK ARN here
  173. * }
  174. * });
  175. *
  176. * @example
  177. * // Using async/await to create an aws key with a keyAltName
  178. * const dataKeyId = await clientEncryption.createDataKey('aws', {
  179. * masterKey: {
  180. * region: 'us-east-1',
  181. * key: 'xxxxxxxxxxxxxx' // CMK ARN here
  182. * },
  183. * keyAltNames: [ 'mySpecialKey' ]
  184. * });
  185. */
  186. createDataKey(provider, options, callback) {
  187. if (typeof options === 'function') (callback = options), (options = {});
  188. const bson = this._bson;
  189. options = sanitizeDataKeyOptions(bson, options);
  190. const dataKeyBson = bson.serialize(Object.assign({ provider }, options.masterKey));
  191. const context = this._mongoCrypt.makeDataKeyContext(dataKeyBson);
  192. const stateMachine = new StateMachine({ bson });
  193. return promiseOrCallback(callback, cb => {
  194. stateMachine.execute(this, context, (err, dataKey) => {
  195. if (err) {
  196. cb(err, null);
  197. return;
  198. }
  199. const dbName = databaseNamespace(this._keyVaultNamespace);
  200. const collectionName = collectionNamespace(this._keyVaultNamespace);
  201. this._keyVaultClient
  202. .db(dbName)
  203. .collection(collectionName)
  204. .insertOne(dataKey, { w: 'majority' }, (err, result) => {
  205. if (err) {
  206. cb(err, null);
  207. return;
  208. }
  209. cb(null, result.insertedId);
  210. });
  211. });
  212. });
  213. }
  214. /**
  215. * @callback ClientEncryption~encryptCallback
  216. * @param {Error} [err] If present, indicates an error that occurred in the process of encryption
  217. * @param {Buffer} [result] If present, is the encrypted result
  218. */
  219. /**
  220. * Explicitly encrypt a provided value. Note that either `options.keyId` or `options.keyAltName` must
  221. * be specified. Specifying both `options.keyId` and `options.keyAltName` is considered an error.
  222. *
  223. * @param {*} value The value that you wish to serialize. Must be of a type that can be serialized into BSON
  224. * @param {object} options
  225. * @param {ClientEncryption~dataKeyId} [options.keyId] The id of the Binary dataKey to use for encryption
  226. * @param {string} [options.keyAltName] A unique string name corresponding to an already existing dataKey.
  227. * @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'`
  228. * @param {ClientEncryption~encryptCallback} [callback] Optional callback to invoke when value is encrypted
  229. * @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.
  230. *
  231. * @example
  232. * // Encryption with callback API
  233. * function encryptMyData(value, callback) {
  234. * clientEncryption.createDataKey('local', (err, keyId) => {
  235. * if (err) {
  236. * return callback(err);
  237. * }
  238. * clientEncryption.encrypt(value, { keyId, algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic' }, callback);
  239. * });
  240. * }
  241. *
  242. * @example
  243. * // Encryption with async/await api
  244. * async function encryptMyData(value) {
  245. * const keyId = await clientEncryption.createDataKey('local');
  246. * return clientEncryption.encrypt(value, { keyId, algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic' });
  247. * }
  248. *
  249. * @example
  250. * // Encryption using a keyAltName
  251. * async function encryptMyData(value) {
  252. * await clientEncryption.createDataKey('local', { keyAltNames: 'mySpecialKey' });
  253. * return clientEncryption.encrypt(value, { keyAltName: 'mySpecialKey', algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic' });
  254. * }
  255. */
  256. encrypt(value, options, callback) {
  257. const bson = this._bson;
  258. const valueBuffer = bson.serialize({ v: value });
  259. const contextOptions = Object.assign({}, options);
  260. if (options.keyId) {
  261. contextOptions.keyId = options.keyId.buffer;
  262. }
  263. if (options.keyAltName) {
  264. const keyAltName = options.keyAltName;
  265. if (options.keyId) {
  266. throw new TypeError(`"options" cannot contain both "keyId" and "keyAltName"`);
  267. }
  268. const keyAltNameType = typeof keyAltName;
  269. if (keyAltNameType !== 'string') {
  270. throw new TypeError(
  271. `"options.keyAltName" must be of type string, but was of type ${keyAltNameType}`
  272. );
  273. }
  274. contextOptions.keyAltName = bson.serialize({ keyAltName });
  275. }
  276. const stateMachine = new StateMachine({ bson });
  277. const context = this._mongoCrypt.makeExplicitEncryptionContext(valueBuffer, contextOptions);
  278. return promiseOrCallback(callback, cb => {
  279. stateMachine.execute(this, context, (err, result) => {
  280. if (err) {
  281. cb(err, null);
  282. return;
  283. }
  284. cb(null, result.v);
  285. });
  286. });
  287. }
  288. /**
  289. * @callback ClientEncryption~decryptCallback
  290. * @param {Error} [err] If present, indicates an error that occurred in the process of decryption
  291. * @param {object} [result] If present, is the decrypted result
  292. */
  293. /**
  294. * Explicitly decrypt a provided encrypted value
  295. *
  296. * @param {Buffer} value An encrypted value
  297. * @param {ClientEncryption~decryptCallback} callback Optional callback to invoke when value is decrypted
  298. * @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.
  299. *
  300. * @example
  301. * // Decrypting value with callback API
  302. * function decryptMyValue(value, callback) {
  303. * clientEncryption.decrypt(value, callback);
  304. * }
  305. *
  306. * @example
  307. * // Decrypting value with async/await API
  308. * async function decryptMyValue(value) {
  309. * return clientEncryption.decrypt(value);
  310. * }
  311. */
  312. decrypt(value, callback) {
  313. const bson = this._bson;
  314. const valueBuffer = bson.serialize({ v: value });
  315. const context = this._mongoCrypt.makeExplicitDecryptionContext(valueBuffer);
  316. const stateMachine = new StateMachine({ bson });
  317. return promiseOrCallback(callback, cb => {
  318. stateMachine.execute(this, context, (err, result) => {
  319. if (err) {
  320. cb(err, null);
  321. return;
  322. }
  323. cb(null, result.v);
  324. });
  325. });
  326. }
  327. }
  328. return { ClientEncryption };
  329. };