|
5 | 5 | use Closure;
|
6 | 6 | use MongoDB\BSON\Binary;
|
7 | 7 | use MongoDB\BSON\Int64;
|
| 8 | +use MongoDB\Client; |
8 | 9 | use MongoDB\Collection;
|
9 | 10 | use MongoDB\Driver\ClientEncryption;
|
10 | 11 | use MongoDB\Driver\Exception\AuthenticationException;
|
|
24 | 25 |
|
25 | 26 | use function base64_decode;
|
26 | 27 | use function basename;
|
| 28 | +use function count; |
27 | 29 | use function explode;
|
28 | 30 | use function file_get_contents;
|
29 | 31 | use function getenv;
|
|
36 | 38 | use function str_repeat;
|
37 | 39 | use function strlen;
|
38 | 40 | use function unserialize;
|
| 41 | +use function version_compare; |
39 | 42 |
|
40 | 43 | use const DIRECTORY_SEPARATOR;
|
41 | 44 | use const PATH_SEPARATOR;
|
@@ -1154,6 +1157,183 @@ static function (self $test, ClientEncryption $clientEncryptionNoClientCert, Cli
|
1154 | 1157 | ];
|
1155 | 1158 | }
|
1156 | 1159 |
|
| 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 | + |
1157 | 1337 | private function createInt64(string $value): Int64
|
1158 | 1338 | {
|
1159 | 1339 | $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
|
1283 | 1463 | return $data->allowed ? $returnData : $data;
|
1284 | 1464 | }
|
1285 | 1465 |
|
| 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 | + |
1286 | 1481 | private function skipIfLocalMongocryptdIsUnavailable(): void
|
1287 | 1482 | {
|
1288 | 1483 | $paths = explode(PATH_SEPARATOR, getenv("PATH"));
|
|
0 commit comments