Skip to content

Commit fb2b4a5

Browse files
authored
test(NODE-4227): add explicit encryption prose tests (#3297)
1 parent 4c07bbb commit fb2b4a5

File tree

5 files changed

+359
-3
lines changed

5 files changed

+359
-3
lines changed

.evergreen/run-tests.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ else
5656
source "$DRIVERS_TOOLS"/.evergreen/csfle/set-temp-creds.sh
5757
fi
5858

59-
npm install mongodb-client-encryption@">=2.2.0-alpha.2"
59+
npm install mongodb-client-encryption@">=2.2.0-alpha.3"
6060
npm install @mongodb-js/zstd
6161
npm install snappy
6262

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

Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ const metadata = {
4040
}
4141
};
4242

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

4553
// In the prose tests LOCAL_MASTERKEY refers to the following base64:
@@ -1426,6 +1434,291 @@ describe('Client Side Encryption Prose Tests', metadata, function () {
14261434
});
14271435
});
14281436

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

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)