Skip to content

Commit 1d7d5d5

Browse files
committed
PHPLIB-885: Explicit encryption prose tests
1 parent 200d443 commit 1d7d5d5

File tree

7 files changed

+305
-0
lines changed

7 files changed

+305
-0
lines changed

tests/FunctionalTestCase.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,11 @@ protected function isMongos()
391391
return $this->getPrimaryServer()->getType() == Server::TYPE_MONGOS;
392392
}
393393

394+
protected function isStandalone()
395+
{
396+
return $this->getPrimaryServer()->getType() == Server::TYPE_STANDALONE;
397+
}
398+
394399
/**
395400
* Return whether serverless (i.e. proxy as mongos) is being utilized.
396401
*/

tests/SpecTests/ClientSideEncryptionSpecTest.php

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Closure;
66
use MongoDB\BSON\Binary;
77
use MongoDB\BSON\Int64;
8+
use MongoDB\Client;
89
use MongoDB\Collection;
910
use MongoDB\Driver\ClientEncryption;
1011
use MongoDB\Driver\Exception\AuthenticationException;
@@ -24,6 +25,7 @@
2425

2526
use function base64_decode;
2627
use function basename;
28+
use function count;
2729
use function explode;
2830
use function file_get_contents;
2931
use function getenv;
@@ -36,6 +38,7 @@
3638
use function str_repeat;
3739
use function strlen;
3840
use function unserialize;
41+
use function version_compare;
3942

4043
use const DIRECTORY_SEPARATOR;
4144
use const PATH_SEPARATOR;
@@ -1154,6 +1157,183 @@ static function (self $test, ClientEncryption $clientEncryptionNoClientCert, Cli
11541157
];
11551158
}
11561159

