Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,293 @@
import { BSON, EJSON } from 'bson';
import { expect } from 'chai';
import * as fs from 'fs/promises';
import * as path from 'path';

import { ClientEncryption } from '../../mongodb';
import { getEncryptExtraOptions } from '../../tools/utils';
import { dropCollection } from '../shared';
import { getKmsProviders } from './client_side_encryption.prose.test';

Comment on lines +6 to +10

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do seem to define the various test interfaces/functions/etc., instead of importing them: currently, no prose file imports anything from another prose test file. This test would be the first to break that pattern. Let's follow the pattern and re-define whatever we need, instead of importing.

const LOCAL_KEY = Buffer.from(
'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk',
'base64'
);

const eeMetadata: MongoDBMetadataUI = {
requires: {
clientSideEncryption: true,
mongodb: '>=7.0.0',
topology: ['replicaset', 'sharded']
}
};

describe('12. Explicit Encryption', eeMetadata, function () {
const data = path.join(__dirname, '..', '..', 'spec', 'client-side-encryption', 'etc', 'data');
let encryptedFields;
let encryptedFieldsC10;
let key1Document;
let key1Id;
let setupClient;
let keyVaultClient;
let clientEncryption;
let encryptedClient;

beforeEach(async function () {
// Load the file encryptedFields.json as encryptedFields.
encryptedFields = EJSON.parse(
await fs.readFile(path.join(data, 'encryptedFields.json'), 'utf8'),
{ relaxed: false }
);
// Load the file encryptedFields-c10.json as encryptedFields_c10.
encryptedFieldsC10 = EJSON.parse(
await fs.readFile(path.join(data, 'encryptedFields-c10.json'), 'utf8'),
{ relaxed: false }
);
// Load the file key1-document.json as key1Document.
key1Document = EJSON.parse(
await fs.readFile(path.join(data, 'keys', 'key1-document.json'), 'utf8'),
{ relaxed: false }
);
// Read the "_id" field of key1Document as key1ID.
key1Id = key1Document._id;
setupClient = this.configuration.newClient();
// Drop and create the collection db.explicit_encryption using encryptedFields as an option.
const db = setupClient.db('db');
await dropCollection(db, 'explicit_encryption', { encryptedFields });
await db.createCollection('explicit_encryption', { encryptedFields });
// Drop and create the collection db.explicit_encryption_c10 using encryptedFields_c10 as an option.
await dropCollection(db, 'explicit_encryption_c10', { encryptedFields: encryptedFieldsC10 });
await db.createCollection('explicit_encryption_c10', { encryptedFields: encryptedFieldsC10 });
// Drop and create the collection keyvault.datakeys.
const kdb = setupClient.db('keyvault');
await dropCollection(kdb, 'datakeys');
await kdb.createCollection('datakeys');
// Insert key1Document in keyvault.datakeys with majority write concern.
await kdb.collection('datakeys').insertOne(key1Document, { writeConcern: { w: 'majority' } });
// Create a MongoClient named keyVaultClient.
keyVaultClient = this.configuration.newClient();
// Create a ClientEncryption object named clientEncryption with these options:
// ClientEncryptionOpts {
// keyVaultClient: <keyVaultClient>;
// keyVaultNamespace: "keyvault.datakeys";
// kmsProviders: { "local": { "key": <base64 decoding of LOCAL_MASTERKEY> } }
// }
clientEncryption = new ClientEncryption(keyVaultClient, {
keyVaultNamespace: 'keyvault.datakeys',
kmsProviders: getKmsProviders(LOCAL_KEY),
bson: BSON,
extraOptions: getEncryptExtraOptions()
});
// Create a MongoClient named ``encryptedClient`` with these ``AutoEncryptionOpts``:
// AutoEncryptionOpts {
// keyVaultNamespace: "keyvault.datakeys";
// kmsProviders: { "local": { "key": <base64 decoding of LOCAL_MASTERKEY> } },
// bypassQueryAnalysis: true
// }
encryptedClient = this.configuration.newClient(
{},
{
autoEncryption: {
bypassQueryAnalysis: true,
keyVaultNamespace: 'keyvault.datakeys',
kmsProviders: getKmsProviders(LOCAL_KEY),
extraOptions: getEncryptExtraOptions()
}
}
);
});

afterEach(async function () {
await setupClient.close();
await keyVaultClient.close();
await encryptedClient.close();
});

context('Case 1: can insert encrypted indexed and find', eeMetadata, function () {
let insertPayload;
let findPayload;

beforeEach(async function () {
// Use clientEncryption to encrypt the value "encrypted indexed value" with these EncryptOpts:
// class EncryptOpts {
// keyId : <key1ID>
// algorithm: "Indexed",
// }
// Store the result in insertPayload.
insertPayload = await clientEncryption.encrypt('encrypted indexed value', {
keyId: key1Id,
algorithm: 'Indexed',
contentionFactor: 0
});
// Use encryptedClient to insert the document { "encryptedIndexed": <insertPayload> }
// into db.explicit_encryption.
await encryptedClient.db('db').collection('explicit_encryption').insertOne({
encryptedIndexed: insertPayload
});
// Use clientEncryption to encrypt the value "encrypted indexed value" with these EncryptOpts:
// class EncryptOpts {
// keyId : <key1ID>
// algorithm: "Indexed",
// queryType: Equality
// }
// Store the result in findPayload.
findPayload = await clientEncryption.encrypt('encrypted indexed value', {
keyId: key1Id,
algorithm: 'Indexed',
queryType: 'equality',
contentionFactor: 0
});
});

it('returns the decrypted value', async function () {
// Use encryptedClient to run a "find" operation on the db.explicit_encryption
// collection with the filter { "encryptedIndexed": <findPayload> }.
// Assert one document is returned containing the field
// { "encryptedIndexed": "encrypted indexed value" }.
const collection = encryptedClient.db('db').collection('explicit_encryption');
const result = await collection.findOne({ encryptedIndexed: findPayload });
expect(result).to.have.property('encryptedIndexed', 'encrypted indexed value');
});
});

context(
'Case 2: can insert encrypted indexed and find with non-zero contention',
eeMetadata,
function () {
let findPayload;

beforeEach(async function () {
for (let i = 0; i < 10; i++) {
// Use clientEncryption to encrypt the value "encrypted indexed value" with these EncryptOpts:
// class EncryptOpts {
// keyId : <key1ID>
// algorithm: "Indexed",
// contentionFactor: 10
// }
// Store the result in insertPayload.
const insertPayload = await clientEncryption.encrypt('encrypted indexed value', {
keyId: key1Id,
algorithm: 'Indexed',
contentionFactor: 10
});
// Use encryptedClient to insert the document { "encryptedIndexed": <insertPayload> }
// into db.explicit_encryption_c10.
await encryptedClient.db('db').collection('explicit_encryption_c10').insertOne({
encryptedIndexed: insertPayload
});
// Repeat the above steps 10 times to insert 10 total documents.
// The insertPayload must be regenerated each iteration.
}
// Use clientEncryption to encrypt the value "encrypted indexed value" with these EncryptOpts:
// class EncryptOpts {
// keyId : <key1ID>
// algorithm: "Indexed",
// queryType: Equality,
// contentionFactor: 10
// }
// Store the result in findPayload.
findPayload = await clientEncryption.encrypt('encrypted indexed value', {
keyId: key1Id,
algorithm: 'Indexed',
queryType: 'equality',
contentionFactor: 10
});
});

it('returns all documents with contention', async function () {
// Use encryptedClient to run a "find" operation on the db.explicit_encryption_c10
// collection with the filter { "encryptedIndexed": <findPayload> }.
// Assert 10 documents are returned. Assert each returned document contains the
// field { "encryptedIndexed": "encrypted indexed value" }.
const collection = encryptedClient.db('db').collection('explicit_encryption_c10');
const result = await collection.find({ encryptedIndexed: findPayload }).toArray();
expect(result.length).to.equal(10);
for (const doc of result) {
expect(doc).to.have.property('encryptedIndexed', 'encrypted indexed value');
}
});
}
);

context('Case 3: can insert encrypted unindexed', eeMetadata, function () {
let insertPayload;

beforeEach(async function () {
// Use clientEncryption to encrypt the value "encrypted unindexed value" with these EncryptOpts:
// class EncryptOpts {
// keyId : <key1ID>
// algorithm: "Unindexed"
// }
// Store the result in insertPayload.
insertPayload = await clientEncryption.encrypt('encrypted unindexed value', {
keyId: key1Id,
algorithm: 'Unindexed'
});
// Use encryptedClient to insert the document { "_id": 1, "encryptedUnindexed": <insertPayload> }
// into db.explicit_encryption.
await encryptedClient.db('db').collection('explicit_encryption').insertOne({
_id: 1,
encryptedUnindexed: insertPayload
});
});

it('returns unindexed documents', async function () {
// Use encryptedClient to run a "find" operation on the db.explicit_encryption
// collection with the filter { "_id": 1 }.
// Assert one document is returned containing the field
// { "encryptedUnindexed": "encrypted unindexed value" }.
const collection = encryptedClient.db('db').collection('explicit_encryption');
const result = await collection.findOne({ _id: 1 });
expect(result).to.have.property('encryptedUnindexed', 'encrypted unindexed value');
});
});

context('Case 4: can roundtrip encrypted indexed', eeMetadata, function () {
let payload;

beforeEach(async function () {
// Use clientEncryption to encrypt the value "encrypted indexed value" with these EncryptOpts:
// class EncryptOpts {
// keyId : <key1ID>
// algorithm: "Indexed",
// }
// Store the result in payload.
payload = await clientEncryption.encrypt('encrypted indexed value', {
keyId: key1Id,
algorithm: 'Indexed',
contentionFactor: 0
});
});

it('decrypts the value', async function () {
// Use clientEncryption to decrypt payload. Assert the returned value
// equals "encrypted indexed value".
const result = await clientEncryption.decrypt(payload);
expect(result).equals('encrypted indexed value');
});
});

context('Case 5: can roundtrip encrypted unindexed', eeMetadata, function () {
let payload;

beforeEach(async function () {
// Use clientEncryption to encrypt the value "encrypted unindexed value" with these EncryptOpts:
// class EncryptOpts {
// keyId : <key1ID>
// algorithm: "Unindexed",
// }
// Store the result in payload.
payload = await clientEncryption.encrypt('encrypted unindexed value', {
keyId: key1Id,
algorithm: 'Unindexed'
});
});

it('decrypts the value', async function () {
// Use clientEncryption to decrypt payload. Assert the returned value
// equals "encrypted unindexed value".
const result = await clientEncryption.decrypt(payload);
expect(result).equals('encrypted unindexed value');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@ const metadataWithoutPreview: MongoDBMetadataUI = {
libmongocrypt: '>=1.15.1'
}
};
// TODO(NODE-7623): substringPreview contention validation broken on MongoDB 9.0+ (SERVER-91887).
const metadataWithoutSubstringPreview: MongoDBMetadataUI = {
requires: {
clientSideEncryption: '>=6.4.0',
mongodb: '>=8.2.0 <9.0.0',
mongodb: '>=8.2.0',
topology: '!single',
libmongocrypt: '>=1.15.1'
}
Expand Down
Loading
Loading