Skip to content

Commit 02c6e3f

Browse files
[DRIVERS-2312] Define a CreateEncryptedCollection helper (#1322)
* Define a CreateEncryptedCollection helper * A very basic beginning test for CreateEncryptedCollection
1 parent b9cdbbb commit 02c6e3f

File tree

2 files changed

+150
-6
lines changed

2 files changed

+150
-6
lines changed

source/client-side-encryption/client-side-encryption.rst

Lines changed: 93 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Client Side Encryption
44

55
:Status: Accepted
66
:Minimum Server Version: 4.2 (CSFLE), 6.0 (Queryable Encryption)
7-
:Last Modified: 2022-11-04
7+
:Last Modified: 2022-11-10
88
:Version: 1.11.0
99

1010
.. _lmc-c-api: https://github.com/mongodb/libmongocrypt/blob/master/src/mongocrypt.h.in
@@ -837,9 +837,13 @@ encryptedFieldsMap
837837

838838
``encryptedFieldsMap`` only applies to Queryable Encryption.
839839

840-
If a collection is present on both the ``encryptedFieldsMap`` and ``schemaMap``, libmongocrypt_ will error on initialization. See :ref:`fle2-and-fle1-error`.
840+
If a collection is present on both the ``encryptedFieldsMap`` and ``schemaMap``,
841+
libmongocrypt_ will error on initialization. See :ref:`fle2-and-fle1-error`.
841842

842-
If a collection is present on the ``encryptedFieldsMap``, the behavior of ``CreateCollection()`` and ``Collection.Drop()`` is altered. See :ref:`fle2-createcollection-drop`.
843+
If a collection has a set of encrypted fields, the behavior of
844+
``CreateCollection()`` and ``Collection.Drop()`` is altered. An additional
845+
helper, ``CreateEncryptedCollection()`` has been defined as a convenience
846+
wrapper around ``CreateCollection()``. See :ref:`fle2-createcollection-drop`.
843847

844848
Automatic encryption in Queryable Encryption is configured with the ``encryptedFields``.
845849

@@ -864,6 +868,42 @@ A collection supporting Queryable Encryption requires an index and three additio
864868
.. _create: https://www.mongodb.com/docs/manual/reference/command/create
865869
.. _drop: https://www.mongodb.com/docs/manual/reference/command/drop
866870

871+
872+
.. _GetEncryptedFields:
873+
874+
Collection ``encryptedFields`` Lookup (GetEncryptedFields)
875+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
876+
877+
The convenience methods support the following lookup process for finding the
878+
``encryptedFields`` associated with a collection.
879+
880+
.. default-role:: math
881+
882+
Assume an exposition-only function
883+
`GetEncryptedFields(opts, collName, dbName, askDb)`, where `opts` is a set of
884+
options, `collName` is the name of the collection, `dbName` is the name of the
885+
database associated with that collection, and `askDb` is a boolean value. The
886+
resulting ``encryptedFields`` `EF` is found by:
887+
888+
1. Let `QualName` be the string formed by joining `dbName` and `collName` with
889+
an ASCII dot ``"."``.
890+
2. If `opts` contains an ``"encryptedFields"`` property, then `EF` is the value
891+
of that property.
892+
3. Otherwise, if ``AutoEncryptionOptions.encryptedFieldsMap`` contains an
893+
element named by `QualName`, then `EF` is the value of that element.
894+
4. Otherwise, if `askDb` is `true`:
895+
896+
1. Issue a ``listCollections`` command against the database named by
897+
`dbName`, filtered by ``{name: <collName>}``. Let the result be the
898+
document `L`.
899+
2. If `L` contains an ``options`` document element, and that element contains
900+
an ``encryptedFields`` document element, `EF` is `L`\
901+
``["options"]["encryptedFields"]``.
902+
3. Otherwise, `EF` is *not-found*
903+
904+
5. Otherwise, `EF` is considered *not-found*.
905+
906+
867907
Create Collection Helper
868908
^^^^^^^^^^^^^^^^^^^^^^^^
869909

@@ -891,6 +931,45 @@ If the collection namespace has an associated ``encryptedFields``, then do the f
891931
- Create the collection ``collectionName`` with ``collectionOptions`` and the option ``encryptedFields`` set to the ``encryptedFields``.
892932
- Create the the index ``{"__safeContent__": 1}`` on collection ``collectionName``.
893933

934+
935+
Create Encrypted Collection Helper
936+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
937+
938+
To support automatic generation of encryption data keys, a helper
939+
`CreateEncryptedCollection(CE, database, collName, collOpts, kmsProvider, dkOpts)`
940+
is defined, where `CE` is a ClientEncryption_ object, `kmsProvider` is a
941+
KMSProviderName_ and `dkOpts` is a DataKeyOpts_. It has the following behavior:
942+
943+
- Let `dbName` be the name of `database`. Look up the encrypted fields `EF` for
944+
the new collection as `GetEncryptedFields(collOpts, collName, dbName, false)`
945+
(`See here <GetEncryptedFields_>`_).
946+
- If `EF` is *not-found*, report an error that there are no ``encryptedFields``
947+
defined for the collection.
948+
- Let `EF'` be a copy of `EF`. Update `EF'` in the following manner:
949+
950+
- Let `Fields` be the ``"fields"`` element within `EF'`.
951+
- If `Fields` is present and is an array value, then for each element `F` of
952+
`Fields`:
953+
954+
- If `F` is not a document element, skip it.
955+
- Otherwise, if `F` has a ``"keyId"`` named element `K` and `K` is a
956+
``null`` value:
957+
958+
- Let `D` be the result of ``CE.createDataKey(kmsProvider, dkOpts)``.
959+
- If generating `D` resulted in an error `E`, the entire
960+
`CreateEncryptedCollection` must now fail with error `E`. Return the
961+
partially-formed `EF'` with the error so that the caller may know what
962+
datakeys have already been created by the helper.
963+
- Replace `K` in `F` with `D`.
964+
965+
- Create a new set of options `collOpts'` duplicating `collOpts`. Set the
966+
``"encryptedFields"`` named element of `collOpts'` to `EF'`.
967+
968+
- Invoke the ``CreateCollection`` helper as
969+
`CreateCollection(database, collName, collOpts')`. Return the resulting
970+
collection and the generated `EF'`.
971+
972+
894973
Drop Collection Helper
895974
^^^^^^^^^^^^^^^^^^^^^^
896975

@@ -915,6 +994,8 @@ If the collection namespace has an associated ``encryptedFields``, then do the f
915994
If ``encryptedFields["ecocCollection"]`` is not set, use the collection name ``enxcol_.<collectionName>.ecoc``.
916995
- Drop the collection ``collectionName``.
917996

997+
.. default-role:: literal
998+
.. _ClientEncryption:
918999

9191000
ClientEncryption
9201001
----------------
@@ -924,6 +1005,12 @@ ClientEncryption
9241005
class ClientEncryption {
9251006
ClientEncryption(opts: ClientEncryptionOpts);
9261007
1008+
// The "Create Encrypted Collection" helper is a convenience function wrapping CreateCollection. It will
1009+
// create a collection with encrypted fields, automatically allocating and assigning new data encryption
1010+
// keys. It returns a handle to the new collection, as well as a document consisting of the generated
1011+
// "encryptedFields" options. Refer to "Create Encrypted Collection Helper"
1012+
createEncryptedCollection(database: Database, collName: string, collOpts, kmsProvider: KMSProviderName, dkOpts: DataKeyOpts): [Collection, Document];
1013+
9271014
// Creates a new key document and inserts into the key vault collection.
9281015
// Returns the _id of the created document as a UUID (BSON binary subtype 0x04).
9291016
createDataKey(kmsProvider: KMSProviderName, opts: DataKeyOpts | null): Binary;
@@ -2515,6 +2602,9 @@ explicit session parameter as described in the
25152602
Changelog
25162603
=========
25172604
2605+
:2022-11-10: Defined a ``CreateEncryptedCollection`` helper for creating new
2606+
encryption keys automatically for the queryable encrypted fields in
2607+
a new collection.
25182608
:2022-11-07: Reformat changelog.
25192609
:2022-11-04: Permit global cache for Azure credentials.
25202610
:2022-10-26: Do not connect to ``mongocryptd`` if shared library is loaded.

source/client-side-encryption/tests/README.rst

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2395,7 +2395,7 @@ The following tests that a mongocryptd client is not created when shared library
23952395

23962396
#. On ``listenerThread``, create a TcpListener on 127.0.0.1 endpoint and port 27021. Start the listener and wait for establishing connections.
23972397
If any connection is established, then signal about this to the main thread.
2398-
2398+
23992399
Drivers MAY pass a different port if they expect their testing infrastructure to be using port 27021. Pass a port that should be free.
24002400

24012401
#. Create a MongoClient configured with auto encryption (referred to as ``client_encrypted``)
@@ -2415,7 +2415,61 @@ The following tests that a mongocryptd client is not created when shared library
24152415
{
24162416
"mongocryptdURI": "mongodb://localhost:27021"
24172417
}
2418-
2419-
#. Use ``client_encrypted`` to insert the document ``{"unencrypted": "test"}`` into ``db.coll``.
2418+
2419+
#. Use ``client_encrypted`` to insert the document ``{"unencrypted": "test"}`` into ``db.coll``.
24202420

24212421
#. Expect no signal from ``listenerThread``.
2422+
2423+
2424+
2425+
21. Automatic Data Encryption Keys
2426+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2427+
2428+
Case 1: Simple Creation and Validation
2429+
``````````````````````````````````````
2430+
2431+
This test is the most basic to verify that CreateEncryptedCollection_ created a
2432+
collection with queryable encryption enabled. It verifies that the server
2433+
rejects an attempt to insert plaintext in an encrypted fields.
2434+
2435+
.. _CreateEncryptedCollection: ../client-side-encryption.rst#create-encrypted-collection-helper
2436+
.. _MongoClient: ../client-side-encryption.rst#mongoclient-changes
2437+
2438+
.. highlight:: typescript
2439+
.. default-role:: math
2440+
2441+
1. Create a ClientEncryption_ object `CE` with the following options::
2442+
2443+
clientEncryptionOptions: {
2444+
keyVaultClient: <new MongoClient>,
2445+
keyVaultNamespace: "keyvault.datakeys",
2446+
kmsProviders: {
2447+
local: { key: base64Decode(LOCAL_MASTERKEY) },
2448+
},
2449+
}
2450+
2451+
2. Create a new create-collection options `Opts` including the following::
2452+
2453+
{
2454+
encryptedFields: {
2455+
fields: [{
2456+
path: "ssn",
2457+
bsonType: "string",
2458+
keyId: null
2459+
}]
2460+
}
2461+
}
2462+
2463+
3. Open a new database handle `DB`.
2464+
4. Invoke `CreateEncryptedCollection(CE, DB, "testing1", Opts, "local", null)`
2465+
to obtain a new collection `Coll`. Expect success.
2466+
5. Attempt to insert the following document into `Coll`::
2467+
2468+
{
2469+
ssn: "123-45-6789"
2470+
}
2471+
2472+
6. Expect an error from the insert operation that indicates that the document
2473+
failed validation. This error indicates that the server expects to receive an
2474+
encrypted field for ``ssn``, but we tried to insert a plaintext field via a
2475+
client that is unaware of the encryption requirements.

0 commit comments

Comments
 (0)