1160+
/**
1161+
* Prose test 12: Explicit Encryption
1162+
*
1163+
* @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.rst#explicit-encryption
1164+
* @dataProvider provideExplicitEncryptionTests
1165+
*/
1166+
public function testExplicitEncryption(Closure $test): void
1167+
{
1168+
if ($this->isStandalone() || ($this->isShardedCluster() && ! $this->isShardedClusterUsingReplicasets())) {
1169+
$this->markTestSkipped('Explicit encryption tests require replica sets');
1170+
}
1171+
1172+
if (version_compare($this->getServerVersion(), '6.0.0', '<')) {
1173+
$this->markTestSkipped('Explicit encryption tests require MongoDB 6.0 or later');
1174+
}
1175+
1176+
// Test setup
1177+
$encryptedFields = $this->prepareEncryptedFields($this->decodeJson(file_get_contents(__DIR__ . '/client-side-encryption/etc/data/encryptedFields.json')));
1178+
$key1Document = $this->decodeJson(file_get_contents(__DIR__ . '/client-side-encryption/etc/data/keys/key1-document.json'));
1179+
$key1Id = $key1Document->_id;
1180+
1181+
$client = static::createTestClient();
1182+
1183+
$database = $client->selectDatabase('db');
1184+
$database->dropCollection('explicit_encryption', ['encryptedFields' => $encryptedFields]);
1185+
$database->createCollection('explicit_encryption', ['encryptedFields' => $encryptedFields]);
1186+
1187+
$database = $client->selectDatabase('keyvault');
1188+
$database->dropCollection('datakeys');
1189+
$database->createCollection('datakeys');
1190+
1191+
$client->selectCollection('keyvault', 'datakeys')->insertOne($key1Document, ['writeConcern' => new WriteConcern(WriteConcern::MAJORITY)]);
1192+
1193+
$keyVaultClient = static::createTestClient();
1194+
1195+
$clientEncryption = new ClientEncryption([
1196+
'keyVaultClient' => $keyVaultClient->getManager(),
1197+
'keyVaultNamespace' => 'keyvault.datakeys',
1198+
'kmsProviders' => ['local' => ['key' => new Binary(base64_decode(self::LOCAL_MASTERKEY), 0)]],
1199+
]);
1200+
1201+
$autoEncryptionOpts = [
1202+
'keyVaultNamespace' => 'keyvault.datakeys',
1203+
'kmsProviders' => ['local' => ['key' => new Binary(base64_decode(self::LOCAL_MASTERKEY), 0)]],
1204+
'bypassQueryAnalysis' => true,
1205+
];
1206+
1207+
$encryptedClient = static::createTestClient(null, [], ['autoEncryption' => $autoEncryptionOpts]);
1208+
1209+
$test($this, $clientEncryption, $encryptedClient, $keyVaultClient, $key1Id);
1210+
}
1211+
1212+
public static function provideExplicitEncryptionTests()
1213+
{
1214+
// See: https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.rst#case-1-can-insert-encrypted-indexed-and-find
1215+
yield 'Case 1: can insert encrypted indexed and find' => [
1216+
static function (self $test, ClientEncryption $clientEncryption, Client $encryptedClient, Client $keyVaultClient, Binary $key1Id): void {
1217+
$value = 'encrypted indexed value';
1218+
1219+
$insertPayload = $clientEncryption->encrypt($value, [
1220+
'keyId' => $key1Id,
1221+
'algorithm' => ClientEncryption::ALGORITHM_INDEXED,
1222+
]);
1223+
1224+
$collection = $encryptedClient->selectCollection('db', 'explicit_encryption');
1225+
$collection->insertOne(['encryptedIndexed' => $insertPayload]);
1226+
1227+
$findPayload = $clientEncryption->encrypt($value, [
1228+
'keyId' => $key1Id,
1229+
'algorithm' => ClientEncryption::ALGORITHM_INDEXED,
1230+
'queryType' => ClientEncryption::QUERY_TYPE_EQUALITY,
1231+
]);
1232+
1233+
$results = $collection->find(['encryptedIndexed' => $findPayload])->toArray();
1234+
1235+
$test->assertCount(1, $results);
1236+
$test->assertSame($value, $results[0]['encryptedIndexed']);
1237+
},
1238+
];
1239+
1240+
// See: https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.rst#case-2-can-insert-encrypted-indexed-and-find-with-non-zero-contention
1241+
yield 'Case 2: can insert encrypted indexed and find with non-zero contention' => [
1242+
static function (self $test, ClientEncryption $clientEncryption, Client $encryptedClient, Client $keyVaultClient, Binary $key1Id): void {
1243+
$value = 'encrypted indexed value';
1244+
1245+
$collection = $encryptedClient->selectCollection('db', 'explicit_encryption');
1246+
1247+
for ($i = 0; $i < 10; $i++) {
1248+
$insertPayload = $clientEncryption->encrypt($value, [
1249+
'keyId' => $key1Id,
1250+
'algorithm' => ClientEncryption::ALGORITHM_INDEXED,
1251+
'contentionFactor' => 10,
1252+
]);
1253+
1254+
$collection->insertOne(['encryptedIndexed' => $insertPayload]);
1255+
}
1256+
1257+
$findPayload = $clientEncryption->encrypt($value, [
1258+
'keyId' => $key1Id,
1259+
'algorithm' => ClientEncryption::ALGORITHM_INDEXED,
1260+
'queryType' => ClientEncryption::QUERY_TYPE_EQUALITY,
1261+
]);
1262+
1263+
$results = $collection->find(['encryptedIndexed' => $findPayload])->toArray();
1264+
1265+
$test->assertLessThan(10, count($results));
1266+
1267+
foreach ($results as $result) {
1268+
$test->assertSame($value, $result['encryptedIndexed']);
1269+
}
1270+
1271+
$findPayload2 = $clientEncryption->encrypt($value, [
1272+
'keyId' => $key1Id,
1273+
'algorithm' => ClientEncryption::ALGORITHM_INDEXED,
1274+
'queryType' => ClientEncryption::QUERY_TYPE_EQUALITY,
1275+
'contentionFactor' => 10,
1276+
]);
1277+
1278+
$results = $collection->find(['encryptedIndexed' => $findPayload2])->toArray();
1279+
1280+
$test->assertCount(10, $results);
1281+
1282+
foreach ($results as $result) {
1283+
$test->assertSame($value, $result['encryptedIndexed']);
1284+
}
1285+
},
1286+
];
1287+
1288+
// See: https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.rst#case-3-can-insert-encrypted-unindexed
1289+
yield 'Case 3: can insert encrypted unindexed' => [
1290+
static function (self $test, ClientEncryption $clientEncryption, Client $encryptedClient, Client $keyVaultClient, Binary $key1Id): void {
1291+
$value = 'encrypted unindexed value';
1292+
1293+
$insertPayload = $clientEncryption->encrypt($value, [
1294+
'keyId' => $key1Id,
1295+
'algorithm' => ClientEncryption::ALGORITHM_UNINDEXED,
1296+
]);
1297+
1298+
$collection = $encryptedClient->selectCollection('db', 'explicit_encryption');
1299+
$collection->insertOne(['_id' => 1, 'encryptedUnindexed' => $insertPayload]);
1300+
1301+
$results = $collection->find(['_id' => 1])->toArray();
1302+
1303+
$test->assertCount(1, $results);
1304+
$test->assertSame($value, $results[0]['encryptedUnindexed']);
1305+
},
1306+
];
1307+
1308+
// See: https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.rst#case-4-can-roundtrip-encrypted-indexed
1309+
yield 'Case 4: can roundtrip encrypted indexed' => [
1310+
static function (self $test, ClientEncryption $clientEncryption, Client $encryptedClient, Client $keyVaultClient, Binary $key1Id): void {
1311+
$value = 'encrypted indexed value';
1312+
1313+
$payload = $clientEncryption->encrypt($value, [
1314+
'keyId' => $key1Id,
1315+
'algorithm' => ClientEncryption::ALGORITHM_INDEXED,
1316+
]);
1317+
1318+
$test->assertSame($value, $clientEncryption->decrypt($payload));
1319+
},
1320+
];
1321+
1322+
// See: https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.rst#case-5-can-roundtrip-encrypted-unindexed
1323+
yield 'Case 5: can roundtrip encrypted unindexed' => [
1324+
static function (self $test, ClientEncryption $clientEncryption, Client $encryptedClient, Client $keyVaultClient, Binary $key1Id): void {
1325+
$value = 'encrypted unindexed value';
1326+
1327+
$payload = $clientEncryption->encrypt($value, [
1328+
'keyId' => $key1Id,
1329+
'algorithm' => ClientEncryption::ALGORITHM_UNINDEXED,
1330+
]);
1331+
1332+
$test->assertSame($value, $clientEncryption->decrypt($payload));
1333+
},
1334+
];
1335+
}
1336+
11571337
private function createInt64(string $value): Int64
11581338
{
11591339
$array = sprintf('a:1:{s:7:"integer";s:%d:"%s";}', strlen($value), $value);
@@ -1283,6 +1463,21 @@ private function prepareCorpusData(string $fieldName, stdClass $data, ClientEncr
12831463
return $data->allowed ? $returnData : $data;
12841464
}
12851465

1466+
/**
1467+
* @todo Remove this once SERVER-66901 is implemented
1468+
* @see https://jira.mongodb.org/browse/PHPLIB-884
1469+
*/
1470+
private function prepareEncryptedFields(stdClass $encryptedFields): stdClass
1471+
{
1472+
foreach ($encryptedFields->fields as $field) {
1473+
if (isset($field->queries->contention)) {
1474+
$field->queries->contention = $this->createInt64($field->queries->contention);
1475+
}
1476+
}
1477+
1478+
return $encryptedFields;
1479+
}
1480+
12861481
private function skipIfLocalMongocryptdIsUnavailable(): void
12871482
{
12881483
$paths = explode(PATH_SEPARATOR, getenv("PATH"));
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+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"$binary": {
3+
"base64": "EjRWeBI0mHYSNBI0VniQEg==",
4+
"subType": "04"
5+
}
6+
}
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": "q83vqxI0mHYSNBI0VniQEg==",
5+
"subType": "04"
6+
}
7+
},
8+
"keyMaterial": {
9+
"$binary": {
10+
"base64": "HBk9BWihXExNDvTp1lUxOuxuZK2Pe2ZdVdlsxPEBkiO1bS4mG5NNDsQ7zVxJAH8BtdOYp72Ku4Y3nwc0BUpIKsvAKX4eYXtlhv5zUQxWdeNFhg9qK7qb8nqhnnLeT0f25jFSqzWJoT379hfwDeu0bebJHr35QrJ8myZdPMTEDYF08QYQ48ShRBli0S+QzBHHAQiM2iJNr4svg2WR8JSeWQ==",
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+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"$binary": {
3+
"base64": "q83vqxI0mHYSNBI0VniQEg==",
4+
"subType": "04"
5+
}
6+
}

0 commit comments

Comments
 (0)