Skip to content

Commit

Permalink
test(NODE-4296): add csfle decryption events prose tests (#3295)
Browse files Browse the repository at this point in the history
  • Loading branch information
durran committed Jun 23, 2022
1 parent aa652ae commit 4a91444
Showing 1 changed file with 222 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ const path = require('path');
const { deadlockTests } = require('./client_side_encryption.prose.deadlock');
const { dropCollection, APMEventCollector } = require('../shared');

const { EJSON } = BSON;
const { EJSON, Binary } = BSON;
const { LEGACY_HELLO_COMMAND } = require('../../../src/constants');
const { MongoNetworkError } = require('../../../src/error');

const getKmsProviders = (localKey, kmipEndpoint, azureEndpoint, gcpEndpoint) => {
const result = BSON.EJSON.parse(process.env.CSFLE_KMS_PROVIDERS || '{}');
Expand Down Expand Up @@ -1424,4 +1425,224 @@ describe('Client Side Encryption Prose Tests', metadata, function () {
});
});
});

context('14. Decryption Events', metadata, function () {
let setupClient;
let clientEncryption;
let keyId;
let cipherText;
let malformedCiphertext;
let encryptedClient;
let aggregateSucceeded;
let aggregateFailed;

beforeEach(async function () {
const mongodbClientEncryption = this.configuration.mongodbClientEncryption;
// Create a MongoClient named ``setupClient``.
setupClient = this.configuration.newClient();
// Drop and create the collection ``db.decryption_events``.
const db = setupClient.db('db');
await dropCollection(db, 'decryption_events');
await db.createCollection('decryption_events');
// Create a ClientEncryption object named ``clientEncryption`` with these options:
// ClientEncryptionOpts {
// keyVaultClient: <setupClient>,
// keyVaultNamespace: "keyvault.datakeys",
// kmsProviders: { "local": { "key": <base64 decoding of LOCAL_MASTERKEY> } }
// }
clientEncryption = new mongodbClientEncryption.ClientEncryption(setupClient, {
keyVaultNamespace: 'keyvault.datakeys',
kmsProviders: getKmsProviders(LOCAL_KEY),
bson: BSON
});
// Create a data key with the "local" KMS provider.
// Storing the result in a variable named ``keyID``.
keyId = await clientEncryption.createDataKey('local');
// Use ``clientEncryption`` to encrypt the string "hello" with the following ``EncryptOpts``:
// EncryptOpts {
// keyId: <keyID>,
// algorithm: "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic"
// }
// Store the result in a variable named ``ciphertext``.
cipherText = await clientEncryption.encrypt('hello', {
keyId: keyId,
algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'
});
// Copy ``ciphertext`` into a variable named ``malformedCiphertext``.
// Change the last byte to 0. This will produce an invalid HMAC tag.
const buffer = Buffer.from(cipherText.buffer);
buffer.writeInt8(0, buffer.length - 1);
malformedCiphertext = new Binary(buffer, 6);
// Create a MongoClient named ``encryptedClient`` with these ``AutoEncryptionOpts``:
// AutoEncryptionOpts {
// keyVaultNamespace: "keyvault.datakeys";
// kmsProviders: { "local": { "key": <base64 decoding of LOCAL_MASTERKEY> } }
// }
// Configure ``encryptedClient`` with "retryReads=false".
encryptedClient = this.configuration.newClient(
{},
{
retryReads: false,
monitorCommands: true,
autoEncryption: {
keyVaultNamespace: 'keyvault.datakeys',
kmsProviders: getKmsProviders(LOCAL_KEY)
}
}
);
// Register a listener for CommandSucceeded events on ``encryptedClient``.
encryptedClient.on('commandSucceeded', event => {
if (event.commandName === 'aggregate') {
aggregateSucceeded = event;
}
});
// The listener must store the most recent CommandFailedEvent error for the "aggregate" command.
encryptedClient.on('commandFailed', event => {
if (event.commandName === 'aggregate') {
aggregateFailed = event;
}
});
});

afterEach(async function () {
aggregateSucceeded = undefined;
aggregateFailed = undefined;
await setupClient.close();
await encryptedClient.close();
});

context('Case 1: Command Error', metadata, function () {
beforeEach(async function () {
// Use ``setupClient`` to configure the following failpoint:
// {
// "configureFailPoint": "failCommand",
// "mode": {
// "times": 1
// },
// "data": {
// "errorCode": 123,
// "failCommands": [
// "aggregate"
// ]
// }
// }
await setupClient
.db()
.admin()
.command({
configureFailPoint: 'failCommand',
mode: {
times: 1
},
data: {
errorCode: 123,
failCommands: ['aggregate']
}
});
});

it('expects an error and a command failed event', async function () {
// Use ``encryptedClient`` to run an aggregate on ``db.decryption_events``.
// Expect an exception to be thrown from the command error. Expect a CommandFailedEvent.
const collection = encryptedClient.db('db').collection('decryption_events');
try {
await collection.aggregate([]).toArray();
expect.fail('aggregate must fail with error');
} catch (error) {
expect(error.code).to.equal(123);
}
expect(aggregateFailed.failure.code).to.equal(123);
});
});

context('Case 2: Network Error', metadata, function () {
beforeEach(async function () {
// Use ``setupClient`` to configure the following failpoint:
// {
// "configureFailPoint": "failCommand",
// "mode": {
// "times": 1
// },
// "data": {
// "errorCode": 123,
// "closeConnection": true,
// "failCommands": [
// "aggregate"
// ]
// }
// }
await setupClient
.db()
.admin()
.command({
configureFailPoint: 'failCommand',
mode: {
times: 1
},
data: {
errorCode: 123,
closeConnection: true,
failCommands: ['aggregate']
}
});
});

it('expects an error and a command failed event', async function () {
// Use ``encryptedClient`` to run an aggregate on ``db.decryption_events``.
// Expect an exception to be thrown from the network error. Expect a CommandFailedEvent.
const collection = encryptedClient.db('db').collection('decryption_events');
try {
await collection.aggregate([]).toArray();
expect.fail('aggregate must fail with error');
} catch (error) {
expect(error).to.be.instanceOf(MongoNetworkError);
}
expect(aggregateFailed.failure.message).to.include('closed');
});
});

context('Case 3: Decrypt Error', metadata, function () {
it('errors on decryption but command succeeds', async function () {
// Use ``encryptedClient`` to insert the document ``{ "encrypted": <malformedCiphertext> }``
// into ``db.decryption_events``.
// Use ``encryptedClient`` to run an aggregate on ``db.decryption_events``.
// Expect an exception to be thrown from the decryption error.
// Expect a CommandSucceededEvent. Expect the CommandSucceededEvent.reply
// to contain BSON binary for the field
// ``cursor.firstBatch.encrypted``.
const collection = encryptedClient.db('db').collection('decryption_events');
await collection.insertOne({ encrypted: malformedCiphertext });
try {
await collection.aggregate([]).toArray();
expect.fail('aggregate must fail with error');
} catch (error) {
expect(error.message).to.include('HMAC validation failure');
}
const doc = aggregateSucceeded.reply.cursor.firstBatch[0];
expect(doc.encrypted).to.be.instanceOf(Binary);
});
});

context('Case 4: Decrypt Success', metadata, function () {
it('succeeds on decryption and command succeeds', async function () {
// Use ``encryptedClient`` to insert the document ``{ "encrypted": <ciphertext> }``
// into ``db.decryption_events``.
// Use ``encryptedClient`` to run an aggregate on ``db.decryption_events``.
// Expect no exception.
// Expect a CommandSucceededEvent. Expect the CommandSucceededEvent.reply
// to contain BSON binary for the field ``cursor.firstBatch.encrypted``.
const collection = encryptedClient.db('db').collection('decryption_events');
await collection.insertOne({ encrypted: cipherText });
let result;
try {
result = await collection.aggregate([]).toArray();
} catch (error) {
expect.fail(`aggregate must not fail, got ${error.message}`);
}
expect(result[0].encrypted).to.equal('hello');
const doc = aggregateSucceeded.reply.cursor.firstBatch[0];
expect(doc.encrypted).to.be.instanceOf(Binary);
});
});
});
});

0 comments on commit 4a91444

Please sign in to comment.