From b4bfd0e4540f60d98dc4672f7cc71b67bd4824e8 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Sun, 9 Jul 2023 21:27:48 -0400 Subject: [PATCH] Code review feedback Demonstrates schema validation errors in example scripts Relocates local schema section after server-side schema, and clarifies that it should be used in conjunction with server-side schemas (not instead of). Revise comments and variable names in scripts and update expected output for tests accordingly. --- docs/examples/create_data_key.php | 13 ++- ...sfle-automatic_encryption-local_schema.php | 74 +++++++----- ...utomatic_encryption-server_side_schema.php | 57 +++++----- docs/examples/csfle-explicit_encryption.php | 24 ++-- ...plicit_encryption_automatic_decryption.php | 17 +-- docs/examples/key_alt_name.php | 21 +++- .../queryable_encryption-automatic.php | 38 +++---- .../queryable_encryption-explicit.php | 59 +++++----- docs/tutorial/encryption.txt | 70 ++++++------ tests/ExamplesTest.php | 106 +++++++++--------- 10 files changed, 260 insertions(+), 219 deletions(-) diff --git a/docs/examples/create_data_key.php b/docs/examples/create_data_key.php index 9de810d07..4fdec0664 100644 --- a/docs/examples/create_data_key.php +++ b/docs/examples/create_data_key.php @@ -11,10 +11,10 @@ // Generate a secure local key to use for this script $localKey = new Binary(random_bytes(96)); -/* Create a client with no encryption options. Additionally, create a - * ClientEncryption object to manage data keys. */ +// Create a client with no encryption options $client = new Client($uri); +// Create a ClientEncryption object to manage data encryption keys $clientEncryption = $client->createClientEncryption([ 'keyVaultNamespace' => 'encryption.__keyVault', 'kmsProviders' => [ @@ -22,10 +22,13 @@ ], ]); -/* Drop the key vault collection and create an encryption key. This would - * typically be done during application deployment. To store the key ID for - * later use, you can use serialize() or var_export(). */ +/* Create a new key vault collection for this script. The application must also + * ensure that a unique index exists for keyAltNames. */ $client->selectCollection('encryption', '__keyVault')->drop(); +$client->selectCollection('encryption', '__keyVault')->createIndex(['keyAltNames' => 1], ['unique' => true]); + +/* Create a data encryption key. To store the key ID for later use, you can use + * serialize(), var_export(), etc. */ $keyId = $clientEncryption->createDataKey('local'); print_r($keyId); diff --git a/docs/examples/csfle-automatic_encryption-local_schema.php b/docs/examples/csfle-automatic_encryption-local_schema.php index d61be162e..bfa8e0456 100644 --- a/docs/examples/csfle-automatic_encryption-local_schema.php +++ b/docs/examples/csfle-automatic_encryption-local_schema.php @@ -3,6 +3,7 @@ use MongoDB\BSON\Binary; use MongoDB\Client; use MongoDB\Driver\ClientEncryption; +use MongoDB\Driver\Exception\ServerException; require __DIR__ . '/../../vendor/autoload.php'; @@ -11,10 +12,10 @@ // Generate a secure local key to use for this script $localKey = new Binary(random_bytes(96)); -/* Create a client with no encryption options. Additionally, create a - * ClientEncryption object to manage data keys. */ +// Create a client with no encryption options $client = new Client($uri); +// Create a ClientEncryption object to manage data encryption keys $clientEncryption = $client->createClientEncryption([ 'keyVaultNamespace' => 'encryption.__keyVault', 'kmsProviders' => [ @@ -22,51 +23,64 @@ ], ]); -/* Drop the key vault collection and create an encryption key. Alternatively, - * this key ID could be read from a configuration file. */ +/* Create a new key vault collection and data encryption key for this script. + * Alternatively, this key ID could be read from a configuration file. */ $client->selectCollection('encryption', '__keyVault')->drop(); +$client->selectCollection('encryption', '__keyVault')->createIndex(['keyAltNames' => 1], ['unique' => true]); $keyId = $clientEncryption->createDataKey('local'); -/* Create a client with automatic encryption enabled. Specify a schemaMap option - * to enforce a local JSON schema. */ +/* Define a JSON schema for the encrypted collection. Since this only utilizes + * encryption schema syntax, it can be used for both the server-side and local + * schema. */ +$schema = [ + 'bsonType' => 'object', + 'properties' => [ + 'encryptedField' => [ + 'encrypt' => [ + 'keyId' => [$keyId], + 'bsonType' => 'string', + 'algorithm' => ClientEncryption::AEAD_AES_256_CBC_HMAC_SHA_512_DETERMINISTIC, + ], + ], + ], +]; + +/* Create another client with automatic encryption enabled. Configure a local + * schema for the encrypted collection using the "schemaMap" option. */ $encryptedClient = new Client($uri, [], [ 'autoEncryption' => [ 'keyVaultNamespace' => 'encryption.__keyVault', 'kmsProviders' => ['local' => ['key' => $localKey]], - 'schemaMap' => [ - 'test.coll' => [ - 'bsonType' => 'object', - 'properties' => [ - 'encryptedField' => [ - 'encrypt' => [ - 'keyId' => [$keyId], - 'bsonType' => 'string', - 'algorithm' => ClientEncryption::AEAD_AES_256_CBC_HMAC_SHA_512_DETERMINISTIC, - ], - ], - ], - ], - ], + 'schemaMap' => ['test.coll' => $schema], ], ]); -// Drop and create the collection. +/* Create a new collection for this script. Configure a server-side schema by + * explicitly creating the collection with a "validator" option. + * + * Note: without a server-side schema, another client could potentially insert + * unencrypted data into the collection. Therefore, a local schema should always + * be used in conjunction with a server-side schema. */ $encryptedClient->selectDatabase('test')->dropCollection('coll'); -$encryptedClient->selectDatabase('test')->createCollection('coll'); +$encryptedClient->selectDatabase('test')->createCollection('coll', ['validator' => ['$jsonSchema' => $schema]]); $encryptedCollection = $encryptedClient->selectCollection('test', 'coll'); -/* Using the encrypted client, insert and find a document. The encrypted field - * will be automatically encrypted and decrypted. */ -$encryptedCollection->insertOne([ - '_id' => 1, - 'encryptedField' => 'mySecret', -]); +/* Using the encrypted client, insert and find a document to demonstrate that + * the encrypted field is automatically encrypted and decrypted. */ +$encryptedCollection->insertOne(['_id' => 1, 'encryptedField' => 'mySecret']); print_r($encryptedCollection->findOne(['_id' => 1])); /* Using the client configured without encryption, find the same document and - * observe that the field is not automatically decrypted. Additionally, the JSON - * schema will prohibit inserting a document with an unencrypted field value. */ + * observe that the field is not automatically decrypted. */ $unencryptedCollection = $client->selectCollection('test', 'coll'); print_r($unencryptedCollection->findOne(['_id' => 1])); + +/* Attempt to insert another document with an unencrypted field value to + * demonstrate that the server-side schema is enforced. */ +try { + $unencryptedCollection->insertOne(['_id' => 2, 'encryptedField' => 'myOtherSecret']); +} catch (ServerException $e) { + printf("Error inserting document: %s\n", $e->getMessage()); +} diff --git a/docs/examples/csfle-automatic_encryption-server_side_schema.php b/docs/examples/csfle-automatic_encryption-server_side_schema.php index 0474da7a2..e47314a0e 100644 --- a/docs/examples/csfle-automatic_encryption-server_side_schema.php +++ b/docs/examples/csfle-automatic_encryption-server_side_schema.php @@ -3,6 +3,7 @@ use MongoDB\BSON\Binary; use MongoDB\Client; use MongoDB\Driver\ClientEncryption; +use MongoDB\Driver\Exception\ServerException; require __DIR__ . '/../../vendor/autoload.php'; @@ -11,10 +12,10 @@ // Generate a secure local key to use for this script $localKey = new Binary(random_bytes(96)); -/* Create a client with no encryption options. Additionally, create a - * ClientEncryption object to manage data keys. */ +// Create a client with no encryption options $client = new Client($uri); +// Create a ClientEncryption object to manage data encryption keys $clientEncryption = $client->createClientEncryption([ 'keyVaultNamespace' => 'encryption.__keyVault', 'kmsProviders' => [ @@ -22,12 +23,13 @@ ], ]); -/* Drop the key vault collection and create an encryption key. Alternatively, - * this key ID could be read from a configuration file. */ +/* Create a new key vault collection and data encryption key for this script. + * Alternatively, this key ID could be read from a configuration file. */ $client->selectCollection('encryption', '__keyVault')->drop(); +$client->selectCollection('encryption', '__keyVault')->createIndex(['keyAltNames' => 1], ['unique' => true]); $keyId = $clientEncryption->createDataKey('local'); -// Create a client with automatic encryption enabled +// Create another client with automatic encryption enabled $encryptedClient = new Client($uri, [], [ 'autoEncryption' => [ 'keyVaultNamespace' => 'encryption.__keyVault', @@ -35,39 +37,42 @@ ], ]); -/* Drop and create the collection. Specify a validator option when creating the - * collection to enforce a server-side JSON schema. */ -$validator = [ - '$jsonSchema' => [ - 'bsonType' => 'object', - 'properties' => [ - 'encryptedField' => [ - 'encrypt' => [ - 'keyId' => [$keyId], - 'bsonType' => 'string', - 'algorithm' => ClientEncryption::AEAD_AES_256_CBC_HMAC_SHA_512_DETERMINISTIC, - ], +// Define a JSON schema for the encrypted collection +$schema = [ + 'bsonType' => 'object', + 'properties' => [ + 'encryptedField' => [ + 'encrypt' => [ + 'keyId' => [$keyId], + 'bsonType' => 'string', + 'algorithm' => ClientEncryption::AEAD_AES_256_CBC_HMAC_SHA_512_DETERMINISTIC, ], ], ], ]; +/* Create a new collection for this script. Configure a server-side schema by + * explicitly creating the collection with a "validator" option. */ $encryptedClient->selectDatabase('test')->dropCollection('coll'); -$encryptedClient->selectDatabase('test')->createCollection('coll', ['validator' => $validator]); +$encryptedClient->selectDatabase('test')->createCollection('coll', ['validator' => ['$jsonSchema' => $schema]]); $encryptedCollection = $encryptedClient->selectCollection('test', 'coll'); -/* Using the encrypted client, insert and find a document. The encrypted field - * will be automatically encrypted and decrypted. */ -$encryptedCollection->insertOne([ - '_id' => 1, - 'encryptedField' => 'mySecret', -]); +/* Using the encrypted client, insert and find a document to demonstrate that + * the encrypted field is automatically encrypted and decrypted. */ +$encryptedCollection->insertOne(['_id' => 1, 'encryptedField' => 'mySecret']); print_r($encryptedCollection->findOne(['_id' => 1])); /* Using the client configured without encryption, find the same document and - * observe that the field is not automatically decrypted. Additionally, the JSON - * schema will prohibit inserting a document with an unencrypted field value. */ + * observe that the field is not automatically decrypted. */ $unencryptedCollection = $client->selectCollection('test', 'coll'); print_r($unencryptedCollection->findOne(['_id' => 1])); + +/* Attempt to insert another document with an unencrypted field value to + * demonstrate that the server-side schema is enforced. */ +try { + $unencryptedCollection->insertOne(['_id' => 2, 'encryptedField' => 'myOtherSecret']); +} catch (ServerException $e) { + printf("Error inserting document: %s\n", $e->getMessage()); +} diff --git a/docs/examples/csfle-explicit_encryption.php b/docs/examples/csfle-explicit_encryption.php index 34fc5b81c..ac2d0a59d 100644 --- a/docs/examples/csfle-explicit_encryption.php +++ b/docs/examples/csfle-explicit_encryption.php @@ -11,10 +11,10 @@ // Generate a secure local key to use for this script $localKey = new Binary(random_bytes(96)); -/* Create a client with no encryption options. Additionally, create a - * ClientEncryption object to manage data keys. */ +// Create a client with no encryption options $client = new Client($uri); +// Create a ClientEncryption object to manage data encryption keys $clientEncryption = $client->createClientEncryption([ 'keyVaultNamespace' => 'encryption.__keyVault', 'kmsProviders' => [ @@ -22,12 +22,13 @@ ], ]); -/* Drop the key vault collection and create an encryption key. Alternatively, - * this key ID could be read from a configuration file. */ +/* Create a new key vault collection and data encryption key for this script. + * Alternatively, this key ID could be read from a configuration file. */ $client->selectCollection('encryption', '__keyVault')->drop(); +$client->selectCollection('encryption', '__keyVault')->createIndex(['keyAltNames' => 1], ['unique' => true]); $keyId = $clientEncryption->createDataKey('local'); -// Select and drop a collection to use for this example +// Create a new collection for this script $collection = $client->selectCollection('test', 'coll'); $collection->drop(); @@ -37,12 +38,13 @@ 'keyId' => $keyId, ]); -$collection->insertOne(['encryptedField' => $encryptedValue]); +$collection->insertOne(['_id' => 1, 'encryptedField' => $encryptedValue]); -/* Query for the document. The field will not be automatically decrypted - * because the client was not configured with an autoEncryption driver option. - * Manually decrypt the field value using the ClientEncryption object. */ +/* Using the client configured without encryption, find the document and observe + * that the field is not automatically decrypted. */ $document = $collection->findOne(); -print_r($document->encryptedField); -print_r($clientEncryption->decrypt($document->encryptedField)); +print_r($document); + +// Manually decrypt the field +printf("Decrypted: %s\n", $clientEncryption->decrypt($document->encryptedField)); diff --git a/docs/examples/csfle-explicit_encryption_automatic_decryption.php b/docs/examples/csfle-explicit_encryption_automatic_decryption.php index a1cbadf68..f53d67018 100644 --- a/docs/examples/csfle-explicit_encryption_automatic_decryption.php +++ b/docs/examples/csfle-explicit_encryption_automatic_decryption.php @@ -20,7 +20,7 @@ ], ]); -// Create a ClientEncryption object to manage data keys +// Create a ClientEncryption object to manage data encryption keys $clientEncryption = $client->createClientEncryption([ 'keyVaultNamespace' => 'encryption.__keyVault', 'kmsProviders' => [ @@ -28,12 +28,13 @@ ], ]); -/* Drop the key vault collection and create an encryption key. Alternatively, - * this key ID could be read from a configuration file. */ +/* Create a new key vault collection and data encryption key for this script. + * Alternatively, this key ID could be read from a configuration file. */ $client->selectCollection('encryption', '__keyVault')->drop(); +$client->selectCollection('encryption', '__keyVault')->createIndex(['keyAltNames' => 1], ['unique' => true]); $keyId = $clientEncryption->createDataKey('local'); -// Select and drop a collection to use for this example +// Create a new collection for this script $collection = $client->selectCollection('test', 'coll'); $collection->drop(); @@ -43,10 +44,10 @@ 'keyId' => $keyId, ]); -$collection->insertOne(['encryptedField' => $encryptedValue]); +$collection->insertOne(['_id' => 1, 'encryptedField' => $encryptedValue]); -/* Query for the document. The field will still be automatically decrypted - * because the client was configured with an autoEncryption driver option. */ +/* Using the client configured with encryption (but not automatic encryption), + * find the document and observe that the field is automatically decrypted. */ $document = $collection->findOne(); -print_r($document->encryptedField); +print_r($document); diff --git a/docs/examples/key_alt_name.php b/docs/examples/key_alt_name.php index b8dadc292..8071078bb 100644 --- a/docs/examples/key_alt_name.php +++ b/docs/examples/key_alt_name.php @@ -3,6 +3,7 @@ use MongoDB\BSON\Binary; use MongoDB\Client; use MongoDB\Driver\ClientEncryption; +use MongoDB\Driver\Exception\ServerException; require __DIR__ . '/../../vendor/autoload.php'; @@ -11,10 +12,10 @@ // Generate a secure local key to use for this script $localKey = new Binary(random_bytes(96)); -/* Create a client with no encryption options. Additionally, create a - * ClientEncryption object to manage data keys. */ +// Create a client with no encryption options $client = new Client($uri); +// Create a ClientEncryption object to manage data encryption keys $clientEncryption = $client->createClientEncryption([ 'keyVaultNamespace' => 'encryption.__keyVault', 'kmsProviders' => [ @@ -22,12 +23,22 @@ ], ]); -/* Drop the key vault collection and create an encryption key with an alternate - * name. This would typically be done during application deployment. To store - * the key ID for later use, you can use serialize() or var_export(). */ +/* Create a new key vault collection for this script. The application must also + * ensure that a unique index exists for keyAltNames. */ $client->selectCollection('encryption', '__keyVault')->drop(); +$client->selectCollection('encryption', '__keyVault')->createIndex(['keyAltNames' => 1], ['unique' => true]); + +// Create a data encryption key with an alternate name $clientEncryption->createDataKey('local', ['keyAltNames' => ['myDataKey']]); +/* Attempt to create a second key with the same name to demonstrate that the + * unique index is enforced. */ +try { + $clientEncryption->createDataKey('local', ['keyAltNames' => ['myDataKey']]); +} catch (ServerException $e) { + printf("Error creating key: %s\n", $e->getMessage()); +} + // Encrypt a value, using the "keyAltName" option instead of "keyId" $encryptedValue = $clientEncryption->encrypt('mySecret', [ 'algorithm' => ClientEncryption::AEAD_AES_256_CBC_HMAC_SHA_512_DETERMINISTIC, diff --git a/docs/examples/queryable_encryption-automatic.php b/docs/examples/queryable_encryption-automatic.php index 40dda5827..9fa530312 100644 --- a/docs/examples/queryable_encryption-automatic.php +++ b/docs/examples/queryable_encryption-automatic.php @@ -8,27 +8,27 @@ $uri = getenv('MONGODB_URI') ?: 'mongodb://127.0.0.1/'; -/* Create a local key for this script. In practice, this value would be read - * from a file, constant, or environment variable. Production apps should use - * a cloud provider instead of a local key. */ +// Generate a secure local key to use for this script $localKey = new Binary(random_bytes(96)); -/* Create a client with no encryption options. Additionally, create a - * ClientEncryption object to manage data keys. */ +// Create a client with no encryption options $client = new Client($uri); +// Create a ClientEncryption object to manage data encryption keys $clientEncryption = $client->createClientEncryption([ 'keyVaultNamespace' => 'encryption.__keyVault', 'kmsProviders' => ['local' => ['key' => $localKey]], ]); -// Drop the key vault collection and create two data keys (one for each encrypted field) +/* Create a new key vault collection and data encryption keys for this script. + * Alternatively, the key IDs could be read from a configuration file. */ $client->selectCollection('encryption', '__keyVault')->drop(); -$dataKeyId1 = $clientEncryption->createDataKey('local'); -$dataKeyId2 = $clientEncryption->createDataKey('local'); +$client->selectCollection('encryption', '__keyVault')->createIndex(['keyAltNames' => 1], ['unique' => true]); +$keyId1 = $clientEncryption->createDataKey('local'); +$keyId2 = $clientEncryption->createDataKey('local'); -/* Create a client with automatic encryption enabled. Define encryptedFields for - * the collection in encryptedFieldsMap. */ +/* Create another client with automatic encryption enabled. Configure the + * encrypted collection using the "encryptedFields" option. */ $encryptedClient = new Client($uri, [], [ 'autoEncryption' => [ 'keyVaultNamespace' => 'encryption.__keyVault', @@ -39,13 +39,13 @@ [ 'path' => 'encryptedIndexed', 'bsonType' => 'string', - 'keyId' => $dataKeyId1, + 'keyId' => $keyId1, 'queries' => ['queryType' => ClientEncryption::QUERY_TYPE_EQUALITY], ], [ 'path' => 'encryptedUnindexed', 'bsonType' => 'string', - 'keyId' => $dataKeyId2, + 'keyId' => $keyId2, ], ], ], @@ -53,24 +53,22 @@ ], ]); -/* Drop and create the collection. Each method will infer encryptedFields from - * the client and manage internal encryption collections automatically. */ +/* Create a new collection for this script. The drop and create helpers will + * infer encryptedFields from the client and manage internal encryption + * collections automatically. */ $encryptedClient->selectDatabase('test')->dropCollection('coll'); $encryptedClient->selectDatabase('test')->createCollection('coll'); $encryptedCollection = $encryptedClient->selectCollection('test', 'coll'); /* Using the encrypted client, insert a document and find it by querying on the * encrypted field. Fields will be automatically encrypted and decrypted. */ -$indexedValue = 'indexedValue'; -$unindexedValue = 'unindexedValue'; - $encryptedCollection->insertOne([ '_id' => 1, - 'encryptedIndexed' => $indexedValue, - 'encryptedUnindexed' => $unindexedValue, + 'encryptedIndexed' => 'indexedValue', + 'encryptedUnindexed' => 'unindexedValue', ]); -print_r($encryptedCollection->findOne(['encryptedIndexed' => $indexedValue])); +print_r($encryptedCollection->findOne(['encryptedIndexed' => 'indexedValue'])); /* Using the client configured without encryption, find the same document and * observe that fields are not automatically decrypted. */ diff --git a/docs/examples/queryable_encryption-explicit.php b/docs/examples/queryable_encryption-explicit.php index a09455ae7..aeac2876e 100644 --- a/docs/examples/queryable_encryption-explicit.php +++ b/docs/examples/queryable_encryption-explicit.php @@ -8,26 +8,26 @@ $uri = getenv('MONGODB_URI') ?: 'mongodb://127.0.0.1/'; -/* Create a local key for this script. In practice, this value would be read - * from a file, constant, or environment variable. Production apps should use - * a cloud provider instead of a local key. */ +// Generate a secure local key to use for this script $localKey = new Binary(random_bytes(96)); -/* Create a client with no encryption options. Additionally, create a - * ClientEncryption object to manage data keys. */ +// Create a client with no encryption options $client = new Client($uri); +// Create a ClientEncryption object to manage data encryption keys $clientEncryption = $client->createClientEncryption([ 'keyVaultNamespace' => 'encryption.__keyVault', 'kmsProviders' => ['local' => ['key' => $localKey]], ]); -// Drop the key vault collection and create two data keys (one for each encrypted field) +/* Create a new key vault collection and data encryption keys for this script. + * Alternatively, the key IDs could be read from a configuration file. */ $client->selectCollection('encryption', '__keyVault')->drop(); -$dataKeyId1 = $clientEncryption->createDataKey('local'); -$dataKeyId2 = $clientEncryption->createDataKey('local'); +$client->selectCollection('encryption', '__keyVault')->createIndex(['keyAltNames' => 1], ['unique' => true]); +$keyId1 = $clientEncryption->createDataKey('local'); +$keyId2 = $clientEncryption->createDataKey('local'); -// Create a client with automatic encryption disabled +// Create another client with automatic encryption disabled $encryptedClient = new Client($uri, [], [ 'autoEncryption' => [ 'keyVaultNamespace' => 'encryption.__keyVault', @@ -36,59 +36,56 @@ ], ]); -// Define encryptedFields for the collection +// Define encrypted fields for the collection $encryptedFields = [ 'fields' => [ [ 'path' => 'encryptedIndexed', 'bsonType' => 'string', - 'keyId' => $dataKeyId1, + 'keyId' => $keyId1, 'queries' => ['queryType' => ClientEncryption::QUERY_TYPE_EQUALITY], ], [ 'path' => 'encryptedUnindexed', 'bsonType' => 'string', - 'keyId' => $dataKeyId2, + 'keyId' => $keyId2, ], ], ]; -/* Drop and create the collection. Pass encryptedFields to each method to ensure - * that internal encryption collections are managed. */ +/* Create a new collection for this script. Pass encryptedFields to the drop and + * create helpers to ensure that internal encryption collections are managed. */ $encryptedClient->selectDatabase('test')->dropCollection('coll', ['encryptedFields' => $encryptedFields]); $encryptedClient->selectDatabase('test')->createCollection('coll', ['encryptedFields' => $encryptedFields]); -$collection = $encryptedClient->selectCollection('test', 'coll'); +$encryptedCollection = $encryptedClient->selectCollection('test', 'coll'); // Insert a document with manually encrypted fields -$indexedValue = 'indexedValue'; -$unindexedValue = 'unindexedValue'; - -$insertPayloadIndexed = $clientEncryption->encrypt($indexedValue, [ +$indexedInsertPayload = $clientEncryption->encrypt('indexedValue', [ 'algorithm' => ClientEncryption::ALGORITHM_INDEXED, 'contentionFactor' => 1, - 'keyId' => $dataKeyId1, + 'keyId' => $keyId1, ]); -$insertPayloadUnindexed = $clientEncryption->encrypt($unindexedValue, [ +$unindexedInsertPayload = $clientEncryption->encrypt('unindexedValue', [ 'algorithm' => ClientEncryption::ALGORITHM_UNINDEXED, - 'keyId' => $dataKeyId2, + 'keyId' => $keyId2, ]); -$collection->insertOne([ +$encryptedCollection->insertOne([ '_id' => 1, - 'encryptedIndexed' => $insertPayloadIndexed, - 'encryptedUnindexed' => $insertPayloadUnindexed, + 'encryptedIndexed' => $indexedInsertPayload, + 'encryptedUnindexed' => $unindexedInsertPayload, ]); /* Encrypt the payload for an "equality" query using the same key that was used - * to encrypt the insert payload. */ -$findPayload = $clientEncryption->encrypt($indexedValue, [ + * to encrypt the corresponding insert payload. */ +$indexedFindPayload = $clientEncryption->encrypt('indexedValue', [ 'algorithm' => ClientEncryption::ALGORITHM_INDEXED, 'queryType' => ClientEncryption::QUERY_TYPE_EQUALITY, 'contentionFactor' => 1, - 'keyId' => $dataKeyId1, + 'keyId' => $keyId1, ]); -/* Find the inserted document. Fields will still be automatically decrypted - * because the client was configured with an autoEncryption driver option. */ -print_r($collection->findOne(['encryptedIndexed' => $findPayload])); +/* Using the client configured with encryption (but not automatic encryption), + * find the document and observe that the fields are automatically decrypted. */ +print_r($encryptedCollection->findOne(['encryptedIndexed' => $findPayload])); diff --git a/docs/tutorial/encryption.txt b/docs/tutorial/encryption.txt index 3e7af07bd..b43e33331 100644 --- a/docs/tutorial/encryption.txt +++ b/docs/tutorial/encryption.txt @@ -92,24 +92,26 @@ Creating an Encryption Key To create an encryption key, create a :php:`MongoDB\\Driver\\ClientEncryption ` -instance with encryption options and create a new data key. The method will -return the key ID which can be used to reference the key later. You can also -pass multiple alternate names for this key and reference the key by these names -instead of the key ID. Creating a new data encryption key would typically be -done on initial deployment, but depending on your use case you may want to use -more than one encryption key or create them dynamically. +instance with encryption options and use the +:php:`createDataKey() ` +method. The method will return the key ID which can be used to reference the key +later. You can also pass multiple :ref:`alternate names ` for this key +and reference the key by these names instead of the key ID. + +Creating a new data encryption key would typically be done on initial +deployment, but depending on your use case you may want to use more than one +encryption key (e.g. user-specific encryption keys) or create them dynamically. .. literalinclude:: /examples/create_data_key.php :language: php +.. _alt_name: + Referencing Encryption Keys by an Alternative Name ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -While it is possible to create an encryption key every time data is encrypted, -this is not the recommended approach. Instead, you should create your encryption -keys depending on your use case, e.g. by creating a user-specific encryption -key. To reference keys in your software, you can use the ``keyAltName`` +To reference keys in your application, you can use the ``keyAltName`` attribute specified when creating the key. The following example creates an encryption key with an alternative name, which could be done when deploying the application. The script then encrypts data by referencing the key by its @@ -156,26 +158,7 @@ level encryption and use a object to create a new encryption key. -Providing Local Automatic Encryption Rules -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The following example uses the ``schemaMap`` auto encryption driver option to -define encrypted fields using a -:manual:`strict subset of the JSON schema syntax `. - -Specifying a ``schemaMap`` this way provides more security than relying on JSON -schemas obtained from the server. It protects against a malicious server -advertising a false JSON schema, which could trick the client into sending -unencrypted data that should be encrypted. - -JSON schemas specified in the ``schemaMap`` auto encryption driver option only -apply to configuring automatic client-side field level encryption. Other -validation rules in the JSON schema will not be enforced by the driver and will -result in an error. - -.. literalinclude:: /examples/csfle-automatic_encryption-local_schema.php - :language: php - +.. _server-side: Server-Side Field Level Encryption Enforcement ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -183,7 +166,7 @@ Server-Side Field Level Encryption Enforcement The MongoDB 4.2+ server supports using schema validation to enforce encryption of specific fields in a collection. This schema validation will prevent an application from inserting unencrypted values for any fields marked with the -:manual:`"encrypt" JSON schema keyword `. +:manual:`"encrypt" schema keyword `. The following example sets up a collection with automatic encryption using a ``$jsonSchema`` validator and @@ -195,6 +178,29 @@ decrypted when reading on the client side. :language: php +Providing Local Automatic Encryption Rules +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The following example uses the ``schemaMap`` auto encryption driver option to +define encrypted fields using a +:manual:`strict subset of the JSON schema syntax `. + +Using ``schemaMap`` in conjunction with a :ref:`server-side schema ` +provides more security than relying entirely on a schema obtained from the +server. It protects against a malicious server advertising a false schema, which +could trick the client into sending unencrypted data that should be encrypted. + +.. note:: + + Only :manual:`Encryption Schema syntax ` + can be used with the ``schemaMap`` option. Do not specify document validation + keywords in the automatic encryption rules. To define document validation + rules, configure :manual:`schema validation `. + +.. literalinclude:: /examples/csfle-automatic_encryption-local_schema.php + :language: php + + Explicit Encryption ~~~~~~~~~~~~~~~~~~~ @@ -258,7 +264,7 @@ Explicit encryption in Queryable Encryption is performed using the :php:`MongoDB\Driver\ClientEncryption::encrypt() ` and :php:`decrypt() ` methods. Although values must be explicitly encrypted (e.g. insertions, query criteria), automatic -decryption for queries is possible by configuring ``encryptedFields`` on the +*decryption* for queries is possible by configuring ``encryptedFields`` on the collection, as demonstrated in the following example: .. literalinclude:: /examples/queryable_encryption-explicit.php diff --git a/tests/ExamplesTest.php b/tests/ExamplesTest.php index 3c9002385..c1236f31d 100644 --- a/tests/ExamplesTest.php +++ b/tests/ExamplesTest.php @@ -210,7 +210,9 @@ public function testEncryptionExamples(string $file, string $expectedOutput): vo public static function provideEncryptionExamples(): Generator { - $expectedOutput = <<<'OUTPUT' + yield 'create_data_key' => [ + 'file' => __DIR__ . '/../docs/examples/create_data_key.php', + 'expectedOutput' => <<<'OUTPUT' MongoDB\BSON\Binary Object ( [data] => %a @@ -221,27 +223,24 @@ public static function provideEncryptionExamples(): Generator [data] => %a [type] => 6 ) -OUTPUT; - - yield 'create_data_key' => [ - 'file' => __DIR__ . '/../docs/examples/create_data_key.php', - 'expectedOutput' => $expectedOutput, +OUTPUT, ]; - $expectedOutput = <<<'OUTPUT' + yield 'key_alt_name' => [ + 'file' => __DIR__ . '/../docs/examples/key_alt_name.php', + 'expectedOutput' => <<<'OUTPUT' +Error creating key: E11000 duplicate key error %s: encryption.__keyVault%sdup key: { keyAltNames: "myDataKey" } MongoDB\BSON\Binary Object ( [data] => %a [type] => 6 ) -OUTPUT; - - yield 'key_alt_name' => [ - 'file' => __DIR__ . '/../docs/examples/key_alt_name.php', - 'expectedOutput' => $expectedOutput, +OUTPUT, ]; - $expectedOutput = <<<'OUTPUT' + yield 'csfle-automatic_encryption-local_schema' => [ + 'file' => __DIR__ . '/../docs/examples/csfle-automatic_encryption-local_schema.php', + 'expectedOutput' => <<<'OUTPUT' MongoDB\Model\BSONDocument Object ( [storage:ArrayObject:private] => Array @@ -265,14 +264,13 @@ public static function provideEncryptionExamples(): Generator ) ) -OUTPUT; - - yield 'csfle-automatic_encryption-local_schema' => [ - 'file' => __DIR__ . '/../docs/examples/csfle-automatic_encryption-local_schema.php', - 'expectedOutput' => $expectedOutput, +Error inserting document: Document failed validation +OUTPUT, ]; - $expectedOutput = <<<'OUTPUT' + yield 'csfle-automatic_encryption-server_side_schema' => [ + 'file' => __DIR__ . '/../docs/examples/csfle-automatic_encryption-server_side_schema.php', + 'expectedOutput' => <<<'OUTPUT' MongoDB\Model\BSONDocument Object ( [storage:ArrayObject:private] => Array @@ -296,34 +294,44 @@ public static function provideEncryptionExamples(): Generator ) ) -OUTPUT; - - yield 'csfle-automatic_encryption-server_side_schema' => [ - 'file' => __DIR__ . '/../docs/examples/csfle-automatic_encryption-server_side_schema.php', - 'expectedOutput' => $expectedOutput, +Error inserting document: Document failed validation +OUTPUT, ]; - $expectedOutput = <<<'OUTPUT' -MongoDB\BSON\Binary Object -( - [data] => %a - [type] => 6 -) -mySecret -OUTPUT; - yield 'csfle-explicit_encryption' => [ 'file' => __DIR__ . '/../docs/examples/csfle-explicit_encryption.php', - 'expectedOutput' => $expectedOutput, - ]; + 'expectedOutput' => <<<'OUTPUT' +MongoDB\Model\BSONDocument Object +( + [storage:ArrayObject:private] => Array + ( + [_id] => 1 + [encryptedField] => MongoDB\BSON\Binary Object + ( + [data] => %a + [type] => 6 + ) - $expectedOutput = <<<'OUTPUT' -mySecret -OUTPUT; + ) + +) +Decrypted: mySecret +OUTPUT, + ]; yield 'csfle-explicit_encryption_automatic_decryption' => [ 'file' => __DIR__ . '/../docs/examples/csfle-explicit_encryption_automatic_decryption.php', - 'expectedOutput' => $expectedOutput, + 'expectedOutput' => <<<'OUTPUT' +MongoDB\Model\BSONDocument Object +( + [storage:ArrayObject:private] => Array + ( + [_id] => 1 + [encryptedField] => mySecret + ) + +) +OUTPUT, ]; } @@ -343,7 +351,9 @@ public function testQueryableEncryptionExamples(string $file, string $expectedOu public static function provideQueryableEncryptionExamples(): Generator { - $expectedOutput = <<<'OUTPUT' + yield 'queryable_encryption-automatic' => [ + 'file' => __DIR__ . '/../docs/examples/queryable_encryption-automatic.php', + 'expectedOutput' => <<<'OUTPUT' MongoDB\Model\BSONDocument Object ( [storage:ArrayObject:private] => Array @@ -402,14 +412,12 @@ public static function provideQueryableEncryptionExamples(): Generator ) ) -OUTPUT; - - yield 'queryable_encryption-automatic' => [ - 'file' => __DIR__ . '/../docs/examples/queryable_encryption-automatic.php', - 'expectedOutput' => $expectedOutput, +OUTPUT, ]; - $expectedOutput = <<<'OUTPUT' + yield 'queryable_encryption-explicit' => [ + 'file' => __DIR__ . '/../docs/examples/queryable_encryption-explicit.php', + 'expectedOutput' => <<<'OUTPUT' MongoDB\Model\BSONDocument Object ( [storage:ArrayObject:private] => Array @@ -434,11 +442,7 @@ public static function provideQueryableEncryptionExamples(): Generator ) ) -OUTPUT; - - yield 'queryable_encryption-explicit' => [ - 'file' => __DIR__ . '/../docs/examples/queryable_encryption-explicit.php', - 'expectedOutput' => $expectedOutput, +OUTPUT, ]; }