Skip to content

Commit a9e1a3f

Browse files
committed
test(NODE-4227): add explicit encryption prose tests
1 parent aa652ae commit a9e1a3f

File tree

4 files changed

+352
-2
lines changed

4 files changed

+352
-2
lines changed

test/integration/client-side-encryption/client_side_encryption.prose.test.js

Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const BSON = require('bson');
33
const { expect } = require('chai');
44
const fs = require('fs');
55
const path = require('path');
6+
const { readFile } = require('fs/promises');
67

78
const { deadlockTests } = require('./client_side_encryption.prose.deadlock');
89
const { dropCollection, APMEventCollector } = require('../shared');
@@ -39,6 +40,14 @@ const metadata = {
3940
}
4041
};
4142

43+
const eeMetadata = {
44+
requires: {
45+
clientSideEncryption: true,
46+
mongodb: '>=6.0.0',
47+
topology: ['replicaset', 'sharded', 'single']
48+
}
49+
};
50+
4251
// Tests for the ClientEncryption type are not included as part of the YAML tests.
4352

4453
// In the prose tests LOCAL_MASTERKEY refers to the following base64:
@@ -1424,4 +1433,282 @@ describe('Client Side Encryption Prose Tests', metadata, function () {
14241433
});
14251434
});
14261435
});
1436+
1437+
context.only('12. Explicit Encryption', eeMetadata, function () {
1438+
const data = path.join(__dirname, '..', '..', 'spec', 'client-side-encryption', 'etc', 'data');
1439+
let encryptedFields;
1440+
let key1Document;
1441+
let key1Id;
1442+
let setupClient;
1443+
let keyVaultClient;
1444+
let clientEncryption;
1445+
let encryptedClient;
1446+
1447+
beforeEach(async function () {
1448+
const mongodbClientEncryption = this.configuration.mongodbClientEncryption;
1449+
// Load the file encryptedFields.json as encryptedFields.
1450+
encryptedFields = EJSON.parse(await readFile(path.join(data, 'encryptedFields.json')), {
1451+
relaxed: false
1452+
});
1453+
// Load the file key1-document.json as key1Document.
1454+
key1Document = EJSON.parse(await readFile(path.join(data, 'keys', 'key1-document.json')), {
1455+
relaxed: false
1456+
});
1457+
// Read the "_id" field of key1Document as key1ID.
1458+
key1Id = key1Document._id;
1459+
setupClient = this.configuration.newClient();
1460+
// Drop and create the collection db.explicit_encryption using encryptedFields as an option.
1461+
const db = setupClient.db('db');
1462+
await dropCollection(db, 'explicit_encryption', { encryptedFields });
1463+
await db.createCollection('explicit_encryption', { encryptedFields });
1464+
// Drop and create the collection keyvault.datakeys.
1465+
const kdb = setupClient.db('keyvault');
1466+
await dropCollection(kdb, 'datakeys');
1467+
await kdb.createCollection('datakeys');
1468+
// Insert key1Document in keyvault.datakeys with majority write concern.
1469+
await kdb.collection('datakeys').insertOne(key1Document, { writeConcern: { w: 'majority' } });
1470+
// Create a MongoClient named keyVaultClient.
1471+
keyVaultClient = this.configuration.newClient();
1472+
// Create a ClientEncryption object named clientEncryption with these options:
1473+
// ClientEncryptionOpts {
1474+
// keyVaultClient: <keyVaultClient>;
1475+
// keyVaultNamespace: "keyvault.datakeys";
1476+
// kmsProviders: { "local": { "key": <base64 decoding of LOCAL_MASTERKEY> } }
1477+
// }
1478+
clientEncryption = new mongodbClientEncryption.ClientEncryption(keyVaultClient, {
1479+
keyVaultNamespace: 'keyvault.datakeys',
1480+
kmsProviders: getKmsProviders(LOCAL_KEY),
1481+
bson: BSON
1482+
});
1483+
// Create a MongoClient named ``encryptedClient`` with these ``AutoEncryptionOpts``:
1484+
// AutoEncryptionOpts {
1485+
// keyVaultNamespace: "keyvault.datakeys";
1486+
// kmsProviders: { "local": { "key": <base64 decoding of LOCAL_MASTERKEY> } },
1487+
// bypassQueryAnalysis: true
1488+
// }
1489+
encryptedClient = this.configuration.newClient(
1490+
{},
1491+
{
1492+
bypassQueryAnalysis: true,
1493+
autoEncryption: {
1494+
keyVaultNamespace: 'keyvault.datakeys',
1495+
kmsProviders: getKmsProviders(LOCAL_KEY)
1496+
}
1497+
}
1498+
);
1499+
});
1500+
1501+
afterEach(async function () {
1502+
await setupClient.close();
1503+
await keyVaultClient.close();
1504+
await encryptedClient.close();
1505+
});
1506+
1507+
context('Case 1: can insert encrypted indexed and find', eeMetadata, function () {
1508+
let insertPayload;
1509+
let findPayload;
1510+
1511+
beforeEach(async function () {
1512+
// Use clientEncryption to encrypt the value "encrypted indexed value" with these EncryptOpts:
1513+
// class EncryptOpts {
1514+
// keyId : <key1ID>
1515+
// algorithm: "Indexed",
1516+
// }
1517+
// Store the result in insertPayload.
1518+
insertPayload = await clientEncryption.encrypt('encrypted indexed value', {
1519+
keyId: key1Id,
1520+
algorithm: 'Indexed'
1521+
});
1522+
// Use encryptedClient to insert the document { "encryptedIndexed": <insertPayload> }
1523+
// into db.explicit_encryption.
1524+
await encryptedClient.db('db').collection('explicit_encryption').insertOne({
1525+
encryptedIndexed: insertPayload
1526+
});
1527+
// Use clientEncryption to encrypt the value "encrypted indexed value" with these EncryptOpts:
1528+
// class EncryptOpts {
1529+
// keyId : <key1ID>
1530+
// algorithm: "Indexed",
1531+
// queryType: Equality
1532+
// }
1533+
// Store the result in findPayload.
1534+
findPayload = await clientEncryption.encrypt('encrypted indexed value', {
1535+
keyId: key1Id,
1536+
algorithm: 'Indexed',
1537+
queryType: 'equality'
1538+
});
1539+
});
1540+
1541+
it('returns the decrypted value', async function () {
1542+
// Use encryptedClient to run a "find" operation on the db.explicit_encryption
1543+
// collection with the filter { "encryptedIndexed": <findPayload> }.
1544+
// Assert one document is returned containing the field
1545+
// { "encryptedIndexed": "encrypted indexed value" }.
1546+
const result = await encryptedClient.findOne({ encryptedIndexed: findPayload });
1547+
expect(result.encryptedIndexed).to.equal('encrypted indexed value');
1548+
});
1549+
});
1550+
1551+
context(
1552+
'Case 2: can insert encrypted indexed and find with non-zero contention',
1553+
eeMetadata,
1554+
function () {
1555+
let findPayload;
1556+
let findPayload2;
1557+
1558+
beforeEach(async function () {
1559+
for (let i = 0; i < 10; i++) {
1560+
// Use clientEncryption to encrypt the value "encrypted indexed value" with these EncryptOpts:
1561+
// class EncryptOpts {
1562+
// keyId : <key1ID>
1563+
// algorithm: "Indexed",
1564+
// contentionFactor: 10
1565+
// }
1566+
// Store the result in insertPayload.
1567+
const insertPayload = await clientEncryption.encrypt('encrypted indexed value', {
1568+
keyId: key1Id,
1569+
algorithm: 'Indexed',
1570+
contentionFactor: 10
1571+
});
1572+
// Use encryptedClient to insert the document { "encryptedIndexed": <insertPayload> }
1573+
// into db.explicit_encryption.
1574+
await encryptedClient.db('db').collection('explicit_encryption').insertOne({
1575+
encryptedIndexed: insertPayload
1576+
});
1577+
// Repeat the above steps 10 times to insert 10 total documents.
1578+
// The insertPayload must be regenerated each iteration.
1579+
}
1580+
// Use clientEncryption to encrypt the value "encrypted indexed value" with these EncryptOpts:
1581+
// class EncryptOpts {
1582+
// keyId : <key1ID>
1583+
// algorithm: "Indexed",
1584+
// queryType: Equality
1585+
// }
1586+
// Store the result in findPayload.
1587+
findPayload = await clientEncryption.encrypt('encrypted indexed value', {
1588+
keyId: key1Id,
1589+
algorithm: 'Indexed',
1590+
queryType: 'equality'
1591+
});
1592+
// Use clientEncryption to encrypt the value "encrypted indexed value" with these EncryptOpts:
1593+
// class EncryptOpts {
1594+
// keyId : <key1ID>
1595+
// algorithm: "Indexed",
1596+
// queryType: Equality,
1597+
// contentionFactor: 10
1598+
// }
1599+
// Store the result in findPayload2.
1600+
findPayload2 = await clientEncryption.encrypt('encrypted indexed value', {
1601+
keyId: key1Id,
1602+
algorithm: 'Indexed',
1603+
queryType: 'equality',
1604+
contentionFactor: 10
1605+
});
1606+
});
1607+
1608+
it('returns less than the total documents with no contention', async function () {
1609+
// Use encryptedClient to run a "find" operation on the db.explicit_encryption
1610+
// collection with the filter { "encryptedIndexed": <findPayload> }.
1611+
// Assert less than 10 documents are returned. 0 documents may be returned.
1612+
// Assert each returned document contains the field
1613+
// { "encryptedIndexed": "encrypted indexed value" }.
1614+
const result = await encryptedClient.find({ encryptedIndexed: findPayload }).toArray();
1615+
expect(result.length).to.be.below(10);
1616+
for (const doc of result) {
1617+
expect(doc.encryptedIndexed).to.equal('encrypted indexed value');
1618+
}
1619+
});
1620+
1621+
it('returns all documents with contention', async function () {
1622+
// Use encryptedClient to run a "find" operation on the db.explicit_encryption
1623+
// collection with the filter { "encryptedIndexed": <findPayload2> }.
1624+
// Assert 10 documents are returned. Assert each returned document contains the
1625+
// field { "encryptedIndexed": "encrypted indexed value" }.
1626+
const result = await encryptedClient.find({ encryptedIndexed: findPayload2 }).toArray();
1627+
expect(result.length).to.equal(10);
1628+
for (const doc of result) {
1629+
expect(doc.encryptedIndexed).to.equal('encrypted indexed value');
1630+
}
1631+
});
1632+
}
1633+
);
1634+
1635+
context('Case 3: can insert encrypted unindexed', eeMetadata, function () {
1636+
let insertPayload;
1637+
1638+
beforeEach(async function () {
1639+
// Use clientEncryption to encrypt the value "encrypted unindexed value" with these EncryptOpts:
1640+
// class EncryptOpts {
1641+
// keyId : <key1ID>
1642+
// algorithm: "Unindexed"
1643+
// }
1644+
// Store the result in insertPayload.
1645+
insertPayload = await clientEncryption.encrypt('encrypted unindexed value', {
1646+
keyId: key1Id,
1647+
algorithm: 'Unindexed'
1648+
});
1649+
// Use encryptedClient to insert the document { "_id": 1, "encryptedUnindexed": <insertPayload> }
1650+
// into db.explicit_encryption.
1651+
await encryptedClient.db('db').collection('explicit_encryption').insertOne({
1652+
encryptedIndexed: insertPayload
1653+
});
1654+
});
1655+
1656+
it('returns unindexed documents', async function () {
1657+
// Use encryptedClient to run a "find" operation on the db.explicit_encryption
1658+
// collection with the filter { "_id": 1 }.
1659+
// Assert one document is returned containing the field
1660+
// { "encryptedUnindexed": "encrypted unindexed value" }.
1661+
const result = await encryptedClient.findOne({ _id: 1 });
1662+
expect(result.encryptedIndexed).to.equal('encrypted unindexed value');
1663+
});
1664+
});
1665+
1666+
context('Case 4: can roundtrip encrypted indexed', eeMetadata, function () {
1667+
let payload;
1668+
1669+
beforeEach(async function () {
1670+
// Use clientEncryption to encrypt the value "encrypted indexed value" with these EncryptOpts:
1671+
// class EncryptOpts {
1672+
// keyId : <key1ID>
1673+
// algorithm: "Indexed",
1674+
// }
1675+
// Store the result in payload.
1676+
payload = await clientEncryption.encrypt('encrypted indexed value', {
1677+
keyId: key1Id,
1678+
algorithm: 'Indexed'
1679+
});
1680+
});
1681+
1682+
it('decrypts the value', async function () {
1683+
// Use clientEncryption to decrypt payload. Assert the returned value
1684+
// equals "encrypted indexed value".
1685+
const result = await clientEncryption.decrypt(payload);
1686+
expect(result).equals('encrypted indexed value');
1687+
});
1688+
});
1689+
1690+
context('Case 5: can roundtrip encrypted unindexed', eeMetadata, function () {
1691+
let payload;
1692+
1693+
beforeEach(async function () {
1694+
// Use clientEncryption to encrypt the value "encrypted unindexed value" with these EncryptOpts:
1695+
// class EncryptOpts {
1696+
// keyId : <key1ID>
1697+
// algorithm: "Unindexed",
1698+
// }
1699+
// Store the result in payload.
1700+
payload = await clientEncryption.encrypt('encrypted indexed value', {
1701+
keyId: key1Id,
1702+
algorithm: 'Unindexed'
1703+
});
1704+
});
1705+
1706+
it('decrypts the value', async function () {
1707+
// Use clientEncryption to decrypt payload. Assert the returned value
1708+
// equals "encrypted unindexed value".
1709+
const result = await clientEncryption.decrypt(payload);
1710+
expect(result).equals('encrypted unindexed value');
1711+
});
1712+
});
1713+
});
14271714
});

