Skip to content

Commit ca6a034

Browse files
authored
PHPLIB-890: Test auto decryption occurs after CommandSucceeded events (#946)
1 parent a495bf5 commit ca6a034

File tree

1 file changed

+155
-0
lines changed

1 file changed

+155
-0
lines changed

tests/SpecTests/ClientSideEncryptionSpecTest.php

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,15 @@
1010
use MongoDB\Driver\ClientEncryption;
1111
use MongoDB\Driver\Exception\AuthenticationException;
1212
use MongoDB\Driver\Exception\BulkWriteException;
13+
use MongoDB\Driver\Exception\CommandException;
1314
use MongoDB\Driver\Exception\ConnectionException;
1415
use MongoDB\Driver\Exception\ConnectionTimeoutException;
1516
use MongoDB\Driver\Exception\EncryptionException;
1617
use MongoDB\Driver\Exception\RuntimeException;
18+
use MongoDB\Driver\Monitoring\CommandFailedEvent;
19+
use MongoDB\Driver\Monitoring\CommandStartedEvent;
20+
use MongoDB\Driver\Monitoring\CommandSubscriber;
21+
use MongoDB\Driver\Monitoring\CommandSucceededEvent;
1722
use MongoDB\Driver\WriteConcern;
1823
use MongoDB\Tests\CommandObserver;
1924
use PHPUnit\Framework\Assert;
@@ -37,6 +42,7 @@
3742
use function sprintf;
3843
use function str_repeat;
3944
use function strlen;
45+
use function substr;
4046
use function unserialize;
4147
use function version_compare;
4248

@@ -1393,6 +1399,155 @@ static function (self $test, ClientEncryption $clientEncryption, Client $encrypt
13931399
];
13941400
}
13951401

