@@ -3,6 +3,7 @@ const BSON = require('bson');
3
3
const { expect } = require ( 'chai' ) ;
4
4
const fs = require ( 'fs' ) ;
5
5
const path = require ( 'path' ) ;
6
+ const { readFile } = require ( 'fs/promises' ) ;
6
7
7
8
const { deadlockTests } = require ( './client_side_encryption.prose.deadlock' ) ;
8
9
const { dropCollection, APMEventCollector } = require ( '../shared' ) ;
@@ -39,6 +40,14 @@ const metadata = {
39
40
}
40
41
} ;
41
42
43
+ const eeMetadata = {
44
+ requires : {
45
+ clientSideEncryption : true ,
46
+ mongodb : '>=6.0.0' ,
47
+ topology : [ 'replicaset' , 'sharded' , 'single' ]
48
+ }
49
+ } ;
50
+
42
51
// Tests for the ClientEncryption type are not included as part of the YAML tests.
43
52
44
53
// In the prose tests LOCAL_MASTERKEY refers to the following base64:
@@ -1424,4 +1433,282 @@ describe('Client Side Encryption Prose Tests', metadata, function () {
1424
1433
} ) ;
1425
1434
} ) ;
1426
1435
} ) ;
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
+ } ) ;
1427
1714
} ) ;
0 commit comments