test/integration/shared.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ function delay(timeout) {
3838
});
3939
}
4040

41-
function dropCollection(dbObj, collectionName) {
42-
return dbObj.dropCollection(collectionName).catch(ignoreNsNotFound);
41+
function dropCollection(dbObj, collectionName, options = {}) {
42+
return dbObj.dropCollection(collectionName, options).catch(ignoreNsNotFound);
4343
}
4444

4545
function filterForCommands(commands, bag) {
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"escCollection": "enxcol_.default.esc",
3+
"eccCollection": "enxcol_.default.ecc",
4+
"ecocCollection": "enxcol_.default.ecoc",
5+
"fields": [
6+
{
7+
"keyId": {
8+
"$binary": {
9+
"base64": "EjRWeBI0mHYSNBI0VniQEg==",
10+
"subType": "04"
11+
}
12+
},
13+
"path": "encryptedIndexed",
14+
"bsonType": "string",
15+
"queries": {
16+
"queryType": "equality",
17+
"contention": {
18+
"$numberLong": "0"
19+
}
20+
}
21+
},
22+
{
23+
"keyId": {
24+
"$binary": {
25+
"base64": "q83vqxI0mHYSNBI0VniQEg==",
26+
"subType": "04"
27+
}
28+
},
29+
"path": "encryptedUnindexed",
30+
"bsonType": "string"
31+
}
32+
]
33+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"_id": {
3+
"$binary": {
4+
"base64": "EjRWeBI0mHYSNBI0VniQEg==",
5+
"subType": "04"
6+
}
7+
},
8+
"keyMaterial": {
9+
"$binary": {
10+
"base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==",
11+
"subType": "00"
12+
}
13+
},
14+
"creationDate": {
15+
"$date": {
16+
"$numberLong": "1648914851981"
17+
}
18+
},
19+
"updateDate": {
20+
"$date": {
21+
"$numberLong": "1648914851981"
22+
}
23+
},
24+
"status": {
25+
"$numberInt": "0"
26+
},
27+
"masterKey": {
28+
"provider": "local"
29+
}
30+
}

0 commit comments

Comments
 (0)