Skip to content

Commit 95c05ba

Browse files
feat(shell-api): support indexKeyId, contentionFactor, queryType, keyMaterial MONGOSH-1209 (#1301)
1 parent 397fb7c commit 95c05ba

File tree

2 files changed

+103
-26
lines changed

2 files changed

+103
-26
lines changed

packages/shell-api/src/field-level-encryption.spec.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { MongoshInvalidInputError } from '@mongosh/errors';
2-
import { bson, ClientEncryption as FLEClientEncryption, ClientEncryptionTlsOptions, ServiceProvider } from '@mongosh/service-provider-core';
2+
import { bson, ClientEncryption as FLEClientEncryption, ClientEncryptionTlsOptions, ServiceProvider, ClientEncryptionEncryptOptions } from '@mongosh/service-provider-core';
33
import { expect } from 'chai';
44
import { EventEmitter } from 'events';
55
import { promises as fs } from 'fs';
@@ -47,6 +47,12 @@ const AWS_KMS = {
4747
};
4848

4949
const ALGO = 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic';
50+
const ENCRYPT_OPTIONS = {
51+
algorithm: ALGO as ClientEncryptionEncryptOptions['algorithm'],
52+
indexKeyId: new bson.Binary(Buffer.from('12345678123498761234123456789012', 'hex'), 4),
53+
contentionFactor: 10,
54+
queryType: 'Equality'
55+
};
5056

5157
const RAW_CLIENT = { client: 1 } as any;
5258

@@ -161,12 +167,18 @@ describe('Field Level Encryption', () => {
161167
});
162168
});
163169
describe('encrypt', () => {
164-
it('calls encrypt on libmongoc', async() => {
170+
it('calls encrypt with algorithm on libmongoc', async() => {
165171
const value = new bson.ObjectId();
166172
libmongoc.encrypt.resolves();
167173
await clientEncryption.encrypt(KEY_ID, value, ALGO);
168174
expect(libmongoc.encrypt).calledOnceWithExactly(value, { keyId: KEY_ID, algorithm: ALGO });
169175
});
176+
it('calls encrypt with algorithm, indexKeyId, contentionFactor, and queryType on libmongoc', async() => {
177+
const value = new bson.ObjectId();
178+
libmongoc.encrypt.resolves();
179+
await clientEncryption.encrypt(KEY_ID, value, ENCRYPT_OPTIONS);
180+
expect(libmongoc.encrypt).calledOnceWithExactly(value, { keyId: KEY_ID, ...ENCRYPT_OPTIONS });
181+
});
170182
it('throw if failed', async() => {
171183
const value = new bson.ObjectId();
172184
const expectedError = new Error();
@@ -384,6 +396,18 @@ describe('Field Level Encryption', () => {
384396
]);
385397
expect(result).to.deep.equal(r2.value);
386398
});
399+
it('reads keyAltNames and keyMaterial from DataKeyEncryptionKeyOptions', async() => {
400+
const rawResult = { result: 1 };
401+
const keyVault = await mongo.getKeyVault();
402+
const options = {
403+
keyAltNames: ['b'],
404+
keyMaterial: new bson.Binary(Buffer.from('12345678123498761234123456789012', 'hex'), 4)
405+
};
406+
407+
libmongoc.createDataKey.resolves(rawResult);
408+
await keyVault.createKey('local', options);
409+
expect(libmongoc.createDataKey).calledOnceWithExactly('local', options);
410+
});
387411
});
388412
});
389413
describe('Mongo constructor FLE options', () => {

packages/shell-api/src/field-level-encryption.ts

Lines changed: 77 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,33 @@ export interface ClientSideFieldLevelEncryptionOptions {
4747
bypassQueryAnalysis?: boolean;
4848
}
4949

50+
type MasterKey = AWSEncryptionKeyOptions | AzureEncryptionKeyOptions | GCPEncryptionKeyOptions;
51+
type AltNames = string[];
52+
53+
type DataKeyEncryptionKeyOptions = {
54+
masterKey?: MasterKey;
55+
keyAltNames?: AltNames;
56+
keyMaterial?: Buffer | BinaryType
57+
};
58+
59+
type MasterKeyOrAltNamesOrDataKeyOptions = MasterKey | DataKeyEncryptionKeyOptions | AltNames | string;
60+
61+
const isDataKeyEncryptionKeyOptions = (options?: MasterKeyOrAltNamesOrDataKeyOptions): options is DataKeyEncryptionKeyOptions => {
62+
return (
63+
!Array.isArray(options) &&
64+
typeof options === 'object' &&
65+
('masterKey' in options || 'keyAltNames' in options || 'keyMaterial' in options)
66+
);
67+
};
68+
69+
const isMasterKey = (options?: MasterKeyOrAltNamesOrDataKeyOptions): options is MasterKey => {
70+
return (
71+
!Array.isArray(options) &&
72+
typeof options === 'object' &&
73+
!isDataKeyEncryptionKeyOptions(options)
74+
);
75+
};
76+
5077
@shellApiClassDefault
5178
@classPlatforms([ ReplPlatform.CLI ] )
5279
export class ClientEncryption extends ShellApiWithMongoClass {
@@ -80,15 +107,24 @@ export class ClientEncryption extends ShellApiWithMongoClass {
80107
async encrypt(
81108
encryptionId: BinaryType,
82109
value: any,
83-
encryptionAlgorithm: ClientEncryptionEncryptOptions['algorithm']
110+
algorithmOrEncryptionOptions: ClientEncryptionEncryptOptions['algorithm'] | ClientEncryptionEncryptOptions
84111
): Promise<BinaryType> {
85-
assertArgsDefinedType([encryptionId, value, encryptionAlgorithm], [true, true, true], 'ClientEncryption.encrypt');
112+
let encryptionOptions: ClientEncryptionEncryptOptions;
113+
if (typeof algorithmOrEncryptionOptions === 'object') {
114+
encryptionOptions = {
115+
keyId: encryptionId,
116+
...algorithmOrEncryptionOptions
117+
};
118+
} else {
119+
encryptionOptions = {
120+
keyId: encryptionId,
121+
algorithm: algorithmOrEncryptionOptions
122+
};
123+
}
124+
assertArgsDefinedType([encryptionId, value, encryptionOptions], [true, true, true], 'ClientEncryption.encrypt');
86125
return await this._libmongocrypt.encrypt(
87126
value,
88-
{
89-
keyId: encryptionId,
90-
algorithm: encryptionAlgorithm
91-
}
127+
encryptionOptions
92128
);
93129
}
94130

@@ -160,29 +196,46 @@ export class KeyVault extends ShellApiWithMongoClass {
160196

161197
createKey(kms: 'local', keyAltNames?: string[]): Promise<BinaryType>
162198
createKey(kms: ClientEncryptionDataKeyProvider, legacyMasterKey: string, keyAltNames?: string[]): Promise<BinaryType>
163-
createKey(kms: ClientEncryptionDataKeyProvider, options: AWSEncryptionKeyOptions | AzureEncryptionKeyOptions | GCPEncryptionKeyOptions | undefined): Promise<BinaryType>
164-
createKey(kms: ClientEncryptionDataKeyProvider, options: AWSEncryptionKeyOptions | AzureEncryptionKeyOptions | GCPEncryptionKeyOptions | undefined, keyAltNames: string[]): Promise<BinaryType>
199+
createKey(kms: ClientEncryptionDataKeyProvider, options: MasterKey | DataKeyEncryptionKeyOptions | undefined): Promise<BinaryType>
200+
createKey(kms: ClientEncryptionDataKeyProvider, options: MasterKey | DataKeyEncryptionKeyOptions | undefined, keyAltNames: string[]): Promise<BinaryType>
165201
@returnsPromise
166202
@apiVersions([1])
203+
// eslint-disable-next-line complexity
167204
async createKey(
168205
kms: ClientEncryptionDataKeyProvider,
169-
masterKeyOrAltNames?: AWSEncryptionKeyOptions | AzureEncryptionKeyOptions | GCPEncryptionKeyOptions | string | undefined | string[],
170-
keyAltNames?: string[]
206+
masterKeyOrAltNamesOrDataKeyOptions?: MasterKeyOrAltNamesOrDataKeyOptions,
207+
legacyKeyAltNames?: string[]
171208
): Promise<BinaryType> {
209+
let masterKey: MasterKey | undefined;
210+
let keyAltNames;
211+
let keyMaterial;
212+
213+
if (isDataKeyEncryptionKeyOptions(masterKeyOrAltNamesOrDataKeyOptions)) {
214+
masterKey = masterKeyOrAltNamesOrDataKeyOptions?.masterKey;
215+
keyAltNames = masterKeyOrAltNamesOrDataKeyOptions?.keyAltNames;
216+
keyMaterial = masterKeyOrAltNamesOrDataKeyOptions?.keyMaterial;
217+
} else if (isMasterKey(masterKeyOrAltNamesOrDataKeyOptions)) {
218+
masterKey = masterKeyOrAltNamesOrDataKeyOptions;
219+
}
220+
221+
if (legacyKeyAltNames) {
222+
keyAltNames = legacyKeyAltNames;
223+
}
224+
172225
assertArgsDefinedType([kms], [true], 'KeyVault.createKey');
173226

174-
if (typeof masterKeyOrAltNames === 'string') {
175-
if (kms === 'local' && masterKeyOrAltNames === '') {
227+
if (typeof masterKeyOrAltNamesOrDataKeyOptions === 'string') {
228+
if (kms === 'local' && masterKeyOrAltNamesOrDataKeyOptions === '') {
176229
// allowed in the old shell - even enforced prior to 4.2.3
177230
// https://docs.mongodb.com/manual/reference/method/KeyVault.createKey/
178-
masterKeyOrAltNames = undefined;
231+
masterKey = undefined;
179232
} else {
180233
throw new MongoshInvalidInputError(
181234
'KeyVault.createKey does not support providing masterKey as string anymore. For AWS please use createKey("aws", { region: ..., key: ... })',
182235
CommonErrors.Deprecated
183236
);
184237
}
185-
} else if (Array.isArray(masterKeyOrAltNames)) {
238+
} else if (Array.isArray(masterKeyOrAltNamesOrDataKeyOptions)) {
186239
// old signature - one could immediately provide an array of key alt names
187240
// not documented but visible in code: https://github.com/mongodb/mongo/blob/eb2b72cf9c0269f086223d499ac9be8a270d268c/src/mongo/shell/keyvault.js#L19
188241
if (kms !== 'local') {
@@ -198,22 +251,22 @@ export class KeyVault extends ShellApiWithMongoClass {
198251
);
199252
}
200253

201-
keyAltNames = masterKeyOrAltNames;
202-
masterKeyOrAltNames = undefined;
254+
keyAltNames = masterKeyOrAltNamesOrDataKeyOptions;
255+
masterKey = undefined;
203256
}
204257
}
205258

206259
let options: ClientEncryptionCreateDataKeyProviderOptions | undefined;
207-
if (masterKeyOrAltNames) {
208-
options = {
209-
masterKey: masterKeyOrAltNames as ClientEncryptionCreateDataKeyProviderOptions['masterKey']
210-
};
260+
261+
if (masterKey) {
262+
options = { ...(options ?? {}), masterKey };
211263
}
212264
if (keyAltNames) {
213-
options = {
214-
...(options ?? {}),
215-
keyAltNames
216-
};
265+
options = { ...(options ?? {}), keyAltNames };
266+
}
267+
if (keyMaterial) {
268+
// @ts-expect-error waiting for driver release
269+
options = { ...(options ?? {}), keyMaterial };
217270
}
218271

219272
return await this._clientEncryption._libmongocrypt.createDataKey(kms, options as ClientEncryptionCreateDataKeyProviderOptions);

0 commit comments

Comments
 (0)