@@ -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 ;
0 commit comments