Skip to content

Commit afd2724

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

File tree

4 files changed

+353
-2
lines changed

4 files changed

+353
-2
lines changed

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

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

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

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

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)