1402+
/**
1403+
* Prose test 14: Decryption Events
1404+
*
1405+
* @see https://github.com/mongodb/specifications/tree/master/source/client-side-encryption/tests#decryption-events
1406+
* @dataProvider provideDecryptionEventsTests
1407+
*/
1408+
public function testDecryptionEvents(Closure $test): void
1409+
{
1410+
// Test setup
1411+
$setupClient = static::createTestClient();
1412+
$setupClient->selectCollection('db', 'decryption_events')->drop();
1413+
1414+
// Ensure that the key vault is dropped with a majority write concern
1415+
self::insertKeyVaultData($setupClient, []);
1416+
1417+
$clientEncryption = new ClientEncryption([
1418+
'keyVaultClient' => $setupClient->getManager(),
1419+
'keyVaultNamespace' => 'keyvault.datakeys',
1420+
'kmsProviders' => ['local' => ['key' => new Binary(base64_decode(self::LOCAL_MASTERKEY), 0)]],
1421+
]);
1422+
1423+
$keyId = $clientEncryption->createDataKey('local');
1424+
1425+
$cipherText = $clientEncryption->encrypt('hello', [
1426+
'keyId' => $keyId,
1427+
'algorithm' => ClientEncryption::AEAD_AES_256_CBC_HMAC_SHA_512_DETERMINISTIC,
1428+
]);
1429+
1430+
// Flip the last byte in the encrypted string
1431+
$malformedCipherText = new Binary(substr($cipherText->getData(), 0, -1) . ~$cipherText->getData()[-1], Binary::TYPE_ENCRYPTED);
1432+
1433+
$autoEncryptionOpts = [
1434+
'keyVaultNamespace' => 'keyvault.datakeys',
1435+
'kmsProviders' => ['local' => ['key' => new Binary(base64_decode(self::LOCAL_MASTERKEY), 0)]],
1436+
];
1437+
1438+
$encryptedClient = static::createTestClient(null, ['retryReads' => false], ['autoEncryption' => $autoEncryptionOpts]);
1439+
1440+
$subscriber = new class implements CommandSubscriber {
1441+
public $lastAggregateReply;
1442+
public $lastAggregateError;
1443+
1444+
public function commandStarted(CommandStartedEvent $event): void
1445+
{
1446+
}
1447+
1448+
public function commandSucceeded(CommandSucceededEvent $event): void
1449+
{
1450+
if ($event->getCommandName() === 'aggregate') {
1451+
$this->lastAggregateReply = $event->getReply();
1452+
}
1453+
}
1454+
1455+
public function commandFailed(CommandFailedEvent $event): void
1456+
{
1457+
if ($event->getCommandName() === 'aggregate') {
1458+
$this->lastAggregateError = $event->getError();
1459+
}
1460+
}
1461+
};
1462+
1463+
$encryptedClient->getManager()->addSubscriber($subscriber);
1464+
1465+
$test($this, $setupClient, $clientEncryption, $encryptedClient, $subscriber, $cipherText, $malformedCipherText);
1466+
1467+
$encryptedClient->getManager()->removeSubscriber($subscriber);
1468+
}
1469+
1470+
public static function provideDecryptionEventsTests()
1471+
{
1472+
// See: https://github.com/mongodb/specifications/tree/master/source/client-side-encryption/tests#case-1-command-error
1473+
yield 'Case 1: Command Error' => [
1474+
static function (self $test, Client $setupClient, ClientEncryption $clientEncryption, Client $encryptedClient, CommandSubscriber $subscriber, Binary $cipherText, Binary $malformedCipherText): void {
1475+
$setupClient->selectDatabase('admin')->command([
1476+
'configureFailPoint' => 'failCommand',
1477+
'mode' => ['times' => 1],
1478+
'data' => [
1479+
'errorCode' => 123,
1480+
'failCommands' => ['aggregate'],
1481+
],
1482+
]);
1483+
1484+
try {
1485+
$encryptedClient->selectCollection('db', 'decryption_events')->aggregate([]);
1486+
$test->fail('Expected exception to be thrown');
1487+
} catch (CommandException $e) {
1488+
$test->assertSame(123, $e->getCode());
1489+
}
1490+
1491+
$test->assertNotNull($subscriber->lastAggregateError);
1492+
},
1493+
];
1494+
1495+
// See: https://github.com/mongodb/specifications/tree/master/source/client-side-encryption/tests#case-2-network-error
1496+
yield 'Case 2: Network Error' => [
1497+
static function (self $test, Client $setupClient, ClientEncryption $clientEncryption, Client $encryptedClient, CommandSubscriber $subscriber, Binary $cipherText, Binary $malformedCipherText): void {
1498+
$setupClient->selectDatabase('admin')->command([
1499+
'configureFailPoint' => 'failCommand',
1500+
'mode' => ['times' => 1],
1501+
'data' => [
1502+
'closeConnection' => true,
1503+
'failCommands' => ['aggregate'],
1504+
],
1505+
]);
1506+
1507+
try {
1508+
$encryptedClient->selectCollection('db', 'decryption_events')->aggregate([]);
1509+
$test->fail('Expected exception to be thrown');
1510+
} catch (ConnectionTimeoutException $e) {
1511+
$test->addToAssertionCount(1);
1512+
}
1513+
1514+
$test->assertNotNull($subscriber->lastAggregateError);
1515+
},
1516+
];
1517+
1518+
// See: https://github.com/mongodb/specifications/tree/master/source/client-side-encryption/tests#case-3-decrypt-error
1519+
yield 'Case 3: Decrypt Error' => [
1520+
static function (self $test, Client $setupClient, ClientEncryption $clientEncryption, Client $encryptedClient, CommandSubscriber $subscriber, Binary $cipherText, Binary $malformedCipherText): void {
1521+
$collection = $encryptedClient->selectCollection('db', 'decryption_events');
1522+
1523+
$collection->insertOne(['encrypted' => $malformedCipherText]);
1524+
1525+
try {
1526+
$collection->aggregate([]);
1527+
$test->fail('Expected exception to be thrown');
1528+
} catch (EncryptionException $e) {
1529+
$test->assertStringContainsString('HMAC validation failure', $e->getMessage());
1530+
}
1531+
1532+
$test->assertNotNull($subscriber->lastAggregateReply);
1533+
$test->assertEquals($malformedCipherText, $subscriber->lastAggregateReply->cursor->firstBatch[0]->encrypted ?? null);
1534+
},
1535+
];
1536+
1537+
// See: https://github.com/mongodb/specifications/tree/master/source/client-side-encryption/tests#case-4-decrypt-success
1538+
yield 'Case 4: Decrypt Success' => [
1539+
static function (self $test, Client $setupClient, ClientEncryption $clientEncryption, Client $encryptedClient, CommandSubscriber $subscriber, Binary $cipherText, Binary $malformedCipherText): void {
1540+
$collection = $encryptedClient->selectCollection('db', 'decryption_events');
1541+
1542+
$collection->insertOne(['encrypted' => $cipherText]);
1543+
$collection->aggregate([]);
1544+
1545+
$test->assertNotNull($subscriber->lastAggregateReply);
1546+
$test->assertEquals($cipherText, $subscriber->lastAggregateReply->cursor->firstBatch[0]->encrypted ?? null);
1547+
},
1548+
];
1549+
}
1550+
13961551
private function createInt64(string $value): Int64
13971552
{
13981553
$array = sprintf('a:1:{s:7:"integer";s:%d:"%s";}', strlen($value), $value);

0 commit comments

Comments
 (0)